Ioc Comparison: Autoregistration in Autofac
Autofac is a capable and interesting IoC container. I looked at Autofac for basic IoC here. Implementing my suite of autoregistration tests in Autofac did not require any custom extension classes, autofac handled everything that I asked of it.
Autofac adds registrations in an atomic manner, like StructureMap. But Autofac doesn't use lamdbas - you configure a ContainerBuilder in as many steps as needed, and then create a container from this builder in a single operation.
The entry point for autoregistration is ContainerBuilder.RegisterAssemblyTypes which takes a list of assemblies. From there you can use the fluent interface to filter types with Where() and with Except<T>. The Except<T> syntax is shorthand for a Where that excludes that type, i.e. Where(t =>t != typeof(T)). You register types AsImplementedInterfaces or AsSelf or As<T>. Singletons are specified with .SingleInstance() and transient instances are the default.
Putting that together, you get something like:
builder.RegisterAssemblyTypes(typeof (CustomerService).Assembly)
.Where(t => t.HasInterfaces())
.AsImplementedInterfaces().SingleInstance();
Which reads quite nicely. You can specify exceptions to the default with a different version of the Except<T> syntax, like this:
.AsImplementedInterfaces().SingleInstance()
.Except<BusinessProcess>(rb => rb.AsSelf());
I found other ways to do it, e.g. with two different scans of the assembly. In complex cases that may make sense, but where there are relatively few exceptions the Except<> syntax seems preferable.
Autofac does not do automatic resolution, which makes Autoregistration clearer – all types involved must be registered with some configuration. Other than that, there’s not a lot to explain which I think is a good thing for the usability and design of autoregistration in Autofac.
The code for the tests is maintained in the repository on github, but is as shown below:
namespace IoCComparison.AutoregisterTests
{
using System.Collections.Generic;
using System.Linq;
using Autofac;
using AutoregisteredClasses.Interfaces;
using AutoregisteredClasses.Services;
using AutoregisteredClasses.Validators;
using IoCComparison.AutoregisterTests.TypeExtensions;
using NUnit.Framework;
[TestFixture]
public class AutofacTest
{
[Test]
public void CanMakeBusinessProcess()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(CustomerService).Assembly)
.AsImplementedInterfaces()
.Except<BusinessProcess>(rb => rb.AsSelf());
IContainer container = builder.Build();
BusinessProcess businessProcess = container.Resolve<BusinessProcess>();
Assert.IsNotNull(businessProcess);
}
[Test]
public void CanMakeBusinessProcessTwoScans()
{
ContainerBuilder builder = new ContainerBuilder();
// not as good as the Except<T>(rb => ...) syntax
// go through the target assembly twice
// - once for object without interfaces, once for those with interfaces
builder.RegisterAssemblyTypes(typeof(BusinessProcess).Assembly).Where(t => ! t.HasInterfaces());
builder.RegisterAssemblyTypes(typeof(CustomerService).Assembly).AsImplementedInterfaces();
IContainer container = builder.Build();
BusinessProcess businessProcess = container.Resolve<BusinessProcess>();
Assert.IsNotNull(businessProcess);
}
[Test]
public void CanMakeBusinessProcessOneScan()
{
ContainerBuilder builder = new ContainerBuilder();
// not as good as the Except<T>(rb => ...) syntax
// instead of redundant scans, this version has redundant registrations
// e.g. CustomerService is registered as both CustomerService and ICustomerService,
// even though we only care about the latter
builder.RegisterAssemblyTypes(typeof(CustomerService).Assembly)
.AsImplementedInterfaces().AsSelf();
IContainer container = builder.Build();
BusinessProcess businessProcess = container.Resolve<BusinessProcess>();
Assert.IsNotNull(businessProcess);
}
[Test]
public void CanMakeSingletonInstance()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(CustomerService).Assembly)
.AsImplementedInterfaces().AsSelf()
.SingleInstance();
IContainer container = builder.Build();
BusinessProcess businessProcess1 = container.Resolve<BusinessProcess>();
BusinessProcess businessProcess2 = container.Resolve<BusinessProcess>();
Assert.AreSame(businessProcess1, businessProcess2);
}
[Test]
public void CanMakeTransientInstance()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(CustomerService).Assembly)
.AsImplementedInterfaces().AsSelf();
IContainer container = builder.Build();
BusinessProcess businessProcess1 = container.Resolve<BusinessProcess>();
BusinessProcess businessProcess2 = container.Resolve<BusinessProcess>();
Assert.AreNotSame(businessProcess1, businessProcess2);
}
[Test]
public void CanMakeTransientInstanceWithSingletonDependencies()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof (CustomerService).Assembly)
.Where(t => t.HasInterfaces())
.AsImplementedInterfaces().SingleInstance()
.Except<BusinessProcess>(rb => rb.AsSelf());
IContainer container = builder.Build();
BusinessProcess businessProcess1 = container.Resolve<BusinessProcess>();
BusinessProcess businessProcess2 = container.Resolve<BusinessProcess>();
Assert.AreNotSame(businessProcess1, businessProcess2);
Assert.AreSame(businessProcess1.CustomerService, businessProcess2.CustomerService);
Assert.AreSame(businessProcess1.OrderService, businessProcess2.OrderService);
}
[Test]
public void CanGetAllValidators()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(IValidator).Assembly).AsImplementedInterfaces();
IContainer container = builder.Build();
var validators = container.Resolve<IEnumerable<IValidator>>();
Assert.IsNotNull(validators);
Assert.AreEqual(3, validators.Count());
}
[Test]
public void CanFilterOutValidatorRegistrationsWithWhere()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(IValidator).Assembly)
.Where(t => t != typeof(FailValidator))
.AsImplementedInterfaces();
IContainer container = builder.Build();
// excluding the FailValidator should leave 2 of them
var validators = container.Resolve<IEnumerable<IValidator>>();
Assert.IsNotNull(validators);
Assert.AreEqual(2, validators.Count());
}
[Test]
public void CanFilterOutValidatorRegistrationsWithExcept()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(IValidator).Assembly)
.AsImplementedInterfaces()
.Except<FailValidator>();
IContainer container = builder.Build();
// excluding the FailValidator should leave 2 of them
var validators = container.Resolve<IEnumerable<IValidator>>();
Assert.IsNotNull(validators);
Assert.AreEqual(2, validators.Count());
}
}
}