Boost your Helix by overriding the default ServiceProvider with Unity!
Couple of weeks ago, Sitecore released its 9th version of its product. For those you haven't already checked it out,
I urge you to do so. There are a whole set of features that make development very enjoyable with an out of the box flavour.
Among those features, the already famous Sitecore Dependency Injection (DI) support out of the box since Sitecore 8.2 with
its Inversion of Control (IoC).
What if this default container does not fit your need for whatever reason but you still want to override the container the way Sitecore described it in its documentation?
This article provides you with a more in-depth "how-to" steps with detailed explanations and includes the link to my source code as well at the end of the article.
In this article
- What is expected from this article?
- Why choose a different container?
- Let's put it to work
- Let's wire it up together!
- Final touch
- Verification
- Conclusion
What is expected from this article?
After reading this article, you will be able to:
- Override Sitecore's Default DI container with another one of your liking
- No new pipelines added to make it work
- Configure your new DI container using Sitecore's Service Configurators.
Why choose a different container?
Sitecore have designed their framework to use dependency injection as standard everywhere as from Sitecore 8.2. It is based on
Microsoft.Extensions.DependencyInjection
which is pretty much performant and reliable and already used by Sitecore itself.
If you are curious about the performance comparison between Sitecore's in-built container and other popular containers,
you can have a look at this article.
If everything works already fine, why should I choose another one? Well, since Sitecore uses Microsoft's dependency injection, it is a basic container that allows you to use DI in its most simplistic way (register and consume). If your project requires more advanced features such as property injection or convention based registration, you might have to consider using a more fully featured IoC container.
There are many possibilities(e.g Autofac, Ninject) but amongst the list I randomnly picked Unity, so it will be the container we will be working on today.
"Aan de slag" - Let's put it to work!
The first and most important of all step is to ready Sitecore's documentation about its DI Container. It gives a pretty clear guideline on how to work and override it with your own.
You can replace the service provider. Sitecore uses the Microsoft.Extensions.DependencyInjection package default.
To replace default provider:
Inherit from Sitecore.DependencyInjection.BaseServiceProviderBuilder and implement the BuildServiceProvider method:
public class MyProviderBuilder : BaseServiceProviderBuilder
{
protected override IServiceProvider BuildServiceProvider(IServiceCollection serviceCollection)
{
return MyFavouriteServiceProvider.Build(serviceCollection);
}
}
Replace the default builder in Sitecore.config with your builder:
<serviceProviderBuilder type="Sitecore.DependencyInjection.DefaultServiceProviderBuilder, Sitecore.Kernel"/>
You can also patch this node with an include file:<serviceProviderBuilder type="MyProviderBuilder"/>
1. Create our custom ProviderBuilder:
Let's start by creating a scaffolding of our soon to be UnityServiceProvider. For now, I will make it return null because at this point the engine is not ready yet.
namespace Vanilla.DependencyInjection.DI.Provider
{
public class UnityServiceProviderBuilder : BaseServiceProviderBuilder
{
protected override IServiceProvider BuildServiceProvider(IServiceCollection serviceCollection)
{
return null;
}
}
}
2. Create our UnityServiceProvider.cs
class:
To be able to use Unity with Sitecore (following Sitecore's guideline), I need a custom IServiceProvider to act as a wrapper for my IUnityContainer. (ASP Documentation)
namespace Vanilla.DependencyInjection.DI.Unity
{
public class UnityServiceProvider : IServiceProvider
{
private readonly UnityContainer _container;
public UnityContainer Container => _container;
public UnityServiceProvider()
{
_container = new UnityContainer();
}
public object GetService(Type serviceType)
{
///Let the container resolve the service
return _container.Resolve(serviceType);
}
}
}
At this point, I have my custom IServiceProvider which will be used instead of the default one. It will be used at the later stage to wire up our Unity stuffs.
3. Resolving our controller
MVC uses an IControllerActivator interface to handle all the Controller instantiation. What does it means? By creating a custom controller activator, I do not need to load my current assemblies and load every controller to register them anymore.
namespace Vanilla.DependencyInjection.DI.Unity
{
public class UnityControllerActivator : IControllerActivator
{
private IUnityContainer _unityContainer;
public UnityControllerActivator(IUnityContainer container)
{
_unityContainer = container;
}
#region Implementation of IControllerActivator
public void Release(ControllerContext context, object controller)
{
//ignored
}
#endregion
public IController Create(RequestContext requestContext, Type controllerType)
{
return _unityContainer.Resolve(controllerType) as IController;
}
}
}
Important note: You can also use Kamsar's approach here by directly creating an extension to the ServiceCollection class and calling an extension method of the
ServiceCollection
to register all controllers from our ServiceConfigurator class.
But since I choose unity, I decided to go all the way up with the Unity!
4. Provide a fallback method for Sitecore's registered services.
At this point, we pretty much have it, but I would have a problem. If I use only my UnityServiceProvider
, my application will not be able to start.
The simple reason is that, Sitecore uses its default service provider to register and resolve its internal services.
I have two options, either, I re-register everything already register into unity again, or I can create
some sets of fallback methods to tell my custom service provider to try to resolve first using the Unity container and if not being able to resolve them, fallback to the default service provider to resolve them using the UnityContainerExtension.
- UnityFallBackProviderExtension
namespace Vanilla.DependencyInjection.DI.Unity
{
public class UnityFallbackProviderExtension : UnityContainerExtension
{
#region Const
///Used for Resolving the Default Container inside the UnityFallbackProviderStrategy class
public const string FALLBACK_PROVIDER_NAME = "UnityFallbackProvider";
#endregion
#region Vars
// The default Service Provider so I can Register it to the IUnityContainer
private IServiceProvider _defaultServiceProvider;
#endregion
#region Constructors
/// <summary>
/// Creates a new instance of the UnityFallbackProviderExtension class
/// </summary>
/// <param name="defaultServiceProvider">The default Provider used to fall back to</param>
public UnityFallbackProviderExtension(IServiceProvider defaultServiceProvider)
{
_defaultServiceProvider = defaultServiceProvider;
}
#endregion
#region Overrides of UnityContainerExtension
/// <summary>
/// Initializes the container with this extension's functionality.
/// </summary>
/// <remarks>
/// When overridden in a derived class, this method will modify the given
/// <see cref="T:Microsoft.Practices.Unity.ExtensionContext" /> by adding strategies, policies, etc. to
/// install it's functions into the container.</remarks>
protected override void Initialize()
{
// Register the default IServiceProvider with a name.
// Now the UnityFallbackProviderStrategy can Resolve the default Provider if needed
Context.Container.RegisterInstance(FALLBACK_PROVIDER_NAME, _defaultServiceProvider);
// Create the UnityFallbackProviderStrategy with our UnityContainer
var strategy = new UnityFallbackProviderStrategy(Context.Container);
// Adding the UnityFallbackProviderStrategy to be executed with the PreCreation LifeCycleHook
// PreCreation because if it isnt registerd with the IUnityContainer there will be an Exception
// Now if the IUnityContainer "magically" gets a Instance of a Type it will accept it and move on
Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
}
#endregion
}
}
- UnityFallbackProviderStrategy
namespace Vanilla.DependencyInjection.DI.Unity
{
public class UnityFallbackProviderStrategy : BuilderStrategy
{
private IUnityContainer _container;
public UnityFallbackProviderStrategy(IUnityContainer container)
{
_container = container;
}
#region Overrides of BuilderStrategy
/// <summary>
/// Called during the chain of responsibility for a build operation. The
/// PreBuildUp method is called when the chain is being executed in the
/// forward direction.
/// </summary>
/// <param name="context">Context of the build operation.</param>
public override void PreBuildUp(IBuilderContext context)
{
NamedTypeBuildKey key = context.OriginalBuildKey;
// Checking if the Type we are resolving is registered with the Container
if (!_container.IsRegistered(key.Type))
{
// If not we first get our default IServiceProvider and then try to resolve the type with it
// Then we save the Type in the Existing Property of IBuilderContext to tell Unity
// that it doesnt need to resolve the Type
context.Existing = _container
.Resolve<IServiceProvider>(UnityFallbackProviderExtension.FALLBACK_PROVIDER_NAME)
.GetService(key.Type);
}
// Otherwise we do the default stuff
base.PreBuildUp(context);
}
#endregion
}
}
We are almost there! With this implementation, I can "mix" dependencies. If I need any of my services AND an Ioptions interface from Sitecore, my unity container will resolve all of those and inject them into my controllers.
Important note: if the same interface is registered in both Unity and Sitecore's default DI Container, it will not try to resolve the service in the default container anymore as the unity container can already resolve it. It is then really important here to keep track of where you use the service.
5. Let's wire it up together!
namespace Vanilla.DependencyInjection.DI.Provider
{
public class UnityServiceProviderBuilder : BaseServiceProviderBuilder
{
protected override IServiceProvider BuildServiceProvider(IServiceCollection serviceCollection)
{
var unityServiceProvider = new UnityServiceProvider();
IUnityContainer container = unityServiceProvider.Container;
// Adding the Controller Activator
// Caution!!! Do this before you Build the ServiceProvider !!!
serviceCollection.AddSingleton(container);
serviceCollection.AddSingleton<IControllerActivator>(new UnityControllerActivator(container));
//Now build the Service Provider
var defaultProvider = serviceCollection.BuildServiceProvider();
// Configure UnityContainer
// #region Unity
//Add the Fallback extension with the default provider
container.AddExtension(new UnityFallbackProviderExtension(defaultProvider));
//Set the resolver to unity.
DependencyResolver.SetResolver(new UnityServiceLocator(container));
return unityServiceProvider;
}
}
}
Important note: If I start my application at this moment, Sitecore will fail (See next point for the explanation).
6. Final touch.
If you fire up your Sitecore instance after this, it will throw an issue not being able to resolve some of Sitecore's default.
The reason is that the BaseServiceProviderBuilder
class is missing a method that Sitecore uses to register these dependencies.
public override IEnumerable<IServicesConfigurator> GetServicesConfigurators()
{
foreach (IServicesConfigurator servicesConfigurator in base.GetServicesConfigurators())
yield return servicesConfigurator;
yield return (IServicesConfigurator) new ServicesScopeConfigurator();
}
I have two choices here: either, I copy this code an put in into my UnityServiceProvider
or just simply make the class inherit from
DefaultServiceProviderBuilder
instead of BaseServiceProviderBuilder
in order to avoid code redunduncy.
The final piece of code will look like:
namespace Vanilla.DependencyInjection.DI.Provider
{
public class UnityServiceProviderBuilder : DefaultServiceProviderBuilder
{
protected override IServiceProvider BuildServiceProvider(IServiceCollection serviceCollection)
{
var unityServiceProvider = new UnityServiceProvider();
IUnityContainer container = unityServiceProvider.Container;
// Adding the Controller Activator
// Caution!!! Do this before you Build the ServiceProvider !!!
serviceCollection.AddSingleton(container);
serviceCollection.AddSingleton<IControllerActivator>(new UnityControllerActivator(container));
//Now build the Service Provider
var defaultProvider = serviceCollection.BuildServiceProvider();
// Configure UnityContainer
// #region Unity
//Add the Fallback extension with the default provider
container.AddExtension(new UnityFallbackProviderExtension(defaultProvider));
//Set the resolver to unity.
DependencyResolver.SetResolver(new UnityServiceLocator(container));
return unityServiceProvider;
}
}
}
Verification
I've made quite some changes here, let's make sure that my overrides are how they are supposed to be, smoothly integrated into Sitecore.
In order to verify if everything is smooth, let's check the class diagrams
Voilà I'm all set! Ready to "Sitecore-rock" with Unity instead of Microsoft.Extensions.DependencyInjection
.
Conclusion
As you can see, overriding Sitecore's default DI is not really difficult. This article mainly serves as example but you would have to follow the same principle as stated above to override the DI container with the one of your liking.
I've also put the code base of this implementation on my github. You will find there more explanation on how to use it.