前言

异常的处理在我们应用程序中是至关重要的,在 dotNet 中有很多异常处理的机制,比如MVC的异常筛选器, 管道中间件定义try catch捕获异常处理亦或者第三方的解决方案Hellang.Middleware.ProblemDetails等。MVC异常筛选器不太灵活,对管道的部分异常捕获不到,后两种方式大家项目应该经常出现。

dotNet8 发布之后支持了新的异常处理机制 IExceptionHandler或者UseExceptionHandler异常处理程序的lambda配置,配合dotNet7原生支持的ProblemDetail使得异常处理更加规范。

本文用一个简单的 Demo 带大家看一下新的异常处理方式

文末有示例完整的源代码

先起一个 WebApi 的新项目

Problem Details

Problem Details 是一种在 HTTP API 中用于描述错误信息的标准化格式。根据 RFC 7807,Problem Details 提供了一种统一、可机器读取的方式来呈现出发生在 API 请求中的问题。它包括各种属性,如 title、status、detail、type 等,用于清晰地描述错误的性质和原因。通过使用 Problem Details,开发人员可以为 API 的错误响应提供一致性和易于理解的结构化格式,从而帮助客户端更好地处理和解决问题。

项目中使用 Problem Details

builder.Services.AddProblemDetails();

如果我们不对异常进行捕获处理,Asp.Net Core 提供了两种不同的内置集中式机制来处理未经处理的异常

  • app.UseDeveloperExceptionPage();

    开发人员异常中间件

    开发人员异常中间件会显示服务器错误的详细堆栈跟踪,不建议在非开发环境显示,暴漏核心错误信息给客户端,有严重的安全风险

  • app.UseExceptionHandler(); 异常处理程序中间件,

    使用异常处理程序中间件生成的是标准的简化回复

测试 UseDeveloperExceptionPage

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.MapGet("/TestUseDeveloperExceptionPage",
() => { throw new Exception("测试UseDeveloperExceptionPage"); });
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseDeveloperExceptionPage();// 开发人员异常页
}
app.UseStatusCodePages();
app.UseHttpsRedirection();
app.Run();

调用 TestUseDeveloperExceptionPage 接口

回参

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "System.Exception",
"status": 500,
"detail": "测试UseDeveloperExceptionPage",
"exception": {
"details": "System.Exception: 测试UseDeveloperExceptionPage\r\n at Program.<>c.<<Main>$>b__0_0() in C:\\dotNetParadise\\dot-net-paradise-exception\\dotNetParadise-Exception\\dotNetParadise-Exception\\Program.cs:line 7\r\n at lambda_method3(Closure, Object, HttpContext)\r\n at Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)",
"headers": {
"Accept": [
"*/*"
],
"Host": [
"localhost:7130"
],
"User-Agent": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
],
"Accept-Encoding": [
"gzip, deflate, br"
],
"Accept-Language": [
"en-US,en;q=0.9"
],
"Cookie": [
"ajs_anonymous_id=b96604ea-c096-4693-acfb-b3a9e8403f0e; Quasar_admin_Vue3_username=admin; Quasar_admin_Vue3_token=b1aa15b6-02bb-44b9-8668-0157a1d9b6f0; Quasar_admin_Vue3_lang=en-US"
],
"Referer": [
"https://localhost:7130/swagger/index.html"
],
"sec-ch-ua": [
"\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Microsoft Edge\";v=\"122\""
],
"sec-ch-ua-mobile": [
"?0"
],
"sec-ch-ua-platform": [
"\"Windows\""
],
"sec-fetch-site": [
"same-origin"
],
"sec-fetch-mode": [
"cors"
],
"sec-fetch-dest": [
"empty"
]
},
"path": "/TestUseDeveloperExceptionPage",
"endpoint": "HTTP: GET /TestUseDeveloperExceptionPage",
"routeValues": {}
}

可以看到所有的信息都抛出来给到了客户端,适合在开发环境用,非开发环境尤其是生产环境不要启用。

