Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add validation to a MediatR behavior pipeline?

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?

like image 477
grokky Avatar asked Feb 16 '17 19:02

grokky


3 Answers

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();
like image 152
Mickaël Derriey Avatar answered Nov 01 '22 06:11

Mickaël Derriey


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

like image 9
GetoX Avatar answered Nov 01 '22 05:11

GetoX


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!

like image 2
Sharif Ullah Avatar answered Nov 01 '22 04:11

Sharif Ullah