ASP.NET Core 8: IExceptionHandler für APIs

Einleitung
Seit der ersten Version von .NET Core bin ich dabei und habe die Verbesserungen während der Entwicklung erlebt.
Derzeit plane ich eine neue Basis für mein System und überprüfe daher regelmäßig verschiedene Teilbereiche und deren Lösungen in den neueren .NET Core-Versionen. Eine dieser Verbesserungen ist das Exception Handling.
In diesem Beitrag unterscheide ich zwei Arten von Fehlern: Systemfehler und Logikfehler. Systemfehler umfassen unerreichbare Systeme wie Datenbanken, Caches oder externe Dienste sowie Programmierfehler. Logikfehler hingegen stehen im Zusammenhang mit der Geschäftslogik. Zum Beispiel, wenn ein Benutzer versucht, ein bereits storniertes Ticket zu stornieren, ohne die nötigen Berechtigungen zu haben.
Bei Systemfehlern sollten keine Details preisgegeben werden. Ist beispielsweise der SQL Server nicht erreichbar, sollte der Nutzer höchstens erfahren, dass es ein Datenbankproblem gibt, ohne weitere Einzelheiten. Die Weitergabe von Fehlermeldungen könnte sonst die Adresse des SQL-Servers, den Datenbanknamen und möglicherweise den Benutzernamen offenlegen, was ein Sicherheitsrisiko darstellt. Diese Daten müssen daher bereinigt werden.
Bei Logikfehlern sollte der Benutzer hingegen mehr Informationen erhalten. Wenn ein Benutzer versucht, ein storniertes Ticket zu stornieren, sollte ihm mitgeteilt werden, dass dies nicht möglich ist. Im Idealfall sollte auch die fehlende Berechtigung angezeigt werden.
Umsetzung
Das zuvor erwähnte anzeigen von Server-Details, einschließlich der SQL-Verbindungsdaten, müssen etwas präzisiert werden. Asp.Net Core unterscheidet zwischen drei Umgebungen: Development
, Stage
und Production
. Das Verhalten der Anwendung variiert je nach Umgebung. Ist die Umgebung auf Production
eingestellt, werden keinerlei Informationen angezeigt, nicht einmal der Text der Ausnahme.
Vor .Net Core 8
In ASP.NET Core vor Version 8 konnten Fehler mittels Middleware abgefangen werden. Dabei wurde eine Middleware mit einem Try-Catch
-Block implementiert, wobei die Fehlerbehandlung im Catch
-Teil erfolgte.
public class ExceptionHandlingMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
ILogger<ExceptionHandlingMiddleware> logger = context.RequestServices.GetRequiredService<ILogger<ExceptionHandlingMiddleware>>();
logger.LogError(ex, "An unexpected error occurred");
ProblemDetails problemDetails = new()
{
Status = StatusCodes.Status500InternalServerError,
Type = ex.GetType().Name,
Title = "An unexpected error occurred",
Detail = ex.Message,
Instance = $"{context.Request.Method} {context.Request.Path}"
};
context.Response.StatusCode =
StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(problemDetails);
}
}
}
app.UseMiddleware<ExceptionHandlingMiddleware>();
IExceptionHandler
In Asp.Net Core 8 wurde mit dem IExceptionHandler
eine neue Methode zur Fehlerbehandlung eingeführt.
public interface IExceptionHandler
{
ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken);
}
IExceptionHandler Interface
public class DefaultExceptionHandler(ILogger<DefaultExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
{
logger.LogError(exception, "An unexpected error occurred");
await context.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Type = exception.GetType().Name,
Title = "An unexpected error occurred",
Detail = exception.Message,
Instance = $"{context.Request.Method} {context.Request.Path}"
});
return true;
}
}
builder.Services.AddExceptionHandler<DefaultExceptionHandler>();
builder.Services.AddProblemDetails();
app.UseExceptionHandler();

Verkettung (Chaining)
ExceptionHandler
können miteinander verkettet werden, indem der Rückgabewert der Funktion genutzt wird: true
bedeutet, dass der Fehler behandelt wurde, und false
, dass der Fehler nicht behandelt wurde und ein anderer ExceptionHandler
eingreifen muss.
Dies bietet den Vorteil, dass Bibliotheken eigene ExceptionHandler
für ihre spezifischen Exceptions bereitstellen können.