Using custom settings in Orchard, Part 1: Site settings

Tags: orchard, settings, tutorial, english

Sometimes you may need to persist some global settings (eg. license code, service login, default width etc.) to be reused across your module. Orchard makes it really simple and in this article I’ll show you how to do it.

Basically, there are two scopes you can define your settings in:

  1. Site scope – for global site settings. This one will be discussed here.
  2. Content type scope – for settings common to all items of a given type (eg. a Page, a Blog, a BlogPost and so on). I’ll discuss it in the second part of this series.

Defining site scope settings

You define those settings in a separate content part. Imagine such scenario (real one – I used it in Content Sharing module):

“I want to hold my AddThis service login to be reused in share bars across the site”

First, you have to create a content part (you can read about content parts here):

public class ShareBarSettingsPart : ContentPart<ShareBarSettingsPartRecord>
    {
        public string AddThisAccount
        {
            get { return Record.AddThisAccount; }
            set { Record.AddThisAccount = value; }
        }
    }

and a corresponding record for storage

public class ShareBarSettingsPartRecord : ContentPartRecord {
        public virtual string AddThisAccount { get; set; }
}

You can also decorate the content part AddThisAccount property with [Required] attribute to make it a required field when editing site settings.

After creating the content part and record classes, you need to create an appropriate DB mappings, called in Orchard - Data Migration. You shouldn’t do it by hand – there is a command-line command – “codegen datamigration <feature_name>”  which will create the appropriate file for you. You can see how to use it here.

The next step is to create a corresponding driver, which will be responsible for displaying the edit form and saving the posted data. If you have already written some content parts, than this part of code should look familiar:

[UsedImplicitly]
   public class ShareBarSettingsPartDriver : ContentPartDriver<ShareBarSettingsPart>
   {
       public ShareBarSettingsPartDriver(
           INotifier notifier,
           IOrchardServices services)
       {
           _notifier = notifier;
           T = NullLocalizer.Instance;
       }

       public Localizer T { get; set; }
       private const string TemplateName = "Parts/Share.Settings";
       private readonly INotifier _notifier;

       protected override DriverResult Editor(ShareBarSettingsPart part, dynamic shapeHelper)
       {
           return ContentShape("Parts_Share_Settings",
                   () => shapeHelper.EditorTemplate(
                       TemplateName: TemplateName,
                       Model: part,
                       Prefix: Prefix));
       }

       protected override DriverResult Editor(ShareBarSettingsPart part, IUpdateModel updater, dynamic shapeHelper)
       {
           if (updater.TryUpdateModel(part, Prefix, null, null))
           {
               _notifier.Information(T("Content sharing settings updated successfully"));
           }
           else {
               _notifier.Error(T("Error during content sharing settings update!"));
           }
           return Editor(part, shapeHelper);
       }
   }

I omitted some code for checking permissions to edit the settings for better readability, but you are free to take a look at the full source code hosted on Codeplex.

So we have our content part, record and a driver and we need just two more things: a handler in which we define the behavior of  our part, and the view (shape) where we create the HTML markup for our form. The handler looks like:

[UsedImplicitly]
public class ShareBarSettingsPartHandler : ContentHandler {
    public ShareBarSettingsPartHandler(IRepository<ShareBarSettingsPartRecord> repository)
    {
        Filters.Add(new ActivatingFilter<ShareBarSettingsPart>("Site"));
        Filters.Add(StorageFilter.For(repository));
    }
}

And in almost all cases it will be exactly like that. There are two things we’ve done here:

  1. Adding an activating filter, which will tell Orchard to which of the existing content types our ShareBarSettingsPart should be attached to. Site is also a content type, so we attach the part to it. Basically, this is the point that differentiates the ordinary content parts from site settings.
  2. Adding the storage filter to register our settings repository, which will be storing records in the database.

So we’ve got one thing left – the .cshtml shape file with HTML markup – and we’re done. First, you have to create a .cshtml file under /Views/EditorTemplates/Parts/. This file, as the naming convention forces us, should be named Share.Settings.cshtml. This name corresponds to the Parts_Share_Settings shape which we used inside the driver above.

image

We’ve got only a single field (AddThisAccount) in our settings, so the markup inside the Share.Settings.cshtml file will look like this:

@model Szmyd.Orchard.Modules.Sharing.Models.ShareBarSettingsPart <fieldset> <legend>@T("Content sharing settigs")</legend> <div> @Html.LabelFor(m => m.AddThisAccount, @T("AddThis service account"))
        @Html.EditorFor(m => m.AddThisAccount)
        @Html.ValidationMessageFor(m => m.AddThisAccount, "*")
    </div> </fieldset> 

In order to make this visible we’ve got to tell Orchard where in the Site –> Settings pane our form should be displayed. To achieve this, create a Placement.info file in your module root with:

<Placement>  <Place Parts_Share_Settings="Content:0"/> </Placement> 

which will tell Orchard to display our form field at the beginning. We’re now done with creating our settings, but how to use them?

Using site scope settings

This part is basically a one-linerUśmiech:

var shareSettings = _services.WorkContext.CurrentSite.As<ShareBarSettingsPart>();

Where _services is the IOrchardServices object (eg. injected in the constructor). Simple, isn’t it?Uśmiech The full (simplified for readability) example from the ShareBarDriver from Content Sharing module:

[UsedImplicitly]
public class ShareBarPartDriver : ContentPartDriver<ShareBarPart>
{
    private readonly IOrchardServices _services;

    public ShareBarPartDriver(IOrchardServices services)
    {
        _services = services;
    }

    protected override DriverResult Display(ShareBarPart part, string displayType, dynamic shapeHelper)
    {
        var shareSettings = _services.WorkContext.CurrentSite.As<ShareBarSettingsPart>();

        // Prevent share bar from showing if account is not set if (shareSettings == null || string.IsNullOrWhiteSpace(shareSettings.AddThisAccount))
            return null;
        
        return ContentShape("Parts_Share_ShareBar",
                        () => shapeHelper.Parts_Share_ShareBar(Account: shareSettings.AddThisAccount));
    }
}

Hope you found this helpful!

The next part of this short series, as stated at the beginning, will cover the more fine-grained settings - content-type scoped.

Next part: Using custom settings in Orchard, Part 2: Content type settings

Cheers!

3 Comments

  • Ed M. said

    I am attempting to do sort of the same thing. Except I am not editing filling in the setting info via the gui. How do I create the setting via code.

      var settings = _orchardService.WorkContext.CurrentSite.As<CourseSettingsPart>();
      settings.Record.NextCourseUpdate = DateTime.UtcNow.AddDays(1);
    

    How do I persist this record to the DB?

  • Gideon Israel Dsouza said

    Agree with Skywalker. Thanks so much for this.

    @Ed you could get an IRepository from any constructor, it has a Create() method.