Estimated reading time:
If you attended @seanholmseby’s session on making Habitat your home at Sitecore Symposium 2016, you will have heard him discuss dependency injection with Sitecore 8.2 and how you can use this in the Habitat Demo project or a Helix based project. One of the options discussed was to pass the container around in a pipeline and let each project register its own dependencies. This approach was also discussed by Kevin Brechbühl (@aquasonic for those on the slack chat). A new pipeline was created and called from the
Initialize pipeline and the container was part of the pipeline args. While this approach does work it breaks some of the principles that govern good DI practice.
In Mark Seemann’s book he describes the Composition Root pattern. Most agree that we should be using the Constructor Injection pattern when requiring dependencies, we have to also define an area that is responsible for composing those classes and their dependencies. Where should that be? Mark points out that this should be As close as possible to the application’s entry point. This place is called the Composition Root and is usually defined as:
A Composition Root is a (preferably) unique location in an application where modules are composed together.
The problem with the approach discussed above is that it requires your library to reference the container. This breaks the Composition Root pattern which specifies:
A DI Container should only be referenced from the Composition Root. All other modules should have no reference to the container.
And this makes sense - one of the purposes of Dependency Injection is to remove tightly coupled dependencies. Adding in a dependency to the container in our libraries goes against that purpose.
Pre-Habitat/Helix it was a fairly simple process to create a class in
App_Start or if you wanted to keep things ‘Sitecorey’ (not the same as @SiteCorey!) you could add a new processor to the
You can still do this with Helix. But with the Helix design patterns, we have multiple features/projects that all have dependencies. Also - we need to be able to easily add new features, remove features without having to update the main application project each time. In fact, if you look at the website project file for Habitat - it does not contain references to all the feature or foundation libraries.
Dependency Injection really fits in the Foundation layer just as in Kevin’s example. So how can we do this, but not add a dependency to the container in all the projects
With 8.2, Sitecore have embraced DI at last!
One of the features of the MS DI Abstractions is the new interface
IServiceCollection - this specifies the contract for a collection of service descriptors.
This will enable us to build a collection of our dependencies and services, without a direct reference to the container. Also the registrations are not created in our feature. The Sitecore application or our own pipeline can take care of that, obeying the Composition Root pattern.
So how does that work? We have a few options!
The simplest method to register your dependencies is to just use Sitecore’s own DI abstractions and container. Each foundation and feature project just needs a single class and some config. This has been covered in other posts, but for completeness here is the method. Lets update the Accounts Feature from the Habitat demo project.
First we need a configurator, this will add our registrations to the
IServiceCollection for the application:
Then we need to add the config for Sitecore to know about the configurator:
So simple right?
But why might we choose this option? The MS DI Container that Sitecore uses is fast, in test’s it is at least as fast as Simple Injector. Also - it’s Sitecore’s officially supported container. Sitecore use this to test their own code and so you are more likely to get a good response from support if you are using the same container as they do.
Another really good reason is if you want to use any of the Sitecore registrations in your code. These might be
BasePublishManager etc… There are a lot of them! They will all be available in the container.
But the conforming container used in the MS DI Abstractions does not have some advanced container features that you might want to use. So….
Many people already have DI setup in pre-8.2 projects. This could be using AutoFac, Simple Injector, Structure Map - there are a lot of options. So why keep your own container? Familiarity? Performance… or you might use some of the advanced container features that are not available in the MS DI container.
Also there is the argument that you should keep your framework and application containers separate. Whatever your reason, how can you do this and keep the composition root principle.
First we need to make sure that we register our dependencies and set the container before Sitecore does its thing. Why? Here is the code for the Sitecore dependency resolver:
You can see that the current
DependencyResolver is passed in and then stored as the
_innerResolver - then if the Sitecore container cannot resolve the type, it falls back to the
_innerResolver. This may have performance implications on your code, so you could write your own dependency resolver and fall back the other way if you prefer.
Now we can take Kevin’s approach and modify it so that instead of passing the container in the args, we pass an
IServiceCollection and use that to build our service descriptors.
The pipeline args now look like this:
We have to modify the
initialize processor to create a new
ServiceCollection and pass it into the args. Then after the pipeline has finished running, we will take those service descriptors and register them with the container. In conforming containers you can do
container.Populate and pass in the
IServiceCollecion. Simple Injector is not conforming right now so we have to do this bit ourselves. Here is the processor:
Finally we have to register the processor:
Now DI is setup for our own container, but the features need to configure thier own dependencies. So lets look at the Accounts module again and see how our configurator changes:
and our configuration changes to add the processor to the pipeline:
With the new DI features in 8.2, it is possible to build a Helix based site and still obey good DI principles. While we are adding a dependency on the MS DI Abstractions, we are not adding a dependency on the container and we are still registering as close to the application’s entry point as we can. At the same time we are keeping the component/modular based architecture of Helix intact and unpoluted.
So which option should you choose? Well I can’t make that descision for you, I think there are pro’s and con’s for both options presented here. There is also a 3rd option that I haven’t gone into where you can replace Sitecore’s container for another conforming container…. if you really really wanted to do that, it is an option. For me I think the safe option is to just use Sitecore’s own resolver/container. It is fast and makes the configuration simple. But if you want to use advanced features like Simple Injectors
.Verify() method (which btw has saved my bacon many times!) - option 2 is a good alternative.
I would love to hear your comments, suggestions on what you think or how this could be improved. Just add below or give me a shout on the Sitecore Slack channels - I’m @guitarrich. All the code for option 2 can be found on my fork of the habitat source: https://github.com/GuitarRich/Habitat/tree/feature/dependency-injetion-8.2.