I would like to show how I currently do Command and Query validation using ASP.NET MVC, MediatR, and StructureMap.
My goal with this pattern is to simplify and standardize the validation process for the team I work with when dealing with the CQS pattern.
Below is a simple form input to give some context to the code I’ll show below.
And here is a look at the overview of the process before I dive into the code!
Alright, now to the good stuff. Below is a look at the Get and Post actions for this form.
I have a ValidationHandler class that all of my commands and queries pass through before being handled. If the command/query doesn’t have a validator, I have Structuremap provide a default validator that simply returns no errors.
If the request has errors you can see a ValidationException gets thrown. This is used to pass the errors to the calling code.
The ValidationException, ValidationFailure, and ValidationResult classes were all taken from the https://github.com/JeremySkinner/FluentValidation library.
Below is my example Command validator. This class inherits a MessageValidator abstract class.
The ValidateAnnotations() call is made to the inherited MessageValidator class. Feedback is welcome on alternatives to this way of simplifying the data annotation validation process!
Having a message validator like this allows us to have the flexibility of referencing the database context. And in my opinion is intuitive to work with when writing business rule validation in some complex scenarios.
Below, the command data annotations are validated and the result object used by the custom validators is initialized.
I played around using a IValidator interface rather than an abstract base class. But because of being able to simplify the data annotation validation process I went this route. Feedback is welcome on this!
UPDATE: I now catch the validation exception via a BaseController filter attribute.
Simply decorate your MVC BaseController with the [HandleCustomException] attribute.
Via the HandleCustomException Attribute on the controller, the ModelState is exported to TempData and the user is redirect back to the Get method where the errors are displayed.
What are your thoughts on this pattern? Are there things you think could be improved? Please give feedback below!