Merge pull request #8682 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
e4292c236c
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -420,14 +419,16 @@ Hello from /Pages/Shared/";
|
|||
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
|
||||
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
|
||||
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel")
|
||||
{
|
||||
["__RequestVerificationToken"] = token,
|
||||
["ConfirmPassword"] = "",
|
||||
["Password"] = "",
|
||||
["Email"] = ""
|
||||
});
|
||||
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
["__RequestVerificationToken"] = token,
|
||||
["ConfirmPassword"] = "",
|
||||
["Password"] = "",
|
||||
["Email"] = ""
|
||||
})
|
||||
};
|
||||
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
|
||||
|
||||
// Act
|
||||
|
|
@ -443,18 +444,20 @@ Hello from /Pages/Shared/";
|
|||
public async Task PageConventions_CustomizedModelCanWorkWithModelState()
|
||||
{
|
||||
// Arrange
|
||||
var getPage = await Client.GetAsync("/CustomModelTypeModel");
|
||||
var getPage = await Client.GetAsync("/CustomModelTypeModel?Attempts=0");
|
||||
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
|
||||
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel");
|
||||
message.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel?Attempts=3")
|
||||
{
|
||||
["__RequestVerificationToken"] = token,
|
||||
["Email"] = "javi@example.com",
|
||||
["Password"] = "Password.12$",
|
||||
["ConfirmPassword"] = "Password.12$",
|
||||
});
|
||||
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
["__RequestVerificationToken"] = token,
|
||||
["Email"] = "javi@example.com",
|
||||
["Password"] = "Password.12$",
|
||||
["ConfirmPassword"] = "Password.12$",
|
||||
})
|
||||
};
|
||||
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
|
||||
|
||||
// Act
|
||||
|
|
@ -465,6 +468,37 @@ Hello from /Pages/Shared/";
|
|||
Assert.Equal("/", response.Headers.Location.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PageConventions_CustomizedModelCanWorkWithModelState_EnforcesBindRequired()
|
||||
{
|
||||
// Arrange
|
||||
var getPage = await Client.GetAsync("/CustomModelTypeModel?Attempts=0");
|
||||
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(await getPage.Content.ReadAsStringAsync(), "");
|
||||
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(getPage);
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "/CustomModelTypeModel")
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
["__RequestVerificationToken"] = token,
|
||||
["Email"] = "javi@example.com",
|
||||
["Password"] = "Password.12$",
|
||||
["ConfirmPassword"] = "Password.12$",
|
||||
})
|
||||
};
|
||||
message.Headers.TryAddWithoutValidation("Cookie", $"{cookie.Key}={cookie.Value}");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains(
|
||||
"A value for the 'Attempts' parameter or property was not provided.",
|
||||
responseText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidationAttributes_OnTopLevelProperties()
|
||||
{
|
||||
|
|
@ -642,10 +676,12 @@ Hello from /Pages/Shared/";
|
|||
|
||||
var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(response);
|
||||
|
||||
var content = new MultipartFormDataContent();
|
||||
content.Add(new StringContent("property1-value"), property1);
|
||||
content.Add(new StringContent("test-value1"), file1, "test1.txt");
|
||||
content.Add(new StringContent("test-value2"), file3, "test2.txt");
|
||||
var content = new MultipartFormDataContent
|
||||
{
|
||||
{ new StringContent("property1-value"), property1 },
|
||||
{ new StringContent("test-value1"), file1, "test1.txt" },
|
||||
{ new StringContent("test-value2"), file3, "test2.txt" }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||
|
|
@ -179,6 +181,74 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, false)]
|
||||
[InlineData(123, true)]
|
||||
public async Task BindModelAsync_WithBindPageProperty_EnforcesBindRequired(int? input, bool isValid)
|
||||
{
|
||||
// Arrange
|
||||
var propertyInfo = typeof(TestPage).GetProperty(nameof(TestPage.BindRequiredProperty));
|
||||
var propertyDescriptor = new PageBoundPropertyDescriptor
|
||||
{
|
||||
BindingInfo = BindingInfo.GetBindingInfo(new[]
|
||||
{
|
||||
new FromQueryAttribute { Name = propertyInfo.Name },
|
||||
}),
|
||||
Name = propertyInfo.Name,
|
||||
ParameterType = propertyInfo.PropertyType,
|
||||
Property = propertyInfo,
|
||||
};
|
||||
|
||||
var typeInfo = typeof(TestPage).GetTypeInfo();
|
||||
var actionDescriptor = new CompiledPageActionDescriptor
|
||||
{
|
||||
BoundProperties = new[] { propertyDescriptor },
|
||||
HandlerTypeInfo = typeInfo,
|
||||
ModelTypeInfo = typeInfo,
|
||||
PageTypeInfo = typeInfo,
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.Method = "POST";
|
||||
if (input.HasValue)
|
||||
{
|
||||
request.QueryString = new QueryString($"?{propertyDescriptor.Name}={input.Value}");
|
||||
}
|
||||
});
|
||||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
|
||||
var modelBinderFactory = ModelBindingTestHelper.GetModelBinderFactory(modelMetadataProvider);
|
||||
var modelMetadata = modelMetadataProvider
|
||||
.GetMetadataForProperty(typeof(TestPage), propertyDescriptor.Name);
|
||||
|
||||
var pageBinder = PageBinderFactory.CreatePropertyBinder(
|
||||
parameterBinder,
|
||||
modelMetadataProvider,
|
||||
modelBinderFactory,
|
||||
actionDescriptor);
|
||||
var pageContext = new PageContext
|
||||
{
|
||||
ActionDescriptor = actionDescriptor,
|
||||
HttpContext = testContext.HttpContext,
|
||||
RouteData = testContext.RouteData,
|
||||
ValueProviderFactories = testContext.ValueProviderFactories,
|
||||
};
|
||||
|
||||
var page = new TestPage();
|
||||
|
||||
// Act
|
||||
await pageBinder(pageContext, page);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(isValid, pageContext.ModelState.IsValid);
|
||||
if (isValid)
|
||||
{
|
||||
Assert.Equal(input.Value, page.BindRequiredProperty);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("RequiredAndStringLengthProp", null, false)]
|
||||
[InlineData("RequiredAndStringLengthProp", "", false)]
|
||||
|
|
@ -231,12 +301,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
}
|
||||
}
|
||||
|
||||
class TestController
|
||||
private class TestController
|
||||
{
|
||||
[BindNever] public string BindNeverProp { get; set; }
|
||||
[BindRequired] public int BindRequiredProp { get; set; }
|
||||
[Required, StringLength(3)] public string RequiredAndStringLengthProp { get; set; }
|
||||
[DisplayName("My Display Name"), StringLength(3)] public string DisplayNameStringLengthProp { get; set; }
|
||||
}
|
||||
|
||||
private class TestPage : PageModel
|
||||
{
|
||||
[BindRequired]
|
||||
public int BindRequiredProperty { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -13,6 +14,10 @@ namespace RazorPagesWebSite
|
|||
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
[BindRequired]
|
||||
[FromQuery(Name = nameof(Attempts))]
|
||||
public int Attempts { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
|
|
@ -69,10 +74,13 @@ namespace RazorPagesWebSite
|
|||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
Attempts++;
|
||||
RouteData.Values.Add(nameof(Attempts), Attempts);
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
return Redirect("~/");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue