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: .net, ioc container Last modified: September 23, 2021