Merge pull request #8491 from dotnet-maestro-bot/merge/release/2.2-to-master

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
Doug Bunting 2018-09-21 15:40:49 -07:00 committed by GitHub
commit 90089953d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 843 additions and 126 deletions

View File

@ -14,7 +14,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// Error message the model binding system adds when a property with an associated /// Error message the model binding system adds when a property with an associated
/// <c>BindRequiredAttribute</c> is not bound. /// <c>BindRequiredAttribute</c> is not bound.
/// </summary> /// </summary>
/// <value>Default <see cref="string"/> is "A value for the '{0}' property was not provided.".</value> /// <value>
/// Default <see cref="string"/> is "A value for the '{0}' parameter or property was not provided.".
/// </value>
public virtual Func<string, string> MissingBindRequiredValueAccessor { get; } public virtual Func<string, string> MissingBindRequiredValueAccessor { get; }
/// <summary> /// <summary>

View File

@ -38,11 +38,35 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// The <see cref="IModelBinder"/> for binding <typeparamref name="TElement"/>. /// The <see cref="IModelBinder"/> for binding <typeparamref name="TElement"/>.
/// </param> /// </param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public ArrayModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory) public ArrayModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory)
: base(elementBinder, loggerFactory) : base(elementBinder, loggerFactory)
{ {
} }
/// <summary>
/// Creates a new <see cref="ArrayModelBinder{TElement}"/>.
/// </summary>
/// <param name="elementBinder">
/// The <see cref="IModelBinder"/> for binding <typeparamref name="TElement"/>.
/// </param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="allowValidatingTopLevelNodes">
/// Indication that validation of top-level models is enabled. If <see langword="true"/> and
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/> for a top-level model, the binder
/// adds a <see cref="ModelStateDictionary"/> error when the model is not bound.
/// </param>
public ArrayModelBinder(
IModelBinder elementBinder,
ILoggerFactory loggerFactory,
bool allowValidatingTopLevelNodes)
: base(elementBinder, loggerFactory, allowValidatingTopLevelNodes)
{
}
/// <inheritdoc /> /// <inheritdoc />
public override bool CanCreateInstance(Type targetType) public override bool CanCreateInstance(Type targetType)
{ {

View File

@ -4,6 +4,7 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
@ -27,7 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var binderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType); var binderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType);
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>(); var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
return (IModelBinder)Activator.CreateInstance(
binderType,
elementBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
} }
return null; return null;

View File

@ -44,7 +44,29 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// </summary> /// </summary>
/// <param name="elementBinder">The <see cref="IModelBinder"/> for binding elements.</param> /// <param name="elementBinder">The <see cref="IModelBinder"/> for binding elements.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public CollectionModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory) public CollectionModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory)
: this(elementBinder, loggerFactory, allowValidatingTopLevelNodes: false)
{
}
/// <summary>
/// Creates a new <see cref="CollectionModelBinder{TElement}"/>.
/// </summary>
/// <param name="elementBinder">The <see cref="IModelBinder"/> for binding elements.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="allowValidatingTopLevelNodes">
/// Indication that validation of top-level models is enabled. If <see langword="true"/> and
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/> for a top-level model, the binder
/// adds a <see cref="ModelStateDictionary"/> error when the model is not bound.
/// </param>
public CollectionModelBinder(
IModelBinder elementBinder,
ILoggerFactory loggerFactory,
bool allowValidatingTopLevelNodes)
{ {
if (elementBinder == null) if (elementBinder == null)
{ {
@ -58,8 +80,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
ElementBinder = elementBinder; ElementBinder = elementBinder;
Logger = loggerFactory.CreateLogger(GetType()); Logger = loggerFactory.CreateLogger(GetType());
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
} }
// Internal for testing.
internal bool AllowValidatingTopLevelNodes { get; }
/// <summary> /// <summary>
/// Gets the <see cref="IModelBinder"/> instances for binding collection elements. /// Gets the <see cref="IModelBinder"/> instances for binding collection elements.
/// </summary> /// </summary>
@ -94,6 +120,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
model = CreateEmptyCollection(bindingContext.ModelType); model = CreateEmptyCollection(bindingContext.ModelType);
} }
if (AllowValidatingTopLevelNodes)
{
AddErrorIfBindingRequired(bindingContext);
}
bindingContext.Result = ModelBindingResult.Success(model); bindingContext.Result = ModelBindingResult.Success(model);
} }
@ -161,6 +192,34 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
typeof(ICollection<TElement>).IsAssignableFrom(targetType); typeof(ICollection<TElement>).IsAssignableFrom(targetType);
} }
/// <summary>
/// Add a <see cref="ModelError" /> to <see cref="ModelBindingContext.ModelState" /> if
/// <see cref="ModelMetadata.IsBindingRequired" />.
/// </summary>
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
/// <remarks>
/// <para>
/// This method should be called only when <see cref="MvcOptions.AllowValidatingTopLevelNodes" /> is
/// <see langword="true" /> and a top-level model was not bound.
/// </para>
/// <para>
/// For back-compatibility reasons, <see cref="ModelBindingContext.Result" /> must have
/// <see cref="ModelBindingResult.IsModelSet" /> equal to <see langword="true" /> when a
/// top-level model is not bound. Therefore, ParameterBinder can not detect a
/// <see cref="ModelMetadata.IsBindingRequired" /> failure for collections. Add the error here.
/// </para>
/// </remarks>
protected void AddErrorIfBindingRequired(ModelBindingContext bindingContext)
{
var modelMetadata = bindingContext.ModelMetadata;
if (modelMetadata.IsBindingRequired)
{
var messageProvider = modelMetadata.ModelBindingMessageProvider;
var message = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName);
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
}
}
/// <summary> /// <summary>
/// Create an <see cref="object"/> assignable to <paramref name="targetType"/>. /// Create an <see cref="object"/> assignable to <paramref name="targetType"/>.
/// </summary> /// </summary>

View File

