前言 異常的處理在我們應用程式中是至關重要的,在 dotNet 中有很多異常處理的機制,比如MVC的異常篩選器, 管道中間件定義try catch捕獲異常處理亦或者第三方的解決方案Hellang.Middleware.ProblemDetails等。MVC異常篩選器不太靈活,對管道的部分異常捕獲不到 ...
前言
異常的處理在我們應用程式中是至關重要的,在 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 時可以參考本文來修改項目現有的全部異常捕獲方式。
本文來自博客園,作者:董瑞鵬,轉載請註明原文鏈接:https://www.cnblogs.com/ruipeng/p/18075123