Dependency injection
Describes how to use dependency injection.
Dependency injection (DI) in Optimizely Content Management System (CMS) is based on the DI framework in ASP.NET Core located inMicrosoft.Extensions.DependencyInjection API. CMS is configured to use the default DI implementation in ASP.NET Core. The required code inProgram.cs to connect the DI framework in ASP.NET Core with CMS is the call to the extension methodConfigureCmsDefault() as in the 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 toConfigureCmsDefault() with a call toUseServiceProviderFactory, passing in an instance ofServiceLocatorProviderFactoryFacade, 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 theServiceConfiguration attribute 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 as a transient service (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 registeringIService and its implementation Service using theIServiceCollection abstraction. Normally, service registrations are done in theStartup class or in an extension method likeAddCms. The service registration abstraction is also available in initialization modules which implement theIConfigurableModule interface. There are convenient extension methods in namespacesMicrosoft.Extensions.DependencyInjection, Microsoft.Extensions.DependencyInjection.Extensions andEPiServer.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 a context likeHttpContext.RequestServices. You can also use a static propertyServiceLocator.Current to access the DI container in the rare places where there is no other way to get dependencies. If you useServiceLocator.Current then if you use custom scopes, you should create the scopes using extension methodCreateServiceLocatorScope instead of the extension methodCreateScope within .NET Core. This gets the static accessorServiceLocator.Current to be aware of the custom-created scope.
Intercept existing services
Sometimes, it is useful to intercept service calls, such as debugging or adding logging. By using the extension methodIntercept<T> onIServiceCollection, you can replace existing services with another implementation. The provided factory method will have access to the default instance of the service.
If several implementations are registered for a service, registrations are intercepted. The interceptor service has the same scope as the intercepted service was registered with.
This example shows an interceptor registration of theISynchronizedObjectInstanceCache 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); }}Dependency injection use case examples
Optimizely CMS leverages Dependency Injection (DI) to manage the lifecycle and dependencies of services.
Starting from CMS 12, which is built on ASP.NET Core, Optimizely fully integrates with the ASP.NET Core DI framework.
The following sections show dependency injection patterns in Optimizely CMS:
Constructor injection
This is the most common and recommended pattern for DI in Optimizely CMS. You inject dependencies through the constructor of a class. The DI framework automatically provides the required services when the class is instantiated.
Best use case – When your service class has mandatory dependencies that are required to perform its operations.
public class MyService{ private readonly IContentLoader _contentLoader; public MyService(IContentLoader contentLoader) { _contentLoader = contentLoader; } public void SomeMethod() { var content = _contentLoader.Get<IContent>(contentLink); }}Property injection
In property injection, dependencies are injected into properties of the class, rather than through the constructor. The DI framework sets the properties after the object is created.
Best use case – When a dependency is optional, or you want to delay the injection until after the object has been constructed.
public class MyService{ [Inject] public IContentLoader ContentLoader { get; set; } public void SomeMethod() { var content = ContentLoader.Get<IContent>(contentLink); }}Method injection
In method injection, dependencies are provided as parameters to a specific method. This pattern is not as common as constructor injection but can be used when dependencies are only needed for specific methods.
Best use case – When a dependency is only required for a single method, or you want to provide different dependencies for different method calls.
public class MyService{ public void SomeMethod(IContentLoader contentLoader) { var content = contentLoader.Get<IContent>(contentLink); }}Scoped injection
Optimizely CMS supports the typical ASP.NET Core service lifetimes: Transient, Scoped, and Singleton. The Scoped lifetime is particularly useful in Optimizely for services that should be instantiated once per request. This is especially useful when dealing with HTTP context or request-specific data. See.NET dependency injection in the Microsoft documentation.
Updated 10 days ago