Hi there
I maintain a library (Swashbuckle) that relies heavily on ApiDescriptions to generate Swagger descriptions for API's implemented on AspNet Core. I was thrilled to see the introduction of the "ProducesResponseType" attribute as I can leverage it to generate additional Response descriptions and remove the Swashbuckle specific abstractions that I had to introduce for the same purpose.
However, there's one use case that doesn't quite fit and I'm wondering if this is something that's already been considered:
[ProducesResponseType(typeof(ComplexType), 201)]
[ProducesResponseType(typeof(int), 401)]
[ProducesResponseType(typeof(int), 403)]
public ComplexType AnnotatedWithProducesResponseTypes()
{
}
The use case here is to indicate a specific "2xx" response type rather than the default "200" for a non-void operation. However, when I apply the above metadata, the ApiDescription ends up with 4 described responses - the default and 3 additional ones [ 200, 201, 401, 403 ].
Is there a simple way to indicate a status for the success case other than the default 200?
/cc @kichalla
Currently there seems to be no way to override this behavior. We only allow to represent a different return type for 200 but not to remove the 200 response type itself.
https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs#L365
Thanks for investigating. Any idea if this is likely to be added in the near term - I believe this to be a very common scenario in API design?
@domaindrivendev - we're looking at a fix now.
Would it make sense here to just get rid of the "default 200"?
So if you specify any [ProducesResponseType(...)] attributes, you'd have to include all the types/status-codes you want to be included.
A few motivating examples... /cc @kichalla does this seem right to you
public Product GetProduct(int id) { ... }
Today this "just works" by virtue of us choosing a default of 200 and inferring the body-schema from the return type.
[ProducesResponseType(typeof(Product))] // StatusCode defaults to 200
public IActionResult GetProduct(int id) { ... }
In this case I've got an IActionResult-returning method. I need to specify the body "type" if want I want it to be known because IActionResult is opaque.
[ProducesResponseType(typeof(Errors), 403)]
[ProducesResponseType(typeof(Product), 200)]
public IActionResult GetProduct(int id) { ... }
Today specifying the [ProducesResponseType(typeof(Product))] is already necessary because IActionResult is opaque. So there's no real change here.
[ProducesResponseType(typeof(Errors), 403)]
[ProducesResponseType(typeof(Product), 201)]
public IActionResult CreateProduct(Product product) { ... }
Today you can't really get what you want from here, because we stymie you with the extra 200.
@rynowak Couple of questions for clarification:
public Product GetProduct(int id) { ... }
public IActionResult GetProduct(int id) { ... }
ProducesResponseType attribute on an action, the user must specify all the possible status codes etc. i.e the default 200 doesn't apply.For the following cases we will still default to 200 when sending out the response, right?
Correct
But if a user explicitly supplies a ProducesResponseType attribute on an action, the user must specify all the possible status codes etc. i.e the default 200 doesn't apply.
Correct
If an action doesn't specify a status code on the returning result(for us to set on the outgoing response message), do we figure out the status code by mapping the response type -> status code?
There is no runtime behavior associated with [ProducesResponse(...)]
Went through the exact same considerations when coming up with the semantics for the SwaggerResponseAttribute for this exact purpose: https://github.com/domaindrivendev/Swashbuckle/pull/259
In that case, we decided to introduce an explicit SwaggerResponseRemoveDefaults attribute so that people could continue to use the default (200) where it made sense and then be explicit in the (potentially fewer) cases where a different response is required:
[SwaggerResponseRemoveDefaults]
[SwaggerResponse(201, typeof(Product))]
public Product CreateProduct(Product product)
I know it's a little ugly but there's trade-offs with all approaches and this felt like the one that resulted in the most succinct code for the most common case. That is, when the default (200) is good but you just want to specify additional error responses:
[SwaggerResponse(401, typeof(Error))]
[SwaggerResponse(403, typeof(Error))]
public Product CreateProduct(Product product)
Results in [ 200, 401, 403 ]
How would this example work?
[SwaggerResponse(401, typeof(Error))]
[SwaggerResponse(403, typeof(Error))]
public Product CreateProduct(Product product)
Since the method signature returns Product you could only return the Error from a filter.
This really seems like the primary consideration for whether or not you'd want to 'suppress' the default implicitly or explicitly.
@domaindrivendev any thoughts on https://github.com/aspnet/Mvc/issues/4823#issuecomment-224468866?
Yep - you make a good point. If you're not using filters to return errors, then you're forced into IActionResult and hence the explicit annotation for ALL status codes.
So, and I think this was your point, the question is how common a practice is it to use filters for errors. I know I would probably look to a filter to handle common concerns like authorization and validation errors.
And these are typical scenarios that are mapped to specific http codes.
I think we want to go with what's in the PR. Doing something different would new plumbing and I don't think we've identified that's clearly better.
I feel like cases where you're going to return some DTO object directly are limited; a real API will likely use IActionResult for more things. If anyone really wants to apply a 'global default 200' to all their DTO-returning actions this can be done with an ActionModel convention to make it dry.
Most helpful comment
Thanks for investigating. Any idea if this is likely to be added in the near term - I believe this to be a very common scenario in API design?