Global Error Handling in DotNet Core

Global Error Handling in DotNet Core

10. März 2024 | Gregor Koletzki | 2 Min. Lesezeit

In diesem Artikel gehe ich auf die Globale Fehler Behandlung in DotNet Core ein. Im ersten Part wirst du den "alten" weg finden über eine Exception Handling Middleware. Im laufe des Artikels zeige ich dir wie du in DotNet Core 8 jetzt mit dem neuem Interface IExceptionHandler umgehen kann.

Exception Handling mit einer Middleware

Die alte Implementierung zur Behandlung von Exceptions in DotNet Core ist die Verwendung von einer Middleware. Mit einer Middleware kann man vor oder nach der Ausführung einer HTTP-Anfragen Logik ausführen. Ihr könnt dies problemlos erweitern, um das Exception Handling zu implementieren. Füge der Middleware eine Try-Catch-Anweisung hinzu und gib eine HTTP-Fehlerantwort zurück wenn eine Exception im catch block gefangen wird.

Hier ein Beispiel wie eine Exception Handling Middleware aussehen kann:

public class GlobalExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionHandlingMiddleware> _logger;

    public GlobalExceptionHandlingMiddleware(
        RequestDelegate next,
        ILogger<GlobalExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex, "Exception occurred: {Message}", ex.Message);

            var problemDetails = new ProblemDetails
            {
                Status = StatusCodes.Status500InternalServerError,
                Title = "Server Error",
                Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1"
            };

            context.Response.StatusCode =
                StatusCodes.Status500InternalServerError;

            await context.Response.WriteAsJsonAsync(problemDetails);
        }
    }
}

Die Middleware fängt alle Fehler ab die bei der HTTP Anfrage passiert sind und wandelt diese in eine Problem Details Antwort um. Man kann, somit entscheiden welche Informationen an den Aufrufer zurückgegeben werden. Es besteht auch die Möglichkeit für bestimmte Umgebungen unterschiedliche Ergebnisse zurückzugeben.

Damit die Middleware ausgeführt wird, muss diese an der Applikation registriert werden:

app.UseMiddleware<GlobalExceptionHandlingMiddleware>();

Exception Handling mit dem neuem interface IExceptionHandler

Ab DotNet Core 8 wurde uns die Möglichkeit gegeben das interface IExceptionHandler zu implementieren um Exceptions global zu behandeln. Die Methode TryHandleAsync muss implementiert werden. Wenn die übergebene Exception behandelt werden kann, so gibt die Methode ein true zurück, ansonsten wird ein false zurückgegeben. Das ermöglicht uns unterschiedliche Behandlungen von Exceptions.

Hier ist ein Beispiel wie eine Implementierung des IExceptionHandler interfaces aussehen kann:

public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
    {
        _logger = logger;
    }
    
    public async  ValueTask<bool> TryHandleAsync(
        HttpContext httpContext, 
        Exception exception, 
        CancellationToken cancellationToken)
    {
        _logger.LogError(
            exception, "Exception occurred: {Message}", exception.Message);

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "Server Error",
            Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1"
        };

        httpContext.Response.StatusCode =
            StatusCodes.Status500InternalServerError;

        await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
        return true;
    }
}

Damit die Implementierung aufgerufen wird, muss die Applikation dafür noch Konfiguriert werden.

  1. Registriere die IExceptionHandler Implementierung an der Dependency Injection
  2. Die ExceptionHandlerMiddleware muss registriert werden.

Rufe die AddExceptionHandler Methode an der ServiceCollection auf, um die Implementierung als Service an der Dependency Injection zu registrieren. Dieser Service wird als Singleton registriert.

Rufe noch AddProblemDetails auf, um diese für die HTTP Antworten zu registrieren.

builder.Services.AddExceptionHandler<GlobalExceptionHandling>();
builder.Services.AddProblemDetails();

An der Applikation muss noch das Exception Handling registriert werden.

app.UseExceptionHandler();

Verkettung von ExceptionHandlern

Es ist möglich mehrere Implementierungen von dem IExceptionHandler zu machen, die dann in der Reihenfolge aufgerufen werden, wie sie Registriert wurden.
Somit kann man für verschiedene Exceptions unterschiedliches Verhalten auslösen.

Hier z.B. eine DomainExceptionHandler Implementierung:

public class DomainExceptionHandler : IExceptionHandler
{
    private readonly ILogger<DomainExceptionHandler> _logger;

    public DomainExceptionHandler(ILogger<DomainExceptionHandler> logger)
    {
        _logger = logger;
    }
    
    public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, 
        Exception exception, 
        CancellationToken cancellationToken)
    {
        if (exception is not DomainException)
        {
            return false;
        }
        
        _logger.LogError(
            exception, "DomainException occurred: {Message}", exception.Message);

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "Server Error",
            Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1"
        };

        httpContext.Response.StatusCode =
            StatusCodes.Status500InternalServerError;

        await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
        return true;
    }
}

Hier z.B. eine NotFoundExceptionHandler Implementierung:

public class NotFoundExceptionHandler : IExceptionHandler
{
    private readonly ILogger<NotFoundExceptionHandler> _logger;

    public NotFoundExceptionHandler(ILogger<NotFoundExceptionHandler> logger)
    {
        _logger = logger;
    }
    
    public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, 
        Exception exception, 
        CancellationToken cancellationToken)
    {
        if (exception is not NotFoundException)
        {
            return false;
        }
        
        _logger.LogError(
            exception, "DomainException occurred: {Message}", exception.Message);

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status404NotFound,
            Title = "NotFound",
            Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4"
        };

        httpContext.Response.StatusCode =
            StatusCodes.Status500InternalServerError;

        await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
        return true;
    }
}

Beide Exception Handler müssen dann Registriert werden:

builder.Services.AddExceptionHandler<DomainExceptionHandler>();
builder.Services.AddExceptionHandler<NotFoundExceptionHandler>();

Dann wird der DomainExceptionHandler als erstes ausgeführt. Wenn die Exception in dem erstem Handler nicht verarbeitet wird, so wird der nächste Handler in diesem Beispiel NotFoundExceptionHandler ausgeführt.

Weiterführende Artikel

Kategorien

Kontaktieren Sie uns:

Harksheider Weg 60H,
25451 Quickborn
+49 1520 40 73 253
info@gk-itsolutions.de

Schnellzugriff:

Folgt uns auf: