在ASP.NET Core跨平臺應用程式開發中如何捕獲並處理全域性異常
問題描述
在傳統的ASP.NET Web Api 應用程式開發中,我們處理全域性異常的方法通常是實現一個ExceptionFilterAttribute
的子類,如下:
public class ErrorHandlingFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { HandleExceptionAsync(context); context.ExceptionHandled = true; }private static void HandleExceptionAsync(ExceptionContext context) { var exception = context.Exception; if (exception is MyNotFoundException) SetExceptionResult(context, exception, HttpStatusCode.NotFound); else if (exception is MyUnauthorizedException) SetExceptionResult(context, exception, HttpStatusCode.Unauthorized);else if (exception is MyException) SetExceptionResult(context, exception, HttpStatusCode.BadRequest); else SetExceptionResult(context, exception, HttpStatusCode.InternalServerError); } private static void SetExceptionResult( ExceptionContext context, Exception exception, HttpStatusCode code) { context.Result= new JsonResult(new ApiResponse(exception)) { StatusCode = (int)code }; } }
然後,在Startup
過濾器中註冊這個ErrorHandlingFilter
自定義的錯誤處理屬性類,如:
config.Filter.Add(new ErrorHandlingFilter());
這樣,在ASP.NET Web Api 應用程式就可以捕獲全域性的錯誤並進行處理。
但在ASP.NET Core Web Api的應用程式開發開,以上的解決方案就不起作用了。ASP.NET Core Web Api的全域性錯誤捕獲與處理方式與ASP.NET Web Api應用程式的處理方式並非一樣。
所以,我們得使用適合ASP.NET Core Web Api的全域性錯誤捕獲與處理的解決方案。那麼,有哪些方案可以捕獲和處理ASP.NET Core Web Api應用程式的全域性錯誤呢?
方案一
一個比較好並且推薦的做法是使用中介軟體(middleware)來處理。在ASP.NET Core Web Api應用程式開發中,中介軟體是最好的解決方案。它不僅可以處理應用程式的異常,同時也可以捕獲過濾器的異常並且也能建立自定義的響應返回結果(比如json的響應返回結果),以下是一個基於ASP.NET Core Web Api的異常處理的中介軟體:
public class ErrorHandlingMiddleware { private readonly RequestDelegate next; public ErrorHandlingMiddleware(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context /* other dependencies */) { try { await next(context); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private static Task HandleExceptionAsync(HttpContext context, Exception exception) { var code = HttpStatusCode.InternalServerError; // 500 if unexpected if (exception is MyNotFoundException) code = HttpStatusCode.NotFound; else if (exception is MyUnauthorizedException) code = HttpStatusCode.Unauthorized; else if (exception is MyException) code = HttpStatusCode.BadRequest; var result = JsonConvert.SerializeObject(new { error = exception.Message }); context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)code; return context.Response.WriteAsync(result); } }
使用方法,Startup.cs
類檔案中,在呼叫MVC之前註冊錯誤處理中介軟體ErrorHandlingMiddleware
,如:
app.UseMiddleware(typeof(ErrorHandlingMiddleware)); app.UseMvc();
如果中介軟體捕獲到應用程式的異常,則會返回類似如下的響應結果:
{ "error": "Authentication token is not valid." }
在中介軟體ErrorHandlingMiddleware
的方法HandleExceptionAsync
中,可以對當前上下文的錯誤進行任意處理(比如,跟蹤詳細的錯誤資訊,記錄錯誤日誌等等)。
方案二
在ASP.NET Core 2或者以上的.NET Core 版本中,我們可以使用UseExceptionHandler
擴充套件方法來捕獲並處理ASP.NET Core的全域性錯誤。
首頁,在ASP.NET Core 2的Startup.cs
檔案中註冊錯誤處理擴充套件方法並指定一個錯誤處理頁面(這裡是/Error
),如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { // 開發模式... } else { app.UseStatusCodePagesWithReExecute("/Error"); app.UseExceptionHandler("/Error"); } // 其他... }
接著,定義一個可以指定響應狀態碼(HttpStatusCode)的異常類,如下:
public class HttpException : Exception { public HttpException(HttpStatusCode statusCode) { StatusCode = statusCode; } public HttpStatusCode StatusCode { get; private set; } }
最後,在控制器中建立上文提到的/Error
錯誤處理頁面,ASP.NET Core 2捕獲到的錯誤將轉到這個錯誤處理頁面。在這個頁面,我們就可以處理這些錯誤/異常,如下:
[AllowAnonymous] public IActionResult Error() { // 捕獲狀態碼 var statusCode = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error is HttpException httpEx ? httpEx.StatusCode : (HttpStatusCode)Response.StatusCode; // 如果是ASP.NET Core Web Api 應用程式,直接返回狀態碼(不跳轉到錯誤頁面,這裡假設所有API介面的路徑都是以/api/開始的) if (HttpContext.Features.Get<IHttpRequestFeature>().RawTarget.StartsWith("/api/", StringComparison.Ordinal)) return StatusCode((int)statusCode); // 建立一個友好的錯誤處理頁面檢視模型. string text = null; switch (statusCode) { case HttpStatusCode.NotFound: text = "Page not found."; break; // 其他詳細資訊 } return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrorText = text }); }
注: 如果需要讓 API 介面返回json物件,則可以使用
return Json(...)
來替換return StatusCode(...)
方案三
在ASP.NET Core 2.1+版本中,我們可以使用中介軟體Community.AspNetCore.ExceptionHandling.Mvc[1]
來處理,如下:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddExceptionHandlingPolicies(options => { options.For<InitializationException>().Rethrow(); options.For<SomeTransientException>().Retry(ro => ro.MaxRetryCount = 2).NextPolicy(); options.For<SomeBadRequestException>() .Response(e => 400) .Headers((h, e) => h["X-MyCustomHeader"] = e.Message) .WithBody((req,sw, exception) => { byte[] array = Encoding.UTF8.GetBytes(exception.ToString()); return sw.WriteAsync(array, 0, array.Length); }) .NextPolicy(); // 確保所有的異常均被捕獲 options.For<Exception>() .Log(lo => { lo.EventIdFactory = (c, e) => new EventId(123, "UnhandlerException"); lo.Category = (context, exception) => "MyCategory"; }) .Response(null, ResponseAlreadyStartedBehaviour.GoToNextHandler) .ClearCacheHeaders() .WithObjectResult((r, e) => new { msg = e.Message, path = r.Path }) .Handled(); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseExceptionHandlingPolicies(); app.UseMvc(); }