Full stack dev🧑🏻‍💻

How to create a general error handler in ASP.NET Core using an error handler middleware and custom exceptions

Posted

4 min read

Cover Image for How to create a general error handler in ASP.NET Core using an error handler middleware and custom exceptions

If you're like me and hate it when your API controllers get cluttered with a lot of duplicated code to handle errors, such as having a bunch of try-catch blocks, then you might think this is a neat way to handle errors.

Background

Middlewares

I prefer to use middlewares in .NET Core that will contain my general error handling so that the API controllers can just focus on validating a model, and call the service layer to fetch data and execute various requests.

A middleware is basically a component that is executed for every request. You can easily create your own middlewares and have them run in the order you want. Some examples of my own custom middlewares I've created before are middlewares for: JWT validation, error handling, and extraction of user info from JWT to add to HttpContext for the duration of the request. Read more about what middlewares are here in the Microsoft docs: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0

Custom exceptions (optional)

I like to combine my error handling middleware with the usage of custom exceptions to allow myself to easily throw an exception from any layer in my code that I want, for example, a service layer. It is not necessary to do this in order to implement a custom middleware, but I think it makes it a nicer solution.

By utilizing custom exceptions, I can make it clear to the reader of the code what type of error/exception is occurring, as well as be able to catch and handle it in my middleware to add some specific logging, response to the client, etc.


TL;DR, if you just want to see the code you can go to the Github repo here: https://github.com/fullstackdev-cloud/errorHandlerMiddleware

Tag along to see how I went about creating this simple sample. 🙋🏻‍♂️

1. Create a new ASP.NET Core project

If you don't already have a project set up, simply create one using Visual Studio or just run the command to create a project such as: dotnet new -webapi.

This creates a new web api project, and we'll use it to show how we can implement an error handler using middlewares. Let's keep everything in the project just for the sake of it.

2. Create a custom exception

Create a new folder called Exceptions and add a new class to represent your exception. Since naming is one of the toughest things in development, I just called mine YouDidSomethingStupidException in this sample repo.

For more information about exceptions and creating custom exceptions, see the Microsoft docs on exceptions.

1namespace errorHandlerMiddleware.Exceptions
2{
3    public class YouDidSomethingStupidException : Exception
4    {
5        public YouDidSomethingStupidException(string message) : base(message)
6        {
7        }
8    }
9}

3. Create a middleware

Create a new folder called Middlewares and add a new class called ErrorHandlerMiddleware. This class will hold all of the functionality that our error handler should have.

To learn more about creating custom middlewares, please refer to the Microsoft docs on middlewares.

1using System.Net;
2using errorHandlerMiddleware.Exceptions;
3
4namespace errorHandlerMiddleware.Middlewares
5{
6    public class ErrorHandlerMiddleware
7    {
8        private readonly RequestDelegate _next;
9        private readonly ILogger<ErrorHandlerMiddleware> _logger;
10
11        public ErrorHandlerMiddleware(
12            RequestDelegate next,
13            ILogger<ErrorHandlerMiddleware> logger)
14        {
15            _next = next;
16            _logger = logger;
17        }
18
19        public async Task Invoke(HttpContext context)
20        {
21            try
22            {
23                await _next(context);
24            }
25            catch (Exception ex)
26            {
27                var response = context.Response;
28                response.ContentType = "application/json";
29
30                switch (ex)
31                {
32                    case YouDidSomethingStupidException:
33                        _logger.LogError(ex, "Something stupid happened");
34                        response.StatusCode = (int)HttpStatusCode.Forbidden; // or whatever status you want...
35                        await response.WriteAsync("You are not allowed to do stupid things!");
36                        return;
37                    default:
38                        _logger.LogError(ex, "An unexpected error occurred");
39                        response.StatusCode = (int)HttpStatusCode.InternalServerError;
40                        return;
41                }
42            }
43        }
44    }
45}

The middleware will contain a try catch block with a switch statement in the catch block to look for specific exceptions to act on. Make sure to add a default so that any unhandled exceptions are caught and dealt with.

