Hide menu Last updated: Jul 14 2016

Dependency injection

Dependency injection in Episerver products is based on StructureMap. It is possible to use either StructureMap directly or Episerver's StructureMap abstractions. The recommended approach is to use the abstractions documented in this topic.

Implicit registration of services

Below are examples of using implicit registration. To accomplish this, use the ServiceConfiguration attribute, which makes it easier to read classes since the registration is done in the same class as the implementation.
This 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 = ServiceInstanceScope.Singleton)]
    public class SingletonService : IService
    {
        public void Sample()
        {
            throw new NotImplementedException();
        }
    }

Explicit registration of services [New in 9.5]

The code below provides examples of registering IService and its implementation Service using the Episerver abstractions. The abstractions are available in initialization modules that implement the IConfigurableModule interface. 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 HTTP request or per thread if no HTTP request exist
            context.Services.AddHttpContextOrThreadScoped<IService, Service>();
        }
        public void Initialize(InitializationEngine context)
        {
        }
        public void Uninitialize(InitializationEngine context)
        {
        }
    }

Converting code using StructureMap to using the Episerver abstractions [New in 9.5]

The namespace EPiServer.ServiceLocator.Compatibility contains extension methods to the Episerver abstractions for some common StructureMap APIs. The namespace simplifies migration from StructureMap to the Episerver abstractions.

This example removes usage of the Container property, which is an instance of StructureMap.IContainer, and adds usage of the Services property, provided by Episerver. Compatibility extension methods, such as Configure, are used.

using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using EPiServer.ServiceLocation.Compatibility;
namespace EPiServerSamples
{
   [InitializableModule]
   public class ModuleWithServices : IConfigurableModule
   {
       public void ConfigureContainer(ServiceConfigurationContext context)
       {
           //Remove code using Container (StructureMap)
           //context.Container.Configure(c =>
           //{
           //    c.For<IService>().Use<Service>();
           //});
           context.Services.Configure(c =>
           {
               c.For<IService>().Use<Service>();
           });
       }
       public void Initialize(InitializationEngine context)
       {
       }
       public void Uninitialize(InitializationEngine context)
       {
       }
   }
}

ServiceLocator

To get an instance of a service without using dependency injection, use the ServiceLocator class and its Current property. Example:

var myService = ServiceLocator.Current.GetInstance<IService>();

Intercept existing services [New in 9.10]

There are occations where it is useful to intercept calls to a service. For example to debug or to add logging. By using IServiceConfigurationProvider.Intercept<T> method it is possible to replace existing services with an other implementation. The provided factory method will have access to the default instance of the service.

In case there are several implementations registered for a service then all registrations will be intercepted. The interceptor service will have 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);
    }
}

Integration with MVC 5 dependency resolver

Templates developed on Episerver can set StructureMap to resolve dependencies in controllers in MVC 5. The following code provides an example of integrating StructureMap. This sample is used in the Alloy sample templates.

    [InitializableModule]
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class DependencyResolverInitialization : IConfigurableModule
    {
        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            DependencyResolver.SetResolver(new StructureMapDependencyResolver(context.Container));
        }
        public void Initialize(InitializationEngine context)
        {
        }
        public void Uninitialize(InitializationEngine context)
        {
        }
        public void Preload(string[] parameters)
        {
        }
    }
    public class StructureMapDependencyResolver : IDependencyResolver
    {
        readonly IContainer _container;
        public StructureMapDependencyResolver(IContainer container)
        {
            _container = container;
        }
        public object GetService(Type serviceType)
        {
            try
            {
                return _container.GetInstance(serviceType);
            }
            catch (StructureMapException)
            {
                return null;
            }
        }
        public IEnumerable<object> GetServices(Type serviceType)
        {
            return _container.GetAllInstances(serviceType).Cast<object>();
        }
    }

Comments

This falls short of how to implement this, in say a seperate unit test project...How do I use the to configure the services in a seperate unit test project, such that I can do Integration tests, not just mocking unit tests.

When I´m using context.Service.Configure it´s says that Service is a pre-release API that is unstable and might not satisfy the compatibility requirements. Is it a pre-release still or is the note just not removed?

BR, Petra

It doesn´t look like we can use StructureMaps method Scan to automatically scan an assembly and automatically mapping interfaces to its concrete implementation if we use Services?

BR, Petra

The public IServiceConfigurationProvider Services still pre-released. If the target framework is NET45 the underlying container is structure maps and it same as use the Container properties. The  IServiceConfigurationProvider Services is layer between structuremap implementation (target NET45 framework) or IServiceCollection (DNX46 framewrok).

Thomas Skyldahl
Post by Thomas Skyldahl deleted, 5/24/2016 4:57:21 PM
Jonatan Dahl
Post by Jonatan Dahl deleted, 6/7/2016 2:34:43 PM

About the TryGetInstance -comment:

Does anyone have a sample of the StructureMapDependencyResolver -class where TryGetInstance isn´t used?

What would be the appropriate action if InvalidOperationException is thrown?

The example above has been updated so it no longer uses TryGetInstance. This example captures StructureMap exceptions (e.g. when requesting for something that is not registered) and returns null instead.

Aha great! Thanks!

I deleted my comment to avoid confusion now that the code has been updated

(By Johan Björnfot, 6/7/2016 3:17:45 PM)

The example above has been updated so it no longer uses TryGetInstance. This example captures StructureMap exceptions (e.g. when requesting for something that is not registered) and returns null instead.


Why return null? Why not throw an exception?

In  the case of returning null, it is likely to have hidden bugs with NullReferenceException. It would be better to throw an appropriate exception so the developer could fix it by adding missing service.