So I have a marker interface called IMessage.Then I have classes like:
public class MessageA: IMessage
{
}
Then I have message handlers defined as:
internal interface IMessageHandler<in T> where T: IMessage
{
void Handle(T message);
}
public class MessageAHandler : IMessageHandler<MessageA>
{
public void Handle(T message)
{
//Some logic here
}
}
I want to re-route these messages that I get to the corresponding message handlers when I get a new message. Example:
public class MessageReceiver
{
public void ReceiveMessage(IMessage message)
{
//somehow resolve the appropiate message handler here
messageHandler.Handle(message);
}
}
I can accomplish this right now by using factories like below but I need to have a dependency on each a new factory per different type of message. So I'm wondering if there is a way to create a single factory that will be smart enough to resolve the appropiate message handler?
public class MessageReceiver
{
private readonly Func<IMessageHandler<MessageA>> _messageAFactory;
public MessageReceiver(Func<IMessageHandler<MessageA>> messageAFactory)
{
_messageAFactory= messageAFactory;
}
public void ReceiveMessage(IMessage message)
{
if (message is MessageA)
{
var messageHandler = _messageAFactory();
messageHandler.Handle(message as MessageA);
}
// Add more if-statements here for more messages
}
}
Autofac Registration
public class InfrastructureModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//Register the types in the infrastructure assembly
builder.RegisterAssemblyTypes(ThisAssembly).AsImplementedInterfaces()
.InstancePerLifetimeScope();
//Register the message handlers
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(x => x.IsAssignableFrom(typeof(IMessageHandler<IMessage>)))
.InstancePerDependency().AsImplementedInterfaces();
}
}
First I'll make a small implementation for your messages, just a basic handled flag, in order to test
public class MessageA : IMessage
{
public bool Handled
{
get;
private set;
}
public void MarkAsHandled()
{
this.Handled = true;
}
}
public class MessageB : IMessage
{
public bool Handled
{
get;
private set;
}
public void MarkAsHandled()
{
this.Handled = true;
}
}
Now let's implement both handlers as:
public class MessageAHandler : IMessageHandler<MessageA>
{
public void Handle(MessageA message)
{
message.MarkAsHandled();
}
}
public class MessageBHandler : IMessageHandler<MessageB>
{
public void Handle(MessageB message)
{
message.MarkAsHandled();
}
}
As a side note, you might want to mark your IMessageHandler interface as public (I get compiler error if visibility is set as internal).
Now let's add a small handler:
public interface IMessageHandler
{
Type MessageType { get; }
void Handle(IMessage message);
}
public class MessageHandlerAdapter<T> : IMessageHandler where T : IMessage
{
private readonly Func<IMessageHandler<T>> handlerFactory;
public MessageHandlerAdapter(Func<IMessageHandler<T>> handlerFactory)
{
this.handlerFactory = handlerFactory;
}
public void Handle(IMessage message)
{
var handler = handlerFactory();
handler.Handle((T)message);
}
public Type MessageType
{
get { return typeof(T); }
}
}
We can now implement MessageReceiver this way:
public class MessageReceiver
{
private readonly IEnumerable<IMessageHandler> handlers;
public MessageReceiver(IEnumerable<IMessageHandler> handlers)
{
this.handlers = handlers;
}
public void ReceiveMessage(IMessage message)
{
var handler = this.handlers.Where(h => h.MessageType == message.GetType()).FirstOrDefault();
if (handler != null)
{
handler.Handle(message);
}
else
{
//Do something here, no handler found for message type
}
}
}
Now to test that our messages are processed properly, here is a small test:
[TestClass]
public class TestSelector
{
private IContainer container;
[TestMethod]
public void TestMethod()
{
var processor = container.Resolve<MessageReceiver>();
MessageA ma = new MessageA();
MessageB mb = new MessageB();
processor.ReceiveMessage(ma);
processor.ReceiveMessage(mb);
Assert.AreEqual(ma.Handled, true);
Assert.AreEqual(mb.Handled, true);
}
}
And we need to modify registration a bit, if opting for manual registration, we do as follow:
public TestSelector()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<MessageAHandler>().As<IMessageHandler<MessageA>>();
containerBuilder.RegisterType<MessageBHandler>().As<IMessageHandler<MessageB>>();
containerBuilder.RegisterType<MessageHandlerAdapter<MessageA>>().As<IMessageHandler>();
containerBuilder.RegisterType<MessageHandlerAdapter<MessageB>>().As<IMessageHandler>();
containerBuilder.RegisterType<MessageReceiver>();
this.container = containerBuilder.Build();
}
In here, we now need to register one handler and the relevant adapter.
It is also of course possible to perform assembly scan, but this requires a little bit more plumbing, since using:
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(x => x.IsAssignableFrom(typeof(IMessageHandler<IMessage>)))
.InstancePerDependency().AsImplementedInterfaces();
will not work
typeof(MessageAHandler).IsAssignableFrom(typeof(IMessageHandler<IMessage>))
will return false, since MessageAHandler implements IMessageHandler, not IMessageHandler
To do automatic discovery and registration, here is a snippet:
public TestSelector()
{
var containerBuilder = new ContainerBuilder();
Func<Type, Type> GetHandlerInterface = (t) => t.GetInterfaces()
.Where(iface => iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IMessageHandler<>)).FirstOrDefault();
var handlerTypes = typeof(IMessage).Assembly.GetTypes()
.Where(type => type.IsClass
&& !type.IsAbstract
&& GetHandlerInterface(type) != null);
foreach (Type handlerType in handlerTypes)
{
Type messageType = GetHandlerInterface(handlerType).GetGenericArguments()[0];
var genericHandler = typeof(MessageHandlerAdapter<>).MakeGenericType(messageType);
containerBuilder.RegisterType(handlerType).AsImplementedInterfaces();
containerBuilder.RegisterType(genericHandler).As<IMessageHandler>();
}
containerBuilder.RegisterType<MessageReceiver>();
this.container = containerBuilder.Build();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With