@ -7,6 +7,7 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
@ -32,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
} }
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>(); var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
// If the model type is ICollection<> then we can call its Add method, so we can always support it. // If the model type is ICollection<> then we can call its Add method, so we can always support it.
var collectionType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(ICollection<>)); var collectionType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(ICollection<>));
@ -41,7 +43,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType)); var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType));
var binderType = typeof(CollectionModelBinder<>).MakeGenericType(collectionType.GenericTypeArguments); var binderType = typeof(CollectionModelBinder<>).MakeGenericType(collectionType.GenericTypeArguments);
return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); return (IModelBinder)Activator.CreateInstance(
binderType,
elementBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
} }
// If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since // If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since
@ -57,7 +63,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType)); var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType));
var binderType = typeof(CollectionModelBinder<>).MakeGenericType(enumerableType.GenericTypeArguments); var binderType = typeof(CollectionModelBinder<>).MakeGenericType(enumerableType.GenericTypeArguments);
return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); return (IModelBinder)Activator.CreateInstance(
binderType,
elementBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
} }
} }

View File

@ -45,9 +45,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// The <see cref="IDictionary{TKey, TValue}"/> of binders to use for binding properties. /// The <see cref="IDictionary{TKey, TValue}"/> of binders to use for binding properties.
/// </param> /// </param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public ComplexTypeModelBinder( public ComplexTypeModelBinder(
IDictionary<ModelMetadata, IModelBinder> propertyBinders, IDictionary<ModelMetadata, IModelBinder> propertyBinders,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
: this(propertyBinders, loggerFactory, allowValidatingTopLevelNodes: false)
{
}
/// <summary>
/// Creates a new <see cref="ComplexTypeModelBinder"/>.
/// </summary>
/// <param name="propertyBinders">
/// The <see cref="IDictionary{TKey, TValue}"/> of binders to use for binding properties.
/// </param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="allowValidatingTopLevelNodes">
/// Indication that validation of top-level models is enabled. If <see langword="true"/> and
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/> for a top-level model, the binder
/// adds a <see cref="ModelStateDictionary"/> error when the model is not bound.
/// </param>
public ComplexTypeModelBinder(
IDictionary<ModelMetadata, IModelBinder> propertyBinders,
ILoggerFactory loggerFactory,
bool allowValidatingTopLevelNodes)
{ {
if (propertyBinders == null) if (propertyBinders == null)
{ {
@ -61,8 +85,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
_propertyBinders = propertyBinders; _propertyBinders = propertyBinders;
_logger = loggerFactory.CreateLogger<ComplexTypeModelBinder>(); _logger = loggerFactory.CreateLogger<ComplexTypeModelBinder>();
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
} }
// Internal for testing.
internal bool AllowValidatingTopLevelNodes { get; }
/// <inheritdoc /> /// <inheritdoc />
public Task BindModelAsync(ModelBindingContext bindingContext) public Task BindModelAsync(ModelBindingContext bindingContext)
{ {
@ -91,9 +119,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
bindingContext.Model = CreateModel(bindingContext); bindingContext.Model = CreateModel(bindingContext);
} }
for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) var modelMetadata = bindingContext.ModelMetadata;
var attemptedPropertyBinding = false;
for (var i = 0; i < modelMetadata.Properties.Count; i++)
{ {
var property = bindingContext.ModelMetadata.Properties[i]; var property = modelMetadata.Properties[i];
if (!CanBindProperty(bindingContext, property)) if (!CanBindProperty(bindingContext, property))
{ {
continue; continue;
@ -127,15 +157,32 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
if (result.IsModelSet) if (result.IsModelSet)
{ {
attemptedPropertyBinding = true;
SetProperty(bindingContext, modelName, property, result); SetProperty(bindingContext, modelName, property, result);
} }
else if (property.IsBindingRequired) else if (property.IsBindingRequired)
{ {
attemptedPropertyBinding = true;
var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
bindingContext.ModelState.TryAddModelError(modelName, message); bindingContext.ModelState.TryAddModelError(modelName, message);
} }
} }
// Have we created a top-level model despite an inability to bind anything in said model and a lack of
// other IsBindingRequired errors? Does that violate [BindRequired] on the model? This case occurs when
// 1. The top-level model has no public settable properties.
// 2. All properties in a [BindRequired] model have [BindNever] or are otherwise excluded from binding.
// 3. No data exists for any property.
if (AllowValidatingTopLevelNodes &&
!attemptedPropertyBinding &&
bindingContext.IsTopLevelObject &&
modelMetadata.IsBindingRequired)
{
var messageProvider = modelMetadata.ModelBindingMessageProvider;
var message = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName);
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
}
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
_logger.DoneAttemptingToBindModel(bindingContext); _logger.DoneAttemptingToBindModel(bindingContext);
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
@ -31,7 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
} }
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>(); var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ComplexTypeModelBinder(propertyBinders, loggerFactory); var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
return new ComplexTypeModelBinder(
propertyBinders,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
} }
return null; return null;

View File

