Make DefaultApiDescriptionProvider understand ActionResult<T>

Fixes #6784
This commit is contained in:
Pranav K 2017-09-08 11:28:34 -07:00
parent 6bf165f22f
commit 63397653fa
4 changed files with 59 additions and 11 deletions

View File

@ -193,8 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
parameter.Source == BindingSource.ModelBinding || parameter.Source == BindingSource.ModelBinding ||
parameter.Source == BindingSource.Custom) parameter.Source == BindingSource.Custom)
{ {
ApiParameterRouteInfo routeInfo; if (routeParameters.TryGetValue(parameter.Name, out var routeInfo))
if (routeParameters.TryGetValue(parameter.Name, out routeInfo))
{ {
parameter.RouteInfo = routeInfo; parameter.RouteInfo = routeInfo;
routeParameters.Remove(parameter.Name); routeParameters.Remove(parameter.Name);
@ -322,8 +321,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{ {
foreach (var formatter in _inputFormatters) foreach (var formatter in _inputFormatters)
{ {
var requestFormatMetadataProvider = formatter as IApiRequestFormatMetadataProvider; if (formatter is IApiRequestFormatMetadataProvider requestFormatMetadataProvider)
if (requestFormatMetadataProvider != null)
{ {
var supportedTypes = requestFormatMetadataProvider.GetSupportedContentTypes(contentType, type); var supportedTypes = requestFormatMetadataProvider.GetSupportedContentTypes(contentType, type);
@ -445,7 +443,10 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
} }
// Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled. // Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled.
var unwrappedType = GetTaskInnerTypeOrNull(declaredReturnType) ?? declaredReturnType; var unwrappedType = UnwrapGenericType(declaredReturnType, typeof(Task<>));
// Unwrap the type if it's ActionResult<T> or Task<ActionResult<T>>.
unwrappedType = UnwrapGenericType(unwrappedType, typeof(ActionResult<>));
// If the method is declared to return IActionResult or a derived class, that information // If the method is declared to return IActionResult or a derived class, that information
// isn't valuable to the formatter. // isn't valuable to the formatter.
@ -457,13 +458,12 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{ {
return unwrappedType; return unwrappedType;
} }
}
private static Type GetTaskInnerTypeOrNull(Type type) Type UnwrapGenericType(Type type, Type queryType)
{ {
var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(Task<>)); var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, queryType);
return genericType?.GenericTypeArguments[0] ?? type;
return genericType?.GenericTypeArguments[0]; }
} }
private Type GetRuntimeReturnType(Type declaredReturnType) private Type GetRuntimeReturnType(Type declaredReturnType)

View File

@ -394,6 +394,42 @@ namespace Microsoft.AspNetCore.Mvc.Description
Assert.NotNull(responseType.ModelMetadata); Assert.NotNull(responseType.ModelMetadata);
} }
[Theory]
[InlineData(nameof(ReturnsActionResultOfProduct))]
[InlineData(nameof(ReturnsTaskOfActionResultOfProduct))]
public void GetApiDescription_PopulatesResponseType_ForActionResultOfT(string methodName)
{
// Arrange
var action = CreateActionDescriptor(methodName);
// Act
var descriptions = GetApiDescriptions(action);
// Assert
var description = Assert.Single(descriptions);
var responseType = Assert.Single(description.SupportedResponseTypes);
Assert.Equal(typeof(Product), responseType.Type);
Assert.NotNull(responseType.ModelMetadata);
}
[Theory]
[InlineData(nameof(ReturnsActionResultOfSequenceOfProducts))]
[InlineData(nameof(ReturnsTaskOfActionResultOfSequenceOfProducts))]
public void GetApiDescription_PopulatesResponseType_ForActionResultOfSequenceOfT(string methodName)
{
// Arrange
var action = CreateActionDescriptor(methodName);
// Act
var descriptions = GetApiDescriptions(action);
// Assert
var description = Assert.Single(descriptions);
var responseType = Assert.Single(description.SupportedResponseTypes);
Assert.Equal(typeof(IEnumerable<Product>), responseType.Type);
Assert.NotNull(responseType.ModelMetadata);
}
[Fact] [Fact]
public void GetApiDescription_PopulatesResponseType_WithTaskOfProduct() public void GetApiDescription_PopulatesResponseType_WithTaskOfProduct()
{ {
@ -1478,6 +1514,14 @@ namespace Microsoft.AspNetCore.Mvc.Description
return null; return null;
} }
private ActionResult<Product> ReturnsActionResultOfProduct() => null;
private ActionResult<IEnumerable<Product>> ReturnsActionResultOfSequenceOfProducts() => null;
private Task<ActionResult<Product>> ReturnsTaskOfActionResultOfProduct() => null;
private Task<ActionResult<IEnumerable<Product>>> ReturnsTaskOfActionResultOfSequenceOfProducts() => null;
private void AcceptsProduct(Product product) private void AcceptsProduct(Product product)
{ {
} }

View File

@ -478,6 +478,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[Theory] [Theory]
[InlineData("GetProduct", "ApiExplorerWebSite.Product")] [InlineData("GetProduct", "ApiExplorerWebSite.Product")]
[InlineData("GetActionResultProduct", "ApiExplorerWebSite.Product")]
[InlineData("GetInt", "System.Int32")] [InlineData("GetInt", "System.Int32")]
[InlineData("GetTaskOfProduct", "ApiExplorerWebSite.Product")] [InlineData("GetTaskOfProduct", "ApiExplorerWebSite.Product")]
[InlineData("GetTaskOfInt", "System.Int32")] [InlineData("GetTaskOfInt", "System.Int32")]

View File

@ -38,6 +38,9 @@ namespace ApiExplorerWebSite
return null; return null;
} }
[HttpGet]
public ActionResult<Product> GetActionResultProduct() => null;
[HttpGet] [HttpGet]
public int GetInt() public int GetInt()
{ {