From eb248c4a987ada4b632f2780c582498e1a65a5e0 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 29 Oct 2018 12:36:40 -0700 Subject: [PATCH] Cleanup InferParameterBindingInfoConvention * Infer BindingSource for collection parameters as Body. Fixes https://github.com/aspnet/Mvc/issues/8536 * Introduce a compat switch to keep 2.1.x LTS behavior for collection parameters * Do not infer BinderModelName in InferParameterBindingInfoConvention --- .../ApiBehaviorOptions.cs | 36 +++ .../ApiBehaviorApplicationModelProvider.cs | 22 +- .../InferParameterBindingInfoConvention.cs | 108 ++----- .../Internal/ApiBehaviorOptionsSetup.cs | 1 + ...ApiBehaviorApplicationModelProviderTest.cs | 14 +- ...InferParameterBindingInfoConventionTest.cs | 277 ++++++------------ .../CompatibilitySwitchIntegrationTest.cs | 4 + 7 files changed, 169 insertions(+), 293 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs index b2b2c2cd6f..54265a0f4d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs @@ -17,6 +17,7 @@ public class ApiBehaviorOptions : IEnumerable { private readonly CompatibilitySwitch _suppressMapClientErrors; private readonly CompatibilitySwitch _suppressUseValidationProblemDetailsForInvalidModelStateResponses; + private readonly CompatibilitySwitch _allowInferringBindingSourceForCollectionTypesAsFromQuery; private readonly ICompatibilitySwitch[] _switches; private Func _invalidModelStateResponseFactory; @@ -28,10 +29,12 @@ public ApiBehaviorOptions() { _suppressMapClientErrors = new CompatibilitySwitch(nameof(SuppressMapClientErrors)); _suppressUseValidationProblemDetailsForInvalidModelStateResponses = new CompatibilitySwitch(nameof(SuppressUseValidationProblemDetailsForInvalidModelStateResponses)); + _allowInferringBindingSourceForCollectionTypesAsFromQuery = new CompatibilitySwitch(nameof(AllowInferringBindingSourceForCollectionTypesAsFromQuery)); _switches = new[] { _suppressMapClientErrors, _suppressUseValidationProblemDetailsForInvalidModelStateResponses, + _allowInferringBindingSourceForCollectionTypesAsFromQuery }; } @@ -151,6 +154,39 @@ public bool SuppressUseValidationProblemDetailsForInvalidModelStateResponses set => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value = value; } + /// + /// Gets or sets a value that determines if for collection types + /// () is inferred as . + /// + /// + /// The default value is if the version is + /// or later; otherwise. + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take + /// precedence over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to or + /// lower then this setting will have the value unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value unless explicitly configured. + /// + /// + public bool AllowInferringBindingSourceForCollectionTypesAsFromQuery + { + get => _allowInferringBindingSourceForCollectionTypesAsFromQuery.Value; + set => _allowInferringBindingSourceForCollectionTypesAsFromQuery.Value = value; + } + /// /// Gets a map of HTTP status codes to . Configured values /// are used to transform to an diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs index 62831919de..35a415297b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs @@ -48,14 +48,15 @@ internal class ApiBehaviorApplicationModelProvider : IApplicationModelProvider var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType); ActionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute)); - var inferParameterBindingInfoConvention = new InferParameterBindingInfoConvention(modelMetadataProvider) + if (!options.SuppressInferBindingSourcesForParameters) { - SuppressInferBindingSourcesForParameters = options.SuppressInferBindingSourcesForParameters - }; - ControllerModelConventions = new List - { - inferParameterBindingInfoConvention, - }; + var convention = new InferParameterBindingInfoConvention(modelMetadataProvider) + { + AllowInferringBindingSourceForCollectionTypesAsFromQuery = options.AllowInferringBindingSourceForCollectionTypesAsFromQuery, + }; + + ActionModelConventions.Add(convention); + } } /// @@ -66,8 +67,6 @@ internal class ApiBehaviorApplicationModelProvider : IApplicationModelProvider public List ActionModelConventions { get; } - public List ControllerModelConventions { get; } - public void OnProvidersExecuted(ApplicationModelProviderContext context) { } @@ -91,11 +90,6 @@ public void OnProvidersExecuting(ApplicationModelProviderContext context) convention.Apply(action); } } - - foreach (var convention in ControllerModelConventions) - { - convention.Apply(controller); - } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs index 15721bd2fa..0d855bf081 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs @@ -2,9 +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.Linq; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing.Template; @@ -13,13 +11,18 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels { /// - /// A that - /// - /// infers binding sources for parameters - /// for bound properties and parameters. - /// + /// An that infers for parameters. /// - public class InferParameterBindingInfoConvention : IControllerModelConvention + /// + /// The goal of this covention is to make intuitive and easy to document inferences. The rules are: + /// + /// A previously specified is never overwritten. + /// A complex type parameter () is assigned. + /// If the parameter name appears in ANY route template, is assigned. + /// All other parameters, is assigned. + /// + /// + public class InferParameterBindingInfoConvention : IActionModelConvention { private readonly IModelMetadataProvider _modelMetadataProvider; @@ -29,41 +32,27 @@ public class InferParameterBindingInfoConvention : IControllerModelConvention _modelMetadataProvider = modelMetadataProvider ?? throw new ArgumentNullException(nameof(modelMetadataProvider)); } - /// - /// Gets or sets a value that determines if model binding sources are inferred for action parameters on controllers is suppressed. - /// - public bool SuppressInferBindingSourcesForParameters { get; set; } + internal bool AllowInferringBindingSourceForCollectionTypesAsFromQuery { get; set; } - protected virtual bool ShouldApply(ControllerModel controller) => true; + protected virtual bool ShouldApply(ActionModel action) => true; - public void Apply(ControllerModel controller) + public void Apply(ActionModel action) { - if (controller == null) + if (action == null) { - throw new ArgumentNullException(nameof(controller)); + throw new ArgumentNullException(nameof(action)); } - if (!ShouldApply(controller)) + if (!ShouldApply(action)) { return; } - InferBoundPropertyModelPrefixes(controller); - - foreach (var action in controller.Actions) - { - InferParameterBindingSources(action); - InferParameterModelPrefixes(action); - } + InferParameterBindingSources(action); } internal void InferParameterBindingSources(ActionModel action) { - if (SuppressInferBindingSourcesForParameters) - { - return; - } - for (var i = 0; i < action.Parameters.Count; i++) { var parameter = action.Parameters[i]; @@ -95,56 +84,17 @@ internal void InferParameterBindingSources(ActionModel action) // Internal for unit testing. internal BindingSource InferBindingSourceForParameter(ParameterModel parameter) { - if (ParameterExistsInAnyRoute(parameter.Action, parameter.ParameterName)) + if (IsComplexTypeParameter(parameter)) { - return BindingSource.Path; + return BindingSource.Body; } - var bindingSource = IsComplexTypeParameter(parameter) ? - BindingSource.Body : - BindingSource.Query; - - return bindingSource; - } - - // For any complex types that are bound from value providers, set the prefix - // to the empty prefix by default. This makes binding much more predictable - // and describable via ApiExplorer - internal void InferBoundPropertyModelPrefixes(ControllerModel controllerModel) - { - foreach (var property in controllerModel.ControllerProperties) + if (ParameterExistsInAnyRoute(parameter.Action, parameter.ParameterName)) { - if (property.BindingInfo != null && - property.BindingInfo.BinderModelName == null && - property.BindingInfo.BindingSource != null && - !property.BindingInfo.BindingSource.IsGreedy && - !IsFormFile(property.ParameterType)) - { - var metadata = _modelMetadataProvider.GetMetadataForProperty( - controllerModel.ControllerType, - property.PropertyInfo.Name); - if (metadata.IsComplexType && !metadata.IsCollectionType) - { - property.BindingInfo.BinderModelName = string.Empty; - } - } + return BindingSource.Path; } - } - internal void InferParameterModelPrefixes(ActionModel action) - { - foreach (var parameter in action.Parameters) - { - var bindingInfo = parameter.BindingInfo; - if (bindingInfo?.BindingSource != null && - bindingInfo.BinderModelName == null && - !bindingInfo.BindingSource.IsGreedy && - !IsFormFile(parameter.ParameterType) && - IsComplexTypeParameter(parameter)) - { - parameter.BindingInfo.BinderModelName = string.Empty; - } - } + return BindingSource.Query; } private bool ParameterExistsInAnyRoute(ActionModel action, string parameterName) @@ -171,13 +121,13 @@ private bool IsComplexTypeParameter(ParameterModel parameter) // No need for information from attributes on the parameter. Just use its type. var metadata = _modelMetadataProvider .GetMetadataForType(parameter.ParameterInfo.ParameterType); - return metadata.IsComplexType && !metadata.IsCollectionType; - } - private static bool IsFormFile(Type parameterType) - { - return typeof(IFormFile).IsAssignableFrom(parameterType) || - typeof(IEnumerable).IsAssignableFrom(parameterType); + if (AllowInferringBindingSourceForCollectionTypesAsFromQuery && metadata.IsCollectionType) + { + return false; + } + + return metadata.IsComplexType; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs index bddaaa2478..02ae3bc30d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs @@ -35,6 +35,7 @@ public class ApiBehaviorOptionsSetup : { dictionary[nameof(ApiBehaviorOptions.SuppressMapClientErrors)] = true; dictionary[nameof(ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses)] = true; + dictionary[nameof(ApiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery)] = true; } return dictionary; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs index 1d00e52dc2..14d2352ea8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs @@ -98,15 +98,8 @@ public void Constructor_SetsUpConventions() { var convention = Assert.IsType(c); Assert.Equal(typeof(ProblemDetails), convention.DefaultErrorResponseType.Type); - }); - - Assert.Collection( - provider.ControllerModelConventions, - c => - { - var convention = Assert.IsType(c); - Assert.False(convention.SuppressInferBindingSourcesForParameters); - }); + }, + c => Assert.IsType(c)); } [Fact] @@ -146,8 +139,7 @@ public void Constructor_SetsSuppressInferBindingSourcesForParametersIsSet() var provider = GetProvider(new ApiBehaviorOptions { SuppressInferBindingSourcesForParameters = true }); // Act & Assert - var convention = Assert.Single(provider.ControllerModelConventions.OfType()); - Assert.True(convention.SuppressInferBindingSourcesForParameters); + Assert.Empty(provider.ActionModelConventions.OfType()); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs index 5f27a66df8..8b82f3d6ad 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs @@ -28,7 +28,7 @@ public void Apply_DoesNotInferBindingSourceForParametersWithBindingInfo() var action = GetActionModel(typeof(ParameterWithBindingInfo), actionName); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameterModel = Assert.Single(action.Parameters); @@ -126,38 +126,6 @@ public void InferParameterBindingSources_InfersSources() }); } - [Fact] - public void InferParameterBindingSources_DoesNotInferSources_IfSuppressInferBindingSourcesForParametersIsSet() - { - // Arrange - var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken); - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var convention = GetConvention(modelMetadataProvider); - var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); - - convention.SuppressInferBindingSourcesForParameters = true; - - // Act - convention.InferParameterBindingSources(action); - - // Assert - Assert.Collection( - action.Parameters, - parameter => - { - Assert.Equal("model", parameter.Name); - Assert.Null(parameter.BindingInfo); - }, - parameter => - { - Assert.Equal("cancellationToken", parameter.Name); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Equal(BindingSource.Special, bindingInfo.BindingSource); - }); - } - [Fact] public void Apply_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinder_AndExplicitName() { @@ -168,7 +136,7 @@ public void Apply_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinder var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -189,7 +157,7 @@ public void Apply_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinder var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -210,7 +178,7 @@ public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_Parameter var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -267,10 +235,10 @@ public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRo } [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInAbsoluteRoute() + public void InferBindingSourceForParameter_ReturnsBody_ForComplexTypeParameterThatAppearsInRoute() { // Arrange - var actionName = nameof(ParameterBindingController.AbsoluteRoute); + var actionName = nameof(ParameterBindingController.ComplexTypeInRoute); var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); var convention = GetConvention(); @@ -278,7 +246,7 @@ public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInAb var result = convention.InferBindingSourceForParameter(parameter); // Assert - Assert.Same(BindingSource.Path, result); + Assert.Same(BindingSource.Body, result); } [Fact] @@ -472,7 +440,7 @@ public void InferParameterBindingSources_SetsCorrectBindingSourceForComplexTypes } [Fact] - public void InferBindingSourceForParameter_ReturnsBodyForSimpleTypes() + public void InferBindingSourceForParameter_ReturnsQueryForSimpleTypes() { // Arrange var actionName = nameof(ParameterBindingController.SimpleTypeModel); @@ -486,6 +454,68 @@ public void InferBindingSourceForParameter_ReturnsBodyForSimpleTypes() Assert.Same(BindingSource.Query, result); } + [Fact] + public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfSimpleTypes() + { + // Arrange + var actionName = nameof(ParameterBindingController.CollectionOfSimpleTypes); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Body, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsQueryForCollectionOfSimpleTypes_WhenAllowInferringBindingSourceForCollectionTypesAsFromQueryIsSet() + { + // Arrange + var actionName = nameof(ParameterBindingController.CollectionOfSimpleTypes); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + convention.AllowInferringBindingSourceForCollectionTypesAsFromQuery = true; + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Query, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfComplexTypes() + { + // Arrange + var actionName = nameof(ParameterBindingController.CollectionOfComplexTypes); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Body, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsQueryForCollectionOfComplexTypes_WhenAllowInferringBindingSourceForCollectionTypesAsFromQueryIsSet() + { + // Arrange + var actionName = nameof(ParameterBindingController.CollectionOfComplexTypes); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + convention.AllowInferringBindingSourceForCollectionTypesAsFromQuery = true; + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Query, result); + } + [Fact] public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName() { @@ -496,7 +526,7 @@ public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultNam var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -517,7 +547,7 @@ public void PreservesBindingSourceInference_ForFromQueryParameter_WithCustomName var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -538,7 +568,7 @@ public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_W var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -558,7 +588,7 @@ public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_W var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -579,7 +609,7 @@ public void PreservesBindingSourceInference_ForFromQueryParameterOnCollectionTyp var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -600,7 +630,7 @@ public void PreservesBindingSourceInference_ForFromQueryOnArrayType() var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -621,7 +651,7 @@ public void PreservesBindingSourceInference_FromQueryOnArrayTypeWithCustomName() var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -642,7 +672,7 @@ public void PreservesBindingSourceInference_ForFromRouteParameter_WithDefaultNam var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -663,7 +693,7 @@ public void PreservesBindingSourceInference_ForFromRouteParameter_WithCustomName var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -684,7 +714,7 @@ public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_W var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -704,7 +734,7 @@ public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_W var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -727,7 +757,7 @@ public void PreservesBindingSourceInference_ForParameterWithRequestPredicateAndP var convention = GetConvention(); // Act - convention.Apply(action.Controller); + convention.Apply(action); // Assert var parameter = Assert.Single(action.Parameters); @@ -740,132 +770,6 @@ public void PreservesBindingSourceInference_ForParameterWithRequestPredicateAndP Assert.Null(bindingInfo.BinderModelName); } - [Fact] - public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() - { - // Arrange - var controller = GetControllerModel(typeof(ControllerWithBoundProperty)); - var convention = GetConvention(); - - // Act - convention.InferBoundPropertyModelPrefixes(controller); - - // Assert - var property = Assert.Single(controller.ControllerProperties, p => p.Name == nameof(ControllerWithBoundProperty.TestProperty)); - Assert.Equal(string.Empty, property.BindingInfo.BinderModelName); - } - - [Fact] - public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForCollectionTypeFromValueProvider() - { - // Arrange - var controller = GetControllerModel(typeof(ControllerWithBoundCollectionProperty)); - var convention = GetConvention(); - - // Act - convention.InferBoundPropertyModelPrefixes(controller); - - // Assert - var property = Assert.Single(controller.ControllerProperties); - Assert.Null(property.BindingInfo.BinderModelName); - } - - [Fact] - public void InferParameterModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() - { - // Arrange - var action = GetActionModel(typeof(ControllerWithBoundProperty), nameof(ControllerWithBoundProperty.SomeAction)); - var convention = GetConvention(); - - // Act - convention.InferParameterModelPrefixes(action); - - // Assert - var parameter = Assert.Single(action.Parameters); - Assert.Equal(string.Empty, parameter.BindingInfo.BinderModelName); - } - - [Fact] - public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileParametersAnnotatedWithFromForm() - { - // Arrange - var action = GetActionModel( - typeof(ParameterBindingController), - nameof(ParameterBindingController.FromFormFormFileParameters), - TestModelMetadataProvider.CreateDefaultProvider()); - var convention = GetConvention(); - - // Act - convention.InferParameterModelPrefixes(action); - - // Assert - Assert.Collection( - action.Parameters, - parameter => - { - Assert.Equal("p1", parameter.Name); - Assert.Null(parameter.BindingInfo.BinderModelName); - }, - parameter => - { - Assert.Equal("p2", parameter.Name); - Assert.Null(parameter.BindingInfo.BinderModelName); - }, - parameter => - { - Assert.Equal("p3", parameter.Name); - Assert.Null(parameter.BindingInfo.BinderModelName); - }); - } - - [Fact] - public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileParameters() - { - // Arrange - var action = GetActionModel( - typeof(ParameterBindingController), - nameof(ParameterBindingController.FormFileParameters), - TestModelMetadataProvider.CreateDefaultProvider()); - var convention = GetConvention(); - - // Act - convention.InferParameterModelPrefixes(action); - - // Assert - Assert.Collection( - action.Parameters, - parameter => - { - Assert.Equal("p1", parameter.Name); - Assert.Null(parameter.BindingInfo.BinderModelName); - }, - parameter => - { - Assert.Equal("p2", parameter.Name); - Assert.Null(parameter.BindingInfo.BinderModelName); - }, - parameter => - { - Assert.Equal("p3", parameter.Name); - Assert.Null(parameter.BindingInfo.BinderModelName); - }); - } - - [Fact] - public void InferBoundPropertyModelPrefixes_DoesNotSetModelPrefix_ForFormFileCollectionPropertiesAnnotatedWithFromForm() - { - // Arrange - var controller = GetControllerModel(typeof(ControllerWithBoundProperty)); - var convention = GetConvention(); - - // Act - convention.InferBoundPropertyModelPrefixes(controller); - - // Assert - var parameter = Assert.Single(controller.ControllerProperties, p => p.Name == nameof(ControllerWithBoundProperty.Files)); - Assert.Null(parameter.BindingInfo.BinderModelName); - } - private static InferParameterBindingInfoConvention GetConvention( IModelMetadataProvider modelMetadataProvider = null) { @@ -887,14 +791,6 @@ public void InferBoundPropertyModelPrefixes_DoesNotSetModelPrefix_ForFormFileCol return context; } - private static ControllerModel GetControllerModel( - Type controllerType, - IModelMetadataProvider modelMetadataProvider = null) - { - var context = GetContext(controllerType, modelMetadataProvider); - return Assert.Single(context.Result.Controllers); - } - private static ActionModel GetActionModel( Type controllerType, string actionName, @@ -930,10 +826,10 @@ private class ParameterBindingController public IActionResult OptionalRouteToken(int id) => null; [HttpDelete("delete-by-status/{status:int?}")] - public IActionResult ConstrainedRouteToken(object status) => null; + public IActionResult ConstrainedRouteToken(int status) => null; [HttpPut("/absolute-route/{status:int}")] - public IActionResult AbsoluteRoute(object status) => null; + public IActionResult ComplexTypeInRoute(object status) => null; [HttpPost("multiple/{id}")] [HttpPut("multiple/{id}")] @@ -1011,6 +907,10 @@ private class ParameterBindingController public IActionResult FromFormFormFileParameters([FromForm] IFormFile p1, [FromForm] IFormFile[] p2, [FromForm] IFormFileCollection p3) => null; public IActionResult FormFileParameters(IFormFile p1, IFormFile[] p2, IFormFileCollection p3) => null; + + public IActionResult CollectionOfSimpleTypes(IList parameter) => null; + + public IActionResult CollectionOfComplexTypes(IList parameter) => null; } [ApiController] @@ -1064,7 +964,6 @@ private class ParameterBindingNoRoutesOnController public IActionResult ParameterInMultipleRoutes(int id) => null; } - private class TestModel { } [TypeConverter(typeof(ConvertibleFromStringConverter))] diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index e4020042f3..d16e2b85d9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -57,6 +57,7 @@ public void CompatibilitySwitches_Version_2_0() Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); + Assert.True(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery); } [Fact] @@ -95,6 +96,7 @@ public void CompatibilitySwitches_Version_2_1() Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); + Assert.True(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery); } [Fact] @@ -133,6 +135,7 @@ public void CompatibilitySwitches_Version_2_2() Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); + Assert.False(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery); } [Fact] @@ -171,6 +174,7 @@ public void CompatibilitySwitches_Version_Latest() Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); + Assert.False(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery); } // This just does the minimum needed to be able to resolve these options.