Search code examples
c#.netasp.net-corefluentvalidationmediatr

Not invoking FluentValidation in MediatR pipeline


I am using: MediatR 12.1.1 FluentValidation 11.6.0

I have created my validator:

internal class CreateTemplateCommandValidator : AbstractValidator<CreateTemplateCommand>
{
    public CreateTemplateCommandValidator(ITemplateRepository templateRepository)
    {
        RuleFor(template => template.Name)
            .NotEmpty()
            .DependentRules(() =>
        {
            RuleFor(template => template.Name)
                .MustAsync(async (name, token) => !(await templateRepository.NameExists(name)))
                .WithMessage("Name must be unique.");
        });
    }
}

I want a validation to be handled via MediatR pipeline. This is my ValidationBehavior class:

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 async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)

    {
        var context = new ValidationContext<TRequest>(request);

        var failures = _validators
            .Select(validator => validator.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(failure => failure is not null)
            .ToList();

        if (failures.Any())
        {
            throw new ValidationException(failures);
        }

        return await next();
    }
}

I also registered these:

        services.AddMediatR(cfg =>
        {
            cfg.RegisterServicesFromAssembly(typeof(Extension).Assembly);
        });


        services.AddValidatorsFromAssemblyContaining(typeof(Extension));
        services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
     

Unfortunately validator is never being called. I am stuck as I couldn't find the answer anywhere else, seems like I've followed the pattern correctly - what am I missing?


Solution

  • Your validator is internal. You need to change your DI to:

    services.AddValidatorsFromAssemblyContaining(typeof(Extension), includeInternalTypes: true);
    

    I'd also recommend using:

    services.AddMediatR(cfg =>
    {
        cfg.RegisterServicesFromAssembly(typeof(Extension).Assembly);
        cfg.AddOpenBehavior(typeof(ValidationBehavior<,>)
    });
    

    over your approach:

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

    Finally, while reproducing your issue I discovered another bug you'll uncover on applying this fix:

    Unhandled exception. FluentValidation.AsyncValidatorInvokedSynchronouslyException: Validator "CreateTemplateCommandValidator" contains asynchronous rules but was invoked synchronously. Please call ValidateAsync rather than Validate.
    

    I suggest using System.Linq.Async and changing your validation code to:

    var failures = _validators
        .ToAsyncEnumerable()
        .SelectAwait(async validator => await validator.ValidateAsync(context))
        .ToEnumerable()
        .SelectMany(result => result.Errors)
        .Where(failure => failure is not null)
        .ToList();