Suppress default status code response type in api descriptions when explicit response types have been provided
[Fixes #4823] How to override the default (200) status code with ProducesResponseType
This commit is contained in:
parent
62803d5979
commit
f1982bd987
|
|
@ -358,13 +358,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
// Build list of all possible return types (and status codes) for an action.
|
||||
var objectTypes = new Dictionary<int, Type>();
|
||||
|
||||
if (type != null && type != typeof(void))
|
||||
{
|
||||
// This return type can be overriden by any response metadata
|
||||
// attributes later if the user wishes to.
|
||||
objectTypes[StatusCodes.Status200OK] = type;
|
||||
}
|
||||
|
||||
// Get the content type that the action explicitly set to support.
|
||||
// Walk through all 'filter' attributes in order, and allow each one to see or override
|
||||
// the results of the previous ones. This is similar to the execution path for content-negotiation.
|
||||
|
|
@ -382,6 +375,14 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
}
|
||||
}
|
||||
|
||||
// Set the default status only when no status has already been set explicitly
|
||||
if (objectTypes.Count == 0
|
||||
&& type != null
|
||||
&& type != typeof(void))
|
||||
{
|
||||
objectTypes[StatusCodes.Status200OK] = type;
|
||||
}
|
||||
|
||||
if (contentTypes.Count == 0)
|
||||
{
|
||||
contentTypes.Add((string)null);
|
||||
|
|
|
|||
|
|
@ -523,6 +523,133 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("application/json", responseFormat.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExplicitResponseTypeDecoration_SuppressesDefaultStatus()
|
||||
{
|
||||
// Arrange
|
||||
var type1 = typeof(ApiExplorerWebSite.Product).FullName;
|
||||
var type2 = typeof(ModelStateDictionary).FullName;
|
||||
var expectedMediaTypes = new[] { "application/json", "text/json", "application/xml", "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(
|
||||
"http://localhost/ApiExplorerResponseTypeWithAttribute/CreateProductWithDefaultResponseContentTypes");
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
||||
var responseType = description.SupportedResponseTypes[0];
|
||||
Assert.Equal(type1, responseType.ResponseType);
|
||||
Assert.Equal(201, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
responseType = description.SupportedResponseTypes[1];
|
||||
Assert.Equal(type2, responseType.ResponseType);
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExplicitResponseTypeDecoration_SuppressesDefaultStatus_AlsoHonorsProducesContentTypes()
|
||||
{
|
||||
// Arrange
|
||||
var type1 = typeof(ApiExplorerWebSite.Product).FullName;
|
||||
var type2 = typeof(ModelStateDictionary).FullName;
|
||||
var expectedMediaTypes = new[] { "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(
|
||||
"http://localhost/ApiExplorerResponseTypeWithAttribute/CreateProductWithLimitedResponseContentTypes");
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
||||
var responseType = description.SupportedResponseTypes[0];
|
||||
Assert.Equal(type1, responseType.ResponseType);
|
||||
Assert.Equal(201, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
responseType = description.SupportedResponseTypes[1];
|
||||
Assert.Equal(type2, responseType.ResponseType);
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExplicitResponseTypeDecoration_WithExplicitDefaultStatus()
|
||||
{
|
||||
// Arrange
|
||||
var type1 = typeof(ApiExplorerWebSite.Product).FullName;
|
||||
var type2 = typeof(ModelStateDictionary).FullName;
|
||||
var expectedMediaTypes = new[] { "application/json", "text/json", "application/xml", "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(
|
||||
"http://localhost/ApiExplorerResponseTypeWithAttribute/UpdateProductWithDefaultResponseContentTypes");
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
||||
var responseType = description.SupportedResponseTypes[0];
|
||||
Assert.Equal(type1, responseType.ResponseType);
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
responseType = description.SupportedResponseTypes[1];
|
||||
Assert.Equal(type2, responseType.ResponseType);
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExplicitResponseTypeDecoration_WithExplicitDefaultStatus_SpecifiedViaProducesAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var type1 = typeof(ApiExplorerWebSite.Product).FullName;
|
||||
var type2 = typeof(ModelStateDictionary).FullName;
|
||||
var expectedMediaTypes = new[] { "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(
|
||||
"http://localhost/ApiExplorerResponseTypeWithAttribute/UpdateProductWithLimitedResponseContentTypes");
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
||||
var responseType = description.SupportedResponseTypes[0];
|
||||
Assert.Equal(type1, responseType.ResponseType);
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
responseType = description.SupportedResponseTypes[1];
|
||||
Assert.Equal(type2, responseType.ResponseType);
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Equal(
|
||||
expectedMediaTypes,
|
||||
responseType.ResponseFormats.Select(responseFormat => responseFormat.MediaType).ToArray());
|
||||
}
|
||||
[Fact]
|
||||
public async Task ApiExplorer_ResponseType_InheritingFromController()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace ApiExplorerWebSite
|
||||
{
|
||||
[Route("ApiExplorerResponseTypeWithAttribute/[Action]")]
|
||||
[Route("[controller]/[Action]")]
|
||||
public class ApiExplorerResponseTypeWithAttributeController : Controller
|
||||
{
|
||||
[HttpGet]
|
||||
|
|
@ -42,5 +43,34 @@ namespace ApiExplorerWebSite
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[ProducesResponseType(typeof(Product), 201)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
public Product CreateProductWithDefaultResponseContentTypes(Product product)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[Produces("text/xml")] // Has status code as 200 but is not applied as it does not set 'Type'
|
||||
[ProducesResponseType(typeof(Product), 201)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
public Product CreateProductWithLimitedResponseContentTypes(Product product)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[ProducesResponseType(typeof(Product), 200)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
public Product UpdateProductWithDefaultResponseContentTypes(Product product)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[Produces("text/xml", Type = typeof(Product))] // Has status code as 200
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
public Product UpdateProductWithLimitedResponseContentTypes(Product product)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue