From 63397653fa11fb43221f9149643026f03e0bd8cb Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 8 Sep 2017 11:28:34 -0700 Subject: [PATCH] Make DefaultApiDescriptionProvider understand ActionResult Fixes #6784 --- .../DefaultApiDescriptionProvider.cs | 22 +++++----- .../DefaultApiDescriptionProviderTest.cs | 44 +++++++++++++++++++ .../ApiExplorerTest.cs | 1 + ...rResponseTypeWithoutAttributeController.cs | 3 ++ 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index 50a1bd04d1..202d63b07e 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -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. 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 or Task>. + 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) diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 5fee3d347d..5cbf480a3c 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -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), 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 ReturnsActionResultOfProduct() => null; + + private ActionResult> ReturnsActionResultOfSequenceOfProducts() => null; + + private Task> ReturnsTaskOfActionResultOfProduct() => null; + + private Task>> ReturnsTaskOfActionResultOfSequenceOfProducts() => null; + private void AcceptsProduct(Product product) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index c2363f7399..936a56fd89 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -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")] diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs index dd11097d18..0dd2a8f9d9 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs @@ -38,6 +38,9 @@ namespace ApiExplorerWebSite return null; } + [HttpGet] + public ActionResult GetActionResultProduct() => null; + [HttpGet] public int GetInt() {