@ -43,6 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// <param name="keyBinder">The <see cref="IModelBinder"/> for <typeparamref name="TKey"/>.</param> /// <param name="keyBinder">The <see cref="IModelBinder"/> for <typeparamref name="TKey"/>.</param>
/// <param name="valueBinder">The <see cref="IModelBinder"/> for <typeparamref name="TValue"/>.</param> /// <param name="valueBinder">The <see cref="IModelBinder"/> for <typeparamref name="TValue"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public DictionaryModelBinder(IModelBinder keyBinder, IModelBinder valueBinder, ILoggerFactory loggerFactory) public DictionaryModelBinder(IModelBinder keyBinder, IModelBinder valueBinder, ILoggerFactory loggerFactory)
: base(new KeyValuePairModelBinder<TKey, TValue>(keyBinder, valueBinder, loggerFactory), loggerFactory) : base(new KeyValuePairModelBinder<TKey, TValue>(keyBinder, valueBinder, loggerFactory), loggerFactory)
{ {
@ -54,6 +58,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
_valueBinder = valueBinder; _valueBinder = valueBinder;
} }
/// <summary>
/// Creates a new <see cref="DictionaryModelBinder{TKey, TValue}"/>.
/// </summary>
/// <param name="keyBinder">The <see cref="IModelBinder"/> for <typeparamref name="TKey"/>.</param>
/// <param name="valueBinder">The <see cref="IModelBinder"/> for <typeparamref name="TValue"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="allowValidatingTopLevelNodes">
/// Indication that validation of top-level models is enabled. If <see langword="true"/> and
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/> for a top-level model, the binder
/// adds a <see cref="ModelStateDictionary"/> error when the model is not bound.
/// </param>
public DictionaryModelBinder(
IModelBinder keyBinder,
IModelBinder valueBinder,
ILoggerFactory loggerFactory,
bool allowValidatingTopLevelNodes)
: base(
new KeyValuePairModelBinder<TKey, TValue>(keyBinder, valueBinder, loggerFactory),
loggerFactory,
// CollectionModelBinder should not check IsRequired, done in this model binder.
allowValidatingTopLevelNodes: false)
{
if (valueBinder == null)
{
throw new ArgumentNullException(nameof(valueBinder));
}
_valueBinder = valueBinder;
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
}
// Internal for testing.
internal new bool AllowValidatingTopLevelNodes { get; }
/// <inheritdoc /> /// <inheritdoc />
public override async Task BindModelAsync(ModelBindingContext bindingContext) public override async Task BindModelAsync(ModelBindingContext bindingContext)
{ {
@ -85,6 +123,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
// No IEnumerableValueProvider available for the fallback approach. For example the user may have // No IEnumerableValueProvider available for the fallback approach. For example the user may have
// replaced the ValueProvider with something other than a CompositeValueProvider. // replaced the ValueProvider with something other than a CompositeValueProvider.
if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject)
{
AddErrorIfBindingRequired(bindingContext);
}
return; return;
} }
@ -94,6 +137,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
if (keys.Count == 0) if (keys.Count == 0)
{ {
// No entries with the expected keys. // No entries with the expected keys.
if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject)
{
AddErrorIfBindingRequired(bindingContext);
}
return; return;
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
@ -34,7 +35,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var binderType = typeof(DictionaryModelBinder<,>).MakeGenericType(dictionaryType.GenericTypeArguments); var binderType = typeof(DictionaryModelBinder<,>).MakeGenericType(dictionaryType.GenericTypeArguments);
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>(); var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return (IModelBinder)Activator.CreateInstance(binderType, keyBinder, valueBinder, loggerFactory); var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
return (IModelBinder)Activator.CreateInstance(
binderType,
keyBinder,
valueBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
} }
return null; return null;

View File

@ -795,7 +795,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
=> GetString("ModelBinderUtil_ModelMetadataCannotBeNull"); => GetString("ModelBinderUtil_ModelMetadataCannotBeNull");
/// <summary> /// <summary>
/// A value for the '{0}' property was not provided. /// A value for the '{0}' parameter or property was not provided.
/// </summary> /// </summary>
internal static string ModelBinding_MissingBindRequiredMember internal static string ModelBinding_MissingBindRequiredMember
{ {
@ -803,7 +803,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
} }
/// <summary> /// <summary>
/// A value for the '{0}' property was not provided. /// A value for the '{0}' parameter or property was not provided.
/// </summary> /// </summary>
internal static string FormatModelBinding_MissingBindRequiredMember(object p0) internal static string FormatModelBinding_MissingBindRequiredMember(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0);

View File

@ -295,7 +295,7 @@
<value>The binding context cannot have a null ModelMetadata.</value> <value>The binding context cannot have a null ModelMetadata.</value>
</data> </data>
<data name="ModelBinding_MissingBindRequiredMember" xml:space="preserve"> <data name="ModelBinding_MissingBindRequiredMember" xml:space="preserve">
<value>A value for the '{0}' property was not provided.</value> <value>A value for the '{0}' parameter or property was not provided.</value>
</data> </data>
<data name="ModelBinding_MissingRequestBodyRequiredMember" xml:space="preserve"> <data name="ModelBinding_MissingRequestBodyRequiredMember" xml:space="preserve">
<value>A non-empty request body is required.</value> <value>A non-empty request body is required.</value>

View File

