Easy way to manage global settings in multilanguage EPiServer website

There are many different ways to handle global settings in an EPiServer website. Common ones include keeping them in a separate tab on the EPiServer site’s start page, creating a separate settings pages, or using plugins for storing them directly in the EPiServer database.

To make things more manageable for a client’s EPiServer website we recently did a little work migrating their global settings form a start page tab to a separate page instance. To avoid the tedious task of finding and fetching this settings page every time a setting was needed, my collegue Thomas Durrani from Atiendo Consulting had come up with a really neat solution.

NOTE: Also see this new article for an updated and slightly extended version. Manage EPiServer global settings in multilanguage multisite environment.

A specific settings page instance for EPiServer settings

First, just a simple code snippet for a settings page. I’ve removed all project specific base classes to make things more clear.

SettingsPage.cs

[ContentType(
  GUID = "3355A8F1-FA58-480E-AAAA-4E2BB8B985B1",
  DisplayName = "[Global Settings]",
  Description = "Some description.",
  AvailableInEditMode = false,
  Order = 9999)]
public class SettingsPage : PageData
{
  [Display(
    Name = "A global setting",
    Description = "Description of a global setting.",
    GroupName = "Some tab",
    Order = 10)]
    public virtual int IntegerSetting { get; set; }

Easy way of accessing Global Settings in an EPiServer website

So, instead of looking for a ContentReference to our settings page instance and loading it through EPiServer’s ContentLoader before being able to access the data, we wanted to be able to do something like below.

var setting = GlobalSettings.Instance.IntegerSetting;

A static instance of the settings page from which we could just retrieve the values.

To achieve this we needed a GlobalSettings class with a static Instance property (33-36). We also needed EPiServer’s ContentRepository as well as ContentTypeRepository (17-31).

GlobalSettings.cs

public class GlobalSettings
{
  private static readonly Lazy<IContentRepository> _contentRepository =
    new Lazy<IContentRepository>(() => ServiceLocator.Current.GetInstance<IContentRepository>());

  private static readonly Lazy<IContentTypeRepository> _contentTypeRepository =
    new Lazy<IContentTypeRepository>(() => ServiceLocator.Current.GetInstance<IContentTypeRepository>());
		
  private static IContentTypeRepository ContentTypeRepository
  {
    get { return _contentTypeRepository.Value; }
  }

  private static IContentRepository ContentRepository
  {
    get { return _contentRepository.Value; }
  }

  public static SettingsPage Instance
  {
    get { return GetOrCreateSettingsPage(); }
  }

Since we don’t want to bother with setting up the settings page manually, the first time that the Instance property is accessed, the settings page will be automatically created (63-66). The Settings page will be created underneath the Root page, on the same level as the start page. In order to retrieve the settings page we use a simple GetChildren call with an auto detected language selector (47-49).

private static SettingsPage GetOrCreateSettingsPage()
{
  var contentLink = GetOrCreate<SettingsPage>(ContentReference.RootPage, DefaultPageName);
  return ContentRepository.Get<SettingsPage>(contentLink);
}

private static ContentReference GetOrCreate<TContentType>(ContentReference parentReference, string name)
  where TContentType : IContentData
{
  var page = ContentRepository
              .GetChildren<TContentType>(parentReference, LanguageSelector.AutoDetect(true))
              .FirstOrDefault(x => ((IContent)x).Name == name);
  if (page != null)
  {
    // Settings page exists.
    var existingReference = ((IContent)page).ContentLink;
    return existingReference;
  }

  // Create new Settings page.
  var clone = ContentRepository.GetDefault<TContentType>(parentReference);
  var content = ((IContent) clone);
  content.Name = name;
  var reference = ContentRepository.Save(content, SaveAction.Publish, AccessLevel.NoAccess);
  return reference;
}

Once we get hold of the existing ContentReference, or have created a new one, we return it to fetch the page. Of course you could add caching if you can’t take the overhead; just remember the language considerations. On the other hand, EPiServer does a good job by itself.

private static string DefaultPageName
{
  get
  {
    var pageType = ContentTypeRepository.Load<SettingsPage>();
    return pageType.DisplayName;
  }
}

The default page name is just the name of the ContentType SettingsPage, which in this example is [GlobalSettings].