ASP.NET Core WebAPI工程中,全局异常处理可以基于ExceptionHandler中间件或ExceptionFilter过滤器实现,这篇笔记我们介绍这两种实现方式。
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
。
相比作用于全局的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
捕捉并处理。
在之前的章节中我们已经介绍过了,在ASP.NET Core的HTTP请求管道中,中间件位于过滤器的“外层”,因此,如果一个异常已被ExceptionFilter过滤器处理,ExceptionHandler中间件就不会再执行了,我们可以同时配置两个组件并打印不同的日志信息来验证这一点。