In this article, we are going to show you how to elegantly integrate a validation pipeline into our project using the MediatR and FluentValidation libraries.
Let’s get started.
VIDEO: FluentValidation with CQRS and MediatR in ASP.NET Core Web API.
What is CQRS?
CQRS or Command Query Responsibility Segregation is a design pattern that is becoming very popular in recent years. The idea behind CQRS is to logically split the flow of your application into two separate flows, either Commands or Queries.
Commands are used to change the state of the application. If we talked about CRUD (Create, Read, Update, Delete), Commands would cover the Create, Update, and Delete parts.
On the other hand, Queries are used to retrieve information in the application. Naturally, they cover the Read part in CRUD.
To learn how to implement CQRS with MediatR in your ASP.NET Core application, be sure to check out this article CQRS and MediatR in ASP.NET Core. You should be familiar with CQRS and MediatR to follow along with this article. So, if you are not, we strongly suggest reading the linked article first.
Advantages and Disadvantages of CQRS
What are the benefits of CQRS, and why should we consider using it in our application?
The benefits of CQRS are:
- Single Responsibility – Commands and Queries have only one job. It is either to change the state of the application or retrieve it. Therefore, they are very easy to reason about and understand.
- Decoupling – The Command or Query is completely decoupled from its handler, giving you a lot of flexibility on the handler side to implement it the best way you see fit.
- Scalability – The CQRS pattern is very flexible in terms of how you can organize your data storage, giving you options for great scalability. You can use one database for both Commands and
- Queries. You can use separate Read/Write databases, for improved performance, with messaging or replication between the databases for synchronization.
- Testability – It is very easy to test Command or Query handlers since they will be very simple by design, and perform only a single job.
Of course, it can’t all be good. Here are some of the disadvantages of CQRS:
- Complexity – CQRS is an advanced design pattern, and it will take you time to fully understand it. It introduces a lot of complexity that will create friction and potential problems in your project. Be sure to consider everything, before deciding to use it in your project.
- Learning Curve – Although it seems like a straightforward design pattern, there is still a learning curve with CQRS. Most developers are used to the procedural (imperative) style of writing code, and CQRS is a big shift away from that.
- Hard to Debug – Since Commands and Queries are decoupled from their handler, there isn’t a natural imperative flow of the application. This makes it harder to debug than traditional applications.
Commands and Queries with MediatR
We talked about Commands and Queries in the previous section, now let’s see how we can implement them using MediatR.
MediatR uses the IRequest
interface to represent both a Command and a Query. For our use case, we will create separate abstractions for Commands and Queries.
First, let’s see how the ICommand
interface is defined:
using MediatR; namespace Application.Abstractions.Messaging { public interface ICommand<out TResponse> : IRequest<TResponse> { } }
Let’s also see how the IQuery
interface is defined:
using MediatR; namespace Application.Abstractions.Messaging { public interface IQuery<out TResponse> : IRequest<TResponse> { } }
We are declaring the generic type TResponse
with the out keyword, which denotes that it is covariant. This allows us to use a more derived type than that specified by the generic parameter. To learn more about covariance and contravariance you check out the Microsoft documentation.
Additionally, we will need separate abstractions for Command and Query handlers, for the sake of completeness. So, let’s inspect them:
using MediatR; namespace Application.Abstractions.Messaging { public interface ICommandHandler<in TCommand, TResponse> : IRequestHandler<TCommand, TResponse> where TCommand : ICommand<TResponse> { } }
using MediatR; namespace Application.Abstractions.Messaging { public interface IQueryHandler<in TQuery, TResponse> : IRequestHandler<TQuery, TResponse> where TQuery : IQuery<TResponse> { } }
Why are we bothering ourselves with defining custom interfaces for Command and Queries?
Isn’t MediatR’s IRequest
interface good enough?
With additional abstractions for Commands and Queries, the approach that we are talking about gives us much more flexibility going forward. Imagine if you want to enrich your Commands and Queries with additional features?
Here’s a simple example: we want all our commands to be idempotent. Idempotent means that they can only execute once.
We can extend the ICommand interface and create a new IIdempotentCommand
interface:
public interface IIdempotentCommand<out TResponse> : ICommand<TResponse> { Guid RequestId { get; set; } }
After that, we have to implement some logic to ensure that we have idempotency. But that is a very complex subject, for a future article.
Also, using additional abstractions for Commands and Queries gives us the ability to perform filtering within the MediatR pipeline, which we are going to see in the next sections.
Validation with FluentValidation
The FluentValidation library allows us to easily define very rich custom validation for our classes. Since we are implementing CQRS, it makes the most sense to define validation for our Commands.
We should not bother ourselves with defining validators for Queries, since they don’t contain any behavior. We use Queries only for fetching data from the application.
For example, we will take a look at the UpdateUserCommand
:
public sealed record UpdateUserCommand(int UserId, string FirstName, string LastName) : ICommand<Unit>;
We will use this command to update an existing user’s first and last name. To learn more about the role of Commands in CQRS be sure to check out CQRS and MediatR in ASP.NET Core.
Let’s see how to implement a validator for the UpdateUserCommand
:
public sealed class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand> { public UpdateUserCommandValidator() { RuleFor(x => x.UserId).NotEmpty(); RuleFor(x => x.FirstName).NotEmpty().MaximumLength(100); RuleFor(x => x.LastName).NotEmpty().MaximumLength(100); } }
With our UpdateUserCommandValidator
we want to make sure that the command’s arguments are not empty, and that the first and last names are less than the allowed maximum length.
That’s it, it is that simple!
In this article, we won’t go into more detail about what the FluentValidation library offers. But if you are not familiar with it, or want to learn more, be sure to check out this article FluentValidation in ASP.NET Core.
Creating Decorators with MediatR PipelineBehavior
The CQRS pattern uses Commands and Queries to convey information, and receive a response. In essence, it represents a request-response pipeline. This gives us the ability to easily introduce additional behavior around each request that is going through the pipeline, without actually modifying the original request.
You may be familiar with this technique under the name Decorator pattern. Another example of using the Decorator pattern is the ASP.NET Core Middleware concept.
MediatR has a similar concept to middleware, and it is called IPipelineBehavior
:
public interface IPipelineBehavior<in TRequest, TResponse> where TRequest : notnull { Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next); }
The pipeline behavior is a wrapper around a request instance and gives you a lot of flexibility with how you can implement it. Pipeline behaviors are a good fit for cross-cutting concerns in your application. Good examples of cross-cutting concerns are logging, caching, and of course, validation!
Creating a Validation PipelineBehavior
To implement validation in our CQRS pipeline, we are going to use the concepts that we just talked about, and those are MediatR’s IPipelineBehavior
and FluentValidation.
Let’s first see the ValidationBehavior
implementation:
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : class, ICommand<TResponse> { private readonly IEnumerable<IValidator<TRequest>> _validators; public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators; public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) { if (!_validators.Any()) { return await next(); } var context = new ValidationContext<TRequest>(request); var errorsDictionary = _validators .Select(x => x.Validate(context)) .SelectMany(x => x.Errors) .Where(x => x != null) .GroupBy( x => x.PropertyName, x => x.ErrorMessage, (propertyName, errorMessages) => new { Key = propertyName, Values = errorMessages.Distinct().ToArray() }) .ToDictionary(x => x.Key, x => x.Values); if (errorsDictionary.Any()) { throw new ValidationException(errorsDictionary); } return await next(); } }
Now, let’s discuss everything that is going on in the ValidationBehavior
.
First of all, notice how we are using the where
clause to only apply this IPipelineBehavior
implementation for classes that also implement the ICommand
interface. Essentially, we are only allowing this IPipelineBehavior
to execute if the request that is going through the pipeline is a Command. Remember how we said earlier that we would only want to validate Commands? This is how we achieve that.
Next, you can see that we are injecting a collection of IValidator
implementations in the constructor. The FluentValidation library will scan our project for all AbstractValidator
implementations for a given type, and then provide us with the instance at runtime. This is how we can apply the actual validators that we implemented in our project.
Lastly, if there are any validation errors we throw a ValidationException
that contains the validation errors dictionary. When the exception is thrown due to a validation error, the pipeline short-circuits and we prevent further execution. The only thing that is missing is to handle the exception in some of the higher layers of the application and present a meaningful response to the consumer. We are going to see how to do this in the next section.
Handling Validation Exceptions
To handle the ValidationException
that is thrown when we encounter a validation error, we can use the ASP.NET Core IMiddleware
interface. We will implement a global exception handler:
internal sealed class ExceptionHandlingMiddleware : IMiddleware { private readonly ILogger<ExceptionHandlingMiddleware> _logger; public ExceptionHandlingMiddleware(ILogger<ExceptionHandlingMiddleware> logger) => _logger = logger; public async Task InvokeAsync(HttpContext context, RequestDelegate next) { try { await next(context); } catch (Exception e) { _logger.LogError(e, e.Message); await HandleExceptionAsync(context, e); } } private static async Task HandleExceptionAsync(HttpContext httpContext, Exception exception) { var statusCode = GetStatusCode(exception); var response = new { title = GetTitle(exception), status = statusCode, detail = exception.Message, errors = GetErrors(exception) }; httpContext.Response.ContentType = "application/json"; httpContext.Response.StatusCode = statusCode; await httpContext.Response.WriteAsync(JsonSerializer.Serialize(response)); } private static int GetStatusCode(Exception exception) => exception switch { BadRequestException => StatusCodes.Status400BadRequest, NotFoundException => StatusCodes.Status404NotFound, ValidationException => StatusCodes.Status422UnprocessableEnttity, _ => StatusCodes.Status500InternalServerError }; private static string GetTitle(Exception exception) => exception switch { ApplicationException applicationException => applicationException.Title, _ => "Server Error" }; private static IReadOnlyDictionary<string, string[]> GetErrors(Exception exception) { IReadOnlyDictionary<string, string[]> errors = null; if (exception is ValidationException validationException) { errors = validationException.ErrorsDictionary; } return errors; } }
To learn more about how to create a global exception handler in your ASP.NET Core applications, be sure to check out this article Global Error Handling in ASP.NET Core Web API.
Setting up Dependency Injection
Before we can run our application, we need to make sure that we registered all of our services with the DI container.
If you are using the newest MediatR library, you will see that below MediatR registration code is not working, we must change it:
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
Let’s see how to register our Commands and Queries with MediatR. In the Startup
class ConfigureServices
we will add the following lines:
services.AddMediatR(typeof(Application.AssemblyReference).Assembly); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
In .NET 6, we have to modify the Program class:
builder.Services.AddMediatR(typeof(Application.AssemblyReference).Assembly); builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
The first method call will scan our Application
assembly and add all our Commands, Queries, and their respective handlers to the DI container.
The second method call is our ValidationBehavior
registration step. Without this, the validation pipeline would not execute at all.
Next, we need to make sure to also add the validators that we implemented using FluentValidation:
services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly); //Or in .NET 6 and above builder.Services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly);
Lastly, we need to add the ExceptionHandlingMiddleware
in the ConfigureServices
:
services.AddTransient<ExceptionHandlingMiddleware>(); //or in .NET 6 and above builder.Services.AddTransient<ExceptionHandlingMiddleware>();
And also in the Configure
method or in the pipeline registration part of the Program
class for .NET 6:
app.UseMiddleware<ExceptionHandlingMiddleware>();
That’s it, we have completed registering our services with the DI container. Now that we have everything wired up, we can proceed to test out our implementation.
Validation Pipeline in Practice
We are going to look at our UsersController
that we can find in the Presentation
project under the Controllers
folder:
/// <summary> /// The users controller. /// </summary> [ApiController] [Route("api/[controller]")] public sealed class UsersController : ControllerBase { private readonly ISender _sender; /// <summary> /// Initializes a new instance of the <see cref="UsersController"/> class. /// </summary> /// <param name="sender"></param> public UsersController(ISender sender) => _sender = sender; /// <summary> /// Updates the user with the specified identifier based on the specified request, if it exists. /// </summary> /// <param name="userId">The user identifier.</param> /// <param name="request">The update user request.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>No content.</returns> [HttpPut("{userId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<IActionResult> UpdateUser(int userId, [FromBody] UpdateUserRequest request, CancellationToken cancellationToken) { var command = request.Adapt<UpdateUserCommand>() with { UserId = userId }; await _sender.Send(command, cancellationToken); return NoContent(); } }
Please note that this isn’t the complete implementation of the UsersController
, we are only going to focus on the API endpoint for updating the user. For the complete implementation, you can visit our source code repository.
We can see that the UpdateUser
action is very simple, it collects the user identifier from the route and the first and last name from the request body, and creates a new UpdateUserCommand
instance. It then sends the command through the pipeline. And returns a 204 – No Content response in the end.
We’re not concerned with the validation at all in our API endpoint. This is the concern of the ValidationBehavior
.
Let’s try to send an invalid request through the Swagger interface:
This is the response we receive from the server:
Once we fix the request body and send the same request (userId 5):
We get the response stating that we don’t have the user with that identifier in our database.
When we change the user identifier to one that exists in the database, we get the expected response from the API with the 204 – No Content status code.
Conclusion
In this article, we have learned some more advanced concepts of the CQRS pattern and how to implement validation as a cross-cutting concern in our application.
We started with a basic explanation of what CQRS is, and what are its advantages and disadvantages. We then saw how to create Commands and Queries with the MediatR library.
Next, we briefly discussed how to use the FluentValidation library to add validation for our Commands.
We then saw how to implement the ValidationBehavior
to encapsulate validation as a cross-cutting concern in our application. We explained why we only want to validate our Commands, and how we can filter the pipeline so that it does not execute validation for Queries.
And finally, we saw how to bring it all together the ExceptionHandlingMiddleware
and how we can wire everything up with the ASP.NET Core DI container.
nice article
Hi, nice article, thank you.
In .net 6, you should specify explicitly as transient to register validators, otherwise they are registered as scoped and throws System.InvalidOperationException: “Cannot resolve ‘…’ from root provider because it requires scoped service ‘…’.”.
Example:
services.AddValidatorsFromAssembly(executingAssembly, ServiceLifetime.Transient);
Hello. Thank you. Well, I am not sure about that suggestion because our Ultimate API book is written in .NET 6 and we have registered validators in the same way we explained in the code snippet for the .NET 6. We didn’t have to specify the lifetime of the service explicitly. So, maybe your error was not related strictly to the version of .NET?
I love the approach.
Two questions though:
Hello Jaytonic. Regarding the first question, I never used it for the requests, so I am not sure what downsides it would have. For the second question, in our book in the CQRS section: https://code-maze.com/ultimate-aspnetcore-webapi-second-edition we cover this approach with the commands containing DTOs. It is pretty much the same, you just have to extract the exact properties from that DTO.
Hi im trying to follow the article to implement it. Dont know if the fluent validation has been updated but i cant throw the validation exception. Its like it does not accept a dictionary as a constructor parameter.
if (errorsDictionary.Any())
{
throw new ValidationException(errorsDictionary);
}
How is this done now?
Thanks for sharing your knowledge!!
Hello Juanma. Please check our source code. You will find a custom ValidationException class inside the Application/Exceptions folder.
Also, check the ValidationBehaviors class for this line of code:
using ValidationException = Application.Exceptions.ValidationException;
Omg didnt see it. Thanksss!!
It happens. You are most welcome and thanks for reading the article.
FluentValidation already has a registration to the built in validation of ASP.NET which works perfectly. What’s the benefit of removing that and using some custom pipeliene behavior and middleware?
Well, this is strictly related to MediatR’s pipeline behavior. We don’t have to use it only for validation, but it is something commonly used. Now, to be honest, I never tried to validate MediatR request/response outside of the pipeline, why should I when that pipeline already exists, but if you can implement it another way around, well great. As we said in the article: “The CQRS pattern uses Commands and Queries to convey information, and receive a response. In essence, it represents a request-response pipeline. This gives us the ability to easily introduce additional behavior around each request that is going through the pipeline, without actually modifying the original request.” So, the pipeline is the perfect place for this type of action.
You put the command as the body parameter in the action method and use the built in modelstate validation with fluent validation.
Well I know how to use FluentValidation, I just said that I didn’t use it with MediatR outside of the pipeline since it is preferred way for me 🙂 And again, this article is focused on pipeline and how you can use it in your projects, it doesn’t have to be the validation logic.
ASP.NET built-in validation is synchronous only, which means that asynchronous, for example entity uniqueness validation against async service is of the table with it, sadly.
Depends on the requirements of course, but this application level and uniform validation handling approach (I mean sync and async are supported) is definitely one of the good options.
P.S. MinimalAPI don’t have built-in validation logic.
Why are you awaiting
next()
in pipeline? At least, i think, you shouldawait next().ConfigureAwait(false)
? This way, without ConfigureAwait, you queue your next(), but with somewhat deeper pipeline its counter-productive, isnt it?or, maybe (and I am NOT sure about this 🙂 ) just drop
async
and returnnext()
without awaiting?Let me be honest here. We didn’t dive deep into this matter. The point is that during the time when I was writing my Global Error Handling article, I was researching this topic, and in the all topics I could find, they all awaited the next delegate. So, I didn’t investigate much about that specific thing but just used it that way. Thus the same implementation here.
Thank you for the article, I agree with your approach.
Other than the argument for expensive exceptions, I have also heard that using exceptions for flow control is an anti-pattern.
https://web.archive.org/web/20140430044213/http://c2.com/cgi-bin/wiki?DontUseExceptionsForFlowControl
Althought they argue that this breaks the “principle of least astonishment” it still seems quite practical.
What are your thoughts on the anti-pattern argument?
Hello Hubert. Well, you can check my first comment in reply to Djordje. That is my opinion. It is a great way to handle the flow when you need to stop it but only in exceptional situations. In our book, we use this approach but also provide an alternative abstraction that you can use if you don’t want to use exceptions. Both are tested and results are quite fast, so fast that users will never notice it. But, as I stated in my comment, if you throw an exception in a loop of 1000 iterations, of course, that will take a lot of time, but that is not something we are doing here.
Hi,
have a question regarding GetStatusCode() of global error handling in
exception switch
BadRequestException etc – no such props in Exception class. Where did you get them?
another question – I use Mediator.Send – and still don’t get into
ValidationBehavior
— on validation error come straight to globalexceptionhandler.Use .net core 5 , FV core
another question – not actual, works fine. Still actual question about Status code.
Hey Gleb, BadRequestException is a custom exception. Please navigate to the definition to see how it’s implemented. You can throw these exceptions yourself when appropriate.
As for the Mediator.Send, have you tried using our code or comparing your own to ours? Does it work?
Hi, I’m actually using this implementation as a guide to handling exceptions and validation errors from an internal api in a consistent manner. We already use CQRS, MediatR, and FluentValidation so it fits perfectly with our implementation.
My question has to do with the use of a Request dto in the controller method to then map to a command. It makes sense in a separation of concerns kind of way, and solves the problem of needing IDs from the route to be part of the command, which wouldn’t work if the command was coming from the request body payload. But did you do this for that reason or because the .NET core model state validation would hijack the validation response? Because that’s what I’m finding happens, and the ValidationBehavior pipeline is never hit. The built in error response is instead returned.
Do you think we need to turn off the built in model state validation and handling so we can just use this pipeline handling?
Hello Brian. We are always using DTOs to accept the request body in a controller or to return a result to the client (or at least this is how it should be done 🙂 ) In our Web API book this is something we strongly suggest due to several reasons. Regarding validation, your [APIController] attribute does that for you before your request hits the action. You don’t have to remove it since it provides a lot of cool features, but you can suppress only its validation part in the Startup or Program (for .NET 6) class.
Thank you! I agree on the separate DTO for the request body vs the command (or query) with the FluentValidation validator applied at the command level (i.e. application layer.) I guess my concern really is that the schema of the response from the built in .NET model validation is different than the custom error response being used in the exception handling middleware. So it ends up being an inconsistent experience for the api client, in our case an angular frontend, when say the model data binding has an error. Like if a string was sent in the request body when an int was expected.
Do you recommend doing anything to sync the schema of those error responses? That is really the only reason I’m considering suppressing that validation at the controller/api layer.
Well, in my projects, I would just suppress the APIController’s validation and add the validation logic with fluent validator and use behaviors. Basically, use either one or another. To be more precise, I suppress that ApiController’s validation and for projects without using MediatR and FluentValidation. The default error is 400 but if the model is invalid it should be 422, and this is something we have to implement.
Thank you! And great article. It’s been very helpful as I try to standardize validation responses for our application across layers.
Just for any others looking to do the same, the way I’m going to turn off the default model state validation is by adding this to the Startup:
services.Configure(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Yeah, that’s the way to do it.
Is there a way to automatically mark required fields (*) in Swagger UI when doing like this? Or do we need to add something to the request DTO?
If you refer to the parameters then as long as they are not optional Swagger should recognize those as required. If you talk about properties inside the DTO, then you can use required attribute for that.
Yeah, I was talking about the DTO properties. So I guess duplicating some of the validation logic is the only way then. Thanks!
As usual, excellent and thorough article! I like the idea to delegate validation to cross cutting stuff.
Thanks!
Thank you too, Branislav for the support and for the comment. Have a great day and all the best.
Hi Marinko,
Whats the .Adapt<UpdateUserCommand>() for?
Can’t find .Adapt method anywhere.
Hello Vid. That’s a method from the Mappster library, something similar to the AutoMapper library. It maps objects from one to another. You can find it installed in the Application project, or you can hover over it and press F12 and VS will take you to the class implementation in the Mapster namespace.
Great! Thank you 🙂
Hi, really nice article. I am using pretty much the same approach.
Few people told me that this kind of handling exception is not good.
They had actually only one argument, that exceptions are expensive.
Their approach is to return the result and never throw exceptions.
TBH I think this approach is great and under control.
I would like to hear your opinion about this?
Thanks
Hello Djordje. Thanks for reading the article and for your comment.
I just had a similar comment on my LinkedIn post, so I can just copy-paste my response 🙂
Thank you for reading the article. Regarding the performance degradation, I don’t agree with that. Yes, the response, once you need to return a validation error will take some more time, but this is still time in milliseconds, which the user can’t notice at all. I’ve read a lot of articles stating that those exceptions cause a performance degradation but all the tests were like put the exception in a for loop, iterate 1000 times, throw an exception, and compare that to the regular response. In those cases the difference is obvious, but this is not something we are doing here. These are exceptional cases, and once the request is sent you are returning a single response, and as I said, yes it takes some more time, but not noticeable to the client at all. Also, you can use the same technique from the Service Layer (if you are not using CQRS and MediatR) to return the response to the client and stop the request flow. Of course, this is my opinion, but it is also tested by us, and tested on a large-scale project, where everything works perfectly. In the end, everything should be tested and checked. Any solution that causes performance degradation, whatever it may be, should be modified.
So obviously, I share your thoughts on this, but as I said at the end of my comment, everything should be checked and tested and if it doesn’t work or causes performance issues it should be modified.