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.
|
// Build list of all possible return types (and status codes) for an action.
|
||||||
var objectTypes = new Dictionary<int, Type>();
|
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.
|
// 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
|
// 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.
|
// 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)
|
if (contentTypes.Count == 0)
|
||||||
{
|
{
|
||||||
contentTypes.Add((string)null);
|
contentTypes.Add((string)null);
|
||||||
|
|
|
||||||
|
|
@ -523,6 +523,133 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal("application/json", responseFormat.MediaType);
|
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]
|
[Fact]
|
||||||
public async Task ApiExplorer_ResponseType_InheritingFromController()
|
public async Task ApiExplorer_ResponseType_InheritingFromController()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace ApiExplorerWebSite
|
namespace ApiExplorerWebSite
|
||||||
{
|
{
|
||||||
[Route("ApiExplorerResponseTypeWithAttribute/[Action]")]
|
[Route("[controller]/[Action]")]
|
||||||
public class ApiExplorerResponseTypeWithAttributeController : Controller
|
public class ApiExplorerResponseTypeWithAttributeController : Controller
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -42,5 +43,34 @@ namespace ApiExplorerWebSite
|
||||||
{
|
{
|
||||||
return null;
|
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