You can set a status code and write some text in the response body. In this sample I've just written some text to the response body and returned a 403 Forbidden status code, but you could easily modify that to fit your needs.

Bonus - send JSON response

If you would like to pass along a JSON object to the client, you could do that by using the response.WriteAsJson() method as shown in the sample below (quick and dirty example, you would likely want to create a DTO/class for that).

Just add another case in your switch case and pass along an object of your choice by writing it to the response:

1case SomethingBadException:
2                        _logger.LogError(ex, "Something bad happened");
3                        response.StatusCode = (int)HttpStatusCode.Conflict;
4
5                        // Some sample object to show how we could pass along some JSON response to the client
6                        var sampleObj = new 
7                        {
8                            Message = "test",
9                            ErrorType = "something_bad",
10                        };
11
12                        // Return a JSON response to the client
13                        await response.WriteAsJsonAsync(sampleObj);
14                        return;

4. Register your middleware

In the startup of your application (Program.cs in ASP.NET Core 6 templates) you need to register your middlewares so that they are actually run. Note, if you have multiple middlewares, you need to make sure to run them in the order you expect them to be executed. The order is important.

To register a middleware, you just simple add the following line in your Program.cs file. In my case I wanted the error handler to be run before any other middlewares, so I placed it right above everything else (see line 22).

1using errorHandlerMiddleware.Middlewares;
2
3var builder = WebApplication.CreateBuilder(args);
4
5// Add services to the container.
6
7builder.Services.AddControllers();
8// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
9builder.Services.AddEndpointsApiExplorer();
10builder.Services.AddSwaggerGen();
11
12var app = builder.Build();
13
14// Configure the HTTP request pipeline.
15if (app.Environment.IsDevelopment())
16{
17    app.UseSwagger();
18    app.UseSwaggerUI();
19}
20
21// Register your middlewares here in the order you want them to be run
22app.UseMiddleware<ErrorHandlerMiddleware>();
23
24app.UseHttpsRedirection();
25
26app.UseAuthorization();
27
28app.MapControllers();
29
30app.Run();

5. Throw the exception

In order to make a simple test out of this, just go in to the already existing WeatherForecastController and replace everything in the Get method so that the only thing it does is to throw our custom exception, the file should look something like this:

1using errorHandlerMiddleware.Exceptions;
2using Microsoft.AspNetCore.Mvc;
3
4namespace errorHandlerMiddleware.Controllers;
5
6[ApiController]
7[Route("[controller]")]
8public class WeatherForecastController : ControllerBase
9{
10    private readonly ILogger<WeatherForecastController> _logger;
11
12    public WeatherForecastController(ILogger<WeatherForecastController> logger)
13    {
14        _logger = logger;
15    }
16
17    [HttpGet(Name = "GetWeatherForecast")]
18    public IEnumerable<WeatherForecast> Get()
19    {
20        _logger.LogInformation("Trying to do something stupid.");
21        throw new YouDidSomethingStupidException("Oops, something broke.");
22    }
23}
24

6. Test it out!

Make sure to build and run your ASP.NET Core project (dotnet build followed by dotnet run).

Test it out by making a call to your API controller where the exception is being thrown and observe the results, see an example from my screenshot using Postman to test it out:

That's it, a general error handler with custom exceptions and a middleware in a few simple steps. The code for this blog post can be found in the github repo here: https://github.com/fullstackdev-cloud/errorHandlerMiddleware

Isa
Isa

More Stories

Query multiple Application Insights instances with KQL (Kusto Query Language)

Query multiple application insights instances using KQL (Kusto Query Language)...

Cover Image for C# Cosmos DB simple "lock" functionality by implementing Optimistic Concurrency Control

C# Cosmos DB simple "lock" functionality by implementing Optimistic Concurrency Control

Prevent data corruption when multiple users concurrently try to update a single Cosmos DB item in an Azure Function. Implement optimistic concurrency control using ETags and retry logic to ensure correct updates....