37

I'm using ASP.NET Core, the built-in container, and MediatR 3 which supports "behavior" pipelines:

public class MyRequest : IRequest<string>
{
    // ...
}

public class MyRequestHandler : IRequestHandler<MyRequest, string>
{
    public string Handle(MyRequest message)
    {
        return "Hello!";
    }
}

public class MyPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    {
        var response = await next();
        return response;
    }
}

// in `Startup.ConfigureServices()`:
services.AddTransient(typeof(IPipelineBehavior<MyRequest,str‌​ing>), typeof(MyPipeline<MyRequest,string>))

I need a FluentValidation validator in the pipeline. In MediatR 2, a validation pipeline was created thus:

public class ValidationPipeline<TRequest, TResponse>
    : IRequestHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{

    public ValidationPipeline(IRequestHandler<TRequest, TResponse> inner, IEnumerable<IValidator<TRequest>> validators)
    {
        _inner = inner;
        _validators = validators;
    }

    public TResponse Handle(TRequest message)
    {
        var failures = _validators
            .Select(v => v.Validate(message))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();
        if (failures.Any())
            throw new ValidationException(failures);
        return _inner.Handle(request);
    }

}

How do I do that now for the new version? How do I set which validator to use?

grokky
  • 8,537
  • 20
  • 62
  • 96

3 Answers3

33

The process is exactly the same, you just have to change the interface to use the new IPipelineBehavior<TRequest, TResponse> interface.

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

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

    public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    {
        var context = new ValidationContext(request);
        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
        {
            throw new ValidationException(failures);
        }

        return next();
    }
}

For the validators, you should register all the validators as IValidator<TRequest> in the built-in container so they'll be injected in the behavior. If you don't want to register them one by one, I suggest that you have a look at the great Scrutor library that brings assembly scanning capabilities. This way it'll find your validators itself.

Also, with the new system, you don't use the decorator pattern anymore, you just register your generic behavior in the container and MediatR will pick it up automatically. It could look something like:

var services = new ServiceCollection();
services.AddMediatR(typeof(Program));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
var provider = services.BuildServiceProvider();
Mickaël Derriey
  • 12,796
  • 1
  • 53
  • 57
  • 1
    One thing I don't get (though I know I can register a specific type as you helped me figure out in that other question), why do all examples for this stuff use open generics for registration? Surely you need a pipeline for a specific set of requests, handlers, etc., and not have every pipeline run for every request? – grokky Feb 17 '17 at 09:27
  • 1
    I guess it depends. I'm happy having the validation behavior applied to all requests, for example. Worst case scenario, no validators have been registered for this specific request and it becomes a no-op that just delegates to the next behavior or the actual request handler. Keeping them generic makes the registration much more straightforward, too. Thanks for reporting the issue, I took that from some code that used Autofac, the built-in container in ASP.NET Core might not support arrays. – Mickaël Derriey Feb 17 '17 at 10:43
  • I take your point about no-op validation. But let's say you have three pipelines registered using open generics (maybe it's a silly scenario, I'm still a MediatR newbie). And suppose a request matches all three. So will they all be run? – grokky Feb 17 '17 at 11:05
  • 1
    Yes. If you register them as open generic the 3 behaviors will run for every request. It's not silly, pipelines are usually used for cross cutting concerns, so you could imagine a validation one, an authorisation one, a logging one, an exception handling one, etc... – Mickaël Derriey Feb 17 '17 at 11:08
  • @MickaëlDerriey When using the IPipelineBehaviour how do we differentiate between a pre and a post behaviour? – oceanexplorer Feb 28 '17 at 15:16
  • @oceanexplorer could you create a new question not to pollute this one? I'll happily answer it if you send a link to the new one. thanks – Mickaël Derriey Feb 28 '17 at 22:14
  • @MickaëlDerriey in your code snippet above, where does the `message` variable come from. Should it be the `request` variable? I can't seem to follow that bit. :( Also, how do you register to inject all the validators with a DI container into this pipeline? I'm using SimpleInjector, but any of the popular ones will do. – Prabu Sep 27 '17 at 08:40
  • @MickaëlDerriey Also, what are your thoughts on using an exception to pass control for failing validation? Is there another way? – Prabu Sep 27 '17 at 09:05
  • 1
    @PrabuWeerasinghe my bad, it was supposed to be `context`, I updated my answer. As for the exception, I'm fine with it. If you're not, you could have all your response type have a property that indicates whether the process was successful or not, and another property to get information about what went wrong. I found the exception throwing to be easier to implement. – Mickaël Derriey Sep 27 '17 at 22:17
  • 9
    Exceptions are not free. This is a terrible design. – DarthVader Nov 29 '18 at 20:51
  • 4
    Despite the terse delivery, I tend to agree with Lord Vader. I'm trying to achieve validation in my pipeline without throwing exceptions. From a design perspective, I can't sign on to the notion that a business validation failure should result in an exception. It's easy to implement, but it just feels wrong. There's gotta be a better way. I'll look at adding properties to the IResponse, which may require either casting, reflection or both, given the open generic types in the pipeline handler. – onefootswill Sep 12 '19 at 22:28
  • 1
    I just want to add that it is not as simple as just adding a couple of properties to the response object. – onefootswill Sep 14 '19 at 06:25
  • @onefootswill did you find a way to return a list of errors instead of throwing an exception? – Rahul Dass May 15 '21 at 05:42
  • 2
    @RahulDass Yeah. This article nails it https://medium.com/the-cloud-builders-guild/validation-without-exceptions-using-a-mediatr-pipeline-behavior-278f124836dc All the best! – onefootswill May 15 '21 at 06:52
  • @MickaëlDerriey i think these validation behavior is only valid for when we do not use AddFluentValidation https://docs.fluentvalidation.net/en/latest/aspnet.html ?? I see that my behavior pipeline does not get executed at all even though i do get validation error. Additionally, when i remove the AddFluentValidation , i do get PipelineBehavior executed, although with empty validator (as there are no validators added to container). This is quite confusing to me because all the pipeline behavior gives this example but it is not working if we added AddFluentValidation to the AspNetCore pipeline? – kuldeep Jan 24 '22 at 09:11
  • Although i think this behavior would still be needed if we are talking about validations that are coming out of the context of asp net core context, e.g. rabbitmq. If my assumption or understanding is correct in previous comment, could you please update your answer ? – kuldeep Jan 24 '22 at 09:13
  • https://github.com/iammukeshm/CleanArchitecture.WebApi/blob/master/Application/Behaviours/ValidationBehaviour.cs https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/Common/Behaviours/ValidationBehaviour.cs – more urgent jest May 30 '22 at 23:10
9

I've packed .net core integration into nuget, feel free to use it: https://www.nuget.org/packages/MediatR.Extensions.FluentValidation.AspNetCore

Just insert in configuration section:

services.AddFluentValidation(new[] {typeof(GenerateInvoiceHandler).GetTypeInfo().Assembly});

GitHub

GetoX
  • 4,225
  • 2
  • 33
  • 30
2

On the new version (MediatR (>= 9.0.0)) you can do something like this:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

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

    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var context = new ValidationContext<TRequest>(request);
        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
        {
            throw new ValidationException(failures);
        }

        return next();
    }
}

Remember to add var context = new ValidationContext<TRequest>(request); in previous version like FluentApi 8.0 or below it used something like this var context = new ValidationContext(request);

for Register in Asp.Net Core in under IServiceCollection wright below code:

services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

Hope that's helpful!

Sharif Ullah
  • 31
  • 1
  • 5