38

I see a fair few approaches in blog posts for using DI with 8.2, particularly around the registration. Is there an approach which would be recommended - particularly with a Helix compatible architecture?

  • I don't think I'm understanding your question. Do you mean an approach to registering an IoC container, to using the container that Sitecore provides in 8.2, to referencing particular dependencies for features in Helix, or for something else? – Zachary Kniebel Oct 4 '16 at 20:29
  • Sorry if it's not clear. There seems to be a range of options to registering dependencies. What would be a recommended approach in a Helix/Habitat architecture? I'm liking this approach, but just wondering if this is considered the recommended practice: ctor.io/… – Eldblom Oct 4 '16 at 20:34
  • @Eldblom the problem with the approach you linked to is that it breaks composition root. If you look at the sitecorenutsbolts.net article you linked to, that is almost identical but without breaking that pattern. You should not have to add a dependency to the container in your projects to enable DI. – Richard Seal Oct 5 '16 at 12:54
27

There are three aspects in choosing a DI approach with Sitecore 8.2:

  1. The decision of which dependency injection container to use.
  2. The way you integrate the container with Sitecore and ASP.NET MVC.
  3. The pattern in which service dependencies and implementations are registered.

Picking a DI container

Sitecore 8.2 uses IServiceProvider / IServiceCollection as its DI container abstraction. Microsoft's implementation of this container, Microsoft.Extensions.DependencyInjection.ServiceProvider, is used by default.

If you want, you can replace the default container completely, or you can override ASP.NET dependency resolvers. Either way, you're not bound to use the DI container Sitecore provides out of the box.

If you chose to use an alternative container, there's plenty to choose from.

The default container doesn't have many advanced features of the leading DI libraries. Just as an example, with Autofac you can mass-register all of your services like this:

builder
    .RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
    .Where(t =>t.GetInterfaces().Any(i => i.IsAssignableFrom(typeof(IService))))
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();

Also, SimpleInjector provides a way to quickly register all MVC Controllers in the container:

container.RegisterMvcControllers(Assembly.GetExecutingAssembly());

In contrast, with the default container you'd have to use quite a bit of custom code to achieve the same result.

As a conclusion, it seems to me that the most "standard" way is to use the default container shipped with Sitecore. It's the most basic way too, though, so you can gain a lot by replacing the container.

Integrating the container with Sitecore

Obviously, if you've chosen to go with the default container, you don't have to do anything.

If you want to use another DI container, you have two options:

  • Let Sitecore use their container internally and use you container of choice in your application code and in ASP.NET controllers. This is done by overriding ASP.NET dependency resolvers and making sure there's a fallback to Sitecore's resolvers. This was the commonly accepted approach before Sitecore 8.2, and it still works well today.
  • Replace the container completely. You do that by creating your own service provider builder and patching the <serviceProviderBuilder> node in Sitecore.config. The benefit of this approach is that your custom services, along with Sitecore's internal services, are now easily accessible, replaceable and injectable through your DI container of choice. There is only one DI container in the system, which makes things simpler and more convenient.

I don't think either of these approaches is standard. My personal preference is to completely replace the default container, as it requires less code, less configuration, and it doesn't leave two containers existing in parallel, thus avoiding confusion and errors.

Registering services in the container

This is the least opinionated part. In simple applications that don't have many modules, registering all services in the composition root is perfectly acceptable.

