繁体   English   中英

如何在 ASP.NET Core 3.1 中为 HTTP 状态 415 创建自定义响应?

[英]How to create a custom response for HTTP status 415 in ASP.NET Core 3.1?

我正在创建一个 ASP.NET Core ( .NET Core 3.1 ) API 接受内容为 JSON 的请求。

如果我 POST 到我的端点非 JSON 请求内容(同时在 VS 中使用 IIS Express 调试运行)我得到类似于以下的响应:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
    "title": "Unsupported Media Type",
    "status": 415,
    "traceId": "|a53c33b7-43fa646efa5c6ab9."
}

我不想在响应中发送这个,而是想为这种情况创建我自己的响应。 如果可能的话,我想通过改变管道来做到这一点。

在我的Startup.cs ,我添加了:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await next.Invoke();

        if (context.Response.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            await context.Response.WriteAsync("Unsupported media type handled");
        }
    });
    
    // other calls on app
}

但是,在运行该应用程序并到达我的端点后,我现在得到:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
    "title": "Unsupported Media Type",
    "status": 415,
    "traceId": "|3ec437d7-4b20f64c28d04f16."
}Unsupported media type handled

我试过调用context.Response.Clear(); 在我写我的新文本之前,但这只是输出上面的 JSON 而没有我的自定义文本。 这似乎更奇怪。

这里发生了什么? 在我的管道开始之前,感觉好像有什么东西正在处理和创建 415 响应。 或者我可能做错了什么?

我强烈建议您重新考虑您最初的想法,即用您自己的自定义响应内容替换您从 ASP.NET 核心获得的响应内容。

ASP.NET 核心对 API 控制器的错误处理有意见。 他们决定使用问题详细信息规范来处理一些客户端错误(例如 415 状态代码)。

问题详细信息 RFC中的引述:

本文档将“问题详细信息”定义为一种在 HTTP 响应中携带机器可读错误详细信息的方法,以避免需要为 HTTP API 定义新的错误响应格式。

HTTP [RFC7230] 状态代码有时不足以传达有关错误的足够信息以提供帮助。 虽然 Web 浏览器背后的人类可以通过 HTML [W3C.REC-html5-20141028] 响应主体了解问题的性质,但所谓的“HTTP API”的非人类消费者通常不会。

本规范定义了简单的 JSON [RFC7159] 和 XML [W3C.REC-xml-20081126] 文档格式来满足此目的。 它们旨在供 HTTP API 重用,这些 API 可以识别特定于其需求的不同“问题类型”。

这是一个 web 标准,用于在机器之间传达错误,因此在 API 控制器错误处理的上下文中,这是一个有意义的选择。

请注意,该标准旨在通过定义自定义错误契约来传达来自 web api 的错误并必须为客户记录它们,从而避免每次都重新发明轮子。

本文档说明,对于某些指示客户端错误的 HTTP 状态代码(例如:您的415 Unsupported Media Type状态代码),符合问题详细信息规范的 JSON 响应由 ASP.NET 核心框架自动创建。

您在这里有几个选择:

  1. 您可以通过设置ApiBehaviorOptions.SuppressMapClientErrors = true; . 这样你会得到一个简单的状态代码(在你的情况下是 415 状态代码)作为响应,根本没有响应内容。
  2. 您可以决定自定义返回的 Problem Details JSON 负载的一些属性,如此所述
  3. 您可以决定控制问题详细信息 JSON 响应的生成,方法是提供您自己的ProblemDetailsFactory实现,如此所述

我会避免选项 3。

在我看来,如果您真的想自定义包含在问题详细信息 JSON object 中的错误消息,那么您可以做的最好的事情就是坚持使用选项 2。

这是一个例子:

public void ConfigureServices(IServiceCollection services)
{
  services
    .AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
      options.ClientErrorMapping[415].Title = "My custom message";
    });
}

您将得到的响应内容(对于无效请求,例如没有内容的请求)如下:

自定义问题详细信息标题

您可以评估的另一个选项是选项 1:抑制由 ASP.NET 核心完成的默认客户端错误处理,并在 415 响应状态代码的情况下完全控制响应。

这是一个例子:

public void ConfigureServices(IServiceCollection services)
{
  services
    .AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
      options.SuppressMapClientErrors = true;
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
  }

  app.Use(async (context, next) =>
  {
    await next();

    // Handle the 415 response
    if (context.Response.StatusCode == 415)
    {
      context.Response.ContentType = "application/json";

      var json = JsonSerializer.Serialize(new { message = "You need to add a request body!" });
      await context.Response.WriteAsync(json);
    }
  });

  app.UseRouting();

  app.UseAuthorization();

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

您将获得的响应负载(对于无效请求,例如没有内容的请求)如下:

自定义回复内容

请注意,在这里我能够完全控制响应内容,因为通过禁用由 ASP.NET 核心 ( options.SuppressMapClientErrors = true; ) 完成的默认客户端错误映射,发送无效 POST 请求时得到的响应是一个普通的415状态码,完全没有响应内容 因此,当写入响应 stream ( await context.Response.WriteAsync(json); ) 时,您正在写入一个空响应 stream,因此只有我们自定义的 JSON 作为响应内容发送到客户端

在您的第一次尝试中,您忘记禁用 ASP.NET 核心在 415 响应状态代码的情况下创建的自动响应,因此代码await context.Response.WriteAsync("Unsupported media type handled"); 正在将内容附加到响应 stream,响应内容已经由 ASP.NET 核心框架自动写入。

从这里

https://learn.microsoft.com/en-us/as.net/core/fundamentals/error-handling?view=as.netcore-6.0#exception-handler-lambda

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM