Cleanup InferParameterBindingInfoConvention (#8665)

* 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
This commit is contained in:
Pranav K 2018-10-31 12:24:37 -07:00 committed by GitHub
parent c74a945dda
commit f2af66bc31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 300 deletions

View File

@ -349,8 +349,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// Gets a value indicating whether <see cref="ModelType"/> is a complex type.
/// </summary>
/// <remarks>
/// A complex type is defined as a <see cref="Type"/> which has a
/// <see cref="TypeConverter"/> that can convert from <see cref="string"/>.
/// A complex type is defined as a <see cref="Type"/> without a <see cref="TypeConverter"/> that can convert
/// from <see cref="string"/>. Most POCO and <see cref="IEnumerable"/> types are therefore complex. Most, if
/// not all, BCL value types are simple types.
/// </remarks>
public bool IsComplexType { get; private set; }

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc
{
private readonly CompatibilitySwitch<bool> _suppressMapClientErrors;
private readonly CompatibilitySwitch<bool> _suppressUseValidationProblemDetailsForInvalidModelStateResponses;
private readonly CompatibilitySwitch<bool> _allowInferringBindingSourceForCollectionTypesAsFromQuery;
private readonly ICompatibilitySwitch[] _switches;
private Func<ActionContext, IActionResult> _invalidModelStateResponseFactory;
@ -28,10 +29,12 @@ namespace Microsoft.AspNetCore.Mvc
{
_suppressMapClientErrors = new CompatibilitySwitch<bool>(nameof(SuppressMapClientErrors));
_suppressUseValidationProblemDetailsForInvalidModelStateResponses = new CompatibilitySwitch<bool>(nameof(SuppressUseValidationProblemDetailsForInvalidModelStateResponses));
_allowInferringBindingSourceForCollectionTypesAsFromQuery = new CompatibilitySwitch<bool>(nameof(AllowInferringBindingSourceForCollectionTypesAsFromQuery));
_switches = new[]
{
_suppressMapClientErrors,
_suppressUseValidationProblemDetailsForInvalidModelStateResponses,
_allowInferringBindingSourceForCollectionTypesAsFromQuery
};
}
@ -151,6 +154,43 @@ namespace Microsoft.AspNetCore.Mvc
set => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value = value;
}
/// <summary>
/// Gets or sets a value that determines if <see cref="BindingInfo.BindingSource"/> for collection types
/// (<see cref="ModelMetadata.IsCollectionType"/>).
/// <para>
/// When <see langword="true" />, the binding source for collection types is inferred as <see cref="BindingSource.Query"/>.
/// Otherwise <see cref="BindingSource.Body"/> is inferred.
/// </para>
/// </summary>
/// <value>
/// The default value is <see langword="false"/> if the version is
/// <see cref="CompatibilityVersion.Version_2_2"/> or later; <see langword="true"/> otherwise.
/// </value>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired value of the compatibility switch by calling this property's setter takes
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// lower then this setting will have the value <see langword="true"/> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
/// higher then this setting will have the value <see langword="false"/> unless explicitly configured.
/// </para>
/// </remarks>
public bool AllowInferringBindingSourceForCollectionTypesAsFromQuery
{
get => _allowInferringBindingSourceForCollectionTypesAsFromQuery.Value;
set => _allowInferringBindingSourceForCollectionTypesAsFromQuery.Value = value;
}
/// <summary>
/// Gets a map of HTTP status codes to <see cref="ClientErrorData"/>. Configured values
/// are used to transform <see cref="IClientErrorActionResult"/> to an <see cref="ObjectResult"/>

View File

@ -48,14 +48,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType);
ActionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute));
var inferParameterBindingInfoConvention = new InferParameterBindingInfoConvention(modelMetadataProvider)
if (!options.SuppressInferBindingSourcesForParameters)
{
SuppressInferBindingSourcesForParameters = options.SuppressInferBindingSourcesForParameters
};
ControllerModelConventions = new List<IControllerModelConvention>
{
inferParameterBindingInfoConvention,
};
var convention = new InferParameterBindingInfoConvention(modelMetadataProvider)
{
AllowInferringBindingSourceForCollectionTypesAsFromQuery = options.AllowInferringBindingSourceForCollectionTypesAsFromQuery,
};
ActionModelConventions.Add(convention);
}
}
/// <remarks>
@ -66,8 +67,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public List<IActionModelConvention> ActionModelConventions { get; }
public List<IControllerModelConvention> ControllerModelConventions { get; }
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
}
@ -91,11 +90,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
convention.Apply(action);
}
}
foreach (var convention in ControllerModelConventions)
{
convention.Apply(controller);
}
}
}

View File

