免费资源网 – https://freexyz.cn/
前言
异常的处理在我们应用程序中是至关重要的,在 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 新的异常处理方式,当时也可以用UseExceptionHandler的lambda方式可以创建,但是不如这种强类型约束的规范,大家在升级 dotNet8 时可以参考本文来修改项目现有的全部异常捕获方式。
Demo 源代码
dotNet 官网教程
免费资源网 – https://freexyz.cn/

评论(0)