Skip to content

ASP.NET Core 8中的全局错误处理

Published: at 12:00 AM

摘要

异常用于处理异常情况。但它们在您的应用程序中不可避免地会发生,您需要处理这些异常。您可以实现全局异常处理机制或只处理特定的异常。ASP.NET Core为您提供了几种实现这一点的选项。那么您应该选择哪一种呢?今天,我想向您展示在ASP.NET Core 8中处理异常的旧方法和新方法。

原文 Global Error Handling in ASP.NET Core 8Milan Jovanović 发表。


异常用于处理异常情况。我甚至写过完全避免异常。

但它们在您的应用程序中不可避免地会发生,您需要处理它们。

您可以实现全局异常处理机制或只处理特定的异常。

ASP.NET Core为您提供了几种实现这一点的选项。那么您应该选择哪一种呢?

今天,我想向您展示ASP.NET Core 8中处理异常的方法和方法。

旧方法:异常处理中间件

在ASP.NET Core中实现异常处理的标准是使用中间件。中间件允许您在执行HTTP请求之前或之后引入逻辑。您可以轻松扩展这一点来实现异常处理。在中间件中添加try-catch语句并返回错误HTTP响应。

在ASP.NET Core中有3种创建中间件的方式

基于约定的方法要求您定义一个InvokeAsync方法。

这是一个按约定定义的ExceptionHandlingMiddleware

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

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

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

            var problemDetails = new ProblemDetails
            {
                Status = StatusCodes.Status500InternalServerError,
                Title = "Server Error"
            };

            context.Response.StatusCode =
                StatusCodes.Status500InternalServerError;

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

ExceptionHandlingMiddleware会捕获任何未处理的异常并返回一个Problem Details响应。您可以决定要向调用者返回多少信息。在此示例中,我隐藏了异常详情。

您还需要将此中间件添加到ASP.NET Core请求管道中:

app.UseMiddleware<ExceptionHandlingMiddleware>();

新方法:IExceptionHandler

ASP.NET Core 8引入了一个新的IExceptionHandler抽象,用于管理异常。内置的异常处理中间件使用IExceptionHandler实现来处理异常。

这个接口只有一个TryHandleAsync方法。

TryHandleAsync尝试在ASP.NET Core管道中处理指定的异常。如果异常可以被处理,它应该返回true。如果异常无法处理,它应该返回false。这允许您为不同的场景实现自定义异常处理逻辑。

这是一个GlobalExceptionHandler实现:

internal sealed 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"
        };

        httpContext.Response.StatusCode = problemDetails.Status.Value;

        await httpContext.Response
            .WriteAsJsonAsync(problemDetails, cancellationToken);

        return true;
    }
}

配置IExceptionHandler实现

您需要两件事来将IExceptionHandler实现添加到ASP.NET Core请求管道中:

  1. 使用依赖注入注册IExceptionHandler服务
  2. 在请求管道中注册ExceptionHandlerMiddleware

您调用AddExceptionHandler方法来注册GlobalExceptionHandler作为服务。它被注册为单例生命周期。因此,请小心注入具有不同生命周期的服务。

我也在调用AddProblemDetails来为常见异常生成Problem Details响应。

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

您还需要调用UseExceptionHandlerExceptionHandlerMiddleware添加到请求管道中:

app.UseExceptionHandler();

链接异常处理器

您可以添加多个IExceptionHandler实现,并按照注册的顺序调用它们。这可能的用例是使用异常进行流程控制。

您可以定义像BadRequestExceptionNotFoundException这样的自定义异常。它们对应于您将从API返回的HTTP状态码。

这是一个BadRequestExceptionHandler实现:

internal sealed class BadRequestExceptionHandler : IExceptionHandler
{
    private readonly ILogger<BadRequestExceptionHandler> _logger;

    public GlobalExceptionHandler(ILogger<BadRequestExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        if (exception is not BadRequestException badRequestException)
        {
            return false;
        }

        _logger.LogError(
            badRequestException,
            "Exception occurred: {Message}",
            badRequestException.Message);

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status400BadRequest,
            Title = "Bad Request",
            Detail = badRequestException.Message
        };

        httpContext.Response.StatusCode = problemDetails.Status.Value;

        await httpContext.Response
            .WriteAsJsonAsync(problemDetails, cancellationToken);

        return true;
    }
}

这是一个NotFoundExceptionHandler实现:

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

    public GlobalExceptionHandler(ILogger<NotFoundExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        if (exception is not NotFoundException notFoundException)
        {
            return false;
        }

        _logger.LogError(
            notFoundException,
            "Exception occurred: {Message}",
            notFoundException.Message);

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status404NotFound,
            Title = "Not Found",
            Detail = notFoundException.Message
        };

        httpContext.Response.StatusCode = problemDetails.Status.Value;

        await httpContext.Response
            .WriteAsJsonAsync(problemDetails, cancellationToken);

        return true;
    }
}

您还需要通过调用AddExceptionHandler来注册这两个异常处理器:

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

BadRequestExceptionHandler将首先执行并尝试处理异常。如果异常未被处理,NotFoundExceptionHandler将接着执行并尝试处理异常。

要点

在ASP.NET Core中使用中间件进行异常处理是一个很好的解决方案。不过,使用IExceptionHandler接口的新选项也很棒。我会在ASP.NET Core 8项目中使用新方法。

我非常反对使用异常进行流程控制。当您无法继续正常应用程序执行时,异常是最后的手段。结果模式是一个更好的替代方案。

正如David Fowler所指出,异常也是极其昂贵的

如果您想在您的代码中摆脱异常,请查看这个视频。