@ -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 @@ using Resources = Microsoft.AspNetCore.Mvc.Core.Resources;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// A <see cref="IControllerModelConvention"/> that
/// <list type="bullet">
/// <item>infers binding sources for parameters</item>
/// <item><see cref="BindingInfo.BinderModelName"/> for bound properties and parameters.</item>
/// </list>
/// An <see cref="IActionModelConvention"/> that infers <see cref="BindingInfo.BindingSource"/> for parameters.
/// </summary>
public class InferParameterBindingInfoConvention : IControllerModelConvention
/// <remarks>
/// The goal of this covention is to make intuitive and easy to document <see cref="BindingSource"/> inferences. The rules are:
/// <list type="number">
/// <item>A previously specified <see cref="BindingInfo.BindingSource" /> is never overwritten.</item>
/// <item>A complex type parameter (<see cref="ModelMetadata.IsComplexType"/>) is assigned <see cref="BindingSource.Body"/>.</item>
/// <item>Parameter with a name that appears as a route value in ANY route template is assigned <see cref="BindingSource.Path"/>.</item>
/// <item>All other parameters are <see cref="BindingSource.Query"/>.</item>
/// </list>
/// </remarks>
public class InferParameterBindingInfoConvention : IActionModelConvention
{
private readonly IModelMetadataProvider _modelMetadataProvider;
@ -29,41 +32,27 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
_modelMetadataProvider = modelMetadataProvider ?? throw new ArgumentNullException(nameof(modelMetadataProvider));
}
/// <summary>
/// Gets or sets a value that determines if model binding sources are inferred for action parameters on controllers is suppressed.
/// </summary>
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 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Internal for unit testing.
internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
{
if (IsComplexTypeParameter(parameter))
{
return BindingSource.Body;
}
if (ParameterExistsInAnyRoute(parameter.Action, parameter.ParameterName))
{
return BindingSource.Path;
}
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 (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;
}
}
}
}
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 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// 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<IFormFile>).IsAssignableFrom(parameterType);
if (AllowInferringBindingSourceForCollectionTypesAsFromQuery && metadata.IsCollectionType)
{
return false;
}
return metadata.IsComplexType;
}
}
}

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
dictionary[nameof(ApiBehaviorOptions.SuppressMapClientErrors)] = true;
dictionary[nameof(ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses)] = true;
dictionary[nameof(ApiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery)] = true;
}
return dictionary;

View File

@ -98,15 +98,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
var convention = Assert.IsType<ApiConventionApplicationModelConvention>(c);
Assert.Equal(typeof(ProblemDetails), convention.DefaultErrorResponseType.Type);
});
Assert.Collection(
provider.ControllerModelConventions,
c =>
{
var convention = Assert.IsType<InferParameterBindingInfoConvention>(c);
Assert.False(convention.SuppressInferBindingSourcesForParameters);
});
},
c => Assert.IsType<InferParameterBindingInfoConvention>(c));
}
[Fact]
@ -140,14 +133,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
}
[Fact]
public void Constructor_SetsSuppressInferBindingSourcesForParametersIsSet()
public void Constructor_DoesNotAddInferParameterBindingInfoConvention_IfSuppressInferBindingSourcesForParametersIsSet()
{
// Arrange
var provider = GetProvider(new ApiBehaviorOptions { SuppressInferBindingSourcesForParameters = true });
// Act & Assert
var convention = Assert.Single(provider.ControllerModelConventions.OfType<InferParameterBindingInfoConvention>());
Assert.True(convention.SuppressInferBindingSourcesForParameters);
Assert.Empty(provider.ActionModelConventions.OfType<InferParameterBindingInfoConvention>());
}
[Fact]

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
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 @@ Environment.NewLine + "int b";
});
}
[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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
}
[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 @@ Environment.NewLine + "int b";
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
Assert.Same(BindingSource.Body, result);
}
[Fact]
@ -472,7 +440,7 @@ Environment.NewLine + "int b";
}
[Fact]
public void InferBindingSourceForParameter_ReturnsBodyForSimpleTypes()
public void InferBindingSourceForParameter_ReturnsQueryForSimpleTypes()
{
// Arrange
var actionName = nameof(ParameterBindingController.SimpleTypeModel);
@ -486,6 +454,68 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
var convention = GetConvention();
// Act
convention.Apply(action.Controller);
convention.Apply(action);
// Assert
var parameter = Assert.Single(action.Parameters);
@ -740,132 +770,6 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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 @@ Environment.NewLine + "int b";
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<int> parameter) => null;
public IActionResult CollectionOfComplexTypes(IList<TestModel> parameter) => null;
}
[ApiController]
@ -1064,7 +964,6 @@ Environment.NewLine + "int b";
public IActionResult ParameterInMultipleRoutes(int id) => null;
}
private class TestModel { }
[TypeConverter(typeof(ConvertibleFromStringConverter))]

View File

@ -227,9 +227,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadAsAsync<Contact>();
Assert.Equal(0, result.ContactId);
Assert.Null(result.Name);
Assert.Null(result.Email);
Assert.Equal(id, result.ContactId);
Assert.Equal(name, result.Name);
Assert.Equal(email, result.Email);
}
[Fact]

View File

@ -57,6 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
Assert.True(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery);
}
[Fact]
@ -95,6 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
Assert.True(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery);
}
[Fact]
@ -133,6 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
Assert.False(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery);
}
[Fact]
@ -171,6 +174,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
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.