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();
}
HandleInvalidCommand
checks ModelState.IsValid
, and if invalid then redirects to the view for the user to correct the dataSo 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.
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.