【譯】ASP.NET Core Web API中的異常處理


原文鏈接:傳送門

這篇文章描述了在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  可以被用來產生一個錯誤負載。

  1. 在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();
        });
    }
  2. 配置控制器Action來響應/error路由。
    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }

上述Error Action向客戶端發送一個兼容 RFC 7807的負載。

在本地的開發環境中,異常處理中間件也可以提供更加詳細的內容協商輸出。使用以下步驟來為開發環境和生產環境提供一致的負載格式。

  1. 在Startup.Configure中,注冊環境特定的異常處理中間件實例。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/error-local-development");
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    }

    在上述代碼中,中間件用如下方式來注冊:

    1. 開發環境中的/error-local-development 路由
    2. 非開發環境中的/error路由
  2. 向控制器的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 的支持可使用如下的步驟添加:

  1. 創建一個眾所周知的異常類型,名為HttpResponseException。
    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
  2. 創建一個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被從最大整形值中減去。減去這個值可以允許其他過濾器運行在管道的末尾。

  3. 在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的結果。

錯誤結果可以通過如下方式之一進行配置:

  1. Implement ProblemDetailsFactory
  2. Use ApiBehaviorOptions.ClientErrorMapping

實現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";
    });

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM