Written by 22:41 Containers, Tools & technologies

Comparison of 6 Best Dependency Injection (DI) Inversion of Control (IoC) Containers

I have often questioned myself which IoC container would be suitable for this or that project best. Their performance is only one side of the coin. The other side of the coin is the simplicity and speed of learning. So, I decided to compare the following containers from this point of view: Autofac, Simple Injector, StructureMap, Ninject, Unity, and Castle Windsor. In my opinion, these are the most popular IoC containers. You can find some of them in the list of the top 20 NuGet IoC packages. Also, I added a few containers based on my personal preferences. I really like Autofac and when writing this article I was reinforced in my choice in most of the cases.

In this article, I will describe the basics of the IoC containers, such as configuration and logging of components. I also want to compare the management of lifetime scope and advanced features. Code examples can be found in the LifetimeScopesExamples GitHub repository.

IOC Documents

When writing this article, I needed to refer to the documentation of certain IoCs. Unfortunately, not every IoC container has a good description and I had to google for a solution. I made up the following table with the summary of my searches.

[table id=24 /]

You can chase up my words – here are links to the documentation files:

IOC Container Configuration

In this article, I do not consider the XML configuration. All examples describe the frequent cases of configuring IoC containers through their interface. Here, you will find the following:

  • Constructor injection.
  • Property injection.
  • Method injections.
  • Expression logging – allows specifying additional creation logic.
  • Conventional logging – allows logging everything automatically.
  • Module logging – allows specifying a class that encapsulates configuration.

The purpose of the article is to provide working examples for each of the cases. Such complex scenarios as parameterized loggings are beyond the scope of this article.

Object Model and Test Scenario Model

I created a simple model for checking the IoC containers. There are several modifications of it to use the property and method injections. Some of the IoC containers require usage of special attributes for initializing through properties or methods. I described it explicitly in each section.

/*************
* Interfaces *
**************/
public interface IAuthorRepository{
    IList<Book> GetBooks(Author parent);
}

public interface IBookRepository{
    IList<Book> FindByParent(int parentId);
}

public interface ILog{
    void Write(string text);
}
/***********************************************
* Implementation for injection via constructor *
***********************************************/
internal class AuthorRepositoryCtro : IAuthorRepository{
    private readonly IBookRepository _bookRepository;
    private readonly ILog _log;
    public AuthorRepositoryCtro(ILog log, IBookRepository bookRepository)    {
        _log = log;
        _bookRepository = bookRepository;
    }
    public IList<Book> GetBooks(Author parent)    {
        _log.Write("AuthorRepository:GetBooks()");
        return _bookRepository.FindByParent(parent.Id);
}}

internal class BookRepositoryCtro : IBookRepository{
    private readonly ILog _log;
    public BookRepositoryCtro(ILog log)    {
        _log = log;
    }
    public IList<Book> FindByParent(int parentId)    {
        _log.Write("BookRepository:FindByParent()");
        return null;
}}

internal class ConsoleLog : ILog{
    public void Write(string text)    {
        Console.WriteLine("{0}", text);
}}

The test script creates a container and retrieves an object from it twice to view how their timelife scope management works.

private static void Main(string[] args){
    var resolver = Configuration.Simple();    
    /***********************************************************
     * both resolving use the same method of IBookRepository   *
     * it depends on lifetime scope configuration whether ILog *
     * would be the same instance (the number in the output    *
     * shows the number of the instance)                       *
     ***********************************************************/
    // the 1st resolving
    var books = resolver.Resolve<IAuthorRepository>().GetBooks(new Author());
    // the 2nd resolving
    resolver.Resolve<IBookRepository>().FindByParent(0);
    System.Console.WriteLine("Press any key...");
    System.Console.ReadKey();
}

Constructor Injection

In its basic version, the configuration does not require any special attributes or names.

Autofac Constructor Injection

var builder = new ContainerBuilder();
builder.RegisterType<AuthorRepositoryCtro>().As<IAuthorRepository>();
builder.RegisterType<BookRepositoryCtro>().As<IBookRepository>();
builder.RegisterType<ConsoleLog>().As<ILog>();
var container = builder.Build();

Simple Injector Constructor Injection

var container = new Container();
container.Register<IAuthorRepository, AuthorRepositoryCtro>();
container.Register<IBookRepository, BookRepositoryCtro>();
container.Register<ILog, ConsoleLog>();

StructureMap Constructor Injection

var container = new Container();
container.Configure(c =>
{
    c.For<IAuthorRepository>().Use<AuthorRepositoryCtro>();
    c.For<IBookRepository>().Use<BookRepositoryCtro>();
    c.For<ILog>().Use<ConsoleLog>();
});

