Search code examples
c#asp.net-corecqrsfluentvalidationmediatr

Double validation in MediatR pipeline


I'm on ASP.NET Core and the new MediatR which supports pipelines. My pipeline includes validation.

Consider this action:

[HttpPost]
[HandleInvalidCommand]
public IActionResult Foo(Command command)
{
    await _mediator.Send(command);
    return View();
}
  • The command is validated (I'm using FluentValidation)
  • HandleInvalidCommand checks ModelState.IsValid, and if invalid then redirects to the view for the user to correct the data
  • Else the action runs
  • The command is sent into the pipeline
  • The pipeline validates the command, AGAIN

So if the command is valid, then validation occurs twice (and validators are expensive to run).

How best can I deal with this?

EDIT: The obvious way is to remove validation from the pipeline, but that is no good because the command may come from the UI, but also from the app itself. And you want validation in both cases.


Solution

  • I found another way. Maybe not the best, but it works.

    Define this interface

    public interface IValidated
    {
        bool AlreadyValidated { get; }
    }
    

    Decorate the request

    public class Command : IRequest, IValidated
    {
        public bool AlreadyValidated { get; set; }
        // etc...
    }
    

    Update the request's validator to use an interceptor:

    public class CommandValidator : AbstractValidator<Command>, IValidatorInterceptor
    {
    
        public CommandValidator() { 
            // validation rules etc.
        }
    
        public ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext)
        {
            return validationContext;
        }
    
    
        public ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result)
        {
            var command = validationContext.InstanceToValidate as Command;
            if (command != null) command.AlreadyValidated = true;
            return result;
        }
    
    }
    

    Update the pipeline:

    public class MyPipeline<TRequest, TResponse>
        : IPipelineBehavior<TRequest, TResponse>
        where TRequest : IRequest<TResponse>, IValidated   // update here
    {
    
        public async Task<TResponse> Handle(
            TRequest message,
            RequestHandlerDelegate<TResponse> next)
        {
    
            if (!message.AlreadyValidated)      // update here
            {
                var context = new ValidationContext(message);
                var failures = _validators
                    .Select(v => v.Validate(context))
                    .SelectMany(e => e.Errors)
                    .Where(e => e != null)
                    .ToList();
    
                if (failures.Count != 0)
                    throw new ValidationException(failures);
            }
    
          return await next();
          }
    
    }
    

    So after validation by MVC/FluentValidation, it sets the flag. Then in the CQRS pipeline, if that flag is set, it doesn't perform validation again.

    However I'm not sure I like this, as I'm leaking stuff into the command that shouldn't be there.