|
前言
异常的处理在我们应用程序中是至关重要的,在 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://www.cnblogs.com/ruipeng/p/18075123
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
|