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.Custom)
{
ApiParameterRouteInfo routeInfo;
if (routeParameters.TryGetValue(parameter.Name, out routeInfo))
if (routeParameters.TryGetValue(parameter.Name, out var routeInfo))
{
parameter.RouteInfo = routeInfo;
routeParameters.Remove(parameter.Name);
@ -322,8 +321,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{
foreach (var formatter in _inputFormatters)
{
var requestFormatMetadataProvider = formatter as IApiRequestFormatMetadataProvider;
if (requestFormatMetadataProvider != null)
if (formatter is IApiRequestFormatMetadataProvider requestFormatMetadataProvider)
{
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.
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
// isn't valuable to the formatter.
@ -457,13 +458,12 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{
return unwrappedType;
}
}
private static Type GetTaskInnerTypeOrNull(Type type)
{
var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(Task<>));
return genericType?.GenericTypeArguments[0];
Type UnwrapGenericType(Type type, Type queryType)
{
var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, queryType);
return genericType?.GenericTypeArguments[0] ?? type;
}
}
private Type GetRuntimeReturnType(Type declaredReturnType)

View File

@ -394,6 +394,42 @@ namespace Microsoft.AspNetCore.Mvc.Description
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]
public void GetApiDescription_PopulatesResponseType_WithTaskOfProduct()
{
@ -1478,6 +1514,14 @@ namespace Microsoft.AspNetCore.Mvc.Description
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)
{
}

View File

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

View File

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