Many times I asked myself what kind of IoC container would be suitable for a particular project. Their performance is only one side of the coin. Full performance comparison can be found here . The other side of the coin is simplicity and speed of learning. So I decided to compare several containers from this point of view and took Autofac, Simple Injector, StructureMap, Ninject, Unity, 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 packages, and I also added others according to my preferences. Personally, I really like Autofac and while working on this article, I became even more convinced that this is the best choice in most cases.
It describes the basics of IoC containers, such as configuration and component registration. There is also an idea to compare the management of lifetime scope and advanced features. Code samples can be found in the LifetimeScopesExamples GitHub repository .
While working on the article, I needed to refer to some of the IoC documentation. Unfortunately, not every IoC container has a good description and I had to look for a solution on Google. Thus the following summary turned out.
Quality | Comment | |
---|---|---|
Autofac | Super | Documentation contains everything you need. In addition, nothing had to google. Examples are clear and useful. |
Simple injector | Good | Documentation is similar to the previous one, but it looks a bit more raw. A few moments had to google, but the solution was quickly found. |
Structure Map | Average | Not all cases are described in the documentation. Descriptions for things like registering with expression, property and method injections are bad. It was necessary to google. |
Ninject | there is | Not all cases are described. Descriptions for things like registering with expression, property and method injections are bad. It was necessary to google. Solutions were sought hard. |
Unity | poorly | Despite the amount of text, the documentation is useless, because You have to understand the "sheets" of the text. All cases had to google, while they are difficult to find. |
Castle windsor | Average | Not all cases are described, or have incomprehensible examples. I had to google. |
Links to the documentation so you can see for yourself:
Here I do not consider configuration via XML. All examples describe the frequent cases of configuring IoC containers through their interface. Here you can find the following:
The purpose of the article is to provide working examples for each of the cases. Such complex scenarios as parameterized registrations are beyond the scope of this text.
In order to check the IoC containers, I created a simple model. There are several modifications to it to use property and method injection. Some of the IoC containers require the use of special attributes to initialize through properties or methods. I clearly wrote about this 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); }}
A test script creates a container and retrieves an object from it twice to see how their timelife scope management works. This will be the next article.
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(); }
The configuration for this does not require any special attributes or names in its basic version.
var builder = new ContainerBuilder(); builder.RegisterType<AuthorRepositoryCtro>().As<IAuthorRepository>(); builder.RegisterType<BookRepositoryCtro>().As<IBookRepository>(); builder.RegisterType<ConsoleLog>().As<ILog>(); var container = builder.Build();
var container = new Container(); container.Register<IAuthorRepository, AuthorRepositoryCtro>(); container.Register<IBookRepository, BookRepositoryCtro>(); container.Register<ILog, ConsoleLog>();
var container = new Container(); container.Configure(c => { c.For<IAuthorRepository>().Use<AuthorRepositoryCtro>(); c.For<IBookRepository>().Use<BookRepositoryCtro>(); c.For<ILog>().Use<ConsoleLog>(); });
var container = new StandardKernel(); container.Bind<IAuthorRepository>().To<AuthorRepositoryCtro>(); container.Bind<IBookRepository>().To<BookRepositoryCtro>(); container.Bind<ILog>().To<ConsoleLog>();
var container = new UnityContainer(); container.RegisterType<IAuthorRepository, AuthorRepositoryCtro>(); container.RegisterType<IBookRepository, BookRepositoryCtro>(); container.RegisterType<ILog, ConsoleLog>();
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>());
Some IoC containers require the use of special attributes that help recognize properties for initialization. I personally do not like this approach, since the object model and IoC container become strongly connected. Ninject requires the use of the attribute [Inject] , Unity requires the attribute [Dependency] . At the same time, Castle Windsor does not require anything to initialize properties, since it happens to him by default.
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();
, .
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>(); }); });
var container = new StandardKernel(); container.Bind<IAuthorRepository>().To<AuthorRepositoryProp>(); container.Bind<IBookRepository>().To<BookRepositoryProp>(); container.Bind<ILog>().To<ConsoleLog>();
var container = new UnityContainer(); container.RegisterType<IAuthorRepository, AuthorRepositoryProp>(); container.RegisterType<IBookRepository, BookRepositoryProp>(); container.RegisterType<ILog, ConsoleLog>();
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>());
This approach, like the previous one, can help with circular references. On the other hand, it introduces another point that should be avoided. In a few words, the API does not give any hint that such initialization is required to fully create an object. Here a little more about the temporal coupling .
There are also some IoC containers that require the use of special attributes with the same disadvantages. Ninject requires the [Inject] attribute for methods. Unity requires the use of the [InjectionMethod] attribute. All methods marked with such attributes will be executed at the moment the object is created by the container.
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();
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());
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>(); });
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>();
var container = new UnityContainer(); container.RegisterType<IAuthorRepository, AuthorRepositoryMtd>(); container.RegisterType<IBookRepository, BookRepositoryMtd>(); container.RegisterType<ILog, ConsoleLog>();
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>());
Most of the cases in the previous sections are nothing more than registering with lambda expressions or delegates. This method of registration will help you add some logic at the moment when objects are created, but this is not a dynamic approach. For dynamics, you should use parameterized registration to be able to create different implementations for one component at run-time.
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();
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());
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()); });
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());
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()));
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 configuring with ToMethod and ToConstructor . In a few words, when you use ToContructor you can also use conditions. The following configuration will not work for ToMethod .
Bind<IFoo>().To<Foo1>().WhenInjectedInto<Service1>(); Bind<IFoo>().To<Foo2>().WhenInjectedInto<Service2>();
In some cases, you do not need to write the configuration code at all. The general scenario is as follows: scanning the assembly to find the necessary types, retrieving their interfaces and registering them in a container, like an interface-implementation pair. This can be useful for very large projects, but it can be difficult for a developer unfamiliar with the project. A few points to remember.
Autofac registers all possible implementations and stores them in the internal array. In accordance with the documentation, it will use the most recent version for the default resolver. Simple Injector has no ready-made methods for automatic registration. You have to do it manually (example below). StructureMap and Unity require public implementation classes, since their scanners others do not see. Ninject requires the optional NuGet Ninject.Extensions.Conventions package. And it also requires public implementation classes.
var builder = new ContainerBuilder(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces(); var container = builder.Build();
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);
var container = new Container(); container.Configure(c => c.Scan(x => { x.TheCallingAssembly(); x.RegisterConcreteTypesAgainstTheFirstInterface(); }));
var container = new StandardKernel(); container.Bind(x => x.FromThisAssembly().SelectAllClasses().BindDefaultInterfaces());
var container = new UnityContainer(); container.RegisterTypes( AllClasses.FromAssemblies(Assembly.GetExecutingAssembly()), WithMappings.FromAllInterfaces);
var container = new WindsorContainer(); container.Register(Classes.FromAssembly(Assembly.GetExecutingAssembly()) .IncludeNonPublicTypes() .Pick() .WithService.DefaultInterfaces());
Modules can help you share 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 for their modules. Here I described the main way to use them.
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();
.
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);
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());
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>();
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());
In the following texts I will consider lifetime scope management and advanced features.
Source: https://habr.com/ru/post/302240/
All Articles