• 【译】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_folderaspnetAspNetCore.Docsaspnetcoreweb-apihandle-errorssamples3.xControllersWeatherForecastController.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";
        });
  • 相关阅读:
    2019暑假集训 windy数
    2019暑假集训 数字游戏
    2019暑假集训 周年纪念晚会
    2019暑假集训 加分二叉树
    0013-求圆柱体体积
    0012-求滑动距离
    0011-绝对值函数
    0010-温度转换
    0009-乘法问题
    0008-三位数倒序问题
  • 原文地址:https://www.cnblogs.com/qianxingmu/p/14023023.html
Copyright © 2020-2023  润新知