IoC comparison: Autoregistration in Castle Windsor

Tags: IoC, IocContainer, DependencyInjection, DI, IocComparison, code, github, Autoregistration, windsor, castle windsor

Castle Windsor emphasises autoregistration, probably more than any other .Net IoC container, and the API to do it is probably the most complete.

Windsor's explicitness about registration - the lack of automatic resolution of unregistered concrete types - turns out to be an asset with autoregistration. In a sense, automatic registration and automatic resolution are two different approaches to the same problem of convention over configuration in that they allow you to create types without mentioning them in the container configuration. But they don't play that well together, and can give confusing results.

What can happen in other containers is that you can't apply custom configuration (e.g. as singleton) to a type that is automatic resolved but not actually registered. For instance, in StructureMap or Unity, you can scan an assembly, and register all types in that assembly in the default way, specifying all as singletons, then create a concrete type (e.g. "Business Process" in my example) out of the assembly, but it is not created as a singleton because only interface types were registered, and the Business process does not have interfaces. Nevertheless, the container will resolve it automatically, but without any configuration. There is potential for silent misconfiguration. But this confusion does not occur with Windsor.

The registration in Windsor is done through a fluent interface, which has quite a few moving parts, but gets the job done. There are things that you have to learn or work out here - e.g. When there's no "Where" to filter the types, put in a "Pick()" instead to move to the next part of the fluent interface. The .WithService.FirstInterface()" construct is used to register types as the first implemented interface. For when there may be more than one interface, there's also .DefaultInterface() which will pick the interface that matches based on the type name, and .AllInterfaces() which does what it looks like.

The Where syntax to filter out registrations is simple. If you need to apply a configuration to all components, you use .Configure(component => component.LifeStyle.Singleton), and if you need an override for one component, you use .ConfigureFor<BusinessProcess>(component => component.LifeStyle.Transient). I wish the other mechanisms of the other containers were so simple and well-thought out.

Related links: The background of my tests for autregistration and the other IoC tests on Castle Windsor. Castle Windsor's auoregistration is mentioned here in Mike Hadlow's series on Windsor Tips and tricks.

The code for windsor's solution to these tests is here in the repository on Github, and is as below:

namespace IoCComparison.AutoregisterTests
{
    using AutoregisteredClasses.Interfaces;
    using AutoregisteredClasses.Services;
    using AutoregisteredClasses.Validators;
    using System.Linq;
    using Castle.MicroKernel.Registration;
    using Castle.Windsor;
    using NUnit.Framework;

    [TestFixture]
    public class WindsorTest
    {
        [Test]
        public void CanMakeBusinessProcess()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(
                AllTypes.FromAssembly(typeof(BusinessProcess).Assembly).Pick()
                .WithService.DefaultInterface());

            BusinessProcess businessProcess = container.Resolve<BusinessProcess>();

            Assert.IsNotNull(businessProcess);
        }

        [Test]
        public void CanMakeSingletonInstance()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(AllTypes.FromAssembly(typeof(BusinessProcess).Assembly).Pick()
                .WithService.DefaultInterface());

            BusinessProcess businessProcess1 = container.Resolve<BusinessProcess>();
            BusinessProcess businessProcess2 = container.Resolve<BusinessProcess>();

            Assert.AreSame(businessProcess1, businessProcess2);
        }

        [Test]
        public void CanMakeTransientInstance()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(AllTypes.FromAssembly(typeof(BusinessProcess).Assembly).Pick()
                .Configure(component => component.LifeStyle.Transient)
                .WithService.DefaultInterface());

            BusinessProcess businessProcess1 = container.Resolve<BusinessProcess>();
            BusinessProcess businessProcess2 = container.Resolve<BusinessProcess>();

            Assert.AreNotSame(businessProcess1, businessProcess2);
        }

        [Test]
        public void CanMakeTransientInstanceWithSingletonDependencies()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(AllTypes.FromAssembly(typeof(BusinessProcess).Assembly).Pick()
                // do the ConfigureFor before the Configure, or else it fails
                .ConfigureFor<BusinessProcess>(component => component.LifeStyle.Transient)
                .Configure(component => component.LifeStyle.Singleton)
                .WithService.DefaultInterface());

            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()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(AllTypes.FromAssembly(typeof(IValidator).Assembly).Pick()
                .WithService.DefaultInterface());

            var validators = container.ResolveAll<IValidator>();

            Assert.IsNotNull(validators);
            Assert.AreEqual(3, validators.Count());
        }

        [Test]
        public void CanFilterOutValidatorRegistrations()
        {
            WindsorContainer container = new WindsorContainer();
            container.Register(AllTypes.FromAssembly(typeof(IValidator).Assembly)
                .Where(t => t != typeof(FailValidator))
                .WithService.DefaultInterface());

            // excluding the FailValidator should leave 2 of them
            var validators = container.ResolveAll<IValidator>();

            Assert.IsNotNull(validators);
            Assert.AreEqual(2, validators.Count());
        }
    }
}