Using asynchronous fluent validations in ASP.NET API
Context
While at work, my friend Ben, and myself came across an interesting problem to fix, when upgrading our APIs to latest .NET and upgrading their respective packages.
Validation is crucial when designing APIs, and we are using the wonderful FluentValidation.AspNetCore
in our APIs. How it seamlessly allow the engineers to define the validations, and integrate with the ASP.NET echo system is a breeze.
Some of our validators contain asynchronous validations, and after upgrading we got the below error.
The error clearly states that our validators contain asynchronous
validations, and the ASP.NET validation pipeline is not asynchronous and hence can’t invoke asynchronous rules.
Yes, it can be debated that validations must be synchronous, but in reality there are many situations where certain validations have to be asynchronous, such as validating with a data store entry or with a web service. Also it can be the opinion, that such actions should be the responsibility of the core layer(application / domain), but this was existing code, and in our opinion the approach was correct ☺️
The code for this article can be found in GitHub
Options Considered
- Move the
async
validations from the validators, and move it to the application or the business layer.
Then we will have to create another level of abstraction to be injected into the controller or the core layers, and to make changes to our existing validators and respective classes. Also we’ll lose all the “async” validations performed at the time of model binding.
- Implementing a custom action filter.
This action filter needs to perform asynchronous operations, and should be able to use the existing validators without any code changes to them. At the time of execution it needs to be able to find the respective IValidator
and perform the validations, and most importantly if it contains validation errors to short circuit the ASP.NET pipeline.
💡 we chose to implement a custom action filter to perform validations.
Demonstrating the problem, with a sample API
This is an API to manage products. For the sake of brevity I have created only a single endpoint which simulates an adding of a product.
In the Program.cs
we register the dependencies.
The Validator
for the DTO
The action method which will expose the endpoint
Note that I have used another Nuget package called HybridModelBinding
to bind the model from different model binders and value providers.
As you can see below the DTO is getting populated by both from the header and from the body. Hybrid model binding is not relevant to the validations, but wanted to demonstrate that since the validation approach must happen after the model binding.
When you run this project, and make an HTTP POST request to this endpoint, you’ll get the above mentioned error.
Custom action filter to validate DTOs
When designing the solution, we wanted to design, and implement the filter to be used with our many other APIs.
So we could make this as a common library such as a nuget package to be used.
Let’s explore what has been implemented in the action filter.
- Inject an
ICustomValidatorFactory
which will get the respectiveIValidator
instance. - Implement the
IAsyncActionFilter
interface and it’sOnActionExecutionAsync
method.
In here for each action argument’s type it tries to get the respective IValidator
instance if available. If a validator instance is available it will call it’s ValidateAsync
method. The ValidateAsync
method will perform both synchronous and asynchronous validations. Finally if there are validation errors it will use these validation errors to create a ProblemDetails
response and set it in the action result and, short circuit the ASP.NET pipeline.
Please see the extension method to return ProblemDetails
from ValidationFailures
.
Most importantly let’s check the CustomValidatorFactory
implementation.
Let’s explore the validator factory now.
- An
IServiceProvider
is injected, this will be injected by ASP.NET itself. - We create an
IServiceScope
using theIServiceProvider
because we wanted this code to be independent of how the actualIValidator
instances are registered with the dependency injection for their lifetime. - Create an
IValidator
instance and return it.
Let’s put these all together
- Extension method to register
FluentValidators
, and theCustomValidatorFactory
Notice that there are two options which can be used, one with a single assembly and the other with a set of assemblies where the validators are located.
In Program.cs
let’s use it. Note that the ApiValidationFilter
has been registered in the controller configuration.
Testing
Please find the unit tests for this in the Demo.Products.Api.Tests
project.
If you would like to run this project, and test I highly recommend the RestClient
either in VSCode
or in Rider
. I am using Rider
, and you can see the results below.