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:
Kiran Challa 2016-06-07 17:22:47 -07:00
parent 62803d5979
commit f1982bd987
3 changed files with 166 additions and 8 deletions

View File

@ -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);

View File

@ -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()
{

View File

@ -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;
}
}
}