Change `CollectionModelBinder` and `ComplexTypeModelBinder` to enforce `[BindRequired]`
- #8180 - add an error when binding fails for top-level model - same case as when MVC creates "default" / empty model i.e. `ParameterBinder` can't detect this - update `CollectionModelBinder` subclasses and the various providers as well - controlled by existing `MvcOptions.AllowValidatingTopLevelNodes` option smaller issue: - change `ModelBinding_MissingBindRequiredMember` resource to mention parameters too
This commit is contained in:
parent
61386d5f67
commit
5c8dfef15e
|
|
@ -14,7 +14,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// Error message the model binding system adds when a property with an associated
|
||||
/// <c>BindRequiredAttribute</c> is not bound.
|
||||
/// </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; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -38,11 +38,35 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
/// The <see cref="IModelBinder"/> for binding <typeparamref name="TElement"/>.
|
||||
/// </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)
|
||||
: 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 />
|
||||
public override bool CanCreateInstance(Type targetType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
|
|
@ -27,7 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
var binderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,29 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
/// </summary>
|
||||
/// <param name="elementBinder">The <see cref="IModelBinder"/> for binding elements.</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)
|
||||
: 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)
|
||||
{
|
||||
|
|
@ -58,8 +80,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
ElementBinder = elementBinder;
|
||||
Logger = loggerFactory.CreateLogger(GetType());
|
||||
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
|
||||
}
|
||||
|
||||
// Internal for testing.
|
||||
internal bool AllowValidatingTopLevelNodes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IModelBinder"/> instances for binding collection elements.
|
||||
/// </summary>
|
||||
|
|
@ -94,6 +120,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
model = CreateEmptyCollection(bindingContext.ModelType);
|
||||
}
|
||||
|
||||
if (AllowValidatingTopLevelNodes)
|
||||
{
|
||||
AddErrorIfBindingRequired(bindingContext);
|
||||
}
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(model);
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +192,34 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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>
|
||||
/// Create an <see cref="object"/> assignable to <paramref name="targetType"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Reflection;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
|
|
@ -32,7 +33,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
}
|
||||
|
||||
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.
|
||||
var collectionType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(ICollection<>));
|
||||
if (collectionType != null)
|
||||
|
|
@ -41,7 +43,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType));
|
||||
|
||||
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
|
||||
|
|
@ -57,7 +63,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType));
|
||||
|
||||
var binderType = typeof(CollectionModelBinder<>).MakeGenericType(enumerableType.GenericTypeArguments);
|
||||
return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory);
|
||||
return (IModelBinder)Activator.CreateInstance(
|
||||
binderType,
|
||||
elementBinder,
|
||||
loggerFactory,
|
||||
mvcOptions.AllowValidatingTopLevelNodes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
/// The <see cref="IDictionary{TKey, TValue}"/> of binders to use for binding properties.
|
||||
/// </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(
|
||||
IDictionary<ModelMetadata, IModelBinder> propertyBinders,
|
||||
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)
|
||||
{
|
||||
|
|
@ -61,8 +85,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
_propertyBinders = propertyBinders;
|
||||
_logger = loggerFactory.CreateLogger<ComplexTypeModelBinder>();
|
||||
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
|
||||
}
|
||||
|
||||
// Internal for testing.
|
||||
internal bool AllowValidatingTopLevelNodes { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
|
|
@ -91,9 +119,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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))
|
||||
{
|
||||
continue;
|
||||
|
|
@ -127,15 +157,32 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
if (result.IsModelSet)
|
||||
{
|
||||
attemptedPropertyBinding = true;
|
||||
SetProperty(bindingContext, modelName, property, result);
|
||||
}
|
||||
else if (property.IsBindingRequired)
|
||||
{
|
||||
attemptedPropertyBinding = true;
|
||||
var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
|
||||
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);
|
||||
_logger.DoneAttemptingToBindModel(bindingContext);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
|
|
@ -31,7 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
/// <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>
|
||||
/// <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)
|
||||
: base(new KeyValuePairModelBinder<TKey, TValue>(keyBinder, valueBinder, loggerFactory), loggerFactory)
|
||||
{
|
||||
|
|
@ -54,6 +58,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
_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 />
|
||||
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
|
||||
// replaced the ValueProvider with something other than a CompositeValueProvider.
|
||||
if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject)
|
||||
{
|
||||
AddErrorIfBindingRequired(bindingContext);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +137,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
if (keys.Count == 0)
|
||||
{
|
||||
// No entries with the expected keys.
|
||||
if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject)
|
||||
{
|
||||
AddErrorIfBindingRequired(bindingContext);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
|
|
@ -34,7 +35,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
var binderType = typeof(DictionaryModelBinder<,>).MakeGenericType(dictionaryType.GenericTypeArguments);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -795,7 +795,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
=> GetString("ModelBinderUtil_ModelMetadataCannotBeNull");
|
||||
|
||||
/// <summary>
|
||||
/// A value for the '{0}' property was not provided.
|
||||
/// A value for the '{0}' parameter or property was not provided.
|
||||
/// </summary>
|
||||
internal static string ModelBinding_MissingBindRequiredMember
|
||||
{
|
||||
|
|
@ -803,7 +803,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value for the '{0}' property was not provided.
|
||||
/// A value for the '{0}' parameter or property was not provided.
|
||||
/// </summary>
|
||||
internal static string FormatModelBinding_MissingBindRequiredMember(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -295,7 +295,7 @@
|
|||
<value>The binding context cannot have a null ModelMetadata.</value>
|
||||
</data>
|
||||
<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 name="ModelBinding_MissingRequestBodyRequiredMember" xml:space="preserve">
|
||||
<value>A non-empty request body is required.</value>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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]
|
||||
public void Create_ForModelMetadataReadOnly_ReturnsNull()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
|
|
@ -42,13 +43,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Assert.Equal(new[] { 42, 84 }, array);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||
private IActionResult ActionWithArrayParameter(string[] parameter) => null;
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
|
||||
bool allowValidatingTopLevelNodes,
|
||||
bool isBindingRequired)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>(
|
||||
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
|
||||
NullLoggerFactory.Instance);
|
||||
NullLoggerFactory.Instance,
|
||||
allowValidatingTopLevelNodes);
|
||||
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.IsTopLevelObject = true;
|
||||
|
|
@ -57,7 +66,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
bindingContext.ModelName = "modelName";
|
||||
|
||||
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>());
|
||||
|
||||
|
|
@ -67,22 +82,74 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Assert
|
||||
Assert.Empty(Assert.IsType<string[]>(bindingContext.Result.Model));
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("param")]
|
||||
public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
[Fact]
|
||||
public async Task ArrayModelBinder_CreatesEmptyCollectionAndAddsError_IfIsTopLevelObject()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>(
|
||||
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();
|
||||
bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ArrayProperty");
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForProperty(typeof(ModelWithArrayProperty), nameof(ModelWithArrayProperty.ArrayProperty))
|
||||
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
|
||||
bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty(
|
||||
typeof(ModelWithArrayProperty),
|
||||
nameof(ModelWithArrayProperty.ArrayProperty));
|
||||
|
|
@ -94,6 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
|
||||
}
|
||||
|
||||
public static TheoryData<int[]> ArrayModelData
|
||||
|
|
@ -177,23 +245,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
private static DefaultModelBindingContext GetBindingContext(IValueProvider valueProvider)
|
||||
{
|
||||
var bindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
ValueProvider = valueProvider,
|
||||
};
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.ModelName = "someName";
|
||||
bindingContext.ValueProvider = valueProvider;
|
||||
|
||||
return bindingContext;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
|
@ -211,13 +212,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Assert.Empty(boundCollection.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||
private IActionResult ActionWithListParameter(List<string> parameter) => null;
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
|
||||
bool allowValidatingTopLevelNodes,
|
||||
bool isBindingRequired)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>(
|
||||
new StubModelBinder(result: ModelBindingResult.Failed()),
|
||||
NullLoggerFactory.Instance);
|
||||
NullLoggerFactory.Instance,
|
||||
allowValidatingTopLevelNodes);
|
||||
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.IsTopLevelObject = true;
|
||||
|
|
@ -226,7 +235,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
bindingContext.ModelName = "modelName";
|
||||
|
||||
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>());
|
||||
|
||||
|
|
@ -236,6 +251,45 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Assert
|
||||
Assert.Empty(Assert.IsType<List<string>>(bindingContext.Result.Model));
|
||||
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
|
||||
|
|
@ -272,19 +326,32 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("param")]
|
||||
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
[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 CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(
|
||||
string prefix,
|
||||
bool allowValidatingTopLevelNodes,
|
||||
bool isBindingRequired)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>(
|
||||
new StubModelBinder(result: ModelBindingResult.Failed()),
|
||||
NullLoggerFactory.Instance);
|
||||
NullLoggerFactory.Instance,
|
||||
allowValidatingTopLevelNodes);
|
||||
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForProperty(typeof(ModelWithListProperty), nameof(ModelWithListProperty.ListProperty))
|
||||
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
|
||||
bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty(
|
||||
typeof(ModelWithListProperty),
|
||||
nameof(ModelWithListProperty.ListProperty));
|
||||
|
|
@ -296,6 +363,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
|
||||
}
|
||||
|
||||
// Model type -> can create instance.
|
||||
|
|
@ -365,15 +433,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
typeof(ModelWithIListProperty),
|
||||
nameof(ModelWithIListProperty.ListProperty));
|
||||
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
ModelMetadata = metadata,
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
ValueProvider = valueProvider,
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
FieldName = "testfieldname",
|
||||
};
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.FieldName = "testfieldname";
|
||||
bindingContext.ModelName = "someName";
|
||||
bindingContext.ModelMetadata = metadata;
|
||||
bindingContext.ValueProvider = valueProvider;
|
||||
|
||||
return bindingContext;
|
||||
}
|
||||
|
|
@ -412,12 +476,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
private static DefaultModelBindingContext CreateContext()
|
||||
{
|
||||
var actionContext = new ActionContext()
|
||||
{
|
||||
HttpContext = new DefaultHttpContext(),
|
||||
};
|
||||
var modelBindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ActionContext = new ActionContext()
|
||||
{
|
||||
HttpContext = new DefaultHttpContext(),
|
||||
},
|
||||
ActionContext = actionContext,
|
||||
ModelState = actionContext.ModelState,
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
};
|
||||
|
||||
return modelBindingContext;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,38 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -275,8 +276,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Assert.Equal(expectedCanCreate, canCreate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CreatesModel_IfIsTopLevelObject()
|
||||
private IActionResult ActionWithComplexParameter(Person parameter) => null;
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
public async Task BindModelAsync_CreatesModel_IfIsTopLevelObject(
|
||||
bool allowValidatingTopLevelNodes,
|
||||
bool isBindingRequired)
|
||||
{
|
||||
// Arrange
|
||||
var mockValueProvider = new Mock<IValueProvider>();
|
||||
|
|
@ -287,6 +295,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Mock binder fails to bind all properties.
|
||||
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
|
||||
{
|
||||
IsTopLevelObject = true,
|
||||
|
|
@ -298,7 +314,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
var model = new Person();
|
||||
|
||||
var testableBinder = new Mock<TestableComplexTypeModelBinder> { CallBase = true };
|
||||
var testableBinder = new Mock<TestableComplexTypeModelBinder>(allowValidatingTopLevelNodes)
|
||||
{
|
||||
CallBase = true
|
||||
};
|
||||
testableBinder
|
||||
.Setup(o => o.CreateModelPublic(bindingContext))
|
||||
.Returns(model)
|
||||
|
|
@ -312,11 +331,149 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
// Assert
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
|
||||
|
||||
var returnedPerson = Assert.IsType<Person>(bindingContext.Result.Model);
|
||||
Assert.Same(model, returnedPerson);
|
||||
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]
|
||||
[InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyInt), false)] // read-only value type
|
||||
[InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyObject), true)]
|
||||
|
|
@ -644,7 +801,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
var modelError = Assert.Single(entry.Errors);
|
||||
Assert.Null(modelError.Exception);
|
||||
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]
|
||||
|
|
@ -678,7 +835,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
var modelError = Assert.Single(entry.Errors);
|
||||
Assert.Null(modelError.Exception);
|
||||
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]
|
||||
|
|
@ -1203,6 +1360,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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
|
||||
{
|
||||
[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)
|
||||
: 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,6 +60,39 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
|
@ -343,14 +344,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Assert.Equal(expectedDictionary, resultDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||
private IActionResult ActionWithDictionaryParameter(Dictionary<string, string> parameter) => null;
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
|
||||
bool allowValidatingTopLevelNodes,
|
||||
bool isBindingRequired)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>(
|
||||
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
|
||||
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
|
||||
NullLoggerFactory.Instance);
|
||||
NullLoggerFactory.Instance,
|
||||
allowValidatingTopLevelNodes);
|
||||
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.IsTopLevelObject = true;
|
||||
|
|
@ -359,7 +368,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
bindingContext.ModelName = "modelName";
|
||||
|
||||
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>());
|
||||
|
||||
|
|
@ -369,23 +384,78 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Assert
|
||||
Assert.Empty(Assert.IsType<Dictionary<string, string>>(bindingContext.Result.Model));
|
||||
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]
|
||||
[InlineData("")]
|
||||
[InlineData("param")]
|
||||
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
[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 DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(
|
||||
string prefix,
|
||||
bool allowValidatingTopLevelNodes,
|
||||
bool isBindingRequired)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<int, int>(
|
||||
new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance),
|
||||
new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance),
|
||||
NullLoggerFactory.Instance);
|
||||
NullLoggerFactory.Instance,
|
||||
allowValidatingTopLevelNodes);
|
||||
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForProperty(
|
||||
typeof(ModelWithDictionaryProperties),
|
||||
nameof(ModelWithDictionaryProperties.DictionaryProperty))
|
||||
.BindingDetails(b => b.IsBindingRequired = isBindingRequired);
|
||||
bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty(
|
||||
typeof(ModelWithDictionaryProperties),
|
||||
nameof(ModelWithDictionaryProperties.DictionaryProperty));
|
||||
|
|
@ -397,6 +467,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
|
||||
}
|
||||
|
||||
// Model type -> can create instance.
|
||||
|
|
@ -436,13 +507,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
private static DefaultModelBindingContext CreateContext()
|
||||
{
|
||||
var actionContext = new ActionContext()
|
||||
{
|
||||
HttpContext = new DefaultHttpContext(),
|
||||
};
|
||||
var modelBindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ActionContext = new ActionContext()
|
||||
{
|
||||
HttpContext = new DefaultHttpContext(),
|
||||
},
|
||||
ModelState = new ModelStateDictionary(),
|
||||
ActionContext = actionContext,
|
||||
ModelState = actionContext.ModelState,
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
};
|
||||
|
||||
|
|
@ -495,14 +567,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
valueProvider.Add(kvp.Key, string.Empty);
|
||||
}
|
||||
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
ModelMetadata = metadata,
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
ValueProvider = valueProvider,
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
};
|
||||
var bindingContext = CreateContext();
|
||||
bindingContext.ModelMetadata = metadata;
|
||||
bindingContext.ModelName = "someName";
|
||||
bindingContext.ValueProvider = valueProvider;
|
||||
|
||||
return bindingContext;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,13 +36,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
BindingSource = Metadata.BindingSource,
|
||||
PropertyFilterProvider = Metadata.PropertyFilterProvider,
|
||||
};
|
||||
Services = GetServices();
|
||||
|
||||
(Services, MvcOptions) = GetServicesAndOptions();
|
||||
}
|
||||
|
||||
public override BindingInfo BindingInfo => _bindingInfo;
|
||||
|
||||
public override ModelMetadata Metadata { get; }
|
||||
|
||||
public MvcOptions MvcOptions { get; }
|
||||
|
||||
public override IModelMetadataProvider MetadataProvider { get; }
|
||||
|
||||
public override IServiceProvider Services { get; }
|
||||
|
|
@ -77,12 +80,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
_binderCreators.Add((m) => m.Equals(metadata) ? binderCreator() : null);
|
||||
}
|
||||
|
||||
private static IServiceProvider GetServices()
|
||||
private static (IServiceProvider, MvcOptions) GetServicesAndOptions()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,10 +89,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
"The RequiredProp field is required.",
|
||||
errors["RequiredProp"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'BindRequiredProp' property was not provided.",
|
||||
"A value for the 'BindRequiredProp' parameter or property was not provided.",
|
||||
errors["BindRequiredProp"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'RequiredAndBindRequiredProp' property was not provided.",
|
||||
"A value for the 'RequiredAndBindRequiredProp' parameter or property was not provided.",
|
||||
errors["RequiredAndBindRequiredProp"]);
|
||||
Assert.Equal(
|
||||
"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.",
|
||||
errors["requiredParam"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'bindRequiredParam' property was not provided.",
|
||||
"A value for the 'bindRequiredParam' parameter or property was not provided.",
|
||||
errors["bindRequiredParam"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'requiredAndBindRequiredParam' property was not provided.",
|
||||
"A value for the 'requiredAndBindRequiredParam' parameter or property was not provided.",
|
||||
errors["requiredAndBindRequiredParam"]);
|
||||
Assert.Equal(
|
||||
"The field optionalStringLengthParam must be a string with a maximum length of 5.",
|
||||
|
|
|
|||
|
|
@ -333,13 +333,13 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||
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;
|
||||
Assert.Null(entry.RawValue);
|
||||
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -2123,7 +2123,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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]
|
||||
|
|
@ -2245,7 +2245,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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]
|
||||
|
|
@ -2299,7 +2299,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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]
|
||||
|
|
@ -2357,7 +2357,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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
|
||||
|
|
@ -2411,7 +2411,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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]
|
||||
|
|
@ -2463,7 +2463,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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]
|
||||
|
|
@ -2563,7 +2563,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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]
|
||||
|
|
@ -2615,7 +2615,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.RawValue);
|
||||
Assert.Null(entry.AttemptedValue);
|
||||
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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue