原文鏈接:傳送門。
這篇文章描述了在ASP.NET Core Web API中如何處理並自定義異常處理。
開發者異常頁
開發者異常頁是一個獲得服務器錯誤詳細跟蹤棧的很有用的工具。它會使用DeveloperExceptionPageMiddleware 來捕獲來自於HTTP管道的同步及異步異常並生成錯誤響應。為了演示,請考慮如下的控制器Action:
[HttpGet("{city}")] public WeatherForecast Get(string city) { if (!string.Equals(city?.TrimEnd(), "Redmond", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException( $"We don't offer a weather forecast for {city}.", nameof(city)); } return GetWeather().First(); }
運行如下的 curl 命令來測試上述代碼:
curl -i https://localhost:5001/weatherforecast/chicago
在ASP.NET Core 3.0及以后的版本中,如果客戶端不請求基於HTTP格式的響應,那么開發者異常頁便會顯示純文本的響應。如下輸出會顯示出來:
HTTP/1.1 500 Internal Server Error Transfer-Encoding: chunked Content-Type: text/plain Server: Microsoft-IIS/10.0 X-Powered-By: ASP.NET Date: Fri, 27 Sep 2019 16:13:16 GMT System.ArgumentException: We don't offer a weather forecast for chicago. (Parameter 'city') at WebApiSample.Controllers.WeatherForecastController.Get(String city) in C:\working_folder\aspnet\AspNetCore.Docs\aspnetcore\web-api\handle-errors\samples\3.x\Controllers\WeatherForecastController.cs:line 34 at lambda_method(Closure , Object , Object[] ) at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context) HEADERS ======= Accept: */* Host: localhost:44312 User-Agent: curl/7.55.1
相應的,為了顯示一段HTML格式的響應,將Accept請求頭設置為text/html媒體類型,比如:
curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago
考慮如下來自於HTTP響應的一段摘錄:
HTTP/1.1 500 Internal Server Error Transfer-Encoding: chunked Content-Type: text/html; charset=utf-8 Server: Microsoft-IIS/10.0 X-Powered-By: ASP.NET Date: Fri, 27 Sep 2019 16:55:37 GMT <!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title>Internal Server Error</title> <style> body { font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif; font-size: .813em; color: #222; background-color: #fff; }
當使用像Postman這樣的工具來測試時,HTML格式的響應便會變得很有用。如下截屏顯示了在Postman中的純文本格式和HTML格式的響應;
【不支持動圖,請跳轉至原文觀看】
警告:僅當app運行於開發環境時,啟用開發者異常頁。當app運行於生產環境時,你不希望將詳細的異常信息公開分享出來。關於配置環境的更多信息,請參考 Use multiple environments in ASP.NET Core。
異常處理
在非開發環境中,Exception Handling Middleware 可以被用來產生一個錯誤負載。
- 在Startup.Configure中,調用UseExceptionHandler 來使用中間件。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/error"); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
- 配置控制器Action來響應/error路由。
[ApiController] public class ErrorController : ControllerBase { [Route("/error")] public IActionResult Error() => Problem(); }
上述Error Action向客戶端發送一個兼容 RFC 7807的負載。
在本地的開發環境中,異常處理中間件也可以提供更加詳細的內容協商輸出。使用以下步驟來為開發環境和生產環境提供一致的負載格式。
- 在Startup.Configure中,注冊環境特定的異常處理中間件實例。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseExceptionHandler("/error-local-development"); } else { app.UseExceptionHandler("/error"); } }
在上述代碼中,中間件用如下方式來注冊:
- 開發環境中的
/error-local-development
路由 - 非開發環境中的/error路由
- 開發環境中的
- 向控制器的Action應用屬性路由。
[ApiController] public class ErrorController : ControllerBase { [Route("/error-local-development")] public IActionResult ErrorLocalDevelopment( [FromServices] IWebHostEnvironment webHostEnvironment) { if (webHostEnvironment.EnvironmentName != "Development") { throw new InvalidOperationException( "This shouldn't be invoked in non-development environments."); } var context = HttpContext.Features.Get<IExceptionHandlerFeature>(); return Problem( detail: context.Error.StackTrace, title: context.Error.Message); } [Route("/error")] public IActionResult Error() => Problem(); }
使用異常來更改響應
響應的內容可以從控制器的外面被改變。在ASP.NET 4.X Web API之中,實現這個的一種方式便是使用HttpResponseException 類型。ASP.NET Core並不包含一個與之對應的類型。對HttpResponseException
的支持可使用如下的步驟添加:
- 創建一個眾所周知的異常類型,名為HttpResponseException。
public class HttpResponseException : Exception { public int Status { get; set; } = 500; public object Value { get; set; } }
- 創建一個Action過濾器,名為HttpResponseExceptionFilter。
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter { public int Order { get; } = int.MaxValue - 10; public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { if (context.Exception is HttpResponseException exception) { context.Result = new ObjectResult(exception.Value) { StatusCode = exception.Status, }; context.ExceptionHandled = true; } } }
在上述的過濾器中,魔法數字10被從最大整形值中減去。減去這個值可以允許其他過濾器運行在管道的末尾。
- 在Startup.ConfigureServices中,將Action過濾器添加到過濾器集合中。
services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter()));
驗證失敗錯誤響應
對於Web API控制器來說,當模型驗證失敗的時候,MVC會以一個ValidationProblemDetails響應作為回復。MVC使用InvalidModelStateResponseFactory的結果來構建一個驗證失敗的錯誤響應。如下的示例使用工廠在Startup.ConfigureServices中將默認的響應類型更改為SerializableError。
services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = context => { var result = new BadRequestObjectResult(context.ModelState); // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames result.ContentTypes.Add(MediaTypeNames.Application.Json); result.ContentTypes.Add(MediaTypeNames.Application.Xml); return result; }; });
客戶端錯誤響應
一個錯誤結果被定義為帶有HTTP 狀態碼400或者更高的的結果。對於Web API控制器來說,MVC將一個錯誤結果轉化為帶有ProblemDetails的結果。
錯誤結果可以通過如下方式之一進行配置:
實現ProblemDetailsFactory
MVC使用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 來產生 ProblemDetails 和 ValidationProblemDetails 的所有的實例。這包含客戶端錯誤響應,驗證失敗錯誤響應,以及 ControllerBase.Problem 和 ControllerBase.ValidationProblem 幫助器方法。
為了自定義問題詳細響應,在Startup.ConfigureServices
:中注冊一個ProblemDetailsFactory 類的自定義實現。
public void ConfigureServices(IServiceCollection serviceCollection) { services.AddControllers(); services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>(); }
使用ApiBehaviorOptions.ClientErrorMapping
使用ClientErrorMapping 屬性來配置ProblemDetails響應的內容。比如,如下在Startup.ConfigureServices中的代碼更改了404響應的type屬性。
services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.SuppressConsumesConstraintForFormFileParameters = true; options.SuppressInferBindingSourcesForParameters = true; options.SuppressModelStateInvalidFilter = true; options.SuppressMapClientErrors = true; options.ClientErrorMapping[StatusCodes.Status404NotFound].Link = "https://httpstatuses.com/404"; });