Net Core API:ProducesResponseType 的作用是什么?

93

我想了解ProducesResponseType的目的。

Microsoft将其定义为指定操作返回的值和状态代码类型的过滤器。

因此,我很好奇如果:

  • 一个人不设置ProductResponseType会有什么后果?
  • 系统会受到不利影响吗?还是存在负面后果?
  • Microsoft API不已经自动固有地了解返回的状态代码的类型/值吗?
[ProducesResponseType(typeof(DepartmentDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]

来自Microsoft的文档:ProducesResponseTypeAttribute类


31
据我所知,它仅仅是一份文档。如果您使用像NSwag或Swashbuckle这样的工具,根据该属性,它将显示端点的可能响应。我不知道它在除生成API文档之外有任何其他影响。 - Chris Pratt
@ChrisPratt 可以随意发布答案,我会发送积分,谢谢! - user11973685
1
我不确定这是唯一的答案。其他人可能知道我不知道的东西。我从来没有在任何不涉及API文档的讨论中看到过这个属性。这并不意味着它实际上没有其他用途。 - Chris Pratt
请查看我的答案,一些反射技巧可以用于不仅仅是文档,还可以用于运行时检查(例如测试)。 - Vladimir Koltunov
4个回答

61
虽然正确答案已经提交,但我愿意提供一个例子。假设您已经将 Swashbuckle.AspNetCore 包添加到了您的项目中,并在 Startup.Configure(...) 中像这样使用了它:
app.UseSwagger();
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "My Web Service API V1");
    options.RoutePrefix = "api/docs";  

});

如果有这样一个测试控制器操作端点:

[HttpGet]        
public ActionResult GetAllItems()
{
    if ((new Random()).Next() % 2 == 0)
    {
        return Ok(new string[] { "value1", "value2" });
    }
    else
    {
        return Problem(detail: "No Items Found, Don't Try Again!");
    }
}

将会得到一个 Swagger UI 的卡片/部分,类似这样(运行项目并导航至/api/docs/index.html):

enter image description here

正如你所看到的,对于该端点没有提供“元数据”。

现在,将端点更新为以下内容:

[HttpGet]
[ProducesResponseType(typeof(IEnumerable<string>), 200)]
[ProducesResponseType(404)]
public ActionResult GetAllItems()
{
    if ((new Random()).Next() % 2 == 0)
    {
        return Ok(new string[] { "value1", "value2" });
    }
    else
    {
        return Problem(detail: "No Items Found, Don't Try Again!");
    }
}

这不会改变您的端点行为,但现在 Swagger 页面看起来像这样:

enter image description here

这样更好,因为现在客户端可以看到可能的响应状态码,以及每个响应状态下返回数据的类型/结构。 请注意,虽然我没有为 404 定义返回类型,但 ASP.NET Core(我使用的是 .NET 5)足够聪明,可以将返回类型设置为 ProblemDetails

如果这是您想要采取的路径,建议将 Web API 分析器 添加到项目中,以接收一些有用的警告。

p.s. 我还想在 app.UseSwaggerUI(...) 配置中使用 options.DisplayOperationId();。这样做后,Swagger UI 将显示映射到每个端点的实际 .NET 方法的名称。例如,上面的端点是针对 /api/sample 的 GET 请求,但实际的 .NET 方法被称为 GetAllItems()


对于返回普通类型而不是ActionResult的控制器,是否应该有一种方式让Swagger自动推断C#返回类型是成功/200,并且不必在每个控制器方法上添加这些多余的[ProducesResponseType(typeof(...), 200)]属性? - Patrick Szalapski
@PatrickSzalapski 我相信你可以使用编译时代码注入器,比如 https://github.com/Fody/Fody 来避免在代码中重复添加属性。我几乎可以确定有一些 Roslyn 编译服务/代码生成功能,能够实现类似的功能,例如将控制器类和操作方法标记为“partial”,然后为其生成一个带有部分方法声明的部分类,并用所有必需的属性进行装饰。(我自己没有做过这个,只是随口想想!哈哈) - Siavash Mortazavi
嗨,Sivasah,你的回答非常有用。我还有一个疑问,是否可能在单个构造方法中有两个 [ProducesResponseType(typeof(IEnumerable<UserDto>), 200)] 和 [ProducesResponseType(typeof(IEnumerable<MemberDto>), 200)]?我们能动态定义它吗? - Thomas Raj
@PatrickSzalapski 你会认为是这样的,对吧?如果不记录任何错误响应,那么它将执行此操作。但是,如果您不记录错误响应,则“ProblemDetails”不会包含在模式中... - Kevin Krumwiede
@ThomasRaj 你是指操作方法,而不是构造函数,对吧?无论可能性如何,我认为这不是一个好的设计决策。例如,如果您的端点是/api/users/{id},基于一些内部逻辑返回UserDto或MemberDto对象非常模糊,并且不要忘记您必须将其转换为Object或其他类型。我建议使用“视图模型”的概念,例如专门用于返回特定视图/端点的对象的模型,并始终返回该类型的对象。p.s. 完全可以保留某些属性,并配置JSON序列化器以将它们保留下来。 - Siavash Mortazavi