Ninject Constructor Injection

var container = new StandardKernel();
container.Bind<IAuthorRepository>().To<AuthorRepositoryCtro>();
container.Bind<IBookRepository>().To<BookRepositoryCtro>();
container.Bind<ILog>().To<ConsoleLog>();

Unity Constructor Injection

var container = new UnityContainer();
container.RegisterType<IAuthorRepository, AuthorRepositoryCtro>();
container.RegisterType<IBookRepository, BookRepositoryCtro>();
container.RegisterType<ILog, ConsoleLog>();

Castle Windsor Constructor Injection

var container = new WindsorContainer();
container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryCtro>());
container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryCtro>());
container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>());

Property Injection

Some IoC containers require the use of special attributes that help to identify properties for initialization. Personally, I do not like the approach since the object model and IoC container become strongly related. Ninject requires the usage of the [Inject] attribute, Unity requires the [Dependency] attribute. At the same time, Castle Windsor does not require anything to initialize properties, since it happens by default.

Autofac Constructor Injection

var builder = new ContainerBuilder();
builder.RegisterType<AuthorRepositoryCtro>().As<IAuthorRepository>().PropertiesAutowired();
builder.RegisterType<BookRepositoryCtro>().As<IBookRepository>().PropertiesAutowired();
builder.RegisterType<ConsoleLog>().As<ILog>();
var container = builder.Build();

Simple Injector Constructor Injection

It does not have inbuilt options for this, but you can use the configuration with help of expressions.

StructureMap Constructor Injection

var container = new Container();
container.Configure(c =>
{
    c.For<IAuthorRepository>().Use<AuthorRepositoryProp>();
    c.For<IBookRepository>().Use<BookRepositoryProp>();
    c.For<ILog>().Use(() => new ConsoleLog());
    c.Policies.SetAllProperties(x => {
        x.OfType<IAuthorRepository>();
        x.OfType<IBookRepository>();
        x.OfType<ILog>();
    });
});

Ninject Constructor Injection

var container = new StandardKernel();
container.Bind<IAuthorRepository>().To<AuthorRepositoryProp>();
container.Bind<IBookRepository>().To<BookRepositoryProp>();
container.Bind<ILog>().To<ConsoleLog>();

Unity Constructor Injection

var container = new UnityContainer();
container.RegisterType<IAuthorRepository, AuthorRepositoryProp>();
container.RegisterType<IBookRepository, BookRepositoryProp>();
container.RegisterType<ILog, ConsoleLog>();

Castle Windsor Constructor Injection

var container = new WindsorContainer();
container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryProp>());
container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryProp>());
container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>());

Method Injection

Similar to the previous method, this approach can help with circular references. From the other side, it creates the situation we need to avoid. In a few words, the API does not give any hint that such initialization is required to fully create an object. Here you can read more about temporal coupling.

Also, some IoC containers require the use of special attributes with the same cons. Ninject requires the [Inject] attribute for methods. Unity requires the usage of the [InjectionMethod] attribute.

All methods marked with such attributes will be executed when the object is created by the container.

Autofac

var builder = new ContainerBuilder();
builder.Register(c => {
    var rep = new AuthorRepositoryMtd();
    rep.SetDependencies(c.Resolve<ILog>(), c.Resolve<IBookRepository>());
    return rep;
}).As<IAuthorRepository>();
builder.Register(c => {
    var rep = new BookRepositoryMtd();
    rep.SetLog(c.Resolve<ILog>());
    return rep;
}).As<IBookRepository>();
builder.Register(c => new ConsoleLog()).As<ILog>();
var container = builder.Build();

Simple Injector

var container = new Container();
container.Register<IAuthorRepository>(() => {
    var rep = new AuthorRepositoryMtd();
    rep.SetDependencies(container.GetInstance<ILog>(), container.GetInstance<IBookRepository>());
    return rep;
});
container.Register<IBookRepository>(() => {
    var rep = new BookRepositoryMtd();
    rep.SetLog(container.GetInstance<ILog>());
    return rep;
});
container.Register<ILog>(() => new ConsoleLog());

StructureMap

var container = new Container();
container.Configure(c => {
    c.For<IAuthorRepository>().Use<AuthorRepositoryMtd>()
        .OnCreation((c, o) => o.SetDependencies(c.GetInstance<ILog>(), c.GetInstance<IBookRepository>()));
    c.For<IBookRepository>().Use<BookRepositoryMtd>()
        .OnCreation((c, o) => o.SetLog(c.GetInstance<ILog>()));
    c.For<ILog>().Use<ConsoleLog>();
});

