Advanced Castle Windsor: custom registration conventions for partially closed types

Jimmy published very interesting post about generic registration of certain partially closed types in StructureMap container. Go read the entire post first, it’s really worth it.

You’re back? OK. Jimmy uses StructureMap container in his sample, and after I saw it I immediately thought about doing the same thing in Windsor. That’s what this post is all about – how to make the following test pass:

Test

[Test]

public void Should_connect_delete_handler()

{

    var container = new WindsorContainer();

    container.AddFacility<HandlerFacility>();

    container.Register(AllTypes.FromAssemblyContaining<DeleteCustomerCommand>()

                        .BasedOn<IEntity>()

                        .BasedOn(typeof(IHandler<>)).WithService.Base()); // [a]

 

    var handler = container.Resolve<IHandler<DeleteEntityCommand<Customer>>>();

    Assert.IsInstanceOf<DeleteEntityCommandHandler<Customer>>(handler);

}

Solution

The line marked with [a] registers types implementing IHandler<>. It will register DeleteEntityCommandHandler<> with IHandler<>.  This is the equivalent of StructureMap’s ConnectImplementationToTypesClosing in Windsor.

As you can see I also registered a facility – HandlerFacility which does the actual magic, and is unsurprisingly quite similar to Jimmy’s DeleteCommandRegistrationConvention.

public class HandlerFacility : AbstractFacility

{

    private static readonly Type openDeleteCommandType = typeof(DeleteEntityCommand<>);

    private static readonly Type openHandlerInterfaceType = typeof(IHandler<>);

    private static readonly Type openDeleteCommandHandlerType = typeof(DeleteEntityCommandHandler<>);

 

    protected override void Init()

    {

        Kernel.HandlerRegistered += OnHandlerRegistered;

    }

 

    private void OnHandlerRegistered(IHandler handler, ref bool statechanged)

    {

        var type = handler.ComponentModel.Implementation;

        if (type.IsAbstract || typeof(IEntity).IsAssignableFrom(type) == false)

        {

            return;

        }

 

        var closedDeleteCommandType = openDeleteCommandType.MakeGenericType(type);

        var closedHandlerInterfaceType = openHandlerInterfaceType.MakeGenericType(closedDeleteCommandType);

        var closedDeleteCommandHandlerType = openDeleteCommandHandlerType.MakeGenericType(type);

 

        Kernel.Register(

            Component.For(closedHandlerInterfaceType)

                .ImplementedBy(closedDeleteCommandHandlerType)

                .LifeStyle.Transient);

    }

}

Just to set the record straight – you don’t really need a facility for that .You could hook an anonymous delegate to container kernel’s event and do that work inline. Having a facility is however my preferred solution since it encapsulates the work nicely, and gives you access to configuration, provided you’d ever want to externalize some part of the facility.

That’s all we needed to make the test pass:

sshot-1

Comments

Phil says:

Great article and encouraging to see a Castle contributor showing the Castle way so soon after Jimmy’s structure map post. But I have a couple of questions about the code. Why in the test code do you chain two BasedOn calls, surely only the .BasedOn<IEntity>() is necessary. Furthermore what is the difference between the BasedOn and Of methods when registering types ie.
AllTypes.Of<IService>().FromAssembly(a)

versus

AllTypes.FromAssembly(a).BasedOn<IEntity>()