Published: January 17 2022

.NET 6.0 - Global Error Handler Tutorial with Example

Tutorial built with .NET 6.0

Other versions available:

This is a quick post to show how to implement a global exception handler in .NET 6.0.

These are the main pieces involved in the error handling process that we'll cover in this tutorial:

  • Global Error Handler Middleware - custom middleware that catches all exceptions and determines which HTTP response code to return based on the exception type.
  • App Exception - a custom exception class to differentiate between handled exceptions thrown by application code and unhandled exceptions thrown by the .NET framework.
  • Program.cs - the entry point to the .NET 6 app, it adds the global error handler middleware to the application request pipeline.
  • Example Error Throwing Service - an example service that shows how to throw application exceptions that will be handled by the global error handler.

The below code snippets are taken from a .NET API tutorial I posted recently, for the full tutorial or to download and test the code locally see .NET 6.0 - User Registration and Login Tutorial with Example API.

 

.NET Global Error Handler Middleware

The global error handler is used catch all errors and remove the need for duplicated error handling code throughout the .NET api. It's configured as middleware in the configure HTTP request pipeline section of the Program.cs file.

Errors of type AppException are treated as custom (app specific) errors that return a 400 Bad Request response, the .NET built-in KeyNotFoundException class is used to return 404 Not Found responses, all other exceptions are unhandled and return a 500 Internal Server Error response.

See the example service for examples of how to throw an AppException or KeyNotFoundException that will be handled by this error handler middleware.

namespace WebApi.Helpers;

using System.Net;
using System.Text.Json;

public class ErrorHandlerMiddleware
{
    private readonly RequestDelegate _next;

    public ErrorHandlerMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception error)
        {
            var response = context.Response;
            response.ContentType = "application/json";

            switch(error)
            {
                case AppException e:
                    // custom application error
                    response.StatusCode = (int)HttpStatusCode.BadRequest;
                    break;
                case KeyNotFoundException e:
                    // not found error
                    response.StatusCode = (int)HttpStatusCode.NotFound;
                    break;
                default:
                    // unhandled error
                    response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    break;
            }

            var result = JsonSerializer.Serialize(new { message = error?.Message });
            await response.WriteAsync(result);
        }
    }
}
 

.NET App Exception

The app exception is a custom exception class used to differentiate between handled and unhandled exceptions in the .NET API. Handled exceptions are generated by application code and used to return friendly error messages, for example business logic or validation exceptions caused by invalid request parameters, whereas unhandled exceptions are generated by the .NET framework or caused by bugs in application code.

namespace WebApi.Helpers;

using System.Globalization;

// custom exception class for throwing application specific exceptions (e.g. for validation) 
// that can be caught and handled within the application
public class AppException : Exception
{
    public AppException() : base() {}

    public AppException(string message) : base(message) { }

    public AppException(string message, params object[] args) 
        : base(String.Format(CultureInfo.CurrentCulture, message, args))
    {
    }
}
 

.NET 6 Program

The .NET 6 Program file contains top-level statements which are converted by the new C# 10 compiler into a Main() method and Program class for the .NET program. The Main() method is the entry point for a .NET application, when an app is started it searches for the Main() method to begin execution. The top-level statements can be located anywhere in the project but are typically placed in the Program.cs file, only one file can contain top-level statements within a .NET application.

The Program class configures .NET Dependency Injection (DI) the HTTP request pipeline for the application. The global error handler middleware is configured on line 51 by calling app.UseMiddleware<ErrorHandlerMiddleware>();

using Microsoft.EntityFrameworkCore;
using WebApi.Authorization;
using WebApi.Helpers;
using WebApi.Services;

var builder = WebApplication.CreateBuilder(args);

// add services to DI container
{
    var services = builder.Services;
    var env = builder.Environment;
 
    // use sql server db in production and sqlite db in development
    if (env.IsProduction())
        services.AddDbContext<DataContext>();
    else
        services.AddDbContext<DataContext, SqliteDataContext>();
 
    services.AddCors();
    services.AddControllers();

    // configure automapper with all automapper profiles from this assembly
    services.AddAutoMapper(typeof(Program));

    // configure strongly typed settings object
    services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));

    // configure DI for application services
    services.AddScoped<IJwtUtils, JwtUtils>();
    services.AddScoped<IUserService, UserService>();
}

var app = builder.Build();

// migrate any database changes on startup (includes initial db creation)
using (var scope = app.Services.CreateScope())
{
    var dataContext = scope.ServiceProvider.GetRequiredService<DataContext>();    
    dataContext.Database.Migrate();
}

// configure HTTP request pipeline
{
    // global cors policy
    app.UseCors(x => x
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader());

    // global error handler
    app.UseMiddleware<ErrorHandlerMiddleware>();

    // custom jwt auth middleware
    app.UseMiddleware<JwtMiddleware>();

    app.MapControllers();
}

app.Run("http://localhost:4000");
 

.NET Example Error Throwing Service

This is an example service to show how to throw custom app exceptions and not found exceptions that will be handled by the global error handler middleware, resulting in 400 and 404 HTTP responses respectively.

namespace WebApi.Services;
    
using WebApi.Helpers;

public class ExampleErrorService
{
    public void ExampleErrors() {
        // a custom app exception that will return a 400 response
        throw new AppException("Email or password is incorrect");

        // a key not found exception that will return a 404 response
        throw new KeyNotFoundException("Account not found");
    }
}

 


Need Some .NET Help?

Search fiverr for freelance .NET developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by