Custom Error Responses With ASP.NET Core 6 Web API and FluentValidation

How to automate error handling in ASP.NET Core

Bohdan Tron
CodeX
4 min readSep 22, 2022

--

Photo by Erik Mclean on Unsplash

Let’s say we’re creating a public API that multiple clients are supposed to use, and we’ve reached a point of validating incoming models and handling bad requests. We want to automate this process as much as possible. In addition, the requirement is to implement a unique code for every error and include it in the response. Let’s consider how to do this using ASP.NET Core 6 and FluentValidation.

Create a Web API Project

Firstly let’s create a new solution with a web project using the following three commands in the command line:

dotnet new sln --name WebAPICustomErrorResponses
dotnet new web --name WebAPI --framework net6.0
dotnet sln add .\WebAPI\WebAPI.csproj

Now let’s go to WebAPI project and add Swagger and FluentValidation packages to it:

cd WebAPI
dotnet add package Swashbuckle.AspNetCore --version 6.2.3
dotnet add package FluentValidation.AspNetCore --version 11.1.2

Go to launchSettings.json and add the following line to WebAPI and IIS Express profiles:

"launchUrl": "swagger"

And make Program.cs look this way:

Program.cs

Implement Endpoints

In our demo app, we will implement a simple API to create and get products. The product model will look this way:

Product.cs

And the controller with GET and POST methods will look like this:

ProductsController.cs

We will not spend much time setting up a database or another storage for our products since that’s not what this article’s about, so let’s just use the Products static field and store our items there (line 9).

To add the controllers to ASP.NET Core, let’s modify Program.cs this way (lines 6 and 15):

Program.cs

Validate Requests

So now we’ve reached the point when we need to validate products before adding them to the storage. As stated earlier, the requirement is to have a unique code for every error. So let’s define the following error model:

Error.cs

Next, let’s create ProductValidator class and add some validation rules to the Product model:

ProductValidator.cs

To make it work we also need to register ProductValidator with the Service Provider, so add the following line to your Program.cs:

Program.cs

And let’s inject our validator into the controller:

ProductsController.cs

To test this, let’s try creating a product with an empty name and quantity:

API Bad Response

So now we can’t create a new product with invalid fields, and the error model meets the requirements as well, but how can we improve our code for further extensions?

Auto Validation

Manual validation works great, but what if we don’t want to write the same code within every endpoint that needs to be validated? Fortunately, FluentValidation gives us this opportunity. First of all, let’s modify Program.cs this way:

Program.cs

We removed the ProductValidator registration and replaced it with automatic registration of all validators from our assembly (lines 10–13). Because of those couple lines of code, we no longer need to register validators in Program.cs.

Moreover, we don’t need to write error handling code in our endpoints, ASP.NET will do this for us. So let’s roll back the ProductsController to the following state:

ProductsController.cs

Let’s check it out:

API Bad Response

The validation still works, but wait! The error model has changed, and the reason behind this is that ASP.NET uses its ModelState to handle all invalid requests.

To override that behavior we need to resort to the approach that is not documented well enough.

At first, we need to implement an interceptor and pass the error code with the message to the ModelState. The interceptor may look this way:

UseCustomErrorModelInterceptor.cs

The truth is that we cannot override ModelState structure in such an interceptor. But we can serialize the error to the appropriate model and pass it forward as a string.

You probably think if we serialize our errors in the interceptor, we also need to deserialize them somewhere else… and you’re right!

Let’s go to Program.cs and modify it the following way:

Program.cs

Lines 13–23 are key here, we’re deserializing the errors that came from the interceptor and changing the default structure of the error response to fit our Error model.

Also, don’t forget to register the interceptor with the Service Provider (line 29).

Let’s call our endpoint one more time:

API Bad Response

We got the expected result! That could be it… but let’s slightly improve and refactor our program.

Refactoring

To make the process of writing new validators more convenient let’s create a small extension method:

ValidatorExtensions.cs

And the usage of that method will be like this:

ProductValidator.cs

Conclusion

Alright, that’s it. Thanks for reading! You can find the source code on my GitHub.

--

--