Validation is an important part of APIs. When using a clean architecture, or another modern application design pattern, most often we leverage CQRS’ commands for actions like POST, PUT, etc. These commands have side effects. While those without side effects like GET requests are implemented in queries. Commands most often need some sort of asynchronous validation, for example, detecting if an entity exists in the database before creating another related entity, etc. It is a best practice to separate validation from the implementation logic. This article will show you how to separate validation logic from the application logic in your commands. With MediatR, this is easy we will do so, there by, respecting the SRP (Single Responsibility Principle).
The Problem
When making a POST request for example, you can use dotnet’s data annotations to validate the properties of the model received by the request before it even reaches your controller’s action logic. For this type of validation, Fluent Validation could also be used (NOTE: With some configuration, Fluent Validation could be used for async validation, but in this article, we’re focusing on validation at the command level). This is great, but what if, after the model is declared valid, your command is called, and you still need some additional asynchronous validation logic before running the application logic inside your command? The easiest thing to do is to mix the validation logic inside your command’s application logic. Here is an example below. Where I call a validation logic inside my “SaveForecast.Command” handler, to check if there is not already an existing similar forecast before saving a new forecast. Here is a link to the source code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class SaveForecast { public record Command(WeatherForecast WeatherForecast) : ICommand<Unit>; public class Handler : IRequestHandler<Command, Unit> { private readonly IDataService _dataService; public Handler(IDataService dataService) { _dataService = dataService; } public async Task<Unit> Handle(Command request, CancellationToken cancellationToken) { //(Validation logic) Checking if a similar forecast already exists first. var forecasts = await _dataService.ReadForecast(); if (forecasts.Any(f => f.Summary.ToLower() == request.WeatherForecast.Summary.ToLower())) throw new Exception("Similar forecast exists"); await _dataService.SaveForecast(request.WeatherForecast); return Unit.Value; } } } |
The Problem with the code above is that it mixes validation logic with application logic. For small functionalities, it might be ok, but it will turn out to be very messy when the code grows. What we want is to separate the validation logic, and let the application logic be alone in the command thereby, respecting SRP. This can be done easily and in a flexible manner with MediatR’s pipeline behaviors.
If you find this article useful, please follow me on Twitter, Github, Linkedin, or like my Facebook page to stay updated.Follow me on social media and stay updated
Separating the Validation Logic
Our next step is to separate the validation logic and make it flexible in such a way that it will be easy to separate validation for each commands in our system.
1- Create a validator interface. We will create several validators that will contain the validation logic for each command.
1 2 3 4 5 | public interface IValidator<ICommand> { Task Validate(ICommand request); } |
2- We then create a specific validator that will contain the validation logic for our SaveForecast command handler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class SaveForecastValidator : IValidator<SaveForecast.Command> { private readonly IDataService _dataService; public SaveForecastValidator(IDataService dataService) { _dataService = dataService; } public async Task Validate(SaveForecast.Command request) { //(Validation logic) Checking if a similar forecast already exists first. var forecasts = await _dataService.ReadForecast(); foreach (var forecast in forecasts.Select(f => f.Summary.ToLower())) { if (forecast == request.WeatherForecast.Summary.ToLower()) throw new Exception("Similar forecast exists"); } if (forecasts.Select(f => f.Summary.ToLower()).Contains(request.WeatherForecast.Summary.ToLower())) throw new Exception("Similar forecast exists"); } } |
3- Using the decorator pattern, MediatR permits us to run code logic before the execution of commands. We will be able to run the validation logic before command execution, with the “IPipelineBehavior”. The decorator pattern is a very famous pattern you must have used in the past even unknowingly. It is the patterned leveraged by ASP.net core middleware. We create a generic validation pipeline behavior for our commands.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : ICommand<TResponse> { private readonly IValidationHandler<TRequest> _validationHandler; public ValidationBehavior(IValidationHandler<TRequest> validationHandler) { _validationHandler = validationHandler; } public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { await _validationHandler.Validate(request); return await next(); } } |
4- We register the validation behavior so that it is called to perform validation logic for each commands automagically.
NOTE: if you want every request to be validated, use the following:
1 | builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); |
NOTE: if you want every request to be validated, use the following:
1 | builder.Services.AddTransient<IPipelineBehavior<SaveForecast.Command, Unit>, ValidationBehavior<SaveForecast.Command, Unit>>(); |
5- We register our validator in the IOC container so that it is called by the pipeline behavior appropriately.
1 | builder.Services.AddTransient<IValidationHandler<SaveForecast.Command>, SaveForecastValidator>(); |
6- Now we have a clean Command, with clean validation done seamlessly without the need for the validation logic to exist in our command’s handler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class SaveForecast { public record Command(WeatherForecast WeatherForecast) : ICommand<Unit>; public class Handler : IRequestHandler<Command, Unit> { private readonly IDataService _dataService; public Handler(IDataService dataService) { _dataService = dataService; } public async Task<Unit> Handle(Command request, CancellationToken cancellationToken) { await _dataService.SaveForecast(request.WeatherForecast); return Unit.Value; } } } |
Conclusion
The validation above relies on exceptions thrown to break the execution flow, instead of returning validation errors only. This is my way of doing things, you could customize it if you want. I’d love to know your opinion about this approach in the comments section. I hope this post helps.
References
https://codeopinion.com/validating-commands/
https://code-maze.com/cqrs-mediatr-fluentvalidation/
Follow me on social media and stay updated