Ninject

var container = new StandardKernel();
container.Bind<IAuthorRepository>().To<AuthorRepositoryMtd>()
    .OnActivation((c, o) => o.SetDependencies(c.Kernel.Get<ILog>(), c.Kernel.Get<IBookRepository>()));
container.Bind<IBookRepository>().To<BookRepositoryMtd>()
    .OnActivation((c, o) => o.SetLog(c.Kernel.Get<ILog>()));
container.Bind<ILog>().To<ConsoleLog>();

Unity

var container = new UnityContainer();
container.RegisterType<IAuthorRepository, AuthorRepositoryMtd>();
container.RegisterType<IBookRepository, BookRepositoryMtd>();
container.RegisterType<ILog, ConsoleLog>();

Castle Windsor

var container = new WindsorContainer();
container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryMtd>()
    .OnCreate((c, o) => ((AuthorRepositoryMtd) o).SetDependencies(c.Resolve<ILog>(), c.Resolve<IBookRepository>())));
container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryMtd>()
    .OnCreate((c, o) => ((BookRepositoryMtd)o).SetLog(c.Resolve<ILog>())));
container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>());

Expression Logging

Most of the cases in previous sections represent logging with lambda expressions or delegates. This way of logging method helps to add some logic at the moment of object creation, but it is not a dynamic approach. For dynamics, parameterized logging is the best choice that allows creating various implementations of the same components in the run-time.

Autofac

var builder = new ContainerBuilder();
builder.Register(c => new AuthorRepositoryCtro(c.Resolve<ILog>(), c.Resolve<IBookRepository>()))
       .As<IAuthorRepository>();
builder.Register(c => new BookRepositoryCtro(c.Resolve<ILog>()))
       .As<IBookRepository>();
builder.Register(c => new ConsoleLog()).As<ILog>();
var container = builder.Build();

Simple Injector

var container = new Container();
container.Register<IAuthorRepository>(() => 
      new AuthorRepositoryCtro(container.GetInstance<ILog>(), container.GetInstance<IBookRepository>()));
container.Register<IBookRepository>(() =>
      new BookRepositoryCtro(container.GetInstance<ILog>()));
container.Register<ILog>(() => new ConsoleLog());

StructureMap

var container = new Container();
container.Configure(r => {
    r.For<IAuthorRepository>()
         .Use(c => new AuthorRepositoryCtro(c.GetInstance<ILog>(), c.GetInstance<IBookRepository>()));
    r.For<IBookRepository>()
         .Use(c => new BookRepositoryCtro(c.GetInstance<ILog>()));
    r.For<ILog>().Use(() => new ConsoleLog());
});

Ninject

var container = new StandardKernel();
container.Bind<IAuthorRepository>().ToConstructor(c => 
          new AuthorRepositoryCtro(c.Inject<ILog>(), c.Inject<IBookRepository>()));
container.Bind<IBookRepository>().ToConstructor(c =>
          new BookRepositoryCtro(c.Inject<ILog>()));
container.Bind<ILog>().ToConstructor(c => new ConsoleLog());

or

container.Bind<IAuthorRepository>().ToMethod(c => 
              new AuthorRepositoryCtro(c.Kernel.Get<ILog>(), c.Kernel.Get<IBookRepository>()));
container.Bind<IBookRepository>().ToMethod(c =>
              new BookRepositoryCtro(c.Kernel.Get<ILog>()));
container.Bind<ILog>().ToMethod(c => new ConsoleLog());

Unity

var container = new UnityContainer();
container.RegisterType<IAuthorRepository>(new InjectionFactory(c =>
        new AuthorRepositoryCtro(c.Resolve<ILog>(), c.Resolve<IBookRepository>())));
container.RegisterType<IBookRepository>(new InjectionFactory(c =>
                                      new BookRepositoryCtro(c.Resolve<ILog>())));
container.RegisterType<ILog>(new InjectionFactory(c => new ConsoleLog()));

Castle Windsor

var container = new WindsorContainer();
container.Register(Component.For<IAuthorRepository>()
        .UsingFactoryMethod(c => new AuthorRepositoryCtro(c.Resolve<ILog>(), c.Resolve<IBookRepository>())));
container.Register(Component.For<IBookRepository>()
                    .UsingFactoryMethod(c => new BookRepositoryCtro(c.Resolve<ILog>())));
container.Register(Component.For<ILog>().UsingFactoryMethod(c => new ConsoleLog()));

