diff --git a/src/Mvc/Mvc.ApiExplorer/src/ApiResponseTypeProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/ApiResponseTypeProvider.cs index 0761a7950a..bd5b4d0f3a 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/ApiResponseTypeProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/ApiResponseTypeProvider.cs @@ -214,7 +214,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { var declaredReturnType = action.MethodInfo.ReturnType; if (declaredReturnType == typeof(void) || - declaredReturnType == typeof(Task)) + declaredReturnType == typeof(Task) || + declaredReturnType == typeof(ValueTask)) { return typeof(void); } @@ -222,7 +223,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer // Unwrap the type if it's a Task. The Task (non-generic) case was already handled. var unwrappedType = declaredReturnType; if (declaredReturnType.IsGenericType && - declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>)) + (declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>) || declaredReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))) { unwrappedType = declaredReturnType.GetGenericArguments()[0]; } diff --git a/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs b/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs index 3d6d97d7a2..121b21d47a 100644 --- a/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs +++ b/src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs @@ -472,6 +472,22 @@ namespace Microsoft.AspNetCore.Mvc.Description Assert.NotNull(responseType.ModelMetadata); } + [Fact] + public void GetApiDescription_PopulatesResponseType_WithValueTaskOfProduct() + { + // Arrange + var action = CreateActionDescriptor(nameof(ReturnsValueTaskOfProduct)); + + // 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(ReturnsObject))] [InlineData(nameof(ReturnsActionResult))] @@ -479,6 +495,9 @@ namespace Microsoft.AspNetCore.Mvc.Description [InlineData(nameof(ReturnsTaskOfObject))] [InlineData(nameof(ReturnsTaskOfActionResult))] [InlineData(nameof(ReturnsTaskOfJsonResult))] + [InlineData(nameof(ReturnsValueTaskOfObject))] + [InlineData(nameof(ReturnsValueTaskOfActionResult))] + [InlineData(nameof(ReturnsValueTaskOfJsonResult))] public void GetApiDescription_DoesNotPopulatesResponseInformation_WhenUnknown(string methodName) { // Arrange @@ -521,7 +540,7 @@ namespace Microsoft.AspNetCore.Mvc.Description }, { typeof(DefaultApiDescriptionProviderTest), - nameof(DefaultApiDescriptionProviderTest.ReturnsActionResult), + nameof(DefaultApiDescriptionProviderTest.ReturnsValueTaskOfActionResult), filterDescriptors }, { @@ -624,6 +643,11 @@ namespace Microsoft.AspNetCore.Mvc.Description nameof(DefaultApiDescriptionProviderTest.ReturnsTask), filterDescriptors }, + { + typeof(DefaultApiDescriptionProviderTest), + nameof(DefaultApiDescriptionProviderTest.ReturnsValueTask), + filterDescriptors + }, { typeof(DerivedProducesController), nameof(DerivedProducesController.ReturnsVoid), @@ -634,6 +658,11 @@ namespace Microsoft.AspNetCore.Mvc.Description nameof(DerivedProducesController.ReturnsTask), filterDescriptors }, + { + typeof(DerivedProducesController), + nameof(DerivedProducesController.ReturnsValueTask), + filterDescriptors + }, }; } } @@ -895,6 +924,7 @@ namespace Microsoft.AspNetCore.Mvc.Description [Theory] [InlineData(nameof(ReturnsVoid))] [InlineData(nameof(ReturnsTask))] + [InlineData(nameof(ReturnsValueTask))] public void GetApiDescription_DefaultVoidStatus(string methodName) { // Arrange @@ -914,6 +944,7 @@ namespace Microsoft.AspNetCore.Mvc.Description [Theory] [InlineData(nameof(ReturnsVoid))] [InlineData(nameof(ReturnsTask))] + [InlineData(nameof(ReturnsValueTask))] public void GetApiDescription_VoidWithResponseTypeAttributeStatus(string methodName) { // Arrange @@ -944,6 +975,10 @@ namespace Microsoft.AspNetCore.Mvc.Description [InlineData(nameof(ReturnsTask))] [InlineData(nameof(ReturnsTaskOfActionResult))] [InlineData(nameof(ReturnsTaskOfJsonResult))] + [InlineData(nameof(ReturnsValueTask))] + [InlineData(nameof(ReturnsValueTaskOfObject))] + [InlineData(nameof(ReturnsValueTaskOfActionResult))] + [InlineData(nameof(ReturnsValueTaskOfJsonResult))] public void GetApiDescription_PopulatesResponseInformation_WhenSetByFilter(string methodName) { // Arrange @@ -2026,6 +2061,31 @@ namespace Microsoft.AspNetCore.Mvc.Description return null; } + private ValueTask ReturnsValueTaskOfProduct() + { + return default; + } + + private ValueTask ReturnsValueTaskOfObject() + { + return default; + } + + private ValueTask ReturnsValueTask() + { + return default; + } + + private ValueTask ReturnsValueTaskOfActionResult() + { + return default; + } + + private ValueTask ReturnsValueTaskOfJsonResult() + { + return default; + } + private Product ReturnsProduct() { return null; @@ -2196,6 +2256,11 @@ namespace Microsoft.AspNetCore.Mvc.Description return null; } + public ValueTask ReturnsValueTask() + { + return default; + } + public void ReturnsVoid() { }