在.NET Core 中使用 IExceptionHandler 处理异常
系列文章列表,点击展示/隐藏
正文
问题背景
在实际开发中,我们经常会遇到各种异常情况。例如,当用户请求一个不存在的资源时,系统可能会抛出一个 NotFoundException
。如果不对这些异常进行妥善处理,用户可能会看到一些不友好的错误信息,甚至可能会暴露系统的敏感信息。
builder.Services.AddProblemDetails();
然后,再去请求那个有问题的接口,你会发现返回的错误信息开始有点模样了,不再是乱糟糟的一团。不过,仔细一瞅,问题依旧存在——堆栈跟踪还是老老实实地杵在那儿呢。这可不行,咱们得把这堆栈跟踪给隐藏起来,只留下关键的错误信息。于是,你可以这么定制一下:
builder.Services.AddProblemDetails(opt =>
opt.CustomizeProblemDetails = context => context.ProblemDetails
= new ProblemDetails
{
Detail = context.ProblemDetails.Detail,
Status = context.ProblemDetails.Status,
Title = context.ProblemDetails.Title,
Type = context.ProblemDetails.Type
});
这样一来,返回的错误信息就干净多了,符合RFC标准,也把堆栈跟踪给完美地藏了起来。但问题又来了,这种定制方式有点繁琐,而且,咱们明明抛出了一个“NotFoundException”,结果返回的状态码还是“500 Internal Server Error”,这明显不对劲啊。咱们得让状态码也变得准确起来,于是乎,代码又得这么改:
builder.Services.AddProblemDetails(opt =>
opt.CustomizeProblemDetails = context =>
{
if (context.Exception is NotFoundException notFoundException)
{
context.ProblemDetails = new ProblemDetails
{
Detail = notFoundException.Message,
Status = StatusCodes.Status404NotFound,
Title = "Resource not found",
Type = context.ProblemDetails.Type
};
}
context.HttpContext.Response.StatusCode
= context.ProblemDetails.Status.Value;
});
这下好了,自定义异常终于能得到妥善处理了,状态码也对上了。不过,你得承认,这代码看起来有点乱,读起来也不太顺溜。要是项目里头自定义异常多了,这代码得膨胀成什么样啊。
别急,还有别的招儿。在ASP.NET Core里头,中间件(Middleware)可是个好东西,它就像是流水线上的一个个工作站,每个都能对请求或者响应干点啥事儿。咱们也可以利用中间件来处理异常。先整一个中间件出来,我比较喜欢用中间件工厂的方式来搞,实现个IMiddleware接口就行:
public class ExceptionHandlingMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (NotFoundException ex)
{
var problemDetails = new ProblemDetails
{
Detail = ex.Message,
Status = StatusCodes.Status404NotFound,
Title = "Resource not found",
Type = ex.GetType().Name
};
context.Response.StatusCode = problemDetails.Status.Value;
await context.Response.WriteAsJsonAsync(problemDetails);
}
}
}
接着,别忘了在依赖注入(DI)里头注册一下这个中间件,并且在应用里头把它用起来:
builder.Services.AddTransient<ExceptionHandlingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();
这样一来,异常处理的逻辑就被清晰地抽离了出来,放在一个单独的类里头,可读性一下子提升了不少,调试起来也方便得很。不过,要是项目里头自定义异常种类繁多,这个中间件里头的catch语句就得越写越长,这也挺让人头疼的。
好在,ASP.NET Core 8给我们带来了新玩意儿——IExceptionHandler。这可不是ASP.NET Core MVC里的IExceptionFilter哦,别弄混了,IExceptionHandler是专门为ASP.NET Core准备的。有了它,咱们处理异常的时候就更优雅了。来看看怎么用吧,先整一个异常处理器出来:
public class NotFoundExceptionHandler : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext context,
Exception exception,
CancellationToken cancellationToken)
{
if (exception is NotFoundException)
{
var problemDetails = new ProblemDetails
{
Detail = exception.Message,
Status = StatusCodes.Status404NotFound,
Title = "Resource not found",
Type = exception.GetType().Name
};
context.Response.StatusCode = problemDetails.Status.Value;
await context.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
return true;
}
return false;
}
}
和之前用中间件的方式比起来,代码逻辑差不多,但是不用再写catch语句了,只需要判断一下异常的类型就行。同样地,别忘了注册和使用这个异常处理器:
builder.Services.AddExceptionHandler<NotFoundExceptionHandler>();
app.UseExceptionHandler(_ => { });
要是你觉得那个_ => {}看着有点别扭,完全可以像之前定制ProblemDetails那样,把ProblemDetails注册进去,这样就能把那丑丑的空实现给干掉。
还有一个细节值得注意,IExceptionHandler的TryHandleAsync方法返回的是一个布尔值。要是返回true,那异常处理就算结束了;要是返回false,那处理流程就会继续往下走,去调用下一个异常处理器。这就意味着,你可以把多个异常处理器串起来用,它们会一个接一个地处理异常,直到有哪一个返回了true。所以啊,注册异常处理器的时候,顺序可就很重要了。要是你还有一个用来处理未捕获异常的全局异常处理器,那它一定要注册到最后面。
builder.Services.AddExceptionHandler<NotFoundExceptionHandler>();
builder.Services.AddExceptionHandler<BadRequestExceptionHandler>();
builder.Services.AddExceptionHandler<UnhandledExceptionHandler>();
你看,有了IExceptionHandler,咱们处理异常的时候是不是更有条理、更优雅了?不过,这也不是ASP.NET Core里头处理异常的唯一方式,更多方法还得去微软官方文档里头好好挖挖呢。
总结
在 ASP.NET Core 8 中,IExceptionHandler 提供了一种非常强大和灵活的异常处理机制。通过使用 IExceptionHandler,我们可以更加方便地处理各种异常情况,同时还能提供更加友好的用户体验。
当然,除了 IExceptionHandler,我们还可以使用 ProblemDetails 和 Middleware 来处理异常。不同的方法有各自的优缺点,我们可以根据具体的需求选择合适的方式。
总之,无论选择哪种方式,我们都需要重视异常处理,确保我们的应用程序能够稳定、可靠地运行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现