标签搜索

使用ProblemDetails中间件处理Web API异常

冰封一夏
2021-08-05 10:05:34 / 1 阅读 / 正在检测是否收录...

ProblemDetails和[ApiController]属性

ASP.NET Core 2.1引入了该[ApiController]属性,该属性将许多常见的API特定约定应用于控制器。在ASP.NET Core 2.2中,添加了额外的约定-将错误状态代码(> = 400)转换为ProblemDetails

ProblemDetails对于所有错误返回一致的类型,将使使用客户端变得更加容易。来自MVC控制器的所有错误,无论是400(错误请求)还是404(未找到),都将返回一个ProblemDetails对象:

NotFound请求的问题详细信息

但是,如果您的应用程序引发异常,则不会得到ProblemDetails响应:

开发人员例外页面

在默认webapi模板(如下所示)中,开发人员异常页面处理开发环境中的错误,并在上面产生错误。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // Only add error handling in development environments
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

在生产环境中,没有注册任何异常中间件,因此您将获得“原始的” 500状态代码,而根本没有消息正文:

生产例外页面

更好的选择是保持一致,并且ProblemDetails也为异常返回一个对象。实现此目标的一种方法是创建自定义错误处理程序,如我在上一篇文章中所述。更好的选择是使用现有的为您处理的NuGet软件包。

问题详细信息中间件

ProblemDetailsMiddleware克里斯蒂安Hellang不正是你所期望的-它处理你的中间件管道异常,将它们转换为ProblemDetails。它有很多配置选项(我将在后面介绍),但是开箱即用它确实可以满足我们的需求。

通过调用将Hellang.Middleware.ProblemDetails添加到您的.csproj文件中dotnet add package Hellang.Middleware.ProblemDetails。在撰写本文时,最新版本是5.0.0:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Hellang.Middleware.ProblemDetails" Version="5.0.0" />
  </ItemGroup>

</Project>

您需要通过调用将所需的服务添加到DI容器AddProblemDetails()。通过调用将中间件本身添加到管道中UseProblemDetails。您应该在管道中尽早添加它,以确保它捕获到任何后续中间件中的错误:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddProblemDetails(); // Add the required services
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseProblemDetails(); // Add the middleware

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

通过这种简单的添加,如果您在管道中的某个地方(例如在控制器中)遇到异常,您仍然会得到ProblemDetails响应。在开发环境中,中间件包括异常详细信息和堆栈跟踪:

开发人员问题详细信息页面

这不仅仅是打电话ToString()Exception,但-响应甚至包括抛出异常(线contextCode)和包括前(源代码preContextCode)后(postContextCode)违规行:

上下文代码

在生产环境中,出于明显的原因,中间件不包含这些详细信息,而是ProblemDetails仅返回基本对象。

生产问题详细信息页面

除了处理异常外,它们ProblemDetailsMiddleware还捕获来自其他中间件的状态代码错误。例如,如果请求与应用程序中的任何端点都不匹配,则管道将返回404。该ApiController属性将无法捕获该属性,因此不会其转换为ProblemDetails对象。

同样,默认情况下,如果您向方法发送POST请求,即使应用了attributeGET,您也将获得405响应,再次没有方法主体:[ApiController]

找不到方法不会引起ProblemDetails响应

随着ProblemDetailsMiddleware在地方,你会得到一个ProblemDetails对这些错误代码也响应:

使用中间件后,找不到方法会导致ProblemDetails响应

这种行为确实提供了我需要的即用型功能,但是如果需要,您也可以广泛地自定义中间件的行为。在下一节中,我将显示其中一些自定义选项。

自定义中间件行为

您可以ProblemDetailsMiddleware通过ProblemDetailsOptionsAddProblemDetails调用中为实例提供配置lambda来自定义的行为:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddProblemDetails(opts => {
        // configure here
    });
}

可能的配置设置很多,如下所示。大多数配置设置都是Func<>属性,可以访问current HttpContext,并让您控制中间件的行为。

public class ProblemDetailsOptions
{
    public int SourceCodeLineCount { get; set; }
    public IFileProvider FileProvider { get; set; }
    public Func<HttpContext, string> GetTraceId { get; set; }
    public Func<HttpContext, Exception, bool> IncludeExceptionDetails { get; set; }
    public Func<HttpContext, bool> IsProblem { get; set; }
    public Func<HttpContext, MvcProblemDetails> MapStatusCode { get; set; }
    public Action<HttpContext, MvcProblemDetails> OnBeforeWriteDetails { get; set; }
    public Func<HttpContext, Exception, MvcProblemDetails, bool> ShouldLogUnhandledException { get; set; }
}

例如,默认情况下,ExceptionDetails仅包含在开发环境中。如果您也想将详细信息包括在暂存环境中,则可以使用如下所示的内容:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddProblemDetails(opts =>
    {
        // Control when an exception is included
        opts.IncludeExceptionDetails = (ctx, ex) =>
        {
            // Fetch services from HttpContext.RequestServices
            var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
            return env.IsDevelopment() || env.IsStaging();
        };
    });
}

值得指出的另一件事是,您可以控制中间件何时将非异常响应转换为ProblemDetailsProblemDetails满足以下条件时,默认配置会将非异常响应转换为:

  • 状态码介于400到600之间。
  • 所述Content-Length报头是空的。
  • 所述Content-Type报头是空的。

正如我在本文开头提到的那样,[ApiController]ASP.NET Core 2.2及更高版本的属性会自动将“原始”状态代码结果转换为ProblemDetails。这些响应将被中间件忽略,因为该响应已经有一个Content-Type

但是,如果您使用该[ApiController]属性,或者仍在使用ASP.NET Core 2.1,则可以使用ProblemDetailsMiddleware来自动将原始状态代码结果转换为ProblemDetails,就像在ASP.NET Core 2.2+中一样。

这些情况下的响应并不相同,但是非常相似。例如,用于TitleType属性的值之间存在细微差异。

另一个选择是ProblemDetailsMiddleware在将Razor Pages与API控制器结合在一起的应用程序中使用。然后,您可以使用该IsProblem函数来确保ProblemDetails仅针对API控制器端点生成该函数。

我只涉及了一些自定义功能,但是您可以使用许多其他挂钩来控制中间件的工作方式。我只是不必使用它们,因为默认值恰好满足了我的需要!

概括

在这篇文章中我描述了ProblemDetailsMiddleware,可与API项目被用来生成ProblemDetails的异常结果。如果您要构建API,这是一个非常方便的库,因为它可以确保所有错误均返回一致的对象。

0

评论

博主关闭了所有页面的评论