Fix Api Explorer not returning type with ActionResult<T> and no type in ProducesResponseTypeAttribute
This commit is contained in:
parent
042c833145
commit
ac4d6366b7
|
|
@ -86,13 +86,24 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
{
|
{
|
||||||
metadataAttribute.SetContentTypes(contentTypes);
|
metadataAttribute.SetContentTypes(contentTypes);
|
||||||
|
|
||||||
if (metadataAttribute.Type != null)
|
if (metadataAttribute.Type == typeof(void) &&
|
||||||
|
type != null &&
|
||||||
|
(metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created))
|
||||||
|
{
|
||||||
|
// ProducesResponseTypeAttribute's constructor defaults to setting "Type" to void when no value is specified.
|
||||||
|
// In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a
|
||||||
|
// [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred
|
||||||
|
// from the return type.
|
||||||
|
objectTypes[metadataAttribute.StatusCode] = type;
|
||||||
|
}
|
||||||
|
else if (metadataAttribute.Type != null)
|
||||||
{
|
{
|
||||||
objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type;
|
objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set the default status only when no status has already been set explicitly
|
// Set the default status only when no status has already been set explicitly
|
||||||
if (objectTypes.Count == 0 && type != null)
|
if (objectTypes.Count == 0 && type != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
responseType =>
|
responseType =>
|
||||||
{
|
{
|
||||||
Assert.Equal(200, responseType.StatusCode);
|
Assert.Equal(200, responseType.StatusCode);
|
||||||
Assert.Equal(typeof(void), responseType.Type);
|
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||||
Assert.False(responseType.IsDefaultResponse);
|
Assert.False(responseType.IsDefaultResponse);
|
||||||
Assert.Empty(responseType.ApiResponseFormats);
|
Assert.Collection(
|
||||||
|
responseType.ApiResponseFormats,
|
||||||
|
format => Assert.Equal("application/json", format.MediaType));
|
||||||
},
|
},
|
||||||
responseType =>
|
responseType =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,216 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(ReturnsActionResultOfProduct))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfActionResultOfProduct))]
|
||||||
|
public void GetApiDescription_ReturnsActionResultOfTWithProducesContentType(
|
||||||
|
string methodName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(methodName);
|
||||||
|
action.FilterDescriptors = new List<FilterDescriptor>()
|
||||||
|
{
|
||||||
|
// Since action is returning Void or Task, it does not make sense to provide a value for the
|
||||||
|
// 'Type' property to ProducesAttribute. But the same action could return other types of data
|
||||||
|
// based on runtime conditions.
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesAttribute("text/json", "application/json"),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(200),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(202),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(typeof(BadData), 400),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500),
|
||||||
|
FilterScope.Action)
|
||||||
|
};
|
||||||
|
var expectedMediaTypes = new[] { "application/json", "text/json" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(4, description.SupportedResponseTypes.Count);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(Product), responseType.Type);
|
||||||
|
Assert.Equal(200, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(void), responseType.Type);
|
||||||
|
Assert.Equal(202, responseType.StatusCode);
|
||||||
|
Assert.Null(responseType.ModelMetadata);
|
||||||
|
Assert.Empty(GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(BadData), responseType.Type);
|
||||||
|
Assert.Equal(400, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(ErrorDetails), responseType.Type);
|
||||||
|
Assert.Equal(500, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(ReturnsActionResultOfProduct))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfActionResultOfProduct))]
|
||||||
|
public void GetApiDescription_ReturnsActionResultOfTWithProducesContentType_ForStatusCode201(
|
||||||
|
string methodName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(methodName);
|
||||||
|
action.FilterDescriptors = new List<FilterDescriptor>()
|
||||||
|
{
|
||||||
|
// Since action is returning Void or Task, it does not make sense to provide a value for the
|
||||||
|
// 'Type' property to ProducesAttribute. But the same action could return other types of data
|
||||||
|
// based on runtime conditions.
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesAttribute("text/json", "application/json"),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(201),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(204),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(typeof(BadData), 400),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500),
|
||||||
|
FilterScope.Action)
|
||||||
|
};
|
||||||
|
var expectedMediaTypes = new[] { "application/json", "text/json" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(4, description.SupportedResponseTypes.Count);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(Product), responseType.Type);
|
||||||
|
Assert.Equal(201, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(void), responseType.Type);
|
||||||
|
Assert.Equal(204, responseType.StatusCode);
|
||||||
|
Assert.Null(responseType.ModelMetadata);
|
||||||
|
Assert.Empty(GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(BadData), responseType.Type);
|
||||||
|
Assert.Equal(400, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(ErrorDetails), responseType.Type);
|
||||||
|
Assert.Equal(500, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(ReturnsActionResultOfSequenceOfProducts))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfActionResultOfSequenceOfProducts))]
|
||||||
|
public void GetApiDescription_ReturnsActionResultOfSequenceOfTWithProducesContentType(
|
||||||
|
string methodName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(methodName);
|
||||||
|
action.FilterDescriptors = new List<FilterDescriptor>()
|
||||||
|
{
|
||||||
|
// Since action is returning Void or Task, it does not make sense to provide a value for the
|
||||||
|
// 'Type' property to ProducesAttribute. But the same action could return other types of data
|
||||||
|
// based on runtime conditions.
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesAttribute("text/json", "application/json"),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(200),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(201),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(typeof(BadData), 400),
|
||||||
|
FilterScope.Action),
|
||||||
|
new FilterDescriptor(
|
||||||
|
new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500),
|
||||||
|
FilterScope.Action)
|
||||||
|
};
|
||||||
|
var expectedMediaTypes = new[] { "application/json", "text/json" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(4, description.SupportedResponseTypes.Count);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(IEnumerable<Product>), responseType.Type);
|
||||||
|
Assert.Equal(200, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(IEnumerable<Product>), responseType.Type);
|
||||||
|
Assert.Equal(201, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(BadData), responseType.Type);
|
||||||
|
Assert.Equal(400, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(typeof(ErrorDetails), responseType.Type);
|
||||||
|
Assert.Equal(500, responseType.StatusCode);
|
||||||
|
Assert.NotNull(responseType.ModelMetadata);
|
||||||
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(nameof(ReturnsVoid))]
|
[InlineData(nameof(ReturnsVoid))]
|
||||||
[InlineData(nameof(ReturnsTask))]
|
[InlineData(nameof(ReturnsTask))]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using ApiExplorerWebSite;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
|
@ -1156,6 +1157,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
||||||
private async Task ApiConvention_ForGetMethod(string action)
|
private async Task ApiConvention_ForGetMethod(string action)
|
||||||
{
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.GetStringAsync(
|
var response = await Client.GetStringAsync(
|
||||||
$"ApiExplorerResponseTypeWithApiConventionController/{action}");
|
$"ApiExplorerResponseTypeWithApiConventionController/{action}");
|
||||||
|
|
@ -1168,9 +1172,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||||
responseType =>
|
responseType =>
|
||||||
{
|
{
|
||||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
Assert.Equal(typeof(Product).FullName, responseType.ResponseType);
|
||||||
Assert.Equal(200, responseType.StatusCode);
|
Assert.Equal(200, responseType.StatusCode);
|
||||||
Assert.Empty(responseType.ResponseFormats);
|
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||||
},
|
},
|
||||||
responseType =>
|
responseType =>
|
||||||
{
|
{
|
||||||
|
|
@ -1198,7 +1202,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||||
responseType =>
|
responseType =>
|
||||||
{
|
{
|
||||||
Assert.Equal(typeof(IEnumerable<ApiExplorerWebSite.Product>).FullName, responseType.ResponseType);
|
Assert.Equal(typeof(IEnumerable<Product>).FullName, responseType.ResponseType);
|
||||||
Assert.Equal(200, responseType.StatusCode);
|
Assert.Equal(200, responseType.StatusCode);
|
||||||
var actualMediaTypes = responseType.ResponseFormats.Select(r => r.MediaType).OrderBy(r => r);
|
var actualMediaTypes = responseType.ResponseFormats.Select(r => r.MediaType).OrderBy(r => r);
|
||||||
Assert.Equal(expectedMediaTypes, actualMediaTypes);
|
Assert.Equal(expectedMediaTypes, actualMediaTypes);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue