What you are asking is indeed possible in Windsor with typed factories; instead of resolving all the factories in your provider and then looking for the ones that can process the message, you could ask Windsor for the handler that is linked to the message type and just use it. You don't really need the second level factory (IMessageHandlerFactory
), because the handler can tell what message it will link to.
Here is a nice resource for this architecture (you've probably read this one already) which I'll summarize very quickly.
Given your interfaces, you start by registering all your handlers
container.Register(Classes.FromAssemblyInThisApplication()
.BasedOn<IMessageHandler>()
.WithServiceAllInterfaces());
Ok, now let's tell Windsor we want a factory that will return a IMessageHandler
. What is nice is that we don't actually have to code anything for the factory.
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>().AsFactory());
Now we can start using the factory
var provider = container.Resolve<IMessageHandlerProvider>();
var msg = new Type2Message();
var msgHandler = provider.Create(msg.MessageType);
The problem is that since there is no link between our message handlers and the string we pass to the factory, Windsor returns the first registered instance of a IMessageHandler
it finds. In order to create this link we can name each message handler after the message type it is supposed to handle.
You can do it in a variety of ways, but I like to create a convention where a message handler type tells what messages it can handle:
container.Register(Classes.FromAssemblyInThisApplication()
.BasedOn<IMessageHandler>()
.WithServiceAllInterfaces().Configure(c => {
c.Named(c.Implementation.Name.Replace("MessageHandler", string.Empty));
}));
Now you need to tell your factory that the message type must be used as the name of the handler you want to resolve. To do that, it is possible to use a class inheriting the DefaulTypedFactoryComponentSelector
. We just override the way component names are determined and return the message type we are receiving:
public class MessageHandlerSelector : DefaultTypedFactoryComponentSelector
{
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
return arguments[0].ToString();
}
}
Now we can plug this selector in the factory
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>()
.AsFactory(c =>c.SelectedWith(new MessageHandlerSelector())));
Here is the full code to handle any messages:
var container = new WindsorContainer();
container.Register(Classes.FromAssemblyInThisApplication()
.BasedOn<IMessageHandler>()
.WithServiceAllInterfaces().Configure(c => {
c.Named(c.Implementation.Name.Replace("MessageHandler", string.Empty));
}));
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>().AsFactory(c =>c.SelectedWith(new MessageHandlerSelector())));
var provider = container.Resolve<IMessageHandlerProvider>();
var msg = new Type2Message();
var msgHandler = provider.Create(msg.MessageType);
msgHandler.Process(msg);
Here are some points I would like to underline:
- as you guessed, you don't need the two factories: one is enough
- the naming convention for the message handlers is not set in stone, and you could decide to have another mechanism in place to override the convention
- I am not talking about releasing the components, but the links contain some info about it which you should look into
- I didn't handle the case where no handler can be found, but Castle will throw by itself when it cannot resolve the handler with a
ComponentNotFoundException
- The system could perhaps be more robust if the handlers were explicit about the message types they handle. For example changing the interface to
IHandlerOf<T>
with T
being a message type implementation.