diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index 9d86de29e5..1661eefc03 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var metadata = _modelMetadataProvider.GetMetadataForProperty( controllerModel.ControllerType, property.PropertyInfo.Name); - if (metadata.IsComplexType) + if (metadata.IsComplexType && !metadata.IsCollectionType) { property.BindingInfo.BinderModelName = string.Empty; } @@ -254,9 +254,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal private bool IsComplexTypeParameter(ParameterModel parameter) { // No need for information from attributes on the parameter. Just use its type. - return _modelMetadataProvider - .GetMetadataForType(parameter.ParameterInfo.ParameterType) - .IsComplexType; + var metadata = _modelMetadataProvider + .GetMetadataForType(parameter.ParameterInfo.ParameterType); + return metadata.IsComplexType && !metadata.IsCollectionType; } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index a90dbc2943..5b4b210d1a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; @@ -619,6 +620,75 @@ Environment.NewLine + "int b"; Assert.Equal("gps", bindingInfo.BinderModelName); } + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameterOnCollectionType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnCollectionType); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryOnArrayType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnArrayType); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_FromQueryOnArrayTypeWithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnArrayTypeWithCustomName); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("ids", bindingInfo.BinderModelName); + } + [Fact] public void PreservesBindingSourceInference_ForFromRouteParameter_WithDefaultName() { @@ -794,6 +864,22 @@ Environment.NewLine + "int b"; Assert.Equal(string.Empty, property.BindingInfo.BinderModelName); } + [Fact] + public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForCollectionTypeFromValueProvider() + { + // Arrange + var controller = GetControllerModel(typeof(ControllerWithBoundCollectionProperty)); + + var provider = GetProvider(); + + // Act + provider.InferBoundPropertyModelPrefixes(controller); + + // Assert + var property = Assert.Single(controller.ControllerProperties); + Assert.Null(property.BindingInfo.BinderModelName); + } + [Fact] public void InferParameterModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() { @@ -1008,7 +1094,7 @@ Environment.NewLine + "int b"; [HttpGet("parameter-with-model-binder-attribute")] public IActionResult ModelBinderAttribute([ModelBinder(Name = "top")] int value) => null; - + [HttpGet("parameter-with-fromquery")] public IActionResult FromQuery([FromQuery] int value) => null; @@ -1021,6 +1107,15 @@ Environment.NewLine + "int b"; [HttpGet("parameter-with-fromquery-on-complextype-and-customname")] public IActionResult FromQueryOnComplexTypeWithCustomName([FromQuery(Name = "gps")] GpsCoordinates gpsCoordinates) => null; + [HttpGet("parameter-with-fromquery-on-collection-type")] + public IActionResult FromQueryOnCollectionType([FromQuery] ICollection value) => null; + + [HttpGet("parameter-with-fromquery-on-array-type")] + public IActionResult FromQueryOnArrayType([FromQuery] int[] value) => null; + + [HttpGet("parameter-with-fromquery-on-array-type-customname")] + public IActionResult FromQueryOnArrayTypeWithCustomName([FromQuery(Name = "ids")] int[] value) => null; + [HttpGet("parameter-with-fromroute")] public IActionResult FromRoute([FromRoute] int value) => null; @@ -1118,6 +1213,15 @@ Environment.NewLine + "int b"; public IActionResult SomeAction([FromQuery] TestModel test) => null; } + [ApiController] + private class ControllerWithBoundCollectionProperty + { + [FromQuery] + public List TestProperty { get; set; } + + public IActionResult SomeAction([FromQuery] List test) => null; + } + private class Car { } [ApiController]