8

I am using MediatR with the following classes:

public class GetPostsRequest : IRequest<Envelope<GetPostsResponse>> {
  public Int32 Age { get; set; }
}

public class GetPostResponse {
  public String Title { get; set; }
  public String Content { get; set; }      
}

Where Envelope is a wrapper class:

public class Envelope<T> {
  public List<T> Result { get; private set; } = new List<T>();  
  public List<Error> Errors { get; private set; } = new List<Error>();
}

The GetPostsRequest, sent by the mediator, is executed by the Handler:

public class GetPostsRequestHandler : IRequestHandler<GetPostsRequest, Envelope<GetPostsResponse>> {

  public async Task<Envelope<GetPostsResponse>> Handle(GetPostsRequest request, CancellationToken cancellationToken) {
  }

}

MediatR allows the use of Behaviors which executes before the Handler of a specific Request. I created a ValidationBehavior as follows:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, Envelope<TResponse>> 
    where TRequest : IRequest<Envelope<TResponse>> {

  private readonly IEnumerable<IValidator<TRequest>> _validators;

  public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) {
    _validators = validators;
  }

  public Task<Envelope<TResponse>> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<Envelope<TResponse>> next) {

    ValidationContext context = new ValidationContext(request);

    List<Error> errors = _validators
      .Select(x => x.Validate(context))
      .SelectMany(x => x.Errors)
      .Select(x => new Error(ErrorCode.DataNotValid, x.ErrorMessage, x.PropertyName))
      .ToList();

    if (errors.Any())
      return Task.FromResult<Envelope<TResponse>>(new Envelope<TResponse>(errors));  

    return next();

  }

}

And I registered the ValidationBehavior on the ASP.NET Core application:

services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

When I call the API I get the following error:

An unhandled exception has occurred while executing the request.
System.ArgumentException: GenericArguments[0], 'TRequest', on 'ValidationBehavior`2[TRequest,TResponse]' violates the constraint of type 'TRequest'. 
---> System.TypeLoadException: GenericArguments[0], 'TRequest', on 'ValidationBehavior`2[TRequest,TResponse]' violates the constraint of type parameter 'TRequest'.

What am I missing?

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • Please can you show how your calling it - with what? params? It's a generic mismatch I think – Alex Aug 29 '18 at 15:04
  • I think it's one to many generic wrappers - `Envelope` != `Envelope` – Alex Aug 29 '18 at 15:10
  • I am not calling it ... It is being injected. Note that on the Behavior I am using generics: TRequest and TResponse. – Miguel Moura Aug 29 '18 at 15:11
  • @Alex Envelope should be different than Envelope. I updated by question to clarify. – Miguel Moura Aug 29 '18 at 15:18
  • @MiguelMoura could you figure out the proper solution for your problem? I am facing exactly the same issue and struggling with it. Thanks in advance! – mirind4 Jan 28 '20 at 15:07

2 Answers2

2
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

I don’t think this line does what you expect it to do.

In your example, your request type is a IRequest<Envelope<Response>>, so when MediatR is looking for a pipeline behavior, it will look for a IPipelineBehavior<Request, Envelope<Response>>.

Since there is a open-generic registration in the DI container for IPipelineBehavior<,>, that implementation will be used. So the generic type arguments from IPipelineBehavior<,> will be used to instantiate a type of ValidationBehavior<,>.

So for IPipelineBehavior<Request, Envelope<Response>>, this will create a ValidationBehavior<Request, Envelope<Response>>. This means that in your generic implementation, TRequest will be Request, and TResponse will be Envelope<Response>. This however means that the type constaint on your type means that TRequest, or Request in this case, needs to implement IRequest<Envelope<Envelope<Response>>>. Of course, that is not the case and as such that explains the type constraint violation you are seeing.

Unfortunately, this means that you cannot do it the way you want it to work. You cannot put a type constraint on your generic type, at least not one for Envelope<T>.

poke
  • 369,085
  • 72
  • 557
  • 602
  • That is why initially I tried to use ValidationBehavior> : IPipelineBehavior> where TRequest : IRequest> but it does not compile. – Miguel Moura Aug 29 '18 at 15:29
  • All my responses are wrapped in an Envelope such as Envelope or Envelope ... In my Behavior I need to use Envelope so in case of error I return and Envelope with those Errors. How would you solve this? – Miguel Moura Aug 29 '18 at 15:30
  • I don’t think there is a good way to do this. What you could to is introduce an `IEnvelope` interface that has the `Errors` property, so you could check for the `IEnvelope` type inside of your behavior and then set the errors without having to access the actual response type. – poke Aug 29 '18 at 17:45
0

Your ValidationBehavior specifies that TRequest must be an IRequest<Envelope<TResponse>>
Your Request implements IRequest<Envelope<Response>> (mismatch - Envelope<TResponse> is not Envelope<Response>)

However, your ValidationBehavior implements IPipelineBehavior<TRequest, Envelope<TResponse>> and has the constraint where TRequest : IRequest<Envelope<TResponse>>

So, change Envelope<TResponse> to Envelope<Response>

Alex
  • 37,502
  • 51
  • 204
  • 332
  • I cannot use that. Request and Response classes where concrete examples. The behavior must use generics only specifying that Response is of type Envelope. I updated my question to try to make it more clear. – Miguel Moura Aug 29 '18 at 15:20