Configuring your Episerver libraries .NET Core way

Quite often when you develop a library, you need to run some initialization code or allow a library user to configure your it. Usually, developers add initializable modules in their libraries and use settings from Web.config to configure their libraries. However, these approaches have some drawbacks.

Drawbacks of initializable modules and configuration settings

The main drawback of initializable modules in libraries is that a library user cannot control when an initializable module is called. The user might want to disable initialization code too.

Another drawback is the startup time. Episerver scans for initializable modules, and it will take slightly more time to find and run those. If each small library would have an initializable module, then a more significant project which uses several libraries might be slow at startup.

Putting configuration settings in the Web.config forces to use only one way of configuration. If a developer wants to retrieve a configuration from another source (for example, database), then there is no way to do it. Also, in the future when we will get to .NET Core in Episerver, the library which forces usage of Web.config will not work anymore.

The new way

First of all, the configuration should be done in the code. This allows a developer to choose the source of configuration. In the simplest form, it is hardcoded in the code.

ASP.NET Core has an excellent example of how to achieve it. It uses ConfigureServices method for IoC configuration and Configure method for request handling (and other) configuration.

In Episerver, we have two similar extension points - IConfigurableModule for IoC configuration and IInitializableModule for other initialization code. Previously, we used to create own IConfigurableModule and IInitializableModule in our libraries, but instead, we should allow our library users to call library configuration code in their modules.

The usage of an imaginable library's - MyLibrary configuration might look like this:

[InitializableModule]
public class IoCModule : IConfigurableModule
{
  public void ConfigureContainer(ServiceConfigurationContext context)
  {
    context.AddMyLibrary();
  }
  public void Initialize(InitializationEngine context)
  {
  }
  public void Uninitialize(InitializationEngine context)
  {
  }
}

[InitializableModule]
[ModuleDependency(
  typeof(EPiServer.Web.InitializationModule),
  typeof(EPiServer.Commerce.Initialization.InitializationModule))]
public class AppInitialization : IInitializableModule
{
  public void Initialize(InitializationEngine context)
  {
    context.UseMyLibrary(settings => {
      settings.MyProperty = 20;
      return settings;
    });
  }

  public void Uninitialize(InitializationEngine context)
  {
  }
}

Here I am using the same naming as in ASP.NET Core - IoC configuration extension methods start with Add and configuration methods in an initializable module start with Use. In the initializable module, you will have an option to configure the library by passing an action with settings configuration as a parameter.

An extension for IoC configuration is simple. It just registers your classes in an IoC container.

public static class IoCExtensions
{
  public static void AddMyLibrary(this ServiceConfigurationContext context)
  {
    context.Services.AddTransient<IService, Service>();
  }
}

An extension with configuration is a little bit more complicated. First of all, you will need a settings class. You can add default settings in the constructor.

public class MySettings
{
  public MySettings()
  {
    MyProperty = 10;
  }
  public int MyProperty { get; set; }
}

Next, you need a class as an entry point for your library initialization. This class is responsible for passing settings to an appropriate destination. It could be some context class, storing in a database, passing settings to the service classes, etc.

public class MyLibraryInitializer
{
  IService _service;

  public MyLibraryInitializer(IService service, IService2 service2 ...)
  {
    _service = service;
  }

  public void Initialize(MySettings settings)
  {
    _service.AddMyProperty(settings.MyProperty);
  }
}

Now you can create an extension. This extension is just responsible for passing configured settings to the initializer.

public static class InitializationExtensions
{
  public static void UseMyLibrary(this this InitializationEngine context, Func<MySettings, MySettings> configure)
  {
    var settings = new MySettings();
    settings = configure(settings);

    var initializer = context.Locate.Advanced.GetInstance<MyLibraryInitializer>();
    initializer.Initialize(settings);
  }
}

Now with this approach developers are free to choose how and when to call the initialization code of your library.