Mediatr: Issue with Generic Wrapper as response type in pipeline

Created on 30 Jun 2017  路  5Comments  路  Source: jbogard/MediatR

When adding a Generic request result wrapper to all of my mediator request response types, the pipeline refuses to execute, throwing a generic type constraint exception that the request does not fit the type constraint of TRequest, which is
where TRequest : IRequest<RequestResponse<TResponse>>
The request sent is of type
IRequest<RequestResponse<Detail>>

The exact exception is
Message "GenericArguments[0], 'Api.MediatR.Requests.GetDetailsRequest', on 'Api.MediatR.Behaviours.ExceptionHandlingBehaviour2[TRequest,TResponse]' violates the constraint of type parameter 'TRequest'." string

The full class in question is

public class ExceptionHandlingBehaviour<TRequest, TResponse> : 
 IPipelineBehavior<TRequest,RequestResponse<TResponse>>
        where TRequest : IRequest<RequestResponse<TResponse>> 
    {
        public async Task<RequestResponse<TResponse>> Handle(TRequest request, RequestHandlerDelegate<RequestResponse<TResponse>> next)
        {
            try
            {
                return await next();
            }
            catch (Exception ex)
            {
                return new RequestResponse<TResponse>(ex);
            }
        }
    }

With RequestResult being

public class RequestResponse<TResponse>
    {
        public RequestResponse(RequestState reason, string message = null)
        {
            RequestState = reason;
            Failure = message != null ? new Exception(message) : new Exception(reason.ToString());
        }
        public RequestResponse(List<ValidationFailure> validationFailures)
        {
            ValidationFailures = validationFailures;
            RequestState = RequestState.ValidationFailure;
        }

        public RequestResponse(TResponse response)
        {
            RequestState = RequestState.Success;
            Response = response;
        }

        public RequestResponse(Exception exception)
        {
            Failure = exception;
            RequestState = RequestState.Error;
        }

        public readonly TResponse Response;
        public readonly IEnumerable<ValidationFailure> ValidationFailures;

        public readonly Exception Failure;

        public RequestState RequestState;
    }

Is this just me stuffing up generics, or is something not working correctly here?

Most helpful comment

@stumpykilo let me know if this is what you are looking for.

public async Task<TResponse> Handle(TCommand request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
    try
    {
        return await next().ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        var responseType = typeof(TResponse);

        if (responseType.IsGenericType)
        {
            var resultType = responseType.GetGenericArguments()[0];
            var response = typeof(RequestResponse<>).MakeGenericType(resultType);
            return Activator.CreateInstance(resultType, ex) as TResponse;
        }

        return new RequestResponse(ex) as TResponse;
    }
}

This is close to what we do in our project.

All 5 comments

Also, with the pipeline registration commented out, the normal request works perfectly.
The registration is as follows
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ExceptionHandlingBehaviour<,>));

@jakerabjohns15 check out these lines

public class ExceptionHandlingBehaviour<TRequest, TResponse> : 
 IPipelineBehavior<TRequest,RequestResponse<TResponse>>
        where TRequest : IRequest<RequestResponse<TResponse>> 

what you are saying here is that ExceptionHandlingBehaviour is a generic class that accepts two types, TRequest and TResponse.

This class also implements a Generic interface that accepts two generic types TRequest (which is the same as the generic from ExceptionHandlingBehaviour and RequestResponse<TResponse>. Here TResponse is also carried over from ExceptionHandlingBehaviour. Are you seeing the problem?

If TRequest is constrained to types of IRequest<RequestResponse<TResponse>> that means that TResponse is intended through mediatr's rules to be of type RequestResponse<TResponse>.

This would imply that TResponse itself is of type RequestResponse<TResponse> which would imply that the pipeline to be limited to IPipelineBehavior<TRequest, RequestResponse<RequestResponse<TResponse>>>. You can see how this might be a problem.

To do what you need you cannot reference the TResponse twice. You will need to encapsulate the Exception part into a parent class.

    public class RequestResponse
    {
        public Exception Failure { get; }

        public RequestResponse(Exception ex)
        {
            Failure = ex;
        }
    }

    public class RequestResponse<T> : RequestResponse
    {
        public RequestResponse(T response, Exception ex = null) : base(ex)
        {
            Response = response;
        }

        public T Response { get; }
    }

And then modify your pipeline handler like so:

public class ExceptionHandlingBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> 
        where TResponse : RequestResponse

This should give you the desired behavior

@lilasquared Could you provide an example of how you would use this in the Handle method of the ExceptionHandlingBehaviour class?

@stumpykilo let me know if this is what you are looking for.

public async Task<TResponse> Handle(TCommand request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
    try
    {
        return await next().ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        var responseType = typeof(TResponse);

        if (responseType.IsGenericType)
        {
            var resultType = responseType.GetGenericArguments()[0];
            var response = typeof(RequestResponse<>).MakeGenericType(resultType);
            return Activator.CreateInstance(resultType, ex) as TResponse;
        }

        return new RequestResponse(ex) as TResponse;
    }
}

This is close to what we do in our project.

@stumpykilo you may also be interested in this PR for a common exception handling behavior #339

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bugproof picture bugproof  路  6Comments

grokky1 picture grokky1  路  4Comments

nevaenuf picture nevaenuf  路  4Comments

TDK1964 picture TDK1964  路  5Comments

orantwolinkme picture orantwolinkme  路  4Comments