Ninject has differences between configuration with ToMethod and ToConstructor. In short, when you use ToContructor, you can also use conditions. The following configuration won’t work for ToMethod.

Bind<IFoo>().To<Foo1>().WhenInjectedInto<Service1>();
Bind<IFoo>().To<Foo2>().WhenInjectedInto<Service2>();

Conventional Logging

In some cases, you don’t need to write configuration code at all. The general scenario looks in the following way: scanning assembly to find the required types, extraction of their interfaces and logging them in a container as the intreface-implementation pair. This may be useful for very large projects but can be hard for a developer who is unfamiliar with the project.

Autofac logs all possible implementations and saves them in an internal array. According to the documentation, it will use the last variant for resolving by default. Simple Injector does not have ready methods for automatic logging. Yo need to make it manually (an example is provided below). StructureMap and Unity require public implementation classes since their scanners are not visible to others. Ninject requires an additional NuGet package, Ninject.Extensions.Conventions. Also, it requires public implementation classes as well.

Autofac

var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();
var container = builder.Build();

Simple Injector

var container = new Container();
var repositoryAssembly = Assembly.GetExecutingAssembly();
var implementationTypes = from type in repositoryAssembly.GetTypes()
    where type.FullName.Contains("Repositories.Constructors")
          || type.GetInterfaces().Contains(typeof (ILog))
    select type;
var registrations =
    from type in implementationTypes
    select new { Service = type.GetInterfaces().Single(), Implementation = type };
foreach (var reg in registrations)
    container.Register(reg.Service, reg.Implementation);

StructureMap

var container = new Container();
container.Configure(c => c.Scan(x => {
    x.TheCallingAssembly();
    x.RegisterConcreteTypesAgainstTheFirstInterface();
}));

Ninject

var container = new StandardKernel();
container.Bind(x => x.FromThisAssembly().SelectAllClasses().BindDefaultInterfaces());

Unity

var container = new UnityContainer();
container.RegisterTypes(
    AllClasses.FromAssemblies(Assembly.GetExecutingAssembly()), 
    WithMappings.FromAllInterfaces);

Castle Windsor

var container = new WindsorContainer();
container.Register(Classes.FromAssembly(Assembly.GetExecutingAssembly())
    .IncludeNonPublicTypes()
    .Pick()
    .WithService.DefaultInterfaces());

Module Logging

Modules can help you to divide your configuration. You can group them by context (data access, business objects) or by purpose (production, test). Some of the IoC containers can scan assemblies in the search for their own modules.

Autofac

public class ImplementationModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<AuthorRepositoryCtro>().As<IAuthorRepository>();
        builder.RegisterType<BookRepositoryCtro>().As<IBookRepository>();
        builder.RegisterType<ConsoleLog>().As<ILog>();
    }
}
/*********
 * usage *
 *********/
var builder = new ContainerBuilder();
builder.RegisterModule(new ImplementationModule());
var container = builder.Build();

Simple Injector

There is nothing of this kind.

StructureMap

public class ImplementationModule : Registry
{
    public ImplementationModule()
    {
        For<IAuthorRepository>().Use<AuthorRepositoryCtro>();
        For<IBookRepository>().Use<BookRepositoryCtro>();
        For<ILog>().Use<ConsoleLog>();
    }
}
/*********
 * usage *
 *********/
var registry = new Registry();
registry.IncludeRegistry<ImplementationModule>();
var container = new Container(registry);

Ninject

public class ImplementationModule : NinjectModule
{
    public override void Load()
    {
        Bind<IAuthorRepository>().To<AuthorRepositoryCtro>();
        Bind<IBookRepository>().To<BookRepositoryCtro>();
        Bind<ILog>().To<ConsoleLog>();
    }
}
/*********
 * usage *
 *********/
var container = new StandardKernel(new ImplementationModule());

Unity

public class ImplementationModule : UnityContainerExtension
{
    protected override void Initialize()
    {
        Container.RegisterType<IAuthorRepository, AuthorRepositoryCtro>();
        Container.RegisterType<IBookRepository, BookRepositoryCtro>();
        Container.RegisterType<ILog, ConsoleLog>();
    }
}
/*********
 * usage *
 *********/
var container = new UnityContainer();
container.AddNewExtension<ImplementationModule>();

Castle Windsor

public class ImplementationModule : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryCtro>());
        container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryCtro>());
        container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>());
    }
}
/*********
 * usage *
 *********/
var container = new WindsorContainer();
container.Install(new ImplementationModule());
Tags: , Last modified: September 23, 2021
Close