If you have many fine-grained components (which you'll definitely have if you use Helix), then it makes a lot of sense to separate service registrations from the composition root and delegate this task to each component.

As far as I'm aware, there are three main approaches here. To register component-specific services in your container, you can:

  • Create a separate configuration file (e.g. use <service> registrations with the default container) for every component.
  • Or use a separate configurator class per component (every modern DI library has these).
  • Or use a custom pipeline—this way, every container that needs to register its services will create its own processor included as a step in the pipeline. Here's another article that mentions the same approach.

I don't believe there's a clear winner here. You'll have to decide what fits best with your architecture and the structure of your solution.

| improve this answer | |
15

I'm not entirely sure there is a "recommended" approach - it mostly boils down to the container you'd like to use and the features you'd like to leverage from that container.

For instance, if you like non-conforming containers like Simple Injector and want to use the Verify() method on the container, then an approach like Rich's, in your third link, is a great choice. It's also a nice example of following the Composition Root pattern.

However, if you really just want basic DI and don't care much about extra features, then use the default container that Sitecore is using that leverages the new MS DI Framework.

Then, there are those that suggest it's entirely appropriate to keep your application container separate from your framework/platform container. That has a lot of merit as well.

So, in short (kindof), I'm not sure there is a single "right" or "recommended" approach.

| improve this answer | |
4

G Killian, I posted a question here about using SimpleInjector which you might find useful also: SimpleInjector as default DI in Sitecore 8.2

It contains a reply from Richard for how you go about swapping out the default DI in Sitecore 8.2 for SimpleInjector which is our preferred framework for our project for a number of reasons but mostly as it's fast and easy to use and has good functionality.

| improve this answer | |
4

A central challenge in respect to Habitat/Helix is the modular architecture - each module is self-contained and does not know of the other modules (and possibly the underlying framework).

Kevin Brechbühl handles the modular architecture, but adds a reference to the container in all modules. This tight coupling breaks the Composition Root pattern as described by Richard Seal. The approach described in Richards blog post removes this dependency (though retaining a dependency on MS DI Abstractions).

| improve this answer | |
2

As discussed in earlier answers, Kevin Brechbühl handles the modular architecture, but adds a reference to the container in all modules. This tight coupling breaks the Composition Root pattern as described by Richard Seal.

Composition Root patterns says “A DI Container should only be referenced from the Composition Root. All other modules should have no reference to the container.

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
  • etc.

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. But we got a way to do it with Microsoft Dependency Injection Abstractions.

We can use reflection to load or register all dependencies (attributed with specific type) at one place in Foundation module.

public static void AddClassesWithServiceAttribute(this IServiceCollection serviceCollection, params Assembly[] assemblies)
{
   var typesWithAttributes = assemblies
       .Where(assembly => !assembly.IsDynamic)
       .SelectMany(GetExportedTypes)
       .Where(type => !type.IsAbstract && !type.IsGenericTypeDefinition)
       .Select(type => new { type.GetCustomAttribute<ServiceAttribute>()?.Lifetime, ServiceType = type, ImplementationType = type.GetCustomAttribute<ServiceAttribute>()?.ServiceType })
       .Where(t => t.Lifetime != null);

   foreach (var type in typesWithAttributes)
   {
       if (type.ImplementationType == null
           serviceCollection.Add(type.ServiceType, type.Lifetime.Value);
       else
           serviceCollection.Add(type.ImplementationType, type.ServiceType, type.Lifetime.Value);
   }
}

And then we can write configurator, this will add our registrations to the IServiceCollection for the application.

namespace Sitecore.Foundation.DependencyInjection.Infrastructure
{
   using Microsoft.Extensions.DependencyInjection;
   using Sitecore.DependencyInjection;

   public class MvcControllerServicesConfigurator : IServicesConfigurator
   {
       public void Configure(IServiceCollection serviceCollection)
       {
          serviceCollection.AddMvcControllers("*.Feature.*");
          serviceCollection.AddClassesWithServiceAttribute("*.Feature.*");
          serviceCollection.AddClassesWithServiceAttribute("*.Foundation.*");
       }
   }
}

Now, we need to add the config for Sitecore to know about the configurator

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
   <sitecore>
       <services>
           <configurator type="Sitecore.Foundation.DependencyInjection.Infrastructure.MvcControllerServicesConfigurator, Sitecore.Foundation.DependencyInjection"/>
       </services>
   </sitecore>
</configuration>

You can find step by step implementation of this concept in Helix based solution here.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.