@ -51,6 +51,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.IsType(typeof(ArrayModelBinder<>).MakeGenericType(modelType.GetElementType()), result); Assert.IsType(typeof(ArrayModelBinder<>).MakeGenericType(modelType.GetElementType()), result);
} }
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForArrayType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
{
// Arrange
var provider = new ArrayModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(int[]));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
Assert.Equal(typeof(int), m.ModelType);
return Mock.Of<IModelBinder>();
});
// Act
var result = provider.GetBinder(context);
// Assert
var binder = Assert.IsType<ArrayModelBinder<int>>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
}
[Fact] [Fact]
public void Create_ForModelMetadataReadOnly_ReturnsNull() public void Create_ForModelMetadataReadOnly_ReturnsNull()
{ {

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
@ -42,13 +43,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.Equal(new[] { 42, 84 }, array); Assert.Equal(new[] { 42, 84 }, array);
} }
[Fact] private IActionResult ActionWithArrayParameter(string[] parameter) => null;
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{ {
// Arrange // Arrange
var binder = new ArrayModelBinder<string>( var binder = new ArrayModelBinder<string>(
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
NullLoggerFactory.Instance); NullLoggerFactory.Instance,
allowValidatingTopLevelNodes);
var bindingContext = CreateContext(); var bindingContext = CreateContext();
bindingContext.IsTopLevelObject = true; bindingContext.IsTopLevelObject = true;
@ -57,7 +66,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
bindingContext.ModelName = "modelName"; bindingContext.ModelName = "modelName";
var metadataProvider = new TestModelMetadataProvider(); var metadataProvider = new TestModelMetadataProvider();
bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[])); var parameter = typeof(ArrayModelBinderTest)
.GetMethod(nameof(ActionWithArrayParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter);
bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>()); bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
@ -67,22 +82,74 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert // Assert
Assert.Empty(Assert.IsType<string[]>(bindingContext.Result.Model)); Assert.Empty(Assert.IsType<string[]>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet); Assert.True(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
} }
[Theory] [Fact]
[InlineData("")] public async Task ArrayModelBinder_CreatesEmptyCollectionAndAddsError_IfIsTopLevelObject()
[InlineData("param")]
public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
{ {
// Arrange // Arrange
var binder = new ArrayModelBinder<string>( var binder = new ArrayModelBinder<string>(
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
NullLoggerFactory.Instance); NullLoggerFactory.Instance,
allowValidatingTopLevelNodes: true);
var bindingContext = CreateContext();
bindingContext.IsTopLevelObject = true;
bindingContext.FieldName = "fieldName";
bindingContext.ModelName = "modelName";
var metadataProvider = new TestModelMetadataProvider();
var parameter = typeof(ArrayModelBinderTest)
.GetMethod(nameof(ActionWithArrayParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = true);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter);
bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.Empty(Assert.IsType<string[]>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet);
var keyValuePair = Assert.Single(bindingContext.ModelState);
Assert.Equal("modelName", keyValuePair.Key);
var error = Assert.Single(keyValuePair.Value.Errors);
Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage);
}
[Theory]
[InlineData("", false, false)]
[InlineData("", true, false)]
[InlineData("", false, true)]
[InlineData("", true, true)]
[InlineData("param", false, false)]
[InlineData("param", true, false)]
[InlineData("param", false, true)]
[InlineData("param", true, true)]
public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(
string prefix,
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{
// Arrange
var binder = new ArrayModelBinder<string>(
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
NullLoggerFactory.Instance,
allowValidatingTopLevelNodes);
var bindingContext = CreateContext(); var bindingContext = CreateContext();
bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ArrayProperty"); bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ArrayProperty");
var metadataProvider = new TestModelMetadataProvider(); var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForProperty(typeof(ModelWithArrayProperty), nameof(ModelWithArrayProperty.ArrayProperty))
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty( bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty(
typeof(ModelWithArrayProperty), typeof(ModelWithArrayProperty),
nameof(ModelWithArrayProperty.ArrayProperty)); nameof(ModelWithArrayProperty.ArrayProperty));
@ -94,6 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert // Assert
Assert.False(bindingContext.Result.IsModelSet); Assert.False(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
} }
public static TheoryData<int[]> ArrayModelData public static TheoryData<int[]> ArrayModelData
@ -177,23 +245,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private static DefaultModelBindingContext GetBindingContext(IValueProvider valueProvider) private static DefaultModelBindingContext GetBindingContext(IValueProvider valueProvider)
{ {
var bindingContext = new DefaultModelBindingContext() var bindingContext = CreateContext();
{ bindingContext.ModelName = "someName";
ModelName = "someName", bindingContext.ValueProvider = valueProvider;
ModelState = new ModelStateDictionary(),
ValueProvider = valueProvider,
};
return bindingContext; return bindingContext;
} }
private static DefaultModelBindingContext CreateContext() private static DefaultModelBindingContext CreateContext()
{ {
var modelBindingContext = new DefaultModelBindingContext() var actionContext = new ActionContext
{ {
ActionContext = new ActionContext() HttpContext = new DefaultHttpContext(),
{ };
HttpContext = new DefaultHttpContext(), var modelBindingContext = new DefaultModelBindingContext
}, {
ActionContext = actionContext,
ModelState = actionContext.ModelState,
}; };
return modelBindingContext; return modelBindingContext;

View File

@ -66,6 +66,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.IsType<CollectionModelBinder<int>>(result); Assert.IsType<CollectionModelBinder<int>>(result);
} }
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForSupportedType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
{
// Arrange
var provider = new CollectionModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(List<int>));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
Assert.Equal(typeof(int), m.ModelType);
return Mock.Of<IModelBinder>();
});
// Act
var result = provider.GetBinder(context);
// Assert
var binder = Assert.IsType<CollectionModelBinder<int>>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
}
private class Person private class Person
{ {
public string Name { get; set; } public string Name { get; set; }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
@ -211,13 +212,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.Empty(boundCollection.Model); Assert.Empty(boundCollection.Model);
} }
[Fact] private IActionResult ActionWithListParameter(List<string> parameter) => null;
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{ {
// Arrange // Arrange
var binder = new CollectionModelBinder<string>( var binder = new CollectionModelBinder<string>(
new StubModelBinder(result: ModelBindingResult.Failed()), new StubModelBinder(result: ModelBindingResult.Failed()),
NullLoggerFactory.Instance); NullLoggerFactory.Instance,
allowValidatingTopLevelNodes);
var bindingContext = CreateContext(); var bindingContext = CreateContext();
bindingContext.IsTopLevelObject = true; bindingContext.IsTopLevelObject = true;
@ -226,7 +235,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
bindingContext.ModelName = "modelName"; bindingContext.ModelName = "modelName";
var metadataProvider = new TestModelMetadataProvider(); var metadataProvider = new TestModelMetadataProvider();
bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List<string>)); var parameter = typeof(CollectionModelBinderTest)
.GetMethod(nameof(ActionWithListParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter);
bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>()); bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
@ -236,6 +251,45 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert // Assert
Assert.Empty(Assert.IsType<List<string>>(bindingContext.Result.Model)); Assert.Empty(Assert.IsType<List<string>>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet); Assert.True(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
}
[Fact]
public async Task CollectionModelBinder_CreatesEmptyCollectionAndAddsError_IfIsTopLevelObject()
{
// Arrange
var binder = new CollectionModelBinder<string>(
new StubModelBinder(result: ModelBindingResult.Failed()),
NullLoggerFactory.Instance,
allowValidatingTopLevelNodes: true);
var bindingContext = CreateContext();
bindingContext.IsTopLevelObject = true;
bindingContext.FieldName = "fieldName";
bindingContext.ModelName = "modelName";
var metadataProvider = new TestModelMetadataProvider();
var parameter = typeof(CollectionModelBinderTest)
.GetMethod(nameof(ActionWithListParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = true);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter);
bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.Empty(Assert.IsType<List<string>>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet);
var keyValuePair = Assert.Single(bindingContext.ModelState);
Assert.Equal("modelName", keyValuePair.Key);
var error = Assert.Single(keyValuePair.Value.Errors);
Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage);
} }
// Setup like CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject except // Setup like CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject except
@ -272,19 +326,32 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
} }
[Theory] [Theory]
[InlineData("")] [InlineData("", false, false)]
[InlineData("param")] [InlineData("", true, false)]
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix) [InlineData("", false, true)]
[InlineData("", true, true)]
[InlineData("param", false, false)]
[InlineData("param", true, false)]
[InlineData("param", false, true)]
[InlineData("param", true, true)]
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(
string prefix,
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{ {
// Arrange // Arrange
var binder = new CollectionModelBinder<string>( var binder = new CollectionModelBinder<string>(
new StubModelBinder(result: ModelBindingResult.Failed()), new StubModelBinder(result: ModelBindingResult.Failed()),
NullLoggerFactory.Instance); NullLoggerFactory.Instance,
allowValidatingTopLevelNodes);
var bindingContext = CreateContext(); var bindingContext = CreateContext();
bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty"); bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
var metadataProvider = new TestModelMetadataProvider(); var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForProperty(typeof(ModelWithListProperty), nameof(ModelWithListProperty.ListProperty))
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty( bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty(
typeof(ModelWithListProperty), typeof(ModelWithListProperty),
nameof(ModelWithListProperty.ListProperty)); nameof(ModelWithListProperty.ListProperty));
@ -296,6 +363,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert // Assert
Assert.False(bindingContext.Result.IsModelSet); Assert.False(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
} }
// Model type -> can create instance. // Model type -> can create instance.
@ -365,15 +433,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
typeof(ModelWithIListProperty), typeof(ModelWithIListProperty),
nameof(ModelWithIListProperty.ListProperty)); nameof(ModelWithIListProperty.ListProperty));
var bindingContext = new DefaultModelBindingContext var bindingContext = CreateContext();
{ bindingContext.FieldName = "testfieldname";
ModelMetadata = metadata, bindingContext.ModelName = "someName";
ModelName = "someName", bindingContext.ModelMetadata = metadata;
ModelState = new ModelStateDictionary(), bindingContext.ValueProvider = valueProvider;
ValueProvider = valueProvider,
ValidationState = new ValidationStateDictionary(),
FieldName = "testfieldname",
};
return bindingContext; return bindingContext;
} }
@ -412,12 +476,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private static DefaultModelBindingContext CreateContext() private static DefaultModelBindingContext CreateContext()
{ {
var actionContext = new ActionContext()
{
HttpContext = new DefaultHttpContext(),
};
var modelBindingContext = new DefaultModelBindingContext() var modelBindingContext = new DefaultModelBindingContext()
{ {
ActionContext = new ActionContext() ActionContext = actionContext,
{ ModelState = actionContext.ModelState,
HttpContext = new DefaultHttpContext(), ValidationState = new ValidationStateDictionary(),
},
}; };
return modelBindingContext; return modelBindingContext;

View File

@ -55,6 +55,38 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.IsType<ComplexTypeModelBinder>(result); Assert.IsType<ComplexTypeModelBinder>(result);
} }
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForSupportedType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
{
// Arrange
var provider = new ComplexTypeModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(Person));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
if (m.ModelType == typeof(int) || m.ModelType == typeof(string))
{
return Mock.Of<IModelBinder>();
}
else
{
Assert.False(true, "Not the right model type");
return null;
}
});
// Act
var result = provider.GetBinder(context);
// Assert
var binder = Assert.IsType<ComplexTypeModelBinder>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
}
private class Person private class Person
{ {
public string Name { get; set; } public string Name { get; set; }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -275,8 +276,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.Equal(expectedCanCreate, canCreate); Assert.Equal(expectedCanCreate, canCreate);
} }
[Fact] private IActionResult ActionWithComplexParameter(Person parameter) => null;
public async Task BindModelAsync_CreatesModel_IfIsTopLevelObject()
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
public async Task BindModelAsync_CreatesModel_IfIsTopLevelObject(
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{ {
// Arrange // Arrange
var mockValueProvider = new Mock<IValueProvider>(); var mockValueProvider = new Mock<IValueProvider>();
@ -287,6 +295,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Mock binder fails to bind all properties. // Mock binder fails to bind all properties.
var mockBinder = new StubModelBinder(); var mockBinder = new StubModelBinder();
var parameter = typeof(ComplexTypeModelBinderTest)
.GetMethod(nameof(ActionWithComplexParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
var metadata = metadataProvider.GetMetadataForParameter(parameter);
var bindingContext = new DefaultModelBindingContext var bindingContext = new DefaultModelBindingContext
{ {
IsTopLevelObject = true, IsTopLevelObject = true,
@ -298,7 +314,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var model = new Person(); var model = new Person();
var testableBinder = new Mock<TestableComplexTypeModelBinder> { CallBase = true }; var testableBinder = new Mock<TestableComplexTypeModelBinder>(allowValidatingTopLevelNodes)
{
CallBase = true
};
testableBinder testableBinder
.Setup(o => o.CreateModelPublic(bindingContext)) .Setup(o => o.CreateModelPublic(bindingContext))
.Returns(model) .Returns(model)
@ -312,11 +331,149 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert // Assert
Assert.True(bindingContext.Result.IsModelSet); Assert.True(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
var returnedPerson = Assert.IsType<Person>(bindingContext.Result.Model); var returnedPerson = Assert.IsType<Person>(bindingContext.Result.Model);
Assert.Same(model, returnedPerson); Assert.Same(model, returnedPerson);
testableBinder.Verify(); testableBinder.Verify();
} }
[Fact]
public async Task BindModelAsync_CreatesModelAndAddsError_IfIsTopLevelObject_WithNoData()
{
// Arrange
var parameter = typeof(ComplexTypeModelBinderTest)
.GetMethod(nameof(ActionWithComplexParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = true);
var metadata = metadataProvider.GetMetadataForParameter(parameter);
var bindingContext = new DefaultModelBindingContext
{
IsTopLevelObject = true,
FieldName = "fieldName",
ModelMetadata = metadata,
ModelName = string.Empty,
ValueProvider = new TestValueProvider(new Dictionary<string, object>()),
ModelState = new ModelStateDictionary(),
};
// Mock binder fails to bind all properties.
var innerBinder = new StubModelBinder();
var binders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (var property in metadataProvider.GetMetadataForProperties(typeof(Person)))
{
binders.Add(property, innerBinder);
}
var binder = new ComplexTypeModelBinder(
binders,
NullLoggerFactory.Instance,
allowValidatingTopLevelNodes: true);
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
Assert.IsType<Person>(bindingContext.Result.Model);
var keyValuePair = Assert.Single(bindingContext.ModelState);
Assert.Equal(string.Empty, keyValuePair.Key);
var error = Assert.Single(keyValuePair.Value.Errors);
Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage);
}
private IActionResult ActionWithNoSettablePropertiesParameter(PersonWithNoProperties parameter) => null;
[Fact]
public async Task BindModelAsync_CreatesModelAndAddsError_IfIsTopLevelObject_WithNoSettableProperties()
{
// Arrange
var parameter = typeof(ComplexTypeModelBinderTest)
.GetMethod(
nameof(ActionWithNoSettablePropertiesParameter),
BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = true);
var metadata = metadataProvider.GetMetadataForParameter(parameter);
var bindingContext = new DefaultModelBindingContext
{
IsTopLevelObject = true,
FieldName = "fieldName",
ModelMetadata = metadata,
ModelName = string.Empty,
ValueProvider = new TestValueProvider(new Dictionary<string, object>()),
ModelState = new ModelStateDictionary(),
};
var binder = new ComplexTypeModelBinder(
new Dictionary<ModelMetadata, IModelBinder>(),
NullLoggerFactory.Instance,
allowValidatingTopLevelNodes: true);
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
Assert.IsType<PersonWithNoProperties>(bindingContext.Result.Model);
var keyValuePair = Assert.Single(bindingContext.ModelState);
Assert.Equal(string.Empty, keyValuePair.Key);
var error = Assert.Single(keyValuePair.Value.Errors);
Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage);
}
private IActionResult ActionWithAllPropertiesExcludedParameter(PersonWithAllPropertiesExcluded parameter) => null;
[Fact]
public async Task BindModelAsync_CreatesModelAndAddsError_IfIsTopLevelObject_WithAllPropertiesExcluded()
{
// Arrange
var parameter = typeof(ComplexTypeModelBinderTest)
.GetMethod(
nameof(ActionWithAllPropertiesExcludedParameter),
BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = true);
var metadata = metadataProvider.GetMetadataForParameter(parameter);
var bindingContext = new DefaultModelBindingContext
{
IsTopLevelObject = true,
FieldName = "fieldName",
ModelMetadata = metadata,
ModelName = string.Empty,
ValueProvider = new TestValueProvider(new Dictionary<string, object>()),
ModelState = new ModelStateDictionary(),
};
var binder = new ComplexTypeModelBinder(
new Dictionary<ModelMetadata, IModelBinder>(),
NullLoggerFactory.Instance,
allowValidatingTopLevelNodes: true);
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
Assert.IsType<PersonWithAllPropertiesExcluded>(bindingContext.Result.Model);
var keyValuePair = Assert.Single(bindingContext.ModelState);
Assert.Equal(string.Empty, keyValuePair.Key);
var error = Assert.Single(keyValuePair.Value.Errors);
Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage);
}
[Theory] [Theory]
[InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyInt), false)] // read-only value type [InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyInt), false)] // read-only value type
[InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyObject), true)] [InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyObject), true)]
@ -644,7 +801,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var modelError = Assert.Single(entry.Errors); var modelError = Assert.Single(entry.Errors);
Assert.Null(modelError.Exception); Assert.Null(modelError.Exception);
Assert.NotNull(modelError.ErrorMessage); Assert.NotNull(modelError.ErrorMessage);
Assert.Equal("A value for the 'Age' property was not provided.", modelError.ErrorMessage); Assert.Equal("A value for the 'Age' parameter or property was not provided.", modelError.ErrorMessage);
} }
[Fact] [Fact]
@ -678,7 +835,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var modelError = Assert.Single(entry.Errors); var modelError = Assert.Single(entry.Errors);
Assert.Null(modelError.Exception); Assert.Null(modelError.Exception);
Assert.NotNull(modelError.ErrorMessage); Assert.NotNull(modelError.ErrorMessage);
Assert.Equal("A value for the 'Age' property was not provided.", modelError.ErrorMessage); Assert.Equal("A value for the 'Age' parameter or property was not provided.", modelError.ErrorMessage);
} }
[Fact] [Fact]
@ -1203,6 +1360,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
public string name = null; public string name = null;
} }
private class PersonWithAllPropertiesExcluded
{
[BindNever]
public DateTime DateOfBirth { get; set; }
[BindNever]
public DateTime? DateOfDeath { get; set; }
[BindNever]
public string FirstName { get; set; }
[BindNever]
public string LastName { get; set; }
public string NonUpdateableProperty { get; private set; }
}
private class PersonWithBindExclusion private class PersonWithBindExclusion
{ {
[BindNever] [BindNever]
@ -1405,13 +1579,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
} }
public TestableComplexTypeModelBinder(bool allowValidatingTopLevelNodes)
: this(new Dictionary<ModelMetadata, IModelBinder>(), allowValidatingTopLevelNodes)
{
}
public TestableComplexTypeModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) public TestableComplexTypeModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
: base(propertyBinders, NullLoggerFactory.Instance) : base(propertyBinders, NullLoggerFactory.Instance)
{ {
Results = new Dictionary<ModelMetadata, ModelBindingResult>();
} }
public Dictionary<ModelMetadata, ModelBindingResult> Results { get; } public TestableComplexTypeModelBinder(
IDictionary<ModelMetadata, IModelBinder> propertyBinders,
bool allowValidatingTopLevelNodes)
: base(propertyBinders, NullLoggerFactory.Instance, allowValidatingTopLevelNodes)
{
}
public Dictionary<ModelMetadata, ModelBindingResult> Results { get; } = new Dictionary<ModelMetadata, ModelBindingResult>();
public virtual Task BindPropertyPublic(ModelBindingContext bindingContext) public virtual Task BindPropertyPublic(ModelBindingContext bindingContext)
{ {

View File

@ -60,6 +60,39 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.IsType<DictionaryModelBinder<string, int>>(result); Assert.IsType<DictionaryModelBinder<string, int>>(result);
} }
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForDictionaryType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
{
// Arrange
var provider = new DictionaryModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(Dictionary<string, string>));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
if (m.ModelType == typeof(KeyValuePair<string, string>) || m.ModelType == typeof(string))
{
return Mock.Of<IModelBinder>();
}
else
{
Assert.False(true, "Not the right model type");
return null;
}
});
// Act
var result = provider.GetBinder(context);
// Assert
var binder = Assert.IsType<DictionaryModelBinder<string, string>>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
Assert.False(((CollectionModelBinder<KeyValuePair<string, string>>)binder).AllowValidatingTopLevelNodes);
}
private class Person private class Person
{ {
public string Name { get; set; } public string Name { get; set; }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
@ -343,14 +344,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.Equal(expectedDictionary, resultDictionary); Assert.Equal(expectedDictionary, resultDictionary);
} }
[Fact] private IActionResult ActionWithDictionaryParameter(Dictionary<string, string> parameter) => null;
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{ {
// Arrange // Arrange
var binder = new DictionaryModelBinder<string, string>( var binder = new DictionaryModelBinder<string, string>(
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
NullLoggerFactory.Instance); NullLoggerFactory.Instance,
allowValidatingTopLevelNodes);
var bindingContext = CreateContext(); var bindingContext = CreateContext();
bindingContext.IsTopLevelObject = true; bindingContext.IsTopLevelObject = true;
@ -359,7 +368,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
bindingContext.ModelName = "modelName"; bindingContext.ModelName = "modelName";
var metadataProvider = new TestModelMetadataProvider(); var metadataProvider = new TestModelMetadataProvider();
bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary<string, string>)); var parameter = typeof(DictionaryModelBinderTest)
.GetMethod(nameof(ActionWithDictionaryParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter);
bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>()); bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
@ -369,23 +384,78 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert // Assert
Assert.Empty(Assert.IsType<Dictionary<string, string>>(bindingContext.Result.Model)); Assert.Empty(Assert.IsType<Dictionary<string, string>>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet); Assert.True(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
}
[Fact]
public async Task DictionaryModelBinder_CreatesEmptyCollectionAndAddsError_IfIsTopLevelObject()
{
// Arrange
var binder = new DictionaryModelBinder<string, string>(
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
NullLoggerFactory.Instance,
allowValidatingTopLevelNodes: true);
var bindingContext = CreateContext();
bindingContext.IsTopLevelObject = true;
bindingContext.FieldName = "fieldName";
bindingContext.ModelName = "modelName";
var metadataProvider = new TestModelMetadataProvider();
var parameter = typeof(DictionaryModelBinderTest)
.GetMethod(nameof(ActionWithDictionaryParameter), BindingFlags.Instance | BindingFlags.NonPublic)
.GetParameters()[0];
metadataProvider
.ForParameter(parameter)
.BindingDetails(b => b.IsBindingRequired = true);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter);
bindingContext.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.Empty(Assert.IsType<Dictionary<string, string>>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet);
var keyValuePair = Assert.Single(bindingContext.ModelState);
Assert.Equal("modelName", keyValuePair.Key);
var error = Assert.Single(keyValuePair.Value.Errors);
Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage);
} }
[Theory] [Theory]
[InlineData("")] [InlineData("", false, false)]
[InlineData("param")] [InlineData("", true, false)]
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix) [InlineData("", false, true)]
[InlineData("", true, true)]
[InlineData("param", false, false)]
[InlineData("param", true, false)]
[InlineData("param", false, true)]
[InlineData("param", true, true)]
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(
string prefix,
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{ {
// Arrange // Arrange
var binder = new DictionaryModelBinder<int, int>( var binder = new DictionaryModelBinder<int, int>(
new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance),
new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance),
NullLoggerFactory.Instance); NullLoggerFactory.Instance,
allowValidatingTopLevelNodes);
var bindingContext = CreateContext(); var bindingContext = CreateContext();
bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty"); bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
var metadataProvider = new TestModelMetadataProvider(); var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForProperty(
typeof(ModelWithDictionaryProperties),
nameof(ModelWithDictionaryProperties.DictionaryProperty))
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty( bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty(
typeof(ModelWithDictionaryProperties), typeof(ModelWithDictionaryProperties),
nameof(ModelWithDictionaryProperties.DictionaryProperty)); nameof(ModelWithDictionaryProperties.DictionaryProperty));
@ -397,6 +467,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert // Assert
Assert.False(bindingContext.Result.IsModelSet); Assert.False(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
} }
// Model type -> can create instance. // Model type -> can create instance.
@ -436,13 +507,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private static DefaultModelBindingContext CreateContext() private static DefaultModelBindingContext CreateContext()
{ {
var actionContext = new ActionContext()
{
HttpContext = new DefaultHttpContext(),
};
var modelBindingContext = new DefaultModelBindingContext() var modelBindingContext = new DefaultModelBindingContext()
{ {
ActionContext = new ActionContext() ActionContext = actionContext,
{ ModelState = actionContext.ModelState,
HttpContext = new DefaultHttpContext(),
},
ModelState = new ModelStateDictionary(),
ValidationState = new ValidationStateDictionary(), ValidationState = new ValidationStateDictionary(),
}; };
@ -495,14 +567,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
valueProvider.Add(kvp.Key, string.Empty); valueProvider.Add(kvp.Key, string.Empty);
} }
var bindingContext = new DefaultModelBindingContext var bindingContext = CreateContext();
{ bindingContext.ModelMetadata = metadata;
ModelMetadata = metadata, bindingContext.ModelName = "someName";
ModelName = "someName", bindingContext.ValueProvider = valueProvider;
ModelState = new ModelStateDictionary(),
ValueProvider = valueProvider,
ValidationState = new ValidationStateDictionary(),
};
return bindingContext; return bindingContext;
} }

