ASP .NET Core 8+ 中的全局异常处理

作者:微信公众号:【架构师老卢】
9-8 18:23
12

概述:在大多数 Web API 项目中,异常处理通常是通过使用中间件、筛选器或使用 mediator 等库来完成的。基本上,当我们有一个控制器时,我们可以为它们分配不同的操作。正如我们的示例,我们有一个控制器和三个不同的操作。操作可以是 POST、/GET 或 /Delete。当用户发送请求时,它会直接进入 Controller,Controller 会根据请求的类型将请求路由到 Action。当任何 action 出现错误时,我们可以在 action 中单独处理该错误。它将向用户返回一个可以理解的错误。基本上,对于每个 action,我们都在编写自己的错误处理程序。单独处理错误我们可以做到这一点的

在大多数 Web API 项目中,异常处理通常是通过使用中间件、筛选器或使用 mediator 等库来完成的。

基本上,当我们有一个控制器时,我们可以为它们分配不同的操作。正如我们的示例,我们有一个控制器和三个不同的操作。操作可以是 POST、/GET 或 /Delete。

当用户发送请求时,它会直接进入 Controller,Controller 会根据请求的类型将请求路由到 Action。当任何 action 出现错误时,我们可以在 action 中单独处理该错误。它将向用户返回一个可以理解的错误。基本上,对于每个 action,我们都在编写自己的错误处理程序。

单独处理错误

我们可以做到这一点的另一种方法是使用中间件,它类似于在我们的请求的管道中运行的拦截器服务。如果出现任何问题,中间件将捕获它并将错误发送给用户。

使用自定义中间件处理错误

H但是**,在 .NET 8 中,**有一个名为 IExceptionHandler 的新接口用于此目的,它能够注册异常处理程序,还可以使用内置中间件以更优雅的方式处理异常。

使用 IExceptionHandler 处理错误

如果存在无法在操作中捕获的任何类型的错误,IExceptionHandler 中间件将能够全局处理它。它用作通用错误处理程序,这意味着无论向它抛出什么类型的异常,它都可以被捕获。

除此之外,它还将利用问题详细信息,基本上,通过问题详细信息,我们可以向用户返回非常详细的错误,而无需透露所有堆栈托盘。

让我们来看简单的演示。

这里我们有一个简单的 Web API 项目,它有一个用于获取 todos 的控制器。为了实现我们的全局异常处理程序,我创建了一个名为 “ErrorHandler” 的新文件夹,并在该文件夹中创建了一个名为 “GlobalExceptionHanlder” 的新类。

项目结构

创建新类后,我必须首先扩展 IExceptionHandler 接口并实现所有必需的方法。

using Microsoft.AspNetCore.Diagnostics;

namespace DemoProject.ErrorHandler
{
    public class GlobalExceptionHandler : IExceptionHandler
    {
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception, 
            CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }
    }
}

在这里,我们可以看到有一个参数很少的方法。这些参数在处理不同的异常时将发挥关键作用。

httpContext:监听传入和传出的请求以进行操作

**例外:**引发的异常

现在进行一些更改以创建我们的全局异常。

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using System.Text.Json;

namespace DemoProject.ErrorHandler
{
    public class GlobalExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<GlobalExceptionHandler> _logger;
        public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) { 
            _logger = logger; 
        }

        private const string UnhandledExceptionMsg = "An unhandled exception has occurred while executing the request.";

        public async ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception, 
            CancellationToken cancellationToken)
        {
            _logger.LogError(exception, exception.Message);

            var problemDetails = CreateProblemDetails(httpContext, exception);
            var jsonDetails = ConvertToJson(problemDetails);
            httpContext.Response.ContentType = "application/problem+json";
            await httpContext.Response.WriteAsync(jsonDetails, cancellationToken);

            return true;
        }

        private ProblemDetails CreateProblemDetails(in HttpContext context, in Exception exception)
        {
            var statusCode = context.Response.StatusCode;
            var reasonPhrase = ReasonPhrases.GetReasonPhrase(statusCode);
            if (string.IsNullOrEmpty(reasonPhrase))
            {
                reasonPhrase = UnhandledExceptionMsg;
            }

            var problemDetails = new ProblemDetails
            {
                Detail = $"API Error {exception.Message}",
                Instance = "API",
                Status = statusCode,
                Title = reasonPhrase,

            };

            return problemDetails;
        }

        private string ConvertToJson(in ProblemDetails problemDetails)
        {
            try
            {
                return JsonSerializer.Serialize(problemDetails);
            }
            catch (Exception ex)
            {
                const string msg = "An exception has occurred while serializing error to JSON";
                _logger.LogError(msg, ex);  
            }

            return string.Empty;
        }
    }
}

实现我们的异常处理程序后,我们需要将其注入到我们的服务中。为此,我们可以按如下方式更改我们的 “program.cs” 文件。

using DemoProject.ErrorHandler;
using DemoProject.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpClient();
builder.Services.AddScoped<ITodoService, TodoService>();

//Add these services to inject
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();

var app = builder.Build();


if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

//Add this to use our exception handler
app.UseExceptionHandler();

app.MapControllers();

app.Run();

现在我们可以测试我们的异常。为了测试异常,我只是更改了 API URL 并收到了响应。

阅读排行