app.UseExceptionHandler();

异常处理程序中间件

// app.UseDeveloperExceptionPage();// 开发人员异常页
app.UseExceptionHandler();//异常处理中间件

测试一下

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An error occurred while processing your request.",
"status": 500
}

可以看到只保留了最基本的报错信息,这样第一步我们已经完成了。

自定义异常 和 IExceptionHandler

创建一个自定义异常信息

public class CustomException(int code, string message) : Exception(message)
{
public int Code { get; private set; } = code; public string Message { get; private set; } = message;
}

集成IExceptionHandler创建自定义异常处理器

public class CustomExceptionHandler(ILogger<CustomException> logger, IWebHostEnvironment environment) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
if (exception is not CustomException customException) return false;
logger.LogError(
exception, "Exception occurred: {Message} {StackTrace} {Source}", exception.Message, exception.StackTrace, exception.Source); var problemDetails = new ProblemDetails
{
Status = customException.Code,
Title = customException.Message,
};
if (environment.IsDevelopment())
{
problemDetails.Detail = $"Exception occurred: {customException.Message} {customException.StackTrace} {customException.Source}";
}
httpContext.Response.StatusCode = problemDetails.Status.Value;
await httpContext.Response
.WriteAsJsonAsync(problemDetails, cancellationToken);
return true;
}
}

可以注册多个自定义异常处理器分别处理不同类型的异常,按默认的注册顺序来处理,如果返回true则会处理此异常返回false会跳到下一个ExceptionHandler,没处理的异常在 UseExceptionHandler 中间件做最后处理。

创建第二个ExceptionHandler 处理系统异常

public class SystemExceptionHandle(ILogger<CustomException> logger, IWebHostEnvironment environment) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
if (exception is CustomException) return false;
logger.LogError(
exception, "Exception occurred: {Message} {StackTrace} {Source}", exception.Message, exception.StackTrace, exception.Source); var problemDetails = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "An error occurred while processing your request",
};
if (environment.IsDevelopment())
{
problemDetails.Detail = $"Exception occurred: {exception.Message} {exception.StackTrace} {exception.Source}";
} httpContext.Response.StatusCode = problemDetails.Status.Value; await httpContext.Response
.WriteAsJsonAsync(problemDetails, cancellationToken); return true; }
}

IOC 容器注册ExceptionHandler

builder.Services.AddExceptionHandler<CustomExceptionHandler>();
builder.Services.AddExceptionHandler<SystemExceptionHandle>();

新加接口测试一下

app.MapGet("/CustomThrow", () =>
{
throw new CustomException(StatusCodes.Status403Forbidden, "你没有权限!");
}).WithOpenApi();

回参

{
"title": "你没有权限!",
"status": 403,
"detail": "Exception occurred: 你没有权限! at Program.<>c.<<Main>$>b__0_1() in C:\\dotNetParadise\\dot-net-paradise-exception\\dotNetParadise-Exception\\dotNetParadise-Exception\\Program.cs:line 15\r\n at lambda_method5(Closure, Object, HttpContext)\r\n at Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task) dotNetParadise-Exception"
}

可以看出全局异常捕获生效了。

最后

本文讲的是 dotNet8 新的异常处理方式,当时也可以用UseExceptionHandlerlambda方式可以创建,但是不如这种强类型约束的规范,大家在升级 dotNet8 时可以参考本文来修改项目现有的全部异常捕获方式。

Demo 源代码

dotNet 官网教程

