Validation with Mediator Pipelines in ASP.NET Core Applications
By: João Simões
Tags: Csharp, Aspnetcore, Software Engineering, Programming, Software Development

The project

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:

The source code is available on GitHub.

The validations

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 IValidator<T> .

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 FluentValidation :

Create a Validations folder at the project root level, with a subfolder Products .

Inside the Products folder create both a validator for CreateProductCommand and 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.

Open the Startup.cs file and register all classes implementing IValidator<T> into the container:

The pipeline

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<TCommand> or IValidator<TEvent> from the container; Invoke the method ValidateAndThrowAsync , failing with a ValidationException if something is invalid;

Inside the Pipelines folder create a ValidationPipeline class extending Pipeline (because we only need some of the methods):

Open the 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:

services.AddMediator(o =>

{

o.AddPipeline<LoggingPipeline>();

o.AddPipeline<TimeoutPipeline>();

o.AddPipeline<ValidationPipeline(); o.AddHandlersFromAssemblyOf<Startup>();

});

If you now start the server and try to create a product with invalid data you will receive a ValidationException .

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:

Install 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:

Conclusion

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: