In our previous article, we discussed one of the options to implement DI in sitecore, the idea was to pass the container around in a pipeline and let each module/feature/project register its own dependencies. This approach was previously discussed by Kevin Brechbühl. A new pipeline was created and called from the Initialize pipeline and the container was part of the pipeline arguments.

While this approach does work, it breaks some of the principles that govern good DI practice.

Problem Statement

The problem with the approach discussed previously 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.

Composition Root

The composition root is the single place in your application where the composition of the object graphs for your application take place, using the dependency injection container (although how this is done is irrelevant, it could be using a container or could be done manually using pure DI).

There should only be one place where this happens and your container should not need to be used outside of the composition root.

Quoting from one of the answers linked to below:

In practice, this means that you should configure the container at the root of your application.

  • In a desktop app, that would be in the Main method (or very close to it)
  • In an ASP.NET (including MVC) application, that would be in Global.asax
  • In WCF, that would be in a ServiceHostFactory

Problem Solution

The solution of this problem is to register all dependency at one place, which seems to be very difficult in component based architecture following Helix principles.  With 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.

Then how and where we can register all dependencies at one single centralized location? And there should not be any impact of adding or removing features. But we got a way to do it. We will use reflection to check all DLLs and register to the container at centralized location.

Microsoft Dependency Injection Abstractions is being used for this solution. We will go through it and try to understand how this problem can be resolved.

Very first thing is to add a reference to Microsoft dependency injection related DLLs. Below two DLLs would be required for the implementation.

  1. Extensions.DependencyInjection
  2. Extensions.DependencyInjection.Abstractions

As we already discussed in previous paragraph, we will be using reflection to load or register all dependencies at application startup. But how to determine, correct implementation for given type or interface to be resolved.

For this, we would use Attributes. We will create an attribute in Foundation layer and use that attribute on implemented class and define its interface type in other modules. Below is the sample code for that attribute in foundation layer “DependencyInjection” project.

And below is the definition of the Lifetime enumerator, used in ServiceAttribute, to define the lifetime for dependency resolution.

Next, we also need some extension methods on ServiceCollection class to make our life little easier. These extension methods can be found at below github location.

https://github.com/Sitecore/Habitat/blob/master/src/Foundation/DependencyInjection/code/ServiceCollectionExtensions.cs

I will try to explain the most important method in this extension class that register the dependencies based on ServiceAttribute discussed previously.

First we will see, how can we load all assemblies with the given filter using reflection? i.e. the filter “*.Feature.*”

This method will load all assemblies having “Feature” anywhere in its name. Next, we would analyze each assembly loaded previously and check whether it has the “ServiceAttribute” associated with it or not. If it has the ServiceAttribute, then we may need to register it with our container.

We would also need to know the implementation type of the current assembly that we need while registering dependency. If implementation types/interface is missing, we would register it with its type only.

Here, we are registering dependency to the service collection with or without implimentation type. And also setting its life time for each dependency.

Next, we will write configurator, this will add our registrations to the IServiceCollection for the application.

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.

Then we need to add the config for Sitecore to know about the configurator

A configurator is probably what you think of when you consider IoC configuration if you’ve been using any modern container library. It’s a C# class that conforms to an interface where you are given a container object, and expected to wire your dependencies to it. You can register as many configurators as you like in the <services> section

We have setup everything atleast for this “DependencyInjection” Foundation module. The overall project solution is looking like this.

Next we just need to use implemented attribute called “ServiceAttribute” to all other modules where we want to register the dependency. For this example we would be implementing it in “News” feature layer module.

With our configuration, we want to use constructor injection for all the Mvc controllers. For the NewsController this means we only need to remove this constructor:

The only constructor left is:

This is how it looks like in the updated NewsController.

References

Thanks to the Habitat team for the great sample demo site implementation following Helix principles based on component based architecture. It really helps us to understand how can we implement a solution that can be easily maintainable in sitecore.

Apart from this, I would also like to thank authors of below great articles that helps us to understand the concept behind implementing dependency injection in sitecore.

https://ctor.io/one-way-to-implement-dependency-injection-for-sitecore-habitat/

https://doc.sitecore.net/sitecore_experience_platform/developing/developing_with_sitecore/dependency_injection

https://kamsar.net/index.php/2016/08/Dependency-Injection-in-Sitecore-8-2/