Ioc Comparison: Autoregistration in Autofac

Tags: IoC, IocContainer, DependencyInjection, DI, IocComparison, code, github, Autoregistration, 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());
        }

    }
}
Add a Comment