I recently put together a small message queueing system for work. The purpose was to just be able to do some work asynchronously to not block the current call. I didn't feel the need was there for an actual messaging system like MassTransit so I rolled my own. Perhaps that will be a post in the future, but this one mostly is focused on how to call a generic method from a non-generic method passing in a instance of a sub class cast to a base class using reflection.
Skip directly to the short version.
Short version long
It starts with enqueueing a message. The Enqueue method accepts any instance that inherits from a base class Message
.
public class Message
{
public string id { get; set; }
// All the props
}
When enqueueing the message is serialized and stored in a database using a Dto.
public class MessageDto
{
public string id { get; set; }
public string TypeName { get; set; }
public string SerializedBody { get; set; }
}
When it's time to read and act on the messages the SerializedBody needs to be deserialized to the type using name stored in TypeName. After that it's time for one or many subscribers to act on the message. A subscriber in this system is just a type inheriting from
public interface IMessageHandler<TMessage>
where TMessage : Message
{
Task<Result> HandleAsync<TMessage>(TMessage message);
}
To act on messages the service reads all messages from storage into a List>MessageDto>
, and deserialized each message using Json.NET.
List<Message> messages = new();
foreach (var messageDto in dtos)
{
var messageType = Type.GetType(messageDto.TypeName);
if (JsonConvert.DeserializeObject(messageDto.SerializedBody, messageType) is Message message)
{
messages.Add(message);
}
else
{
// Error handling when not able to cast goes here.
}
}
I chose to use Json.NET for serialization both because it's really easy to use, but also because json is somewhat human readable which makes it alot easier to understand the data when viewing. Also this code actually runs on .NET Framework so Json.NET seems the obvious choice.
The List<Message> messages
contains instances of types that inherit from Message
. To get these to a subscriber they could be passed straight to the HandleAsync-method and each handler could manually cast the message to it's expected type, but that's not much fun. Also each handler needs to verify and decide what to do if the type is not the expected type. So to simplify the handlers logic the goal was instead to call the generic method HandleAsync<TMessage>(TMessage message);
. Time for some reflection fun.
Reflecting of a generic nature
A subscriber is any class that implements the IMessageHandler<TMesssage>
interface for a specific message. Calling a subscribers HandleAync method is easy peasy form a generic method.
// Declared with the other fields in the class
private List<Message> _handlers = new List<Message>();
// ...
public async Task ProcessMessageAsync<TMessage>(TMessage message)
where TMessage : Message
{
// Select will attempt to cast the handlers from type IMessageHandler to IMessageHandler<TMessage>.
// Any class that can't be cast is returned as null so the .Where gets rid of those.
var handlers = _handlers.Select(handler => handler as IMessageHandler<TActivity>).Where(x => x != null);
foreach (var handler in handlers)
{
// If the order of handlers is not important these calls could be added to a List<Task> and awaited
// using Task.WhenAll(allTasks).
await handler.HandleAsync(message);
}
}
To call the generic ProcessMessageAsync method from a non-generic method involves three steps (and any extra validation you might want to add).
Three step program
I arrived at the following solution to get from a list containing the base class Message
to the subscribers by calling the generic ProcessMessageAsync<TMessage>
using some reflection.
private Task CallGenericProcessMessageAsync(Message message)
{
var method = GetType().GetMethod(nameof(ProcessMessageAsync), BindingFlags.NonPublic | BindingFlags.Instance);
var genericMethod = method.MakeGenericMethod(message.GetType());
var task = (Task)genericMethod.Invoke(this, new object[] { message });
return task;
}
First
First step is to get the MethodInfo
of the method. This is done by calling GetMethod
on the Type
instance of the object that implements the method. In this case it's the current class so a call to GetType()
does the trick.
var method = GetType().GetMethod(nameof(ProcessMessageAsync), BindingFlags.NonPublic | BindingFlags.Instance);
Second
Second step is to get the MethodInfo
for the generic version by calling MakeGenericMethod
passing in the Type
s of the generic parameters. In this case it's the Type
of the message
.
var genericMethod = method.MakeGenericMethod(message.GetType());
Third
Last step is to call Invoke
on the method, passing in the caller (this
), the method arguments as an array of object
s, in this case the message
.
var task = (Task)genericMethod.Invoke(this, new object[] { message });
The code above is not the actual implementation I wound up with. There is logging, validation and some other changes, so the code above might not actually execute.
No feedback yet
Form is loading...