Validation with Mediator Pipelines in ASP.NET Core Applications
When implementing web applications that manipulate information, there is always the need to validate data sent by the clients, ensuring business rules are properly implemented.
Previously I talked about the implementation of Command Query Responsibility Segregation (CQRS) and Event Sourcing (ES) using a mediator pattern and how to support transversal behavior via pipelines.
In this article I’m going to demonstrate how validation can be enforced into your commands and events before reaching the handlers, making sure that invalid data will be rejected by the API.
This approach not only reduces the amount of duplicated code, it also ensures validations won’t be forgotten, unless explicitly stated.
To make it faster to implement the validation pipeline, I’m going to leverage this example on both of my previous articles, in which we implemented an endpoint to manage products and introduced both a logging and timeout pipelines:
Mediator Pattern in ASP.NET Core Applications
Send commands, fetch queries and broadcast events using the mediator pattern
Using Mediator Pipelines in ASP.NET Core Applications
Intercept commands, queries and events to apply transversal behavior
The source code is available on GitHub.
To help us configure the rules we are going to use a very popular and one of my favorites libraries FluentValidation by creating classes implementing the interface
These classes will be added to the dependency injection container and used by the pipeline to validate both the commands and events before reaching the handler.
Lets start by installing the NuGet
Validations folder at the project root level, with a subfolder
Products folder create both a validator for
CreatedProductEvent by extending the class
AbstractValidator<T> (which itself implementes the interface
IValidator<T>) and configure some rules in the constructors:
Feel free to create validators for all other commands and events, I’m just focusing on these for simplicity. You can also check the documentation for supported rules and detailed usage instructions.
Startup.cs file and register all classes implementing
IValidator<T> into the container:
Because for this example we are going to enforce validation only on commands and events, since they either mutate or represent the system state at a given point in time, the pipeline will be implemented as follows:
- Intercept any command or event;
- Resolve a required instance of
IValidator<TEvent>from the container;
- Invoke the method
ValidateAndThrowAsync, failing with a
ValidationExceptionif something is invalid;
Pipelines folder create a
ValidationPipeline class extending
Pipeline (because we only need some of the methods):
Startup.cs file and add the
ValidationPipeline at least after the
LoggingPipeline ensuring, in case invalid data is submitted, we can still see it in the logs:
If you now start the server and try to create a product with invalid data you will receive a
Because returning an HTTP 500 due to invalid data would be confusing to the client, lets just finish this example by creating an ASP.NET Core middleware converting this exception into a more appropriate code, like HTTP 422.
Once again, open the
Startup.cs file and register the middleware immediately after the developer exception page, catching this exception and returning HTTP 422 with a more detailed JSON representation:
Submit invalid data again and a more detailed message should be returned:
Speeding things up!
If just like me, you can see yourself using a pipeline for validation in most of your projects, there is already a pipeline available via NuGet that should be configurable to most use cases while also providing a simpler way to register the validators into the container:
Mediator validation pipeline for commands, queries and events.
SimpleSoft.Mediator.Microsoft.Extensions.ValidationPipeline via NuGet:
Use the extension method
AddPipelineForValidation and enforce both command and event validations and use the extension method
AddValidatorsFromAssemblyOf to scan for all
IValidator<T> classes and register them into the container.
For the current project, the
ConfigureServices method would be similar to the following:
I hope this article gave you a good idea on how to use mediator pipelines to ensure that all your commands, events and even queries are initialized with proper data, either implementing your own pipeline or by using the existing ValidationPipeline NuGet.
I also made an article about transaction management with pipelines, you may also find it helpful:
Small .NET library that helps with the implementation of mediator pattern for commands, events and queries. Using a…