全局异常处理

ASP.NET Core WebAPI工程中,全局异常处理可以基于ExceptionHandler中间件或ExceptionFilter过滤器实现,这篇笔记我们介绍这两种实现方式。

ExceptionHandler中间件

ExceptionHandler是ASP.NET Core的内置中间件,我们可以直接在程序的Program.cs中注册它,它的最基本使用方式如下。

app.UseExceptionHandler("/error");

其中/error指向一个错误页面的路由。

当然,事情并没有这么简单,这种方式通常不符合我们的要求,因为我们这里开发的是WebAPI工程,在纯接口工程中我们不可能返回一个“页面”,即使报错我们也应该输出一些JSON信息,这需要调用UseExceptionHandler的另一个重载。

app.UseExceptionHandler(builder =>
{
    builder.Run(async context =>
    {
        var apiResult = ApiResult.Failure("500", "Internal Server Error");
        await context.Response.WriteAsJsonAsync(apiResult);
    });
});

代码中,ApiResult是我们自定义的JSON响应数据封装类,它由context.Response.WriteAsJsonAsync()方法将参数以JSON序列化并写入HTTP响应的输出流中。当我们的控制器业务逻辑抛出异常时,这段中间件代码就会执行,将错误信息返回给用户。

实际上,上面的异常处理实现还是太简单了,真实项目中我们通常需要针对不同的异常类型返回不同的异常提示,下面例子中,我们实现了自定义异常类型并在异常处理中间件中判断了异常类型。

AppException.cs

namespace DemoWebAPI.Exceptions;

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

Program.cs

app.UseExceptionHandler(builder =>
{
    builder.Run(async context =>
    {
        var exceptionFeature = context.Features.Get<IExceptionHandlerFeature>();
        if (exceptionFeature != null)
        {
            var exception = exceptionFeature.Error;
            if (exception is AppException)
            {
                var apiResult = ApiResult.Failure("501", exception.Message);
                await context.Response.WriteAsJsonAsync(apiResult);
            }
            else
            {
                var apiResult = ApiResult.Failure("500", "Internal Server Error");
                await context.Response.WriteAsJsonAsync(apiResult);
            }
        }
    });
});

中间件的处理逻辑中,我们使用了IExceptionHandlerFeature,它保存了HTTP处理流程中的异常对象,我们判断了该异常对象的类型,如果类型是我们自定义的AppException就输出额外的业务信息,如果是未知异常,就直接输出Internal Server Error

ExceptionFilter过滤器

相比作用于全局的ExceptionHandler中间件,ExceptionFilter过滤器能在控制器方法粒度下处理异常,下面例子代码我们自定义一个ExceptionFilter,它会在抛出异常时指定程序后续如何执行。

using DemoWebAPI.Exceptions;
using DemoWebAPI.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace DemoWebAPI.Filters;

public class MyExceptionFilter : Attribute, IAsyncExceptionFilter
{
    public async Task OnExceptionAsync(ExceptionContext context)
    {
        var exception = context.Exception;
        context.Result = exception is AppException
            ? new ObjectResult(ApiResult.Failure("501", exception.Message))
            : new ObjectResult(ApiResult.Failure("500", "Internal Server Error"));
    }
}

在控制器中,我们可以用注解的形式使用该过滤器。

using DemoWebAPI.Exceptions;
using DemoWebAPI.Filters;
using DemoWebAPI.Model;
using Microsoft.AspNetCore.Mvc;

namespace DemoWebAPI.Controllers;

[ApiController]
[Route("api/Demo")]
public class DemoController : ControllerBase
{
    [HttpGet("DemoAction")]
    [MyExceptionFilter]
    public ActionResult<ApiResult> DemoAction()
    {
        throw new AppException("Fuck!");
        return ApiResult.Success();
    }
}

代码中,DemoAction()控制器方法会抛出我们自定义的AppException异常,它会被MyExceptionFilter捕捉并处理。

同时使用ExceptionHandler中间件和ExceptionFilter过滤器

在之前的章节中我们已经介绍过了,在ASP.NET Core的HTTP请求管道中,中间件位于过滤器的“外层”,因此,如果一个异常已被ExceptionFilter过滤器处理,ExceptionHandler中间件就不会再执行了,我们可以同时配置两个组件并打印不同的日志信息来验证这一点。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap