Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Suppress default status code response type in api descriptions when e…
Browse files Browse the repository at this point in the history
…xplicit response types have been provided

[Fixes #4823] How to override the default (200) status code with ProducesResponseType
  • Loading branch information
kichalla committed Jun 9, 2016
1 parent 62803d5 commit f1982bd
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,6 @@ private IReadOnlyList<ApiResponseType> GetApiResponseTypes(
// 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.
Expand All @@ -382,6 +375,14 @@ private IReadOnlyList<ApiResponseType> GetApiResponseTypes(
}
}

// 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);
Expand Down
127 changes: 127 additions & 0 deletions test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,133 @@ public async Task ApiExplorer_ResponseType_KnownWithAttribute(string action, str
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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -42,5 +43,34 @@ public Product GetProduct()
{
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;
}
}
}

0 comments on commit f1982bd

Please sign in to comment.