dotNet8 全局异常处理的更多相关文章

  1. mvc自定义全局异常处理

    异常信息处理是任何网站必不可少的一个环节,怎么有效显示,记录,传递异常信息又成为重中之重的问题.本篇将基于上篇介绍的html2cancas截图功能,实现mvc自定义全局异常处理.先看一下最终实现效果: ...

  2. 在.NET Core程序中设置全局异常处理

    以前我们想设置全局异常处理只需要这样的代码: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.UnhandledExc ...

  3. springMvc全局异常处理

    本文中只测试了:实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器 对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提 ...

  4. MVC 全局异常处理及禁用显示头

    MVC网站的global.asax中的Application_Start方法里,有这样一段代码: public class MvcApplication : System.Web.HttpApplic ...

  5. Spring MVC 解决无法访问静态文件和"全局异常处理"

    我们都知道,Spring MVC的请求都会去找controller控制器,若果我们页面中引入了一个外部样式,这样是没效果的, 我们引入样式的时候是通过<like href="...&q ...

  6. Spring Boot 2.x 系列教程:WebFlux REST API 全局异常处理 Error Handling

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 本文内容 为什么要全局异常处理? WebFlux REST 全 ...

  7. .NET MVC全局异常处理(二)

    目录 .NET MVC全局异常处理(二) MVC过滤器Filter .NET MVC全局异常处理(二) 对上节的内容进行了补充 MVC过滤器Filter MVC有四种过滤器:Authorization ...

  8. .NET MVC全局异常处理(一)

    目录 .NET MVC全局异常处理 IIS配置 静态错误页配置 .NET错误页配置 程序设置 全局异常配置 .NET MVC全局异常处理 一直知道有.NET有相关的配置,但没有实际做过,以为改下设定就 ...

  9. Spring Boot 全局异常处理

    Spring Boot版本 1.5 @ControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExcept ...

  10. SpringMVC 全局异常处理

    在 JavaEE 项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常需要处理.每个过程都单独处理异常,系统的代码耦合度 ...

随机推荐

  1. (python)学习ing||类学习,@property装饰器

    class pers(): def __init__(self,hp): self._hp=hp @property def hp(self): return self._hp @hp.setter ...

  2. docker 安装的jenkins如何执行宿主机的shell启动jar包

    最近在用docker做自动化部署的时候遇到一个问题,就是用docker装的jenkins可以通过映射执行宿主机脚本.但是,却无法通过shell脚本启动宿主机jar包.经排查最终用以下方案完美解决. 一 ...

  3. 哈希表(HashMap)与字符串哈希

    哈希表 哈希表是一种通过映射来快速查找的数据结构.其通过键值对(key-value)来存储.一个数据通过哈希函数的运算来生成一个属于他自己的键值,尔后将其与键值绑定.当我们想查找这个数据时,就可以直接 ...

  4. Cnpack ctrl+alt+v 来回切换 变量声明区,和代码写区,非常方便

    Cnpack ctrl+alt+v 来回切换 变量声明区,和代码写区,非常方便 非常方便

  5. Google三驾马车之二:MapReduce

    第一次接触mr还是在入门mit6.824的lab1,最近重新读了一遍原始论文,又有了一些新的想法,简单做一些记录. 作为Google分布式系统的重要组成,本篇文章核心在于map/reduce操作带来的 ...

  6. 【Unity3D】水波特效

    1 水波特效原理 ​ 水面特效 中基于 Shader Graph 实现了模拟水面特效,包含波纹.起伏.折射.泡沫等细节,本文将基于屏幕后处理实现环形水波特效. ​ 水波特效属于 Unity3D 后处理 ...

  7. OCP试题解析之052-- DROP PROFILE app_user

    133.You created a profile APP_USER and assigned it to the users. After a month, you decide to drop t ...

  8. Spring源码之springMVC

    目录 web.xml 程序入口 servlet 初始化 运行阶段 销毁阶段 DispatcherServlet 初始化 DispatcherServlet 的逻辑处理 web.xml 它的作用是配置初 ...

  9. 【LeetCode贪心#03】最大子序和

    最大子序和 力扣题目链接 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 子数组 是数组中的一个连续部分. 示例 1: 输入:nums = ...

  10. 【LeetCode二叉树#19】有序数组转换为二叉搜索树(构造二叉树)

    将有序数组转换为二叉搜索树 力扣题目链接(opens new window) 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个 ...