View File

@ -36,13 +36,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
BindingSource = Metadata.BindingSource, BindingSource = Metadata.BindingSource,
PropertyFilterProvider = Metadata.PropertyFilterProvider, PropertyFilterProvider = Metadata.PropertyFilterProvider,
}; };
Services = GetServices();
(Services, MvcOptions) = GetServicesAndOptions();
} }
public override BindingInfo BindingInfo => _bindingInfo; public override BindingInfo BindingInfo => _bindingInfo;
public override ModelMetadata Metadata { get; } public override ModelMetadata Metadata { get; }
public MvcOptions MvcOptions { get; }
public override IModelMetadataProvider MetadataProvider { get; } public override IModelMetadataProvider MetadataProvider { get; }
public override IServiceProvider Services { get; } public override IServiceProvider Services { get; }
@ -77,12 +80,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
_binderCreators.Add((m) => m.Equals(metadata) ? binderCreator() : null); _binderCreators.Add((m) => m.Equals(metadata) ? binderCreator() : null);
} }
private static IServiceProvider GetServices() private static (IServiceProvider, MvcOptions) GetServicesAndOptions()
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory, NullLoggerFactory>(); services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
services.AddSingleton(Options.Create(new MvcOptions()));
return services.BuildServiceProvider(); var mvcOptions = new MvcOptions();
services.AddSingleton(Options.Create(mvcOptions));
return (services.BuildServiceProvider(), mvcOptions);
} }
} }
} }

