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?
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();