4
votes

In my application I have a class named Message. There exists a property in the Message class with the name MessageType of type string. The MessageType property is used to alert the application as to what data schema will exist within the instance of the Message class. The Message class derives from an interface named IMessage.

As an example let's say we have an instance of the Message class who's MessageType property has the value of "com.business.product.RegisterUser".

Each MessageType schema has a corresponding message handler class (MessageHandler) which derives from the interface IMessageHandler. In the case of the above example, there would be a class named RegisterUserMessageHandler. All message handler classes derive from IMessageHandler. IMessageHandler defines a GetMessageType function which will return the derived message instance’s MessageType property.

During registration of the classes / components with the Castle Windsor IoC container I would like to set the Name property to the value of the Message instance’s MessageType property. I would also like to register these classes / components using the ‘register components by conventions’ method to accomplish this.

The ultimate purpose of this exercise is to allow my code to call the Resolve method using the MessageType property of the Message to obtain the correct message handler instance. For example:

string messageType = "com.business.product.RegisterUser";

IMessageHandler registerUserMessageHandler = _container.Resolve<IMessageHandler>(messageType);

I would appreciate any help to solve this. Thanks very much.

2
The challenge I see with this is that the messageType (from your description) is an instance field. At the time you're registering components, you're just dealing with type information -- not actual instances. Or is the messageType static?PatrickSteele
Hi Patrick. The message type is a constant, so it could be defined in any number of ways. A private static const string in the class is a fine way to define it. I have also noticed references in example code to "attributes". I'm not sure what attributes are or represent, but it appears as if it may be a .NET class attribute. Not sure, but would appreciate any information anyone can provide.Raging Llama

2 Answers

3
votes

Here's a solution I came up with, but it does have some drawbacks. You'll have to decide if the drawbacks are acceptable for your situation. However, the benefit is that configuration is based on convention and adding new IMessage and IMessageHandler types won't require any additional Windsor configuration.

I used a combination of Windsor's TypedFactoryFacility along with a custom ITypedFactoryComponentSelector. By using a factory, I remove the need for your code to directly call the container's Resolve method. When a class needs to get an IMessageHandler based on a message type, it can just have a dependency on the factory. Here's the factory:

public interface IMessageHandlerFactory
{
    IMessageHandler GetMessageHandler(string messageType);
}

Since I'm using Windsor's TypedFactoryFacility I don't need to implement this method, but when it comes to calling the GetMessageHandler method, I'll "help" Windsor pick the proper component by defining a custom component selector:

public class HandlerTypeSelector : DefaultTypedFactoryComponentSelector
{
    private readonly WindsorContainer container;

    public HandlerTypeSelector(WindsorContainer container)
    {
        this.container = container;
    }

    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        if (method.Name == "GetMessageHandler")
        {
            var type = arguments[0].ToString();
            var messageHandlers = container.ResolveAll<IMessageHandler>();
            var single = messageHandlers.SingleOrDefault(h => h.GetMessageType() == type);
            if( single != null)
                return single.GetType().FullName;
        }

        return base.GetComponentName(method, arguments);
    }
}

Registering all of this is as simple as:

var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();

container.Register(
    Component.For<IMessageHandlerFactory>().AsFactory(c => c.SelectedWith(new HandlerTypeSelector(container))),
    AllTypes.FromThisAssembly().BasedOn<IMessage>().WithService.AllInterfaces(),
    AllTypes.FromThisAssembly().BasedOn<IMessageHandler>().WithService.AllInterfaces()
    );

Here's some test code I used to verify:

var sampleMessage = new RegisterUserMessage();
var factory = container.Resolve<IMessageHandlerFactory>();

var handler = factory.GetMessageHandler(sampleMessage.MessageType);

And the classes/interfaces I used to test with:

public interface IMessage
{
    string MessageType { get; }
}

public interface IMessageHandler
{
    string GetMessageType();
}

public class RegisterUserMessage : IMessage
{
    public string MessageType
    {
        get { return "RegisterUser"; }
    }
}

public class RegisterUserMessageHandler : IMessageHandler
{
    public string GetMessageType()
    {
        return "RegisterUser";
    }
}

public class RemoveUserMessage : IMessage
{
    public string MessageType
    {
        get { return "RemoveUser"; }
    }
}

public class RemoveUserMessageHandler : IMessageHandler
{
    public string GetMessageType()
    {
        return "RemoveUser";
    }
}    

Now, the drawback: Inside the component selector, I resolve all IMessageHandler's and then decide which one to use based on the name (since I didn't name the message handlers when registering them, they'll be registered in Windsor with their fully qualified name). If your message handlers are lightweight and/or there's not many of them, or they're static, this might not be an issue. If they are transient and/or have significant initialization code, resolving all of them every time you need a single one could be costly. In that case, I'd scrap this whole approach and go with something that is more configuration-heavy, but allows resolving a single message handler directly by name.

0
votes

Probably, this is a little bit late but, You could do like this:

foreach (string messageType in allMessages)
{
    container.Register(
        Component.For<IMessageHangler>()
        .ImplementedBy(GetImplementationFor(messageType))
        .Named(messageType);
    );
}

Type GetImplementationFor(string messageType)
{
    switch (messageType)
    {
        case "com.business.product.RegisterUser": return typeof(RegisterUserService);
        // more ....
        default: throw new Exception("Unhandled message type!");
    }
}

// In work:
void ProcessMessage(IMessage message)
{
    var handler = this.Container.Resolve<IMessageHandler>(message.MessageType);
    handler.handle(message);
}

But make sure, that since this approach You will not be able to register any other component with any of message types names, e.g.

container.Register<IFoo>()
.ImplementedBy<FooHandler>()
.Named("com.business.product.RegisterUser");

will throw an exception, despite that IFoo is not IMessageHandler.