View File

@ -89,10 +89,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
"The RequiredProp field is required.", "The RequiredProp field is required.",
errors["RequiredProp"]); errors["RequiredProp"]);
Assert.Equal( Assert.Equal(
"A value for the 'BindRequiredProp' property was not provided.", "A value for the 'BindRequiredProp' parameter or property was not provided.",
errors["BindRequiredProp"]); errors["BindRequiredProp"]);
Assert.Equal( Assert.Equal(
"A value for the 'RequiredAndBindRequiredProp' property was not provided.", "A value for the 'RequiredAndBindRequiredProp' parameter or property was not provided.",
errors["RequiredAndBindRequiredProp"]); errors["RequiredAndBindRequiredProp"]);
Assert.Equal( Assert.Equal(
"The field OptionalStringLengthProp must be a string with a maximum length of 5.", "The field OptionalStringLengthProp must be a string with a maximum length of 5.",
@ -104,10 +104,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
"The requiredParam field is required.", "The requiredParam field is required.",
errors["requiredParam"]); errors["requiredParam"]);
Assert.Equal( Assert.Equal(
"A value for the 'bindRequiredParam' property was not provided.", "A value for the 'bindRequiredParam' parameter or property was not provided.",
errors["bindRequiredParam"]); errors["bindRequiredParam"]);
Assert.Equal( Assert.Equal(
"A value for the 'requiredAndBindRequiredParam' property was not provided.", "A value for the 'requiredAndBindRequiredParam' parameter or property was not provided.",
errors["requiredAndBindRequiredParam"]); errors["requiredAndBindRequiredParam"]);
Assert.Equal( Assert.Equal(
"The field optionalStringLengthParam must be a string with a maximum length of 5.", "The field optionalStringLengthParam must be a string with a maximum length of 5.",

View File

@ -333,13 +333,13 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
var error = Assert.Single(entry.Errors); var error = Assert.Single(entry.Errors);
Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage);
entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1].Name").Value; entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1].Name").Value;
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
error = Assert.Single(entry.Errors); error = Assert.Single(entry.Errors);
Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]