16

14
这段引用的意思是,“这个属性可以产生更加详细的响应信息,用于生成类似Swagger这样工具生成的Web API帮助页面。”因此,我猜测它主要用于文档编写,并可能可被静态代码分析使用。 - Andy
4
看起来是更多的无用信息。如果样板式的 XML 注释还不足以使您的代码混乱不堪,现在又有了这个。不过,在文本编辑器中看起来真的很聪明! - user1172763

11

它是用于生成开放API元数据,供Swagger (https://swagger.io/)等API探索/可视化工具使用,在文档中指示控制器可能返回的内容。


0

SwaggerResponse和ProducesResponseType属性的检查

你可以在dotnet (.NET 6为例)中使用this extension,强制开发者将OpenAPI(Swagger)描述与方法实现同步。

3种不同的使用选择

跨应用程序控制器:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
        app.UseSwaggerResponseCheck();
  //...
}

使用ValidateStatusCodes属性的每个控制器操作:

[ApiController]
[Route("[controller]")]
public class ExampleController : ControllerBase
{
    [HttpGet]
    [ValidateStatusCodes] // <-- Use this
    [SwaggerOperation("LoginUser")]
    [SwaggerResponse(statusCode: StatusCodes.Status200OK, type: null, description: "signed user email account")]
    [SwaggerResponse(statusCode: StatusCodes.Status400BadRequest, type: null, description: "wrong email or password")]
    [Route("/users/login")]
    public virtual IActionResult LoginUser([FromQuery][Required()] string email, [FromQuery] string password)
    {
            if (email == "email@gmail.com")
              return Ok("success");
            else if (email == "")
              return BadRequest("email required");
            else
              return NotFound("user not found"); // 500 - InternalServerError because not attributed with SwaggerResponse.
    }
    // ...
    [HttpGet]
    [ValidateStatusCodes] // <-- Use this
    [ProducesResponseType(type: typeof(Account), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [Route("/users/login2")]
    public virtual IActionResult LoginUser2([FromQuery][Required()] string email, [FromQuery] string password)
    {
            if (email == "email@gmail.com")
              return Ok("success").Validate();
            else if (email == "")
              return BadRequest("email required").Validate();
            else
              return NotFound("user not found").Validate(); // Throws error in DEBUG or Development.
    }
}

使用IStatusCodeActionResult.Validate()方法的每个结果:

[ApiController]
[Route("[controller]")]
public class ExampleController : ControllerBase
{
    [HttpGet]
    [SwaggerOperation("LoginUser")]
    [SwaggerResponse(statusCode: StatusCodes.Status200OK, type: null, description: "signed user email account")]
    [SwaggerResponse(statusCode: StatusCodes.Status400BadRequest, type: null, description: "wrong email or password")]
    [Route("/users/login")]
    public virtual IActionResult LoginUser([FromQuery][Required()] string email, [FromQuery] string password)
    {
            if (email == "email@gmail.com")
              return Ok("success").Validate();
            else if (email == "")
              return BadRequest("email required").Validate();
            else if (email == "secret")
              return Unauthorized("hello");
                 // Passed, independent of SwaggerResponse attribute.
            else
              return NotFound("user not found").Validate();
                 // 500 - InternalServerError because not attributed with SwaggerResponse.
    }
    // ...
    [HttpGet]
    [ProducesResponseType(type: typeof(Account), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [Route("/users/login2")]
    public virtual IActionResult LoginUser2([FromQuery][Required()] string email, [FromQuery] string password)
    {
            if (email == "email@gmail.com")
              return Ok("success").Validate();
            else if (email == "")
              return BadRequest("email required").Validate();
            else
              return NotFound("user not found").Validate(); // Throws error in DEBUG or Development.
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,