3

If I send a HTTP Get request:

/api/Company/1

I have an OwinMiddleware where I'm using the context to ascertain path and json content of an IAsyncRequest<T>.

To know which async request to use I have a mapping of path to Type of IAsyncRequest<T>

var mappings = new Dictionary<string, Type> { ["api/Company/{id}"] = typeof(GetCompanyRequest) }

Type request;
var result = mappings.TryGetValue(context.Requst.Path.Value, out request);

I use the JObject to create an instance of a GetCompanyRequest

var get = new JObject { ["id"] = "1" /* obtained from the url */ }
var instantiatedRequest = JObject.ToObject(request);

The reason I use JObject is that for PUT and POST requests I deserialise the JSON body straight into a request.

The last piece of the puzzle is now sending off this object instantiatedRequest through the mediator pipeline. Obviously Task<T> SendAsync<T>(IAsyncRequest<T> request) isn't going to work.

The interesting thing is, I don't need to know T because I will always be serialising it into a string to post back to the user.

So can the signature Task<object> SendAsync(object request) be worked into the current mediator framework to accomodate this? (Not asking for it to be done, just is it possible?)

Looking at source code

I found this in the mediator.cs

    private TWrapper GetHandler<TWrapper, TResponse>(object request, Type handlerType, Type wrapperType)
    {
        var requestType = request.GetType();

        var genericHandlerType = _genericHandlerCache.GetOrAdd(requestType, handlerType, (type, root) => root.MakeGenericType(type, typeof(TResponse)));
        var genericWrapperType = _wrapperHandlerCache.GetOrAdd(requestType, wrapperType, (type, root) => root.MakeGenericType(type, typeof(TResponse)));

        var handler = GetHandler(request, genericHandlerType);

        return (TWrapper) Activator.CreateInstance(genericWrapperType, handler);
    }

    private object GetHandler(object request, Type handlerType)
    {
        try
        {
            return _singleInstanceFactory(handlerType);
        }
        catch (Exception e)
        {
            throw BuildException(request, e);
        }
    }

That second GetHandler has the parameters I need, the first one is what is called into by SendAsync, I don't see an issue with sticking something in.

Any concerns with doing it?

Community
  • 1
  • 1
Callum Linington
  • 14,213
  • 12
  • 75
  • 154

2 Answers2

1

This is how I achieved to call MediatR from a RabbitMqRequestHandler. Basically, I wanted to fire any command type using a queue message. This prove of concept helped me to create another feature.

IMPORTANT: This works with (MediatR 7.0.0) My feature stopped working when I upgrade it to version 9.

Here we go:

private async Task HandleMessage(SmartConfigQueueMessage message)
{
    try
    {
        var type = AppDomain.CurrentDomain
            .GetAssemblies()
            .SelectMany(x => x.GetTypes())
            .FirstOrDefault(t => t.Name == message.OperationName);

        var resultType = type.GetInterfaces()
            .Where(r => r.FullName.Contains("SmartConfig"))? //MY ASSEMBLY MAIN NAMESPACE
            .FirstOrDefault().GenericTypeArguments
            .FirstOrDefault();

        dynamic command = message.Variables.ToObject(type);

        var method = _mediator.GetType().GetMethod("Send");
        var generic = method.MakeGenericMethod(resultType);
        var response = generic.InvokeAsync(_mediator, new object[] {command, new CancellationToken()});
    }
    catch (Exception ex)
    {
        _logger.LogInformation($"Consumer handler error: {ex.Message}");
    }

    // We just print this message   
    _logger.LogInformation($"Consumer message received: {message.OperationName}");
}

MediatR handles all queries and commands asynchronously. So, we need this extension:

public static class GenericMethodExtensions
{
    public static async Task<object> InvokeAsync(this MethodInfo @this, object obj, params object[] parameters)
    {
        var task = (Task)@this.Invoke(obj, parameters);
        await task.ConfigureAwait(false);
        var resultProperty = task.GetType().GetProperty("Result");
        return resultProperty?.GetValue(task);
    }
} 
Javier Contreras
  • 871
  • 7
  • 16
0

So there is a way without modifying the source code to achieve this:

var irequestInterface = typeof(GetCompanyRequest).GetInterfaces().FirstOrDefault(x => x.Name.StartsWith("IRequest"));

if (irequestInterface == null)
{
    throw new Exception("IRequest is null");
}

var tresponse = irequestInterface.GenericTypeArguments.FirstOrDefault();

if (tresponse == null)
{
    throw new Exception("Reponse is null");
}

var method = typeof(IMediator).GetMethod("Send");
var generic = method.MakeGenericMethod(tresponse);
generic.Invoke(mediator, new []{ (object)Activator.CreateInstance<GetCompanyRequest>() });

The last bit is from Jon Skeets answer

The first bit is because we don't care about TResponse so I don't want to have to specify it. Our GetCompanyRequest has more than enough information on it to execute SendAsync<TResponse>.

Normally I would be weary of using reflection, but I know that the implementation of Mediator uses a lot of reflection - so you know what - we'll just bundle all these issues into one.

Community
  • 1
  • 1
Callum Linington
  • 14,213
  • 12
  • 75
  • 154
  • How do to get response for given query? mediator.Send() returns Task, so how to await and unpack it? – Dominik Szymański May 08 '20 at 11:15
  • 1
    I think this answer was when MediatR had a sync version of Send. For the Async version you have to downcast Task to Task, then await that, then you can access the result which will be an object – Callum Linington May 10 '20 at 19:20
  • 1
    @DominikSzymański I wrote a library around this to tie up MediatR and AspNetCore - see here https://github.com/weareslate/liaison/blob/master/src/Liaison/Orchestrator/MediatRPipelineExecutorOrchestrator.cs – Callum Linington May 10 '20 at 19:21
  • Thank you @Callum Linington, in the meantime I found the code that lets await task with unknown return type and wrote wrapper for MediatR. – Dominik Szymański May 12 '20 at 15:06