Loading...
Area: Optimizely CMS
Applies to versions: 12 and higher
Other versions:

Dependency injection

Recommended reading 
Note: This documentation is for the preview version of the upcoming release of CMS 12/Commerce 14/Search & Navigation 14. Features included here might not be complete, and might be changed before becoming available in the public release. This documentation is provided for evaluation purposes only.

Dependency injection in Optimizely is based on the DI framework in .NET 5 located in the external-link.png Microsoft.Extensions.DependencyInjection API. By default, CMS is configured to use the default DI implementation in ASP.NET Core. The required code in Program.cs to connect the DI framework in ASP.NET Core with CMS is the call to the extension method ConfigureCmsDefault() as in example below:

public static IHostBuilder CreateHostBuilder(string[] args) =>
   Host.CreateDefaultBuilder(args)
      .ConfigureCmsDefaults()
      .ConfigureWebHostDefaults(webBuilder =>
      {
         webBuilder.UseStartup<Startup>();
      });

Custom Dependency Injection Frameworks 

To use a different DI framework than the built-in, you should replace the call to ConfigureCmsDefault() with a call to UseServiceProviderFactory, passing in an instance of ServiceLocatorProviderFactoryFacade, that should encapsulate the actual implementation to use. The following example shows how to configure the application to use Autofac (given that there is a reference to the Autofac NuGet package):

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(context => 
      new ServiceLocatorProviderFactoryFacade<ContainerBuilder>(context,
        new AutofacServiceProviderFactory()));
    .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.UseStartup<Startup>();
      });

Implicit registration of services

The following example shows how to use implicit registration by using the ServiceConfiguration attribute, which makes it easier to read classes because the registration is done in the same class as the implementation. The code is an example of registering a service, either as a transient service (new instances are created every time), or as a singleton service (a single instance is re-used).

public interface IService 
  { 
    void Sample(); 
  } 
  [ServiceConfiguration(ServiceType = typeof(IService))] 
  public class TransientService : IService 
  { 
    public void Sample() 
    { 
      throw new NotImplementedException(); 
    } 
  } 
  [ServiceConfiguration(ServiceType = typeof(IService), Lifecycle = ServiceLifetime.Singleton)] 
  public class SingletonService : IService 
  { 
    public void Sample() 
    { 
      throw new NotImplementedException(); 
    } 
  }
Explicit registration of services

The following code shows examples of registering IService and its implementation Service using the IServiceCollection abstraction. Normally service registrations are done either in the Startup class or in an extension method like AddCms. The service registration abstraction are also available in initialization modules which implement the IConfigurableModule interface. There are convenient extension methods in namespaces Microsoft.Extensions.DependencyInjectionMicrosoft.Extensions.DependencyInjection.Extensions and EPiServer.ServiceLocation. Inline comments describe the examples in more detail:

[InitializableModule] 
  public class ModuleWithServices : IConfigurableModule 
  { 
    public void ConfigureContainer(ServiceConfigurationContext context) 
    { 
      // Register IService1 with implementation Service1, 
      // create new instance every time context.Services.AddTransient<IService, Service>(); 

      // Register IService1 with implementation Service1, 
      // re-use a single instance context.Services.AddSingleton<IService, Service>(); 

      // Register IService1 with custom factory context.Services.AddTransient<IService>((locator) => new Service()); 

      // Register IService1 to be cached per scope, 
      // e.g. HTTP request context.Services.AddScoped<IService, Service>(); 
    } 
    public void Initialize(InitializationEngine context) 
    { 
    } 
    public void Uninitialize(InitializationEngine context) 
    { 
    } 
  }
ServiceLocator
You should use dependency injection through constructors or use the DI container from context like HttpContext.RequestServices. You can also use a static property ServiceLocator.Current to access the DI container in the rare places where there is no other way to get dependencies. If you use ServiceLocator.Current then if you use custom scopes, you should create the scopes using extension method CreateServiceLocatorScope instead of the extension method CreateScope within .NET Core. This gets the static accessor ServiceLocator.Current to be aware of the custom-created scope.

Intercept existing services

There are occasions when it is useful to intercept calls to a service; for example, to debug or to add logging. By using the extension method Intercept<T> on IServiceCollection, you can replace existing services with another implementation. The provided factory method will have access to the default instance of the service.

If there are several implementations registered for a service, all registrations are intercepted. The interceptor service has the same scope as the intercepted service was registered with.

This example shows an interceptor registration of the ISynchronizedObjectInstanceCache where the interceptor logs remote removals.

using EPiServer.Framework; 
using EPiServer.Framework.Cache; 
using EPiServer.Framework.Initialization; 
using EPiServer.Logging; 
using EPiServer.ServiceLocation; 
using System; 

namespace EPiServerSamples 
{ 
  [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 
  public class LoggingInitializer : IConfigurableModule 
  { 
    public void ConfigureContainer(ServiceConfigurationContext context) 
    { 
      context.Services.Intercept<ISynchronizedObjectInstanceCache>( 
        (locator, defaultCache) => new LoggingSynchronizedCache(defaultCache)); 
    } 
    public void Initialize(InitializationEngine context) 
    { 
    } 
    public void Uninitialize(InitializationEngine context) 
    { 
    } 
  } 
  public class LoggingSynchronizedCache : ISynchronizedObjectInstanceCache 
  { 
    private readonly ISynchronizedObjectInstanceCache _defaultCache; 
    private readonly ILogger _log = LogManager.GetLogger(typeof(LoggingSynchronizedCache)); 
    public LoggingSynchronizedCache(ISynchronizedObjectInstanceCache defaultCache) 
    { 
      _defaultCache = defaultCache; 
    } 
    public void RemoveRemote(string key) 
    { 
      if (_log.IsDebugEnabled()) 
          _log.Debug($"Remote removal called at '{DateTime.Now}' with key '{key}'"); 
      _defaultCache.RemoveRemote(key); 
    } 
    public IObjectInstanceCache ObjectInstanceCache => _defaultCache.ObjectInstanceCache;
    public FailureRecoveryAction SynchronizationFailedStrategy 
    { 
      get 
      { 
        return _defaultCache.SynchronizationFailedStrategy; 
      } 
      set 
      { 
        _defaultCache.SynchronizationFailedStrategy = value; 
      } 
    } 
    public object Get(string key) => _defaultCache.Get(key); 
    public void Insert(string key, object value, CacheEvictionPolicy evictionPolicy) => _defaultCache.Insert(key, value, evictionPolicy); 
    public void Remove(string key) => _defaultCache.Remove(key); 
    public void RemoveLocal(string key) => _defaultCache.RemoveLocal(key); 
  } 
}
Do you find this information helpful? Please log in to provide feedback.

Last updated: Jul 02, 2021

Recommended reading