簡介
全局異常攔截是構建健壯企業級應用的關鍵基礎設施,它能統一處理系統中未捕獲的異常,提供友好的錯誤響應,同時記錄完整的異常信息。
背景和作用
在 ASP.NET Core 應用中,異常可能在控制器、數據庫操作或中間件中發生。如果每個動作方法都手動處理異常(如 try-catch),代碼會變得冗長且難以維護。全局異常攔截器解決了以下問題:
- 統一錯誤處理:集中捕獲所有未處理異常,返回標準化的錯誤響應。
- 標準化響應:符合
RESTful API規範(如RFC 7807 Problem Details)。 - 日誌記錄:記錄異常詳情,便於調試和監控。
- 用户體驗:返回友好的錯誤信息,而非默認錯誤頁面或堆棧跟蹤。
- 性能優化:減少重複的異常處理代碼,提升開發效率。
主要功能
- 捕獲異常:捕獲控制器、服務層或其他代碼中的未處理異常。
- 標準化響應:返回
JSON格式的錯誤詳情(如狀態碼、錯誤消息)。 - 日誌記錄:記錄異常信息(包括堆棧跟蹤)到日誌系統。
- 自定義處理:根據異常類型返回不同狀態碼或消息(如 400、409、500)。
- 異步支持:兼容
async/await,適合異步操作。 DI集成:通過依賴注入訪問服務(如日誌、緩存)。
常見實現方式
異常處理中間件(推薦)
在 ASP.NET Core 管道最前端註冊一箇中間件,捕獲所有後續中間件/終結點拋出的異常。
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(RequestDelegate next,
ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext ctx)
{
try
{
await _next(ctx);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception");
await HandleExceptionAsync(ctx, ex);
}
}
private static Task HandleExceptionAsync(HttpContext ctx, Exception ex)
{
ctx.Response.ContentType = "application/problem+json";
ctx.Response.StatusCode = ex switch
{
ArgumentException _ => StatusCodes.Status400BadRequest,
KeyNotFoundException _ => StatusCodes.Status404NotFound,
_ => StatusCodes.Status500InternalServerError
};
var problem = new ProblemDetails
{
Title = ex.Message,
Status = ctx.Response.StatusCode,
Detail = ctx.Response.StatusCode == 500 ? "請稍後重試或聯繫管理員" : null,
Instance = ctx.Request.Path
};
var json = JsonSerializer.Serialize(problem);
return ctx.Response.WriteAsync(json);
}
}
註冊中間件
在 Program.cs(或 Startup.cs)中最早添加:
app.UseMiddleware<ExceptionHandlingMiddleware>();
// 或更簡潔的擴展方法
app.UseExceptionHandling(); // 需自行實現 UseExceptionHandling 擴展
優勢
- 捕獲範圍最大:包括所有
MVC、Minimal API、靜態文件等。 - 性能開銷小,代碼集中清晰。
- 易於與依賴注入、日誌系統聯動。
全局異常過濾器(IExceptionFilter / IAsyncExceptionFilter)
如果只想攔截 MVC Controller 的異常,可實現異常過濾器。
public class GlobalExceptionFilter : IExceptionFilter
{
private readonly ILogger<GlobalExceptionFilter> _logger;
public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
=> _logger = logger;
public void OnException(ExceptionContext context)
{
var ex = context.Exception;
_logger.LogError(ex, "Unhandled exception in controller");
var problem = new ProblemDetails
{
Title = "請求失敗",
Status = StatusCodes.Status500InternalServerError,
Detail = ex.Message
};
context.Result = new ObjectResult(problem)
{
StatusCode = problem.Status
};
context.ExceptionHandled = true;
}
}
註冊過濾器
services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
});
侷限
- 只攔截通過
MVC管道執行的Action拋出的異常。 - 不會捕獲例如中間件或
Minimal API的異常。
.NET 7+ 新增的 IExceptionHandler(推薦)
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
private readonly IProblemDetailsService _problemDetailsService;
public GlobalExceptionHandler(
ILogger<GlobalExceptionHandler> logger,
IProblemDetailsService problemDetailsService)
{
_logger = logger;
_problemDetailsService = problemDetailsService;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, "全局異常: {Message}", exception.Message);
var statusCode = GetStatusCode(exception);
var problemContext = new ProblemDetailsContext
{
HttpContext = httpContext,
Exception = exception,
ProblemDetails = new ProblemDetails
{
Title = "服務器錯誤",
Status = statusCode,
Detail = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>().IsDevelopment()
? exception.ToString()
: "請稍後再試",
Type = $"https://httpstatuses.io/{statusCode}"
}
};
// 添加自定義擴展
problemContext.ProblemDetails.Extensions.Add("requestId", httpContext.TraceIdentifier);
await _problemDetailsService.WriteAsync(problemContext);
return true; // 標記為已處理
}
}
註冊服務:
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails(); // 添加ProblemDetails支持
var app = builder.Build();
app.UseExceptionHandler(); // 啓用異常處理中間件
內置 UseExceptionHandler
ASP.NET Core 自帶的異常處理終結點:
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async ctx =>
{
var feature = ctx.Features.Get<IExceptionHandlerFeature>();
var ex = feature?.Error;
// 記錄日誌…
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsJsonAsync(new { message = "服務器內部錯誤" });
});
});
}
else
{
app.UseDeveloperExceptionPage();
}
- 優點:無需自定義
Middleware,框架內置。 - 注意:要在其他中間件之前註冊,並在生產/開發環境中分開處理。
資源和文檔
-
官方文檔:
Exception Handling:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/er...Problem Details:https://learn.microsoft.com/en-us/aspnet/core/web-api/handle-...