Search code examples
c#owinmediatr

MediatR Send object without typing


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?


Solution

  • 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.