Net Core API: Purpose of ProducesResponseType

C#.Netasp.net Core.Net Core

C# Problem Overview


I want to understand the purpose of ProducesResponseType.

Microsoft defines as a filter that specifies the type of the value and status code returned by the action.

So I am curious what are consequences if

  • a person does not place ProductResponseType?
  • How is system at a disadvantage, or is there negative consequence?
  • Doesn't Microsoft API already automatically inherently know the type/value of status code returned?
[ProducesResponseType(typeof(DepartmentDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]

Documentation from the Microsoft: ProducesResponseTypeAttribute Class

C# Solutions


Solution 1 - C#

Although the correct answer is already submitted, I would like to provide an example. Assume you have added the Swashbuckle.AspNetCore package to your project, and have used it in Startup.Configure(...) like this:

app.UseSwagger();
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "My Web Service API V1");
    options.RoutePrefix = "api/docs";  

});

Having a test controller action endpoint like this:

[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!");
    }
}

Will result in a swagger UI card/section like this (Run the project and navigate to /api/docs/index.html):

enter image description here

As you can see, there is no 'metadata' provided for the endpoint.

Now, update the endpoint to this:

[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!");
    }
}

This won't change the behavior of your endpoint at all, but now the swagger page looks like this:

enter image description here

This is much nicer, because now the client can see what are the possible response status codes, and for each response status, what is the type/structure of the returned data. Please note that although I have not defined the return type for 404, but ASP.NET Core (I'm using .NET 5) is smart enough to set the return type to ProblemDetails.

If this is the path you want to take, it's a good idea to add the Web API Analyzer to your project, to receive some useful warnings.

p.s. I would also like to use options.DisplayOperationId(); in app.UseSwaggerUI(...) configuration. By doing so, swagger UI will display the name of the actual .NET method that is mapped to each endpoint. For example, the above endpoint is a GET to /api/sample but the actual .NET method is called GetAllItems()

Solution 2 - C#

I think it can come handy for non-success (200) return codes. Say if one of the failure status codes returns a model that describes the problem, you can specify that the status code in that case produces something different than the success case. You can read more about that and find examples here: https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-2.2

Solution 3 - C#

It's for producing Open API metadata for API exploration/visualization tools such as Swagger (https://swagger.io/), to indicate in the documentation what the controller may return.

Solution 4 - C#

SwaggerResponse and ProducesResponseType attributes checking

You can use this extension in dotnet (.NET 6 in my case) for enforcing developers to sync OpenAPI (Swagger) descriptions with implementations of methods.

3 different usage choices

Across all app controllers:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
        app.UseSwaggerResponseCheck();
  //...
}
Per controller action using ValidateStatusCodes attribute:
[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 protected]")
              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 protected]")
              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.
    }
}
Per result using 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 protected]")
              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 protected]")
              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.
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser11973685View Question on Stackoverflow
Solution 1 - C#Siavash MortazaviView Answer on Stackoverflow
Solution 2 - C#MohammadView Answer on Stackoverflow
Solution 3 - C#SeanView Answer on Stackoverflow
Solution 4 - C#Vladimir KoltunovView Answer on Stackoverflow