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?
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
Most helpful comment
@stumpykilo let me know if this is what you are looking for.
This is close to what we do in our project.