View File

@ -2123,7 +2123,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["Customer"].Errors); var error = Assert.Single(modelState["Customer"].Errors);
Assert.Equal("A value for the 'Customer' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'Customer' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]
@ -2245,7 +2245,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["parameter.Customer.Name"].Errors); var error = Assert.Single(modelState["parameter.Customer.Name"].Errors);
Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]
@ -2299,7 +2299,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["Customer.Name"].Errors); var error = Assert.Single(modelState["Customer.Name"].Errors);
Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]
@ -2357,7 +2357,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["customParameter.Customer.Name"].Errors); var error = Assert.Single(modelState["customParameter.Customer.Name"].Errors);
Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage);
} }
private class Order12 private class Order12
@ -2411,7 +2411,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["ProductName"].Errors); var error = Assert.Single(modelState["ProductName"].Errors);
Assert.Equal("A value for the 'ProductName' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'ProductName' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]
@ -2463,7 +2463,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["customParameter.ProductName"].Errors); var error = Assert.Single(modelState["customParameter.ProductName"].Errors);
Assert.Equal("A value for the 'ProductName' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'ProductName' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]
@ -2563,7 +2563,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["OrderIds"].Errors); var error = Assert.Single(modelState["OrderIds"].Errors);
Assert.Equal("A value for the 'OrderIds' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'OrderIds' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]
@ -2615,7 +2615,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.RawValue); Assert.Null(entry.RawValue);
Assert.Null(entry.AttemptedValue); Assert.Null(entry.AttemptedValue);
var error = Assert.Single(modelState["customParameter.OrderIds"].Errors); var error = Assert.Single(modelState["customParameter.OrderIds"].Errors);
Assert.Equal("A value for the 'OrderIds' property was not provided.", error.ErrorMessage); Assert.Equal("A value for the 'OrderIds' parameter or property was not provided.", error.ErrorMessage);
} }
[Fact] [Fact]