当然不要忘记,官网才是最好的老师:docs.microsoft.com/zh-cn/dotnet/core/
沙盒学习指南: 免费环境docs.microsoft.com/zh-cn/learn/browse
posted @ 2020 初久的私房菜 推荐出品

在.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 来处理异常。不同的方法有各自的优缺点,我们可以根据具体的需求选择合适的方式。
总之,无论选择哪种方式,我们都需要重视异常处理,确保我们的应用程序能够稳定、可靠地运行。

posted @   初久的私房菜  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
作者:初久的私房菜
好好学习,天天向上
返回顶部小火箭
好友榜:
如果愿意,把你的博客地址放这里
张弛:https://blog.zhangchi.fun/
点击右上角即可分享
微信分享提示