Support validation and BindBehavior on top-level action parameters and bound properties. Fixes #6790
This commit is contained in:
parent
0fae663b9a
commit
236ef5d1d1
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
|
|
@ -65,6 +66,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
};
|
||||
}
|
||||
|
||||
public static ModelMetadataIdentity ForParameter(ParameterInfo parameter)
|
||||
{
|
||||
if (parameter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parameter));
|
||||
}
|
||||
|
||||
return new ModelMetadataIdentity()
|
||||
{
|
||||
Name = parameter.Name,
|
||||
ModelType = parameter.ParameterType,
|
||||
ParameterInfo = parameter,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Type"/> defining the model property represented by the current
|
||||
/// instance, or <c>null</c> if the current instance does not represent a property.
|
||||
|
|
@ -83,7 +99,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
{
|
||||
get
|
||||
{
|
||||
if (ContainerType != null && Name != null)
|
||||
if (ParameterInfo != null)
|
||||
{
|
||||
return ModelMetadataKind.Parameter;
|
||||
}
|
||||
else if (ContainerType != null && Name != null)
|
||||
{
|
||||
return ModelMetadataKind.Property;
|
||||
}
|
||||
|
|
@ -100,13 +120,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a descriptor for the parameter, or <c>null</c> if this instance
|
||||
/// does not represent a parameter.
|
||||
/// </summary>
|
||||
public ParameterInfo ParameterInfo { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(ModelMetadataIdentity other)
|
||||
{
|
||||
return
|
||||
ContainerType == other.ContainerType &&
|
||||
ModelType == other.ModelType &&
|
||||
Name == other.Name;
|
||||
Name == other.Name &&
|
||||
ParameterInfo == other.ParameterInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -123,6 +150,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
hash.Add(ContainerType);
|
||||
hash.Add(ModelType);
|
||||
hash.Add(Name, StringComparer.Ordinal);
|
||||
hash.Add(ParameterInfo);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,5 +17,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// Used for <see cref="ModelMetadata"/> for a property.
|
||||
/// </summary>
|
||||
Property,
|
||||
|
||||
/// <summary>
|
||||
/// Used for <see cref="ModelMetadata"/> for a parameter.
|
||||
/// </summary>
|
||||
Parameter,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// A provider that can supply instances of <see cref="ModelMetadata"/>.
|
||||
/// </summary>
|
||||
public abstract class ModelMetadataProvider : IModelMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Supplies metadata describing the properties of a <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelType">The <see cref="Type"/>.</param>
|
||||
/// <returns>A set of <see cref="ModelMetadata"/> instances describing properties of the <see cref="Type"/>.</returns>
|
||||
public abstract IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType);
|
||||
|
||||
/// <summary>
|
||||
/// Supplies metadata describing a <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelType">The <see cref="Type"/>.</param>
|
||||
/// <returns>A <see cref="ModelMetadata"/> instance describing the <see cref="Type"/>.</returns>
|
||||
public abstract ModelMetadata GetMetadataForType(Type modelType);
|
||||
|
||||
/// <summary>
|
||||
/// Supplies metadata describing a parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The <see cref="ParameterInfo"/>.</param>
|
||||
/// <returns>A <see cref="ModelMetadata"/> instance describing properties of the <see cref="ActionDescriptor"/>.</returns>
|
||||
public abstract ModelMetadata GetMetadataForParameter(ParameterInfo parameter);
|
||||
}
|
||||
}
|
||||
|
|
@ -231,7 +231,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders);
|
||||
});
|
||||
services.TryAddSingleton<ClientValidatorCache>();
|
||||
services.TryAddSingleton<ParameterBinder>();
|
||||
services.TryAddSingleton<ParameterBinder>(s =>
|
||||
{
|
||||
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||
var metadataProvider = s.GetRequiredService<IModelMetadataProvider>();
|
||||
var modelBinderFactory = s.GetRequiredService<IModelBinderFactory>();
|
||||
var modelValidatorProvider = new CompositeModelValidatorProvider(options.ModelValidatorProviders);
|
||||
return new ParameterBinder(metadataProvider, modelBinderFactory, modelValidatorProvider);
|
||||
});
|
||||
|
||||
//
|
||||
// Random Infrastructure
|
||||
|
|
|
|||
|
|
@ -51,13 +51,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
var parameter = parameters[i];
|
||||
var bindingInfo = parameterBindingInfo[i];
|
||||
var modelMetadata = bindingInfo.ModelMetadata;
|
||||
|
||||
if (!modelMetadata.IsBindingAllowed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
controllerContext,
|
||||
bindingInfo.ModelBinder,
|
||||
valueProvider,
|
||||
parameter,
|
||||
bindingInfo.ModelMetadata,
|
||||
modelMetadata,
|
||||
value: null);
|
||||
|
||||
if (result.IsModelSet)
|
||||
|
|
@ -71,13 +77,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
var property = properties[i];
|
||||
var bindingInfo = propertyBindingInfo[i];
|
||||
var modelMetadata = bindingInfo.ModelMetadata;
|
||||
|
||||
if (!modelMetadata.IsBindingAllowed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
controllerContext,
|
||||
bindingInfo.ModelBinder,
|
||||
valueProvider,
|
||||
property,
|
||||
bindingInfo.ModelMetadata,
|
||||
modelMetadata,
|
||||
value: null);
|
||||
|
||||
if (result.IsModelSet)
|
||||
|
|
@ -103,7 +115,24 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
for (var i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
var metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
|
||||
|
||||
ModelMetadata metadata;
|
||||
if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase
|
||||
&& parameter is ControllerParameterDescriptor controllerParameterDescriptor)
|
||||
{
|
||||
// The default model metadata provider derives from ModelMetadataProvider
|
||||
// and can therefore supply information about attributes applied to parameters.
|
||||
metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For backward compatibility, if there's a custom model metadata provider that
|
||||
// only implements the older IModelMetadataProvider interface, access the more
|
||||
// limited metadata information it supplies. In this scenario, validation attributes
|
||||
// are not supported on parameters.
|
||||
metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
|
||||
}
|
||||
|
||||
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
|
||||
{
|
||||
BindingInfo = parameter.BindingInfo,
|
||||
|
|
|
|||
|
|
@ -68,25 +68,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
context.BindingMetadata.PropertyFilterProvider = composite;
|
||||
}
|
||||
|
||||
if (context.Key.MetadataKind == ModelMetadataKind.Property)
|
||||
var bindingBehavior = FindBindingBehavior(context);
|
||||
if (bindingBehavior != null)
|
||||
{
|
||||
// BindingBehavior can fall back to attributes on the Container Type, but we should ignore
|
||||
// attributes on the Property Type.
|
||||
var bindingBehavior = context.PropertyAttributes.OfType<BindingBehaviorAttribute>().FirstOrDefault();
|
||||
if (bindingBehavior == null)
|
||||
{
|
||||
bindingBehavior =
|
||||
context.Key.ContainerType.GetTypeInfo()
|
||||
.GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true)
|
||||
.OfType<BindingBehaviorAttribute>()
|
||||
.FirstOrDefault();
|
||||
}
|
||||
context.BindingMetadata.IsBindingAllowed = bindingBehavior.Behavior != BindingBehavior.Never;
|
||||
context.BindingMetadata.IsBindingRequired = bindingBehavior.Behavior == BindingBehavior.Required;
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingBehavior != null)
|
||||
{
|
||||
context.BindingMetadata.IsBindingAllowed = bindingBehavior.Behavior != BindingBehavior.Never;
|
||||
context.BindingMetadata.IsBindingRequired = bindingBehavior.Behavior == BindingBehavior.Required;
|
||||
}
|
||||
private static BindingBehaviorAttribute FindBindingBehavior(BindingMetadataProviderContext context)
|
||||
{
|
||||
switch (context.Key.MetadataKind)
|
||||
{
|
||||
case ModelMetadataKind.Property:
|
||||
// BindingBehavior can fall back to attributes on the Container Type, but we should ignore
|
||||
// attributes on the Property Type.
|
||||
var matchingAttributes = context.PropertyAttributes.OfType<BindingBehaviorAttribute>();
|
||||
return matchingAttributes.FirstOrDefault()
|
||||
?? context.Key.ContainerType.GetTypeInfo()
|
||||
.GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true)
|
||||
.OfType<BindingBehaviorAttribute>()
|
||||
.FirstOrDefault();
|
||||
case ModelMetadataKind.Parameter:
|
||||
return context.ParameterAttributes.OfType<BindingBehaviorAttribute>().FirstOrDefault();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// system excludes that property. When applied to a type, the model binding system excludes all properties that
|
||||
/// type defines.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class BindNeverAttribute : BindingBehaviorAttribute
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// requires a value for that property. When applied to a type, the model binding system requires values for all
|
||||
/// properties that type defines.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class BindRequiredAttribute : BindingBehaviorAttribute
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <summary>
|
||||
/// Specifies the <see cref="BindingBehavior"/> that should be applied.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class BindingBehaviorAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
|
||||
Key = key;
|
||||
Attributes = attributes.Attributes;
|
||||
ParameterAttributes = attributes.ParameterAttributes;
|
||||
PropertyAttributes = attributes.PropertyAttributes;
|
||||
TypeAttributes = attributes.TypeAttributes;
|
||||
|
||||
|
|
@ -43,6 +44,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </summary>
|
||||
public ModelMetadataIdentity Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter attributes.
|
||||
/// </summary>
|
||||
public IReadOnlyList<object> ParameterAttributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property attributes.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -259,13 +259,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
{
|
||||
get
|
||||
{
|
||||
if (MetadataKind == ModelMetadataKind.Property)
|
||||
if (MetadataKind == ModelMetadataKind.Type)
|
||||
{
|
||||
return BindingMetadata.IsBindingAllowed;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
return BindingMetadata.IsBindingAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// <summary>
|
||||
/// A default implementation of <see cref="IModelMetadataProvider"/> based on reflection.
|
||||
/// </summary>
|
||||
public class DefaultModelMetadataProvider : IModelMetadataProvider
|
||||
public class DefaultModelMetadataProvider : ModelMetadataProvider
|
||||
{
|
||||
private readonly TypeCache _typeCache = new TypeCache();
|
||||
private readonly Func<ModelMetadataIdentity, ModelMetadataCacheEntry> _cacheEntryFactory;
|
||||
|
|
@ -68,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
protected DefaultModelBindingMessageProvider ModelBindingMessageProvider { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType)
|
||||
public override IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType)
|
||||
{
|
||||
if (modelType == null)
|
||||
{
|
||||
|
|
@ -97,8 +98,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
return cacheEntry.Details.Properties;
|
||||
}
|
||||
|
||||
public override ModelMetadata GetMetadataForParameter(ParameterInfo parameter)
|
||||
{
|
||||
if (parameter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parameter));
|
||||
}
|
||||
|
||||
var cacheEntry = GetCacheEntry(parameter);
|
||||
|
||||
return cacheEntry.Metadata;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ModelMetadata GetMetadataForType(Type modelType)
|
||||
public override ModelMetadata GetMetadataForType(Type modelType)
|
||||
{
|
||||
if (modelType == null)
|
||||
{
|
||||
|
|
@ -139,9 +152,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
return cacheEntry;
|
||||
}
|
||||
|
||||
private ModelMetadataCacheEntry GetCacheEntry(ParameterInfo parameter)
|
||||
{
|
||||
return _typeCache.GetOrAdd(
|
||||
ModelMetadataIdentity.ForParameter(parameter),
|
||||
_cacheEntryFactory);
|
||||
}
|
||||
|
||||
private ModelMetadataCacheEntry CreateCacheEntry(ModelMetadataIdentity key)
|
||||
{
|
||||
var details = CreateTypeDetails(key);
|
||||
DefaultMetadataDetails details;
|
||||
if (key.MetadataKind == ModelMetadataKind.Parameter)
|
||||
{
|
||||
details = CreateParameterDetails(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
details = CreateTypeDetails(key);
|
||||
}
|
||||
|
||||
var metadata = CreateModelMetadata(details);
|
||||
return new ModelMetadataCacheEntry(metadata, details);
|
||||
}
|
||||
|
|
@ -232,15 +261,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </remarks>
|
||||
protected virtual DefaultMetadataDetails CreateTypeDetails(ModelMetadataIdentity key)
|
||||
{
|
||||
return new DefaultMetadataDetails(key, ModelAttributes.GetAttributesForType(key.ModelType));
|
||||
return new DefaultMetadataDetails(
|
||||
key,
|
||||
ModelAttributes.GetAttributesForType(key.ModelType));
|
||||
}
|
||||
|
||||
protected virtual DefaultMetadataDetails CreateParameterDetails(ModelMetadataIdentity key)
|
||||
{
|
||||
return new DefaultMetadataDetails(
|
||||
key,
|
||||
ModelAttributes.GetAttributesForParameter(key.ParameterInfo));
|
||||
}
|
||||
|
||||
private class TypeCache : ConcurrentDictionary<ModelMetadataIdentity, ModelMetadataCacheEntry>
|
||||
{
|
||||
public TypeCache()
|
||||
: base(ModelMetadataIdentityComparer.Instance)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private struct ModelMetadataCacheEntry
|
||||
|
|
@ -255,36 +289,5 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
|
||||
public DefaultMetadataDetails Details { get; }
|
||||
}
|
||||
|
||||
private class ModelMetadataIdentityComparer : IEqualityComparer<ModelMetadataIdentity>
|
||||
{
|
||||
public static readonly ModelMetadataIdentityComparer Instance = new ModelMetadataIdentityComparer();
|
||||
|
||||
public bool Equals(ModelMetadataIdentity x, ModelMetadataIdentity y)
|
||||
{
|
||||
return
|
||||
x.ContainerType == y.ContainerType &&
|
||||
x.ModelType == y.ModelType &&
|
||||
x.Name == y.Name;
|
||||
}
|
||||
|
||||
public int GetHashCode(ModelMetadataIdentity obj)
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 23 + obj.ModelType.GetHashCode();
|
||||
|
||||
if (obj.ContainerType != null)
|
||||
{
|
||||
hash = hash * 23 + obj.ContainerType.GetHashCode();
|
||||
}
|
||||
|
||||
if (obj.Name != null)
|
||||
{
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,27 +5,26 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the combined list of attributes associated a <see cref="Type"/> or property.
|
||||
/// Provides access to the combined list of attributes associated with a <see cref="Type"/>, property, or parameter.
|
||||
/// </summary>
|
||||
public class ModelAttributes
|
||||
{
|
||||
private static readonly IEnumerable<object> _emptyAttributesCollection = Enumerable.Empty<object>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ModelAttributes"/> for a <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <param name="typeAttributes">The set of attributes for the <see cref="Type"/>.</param>
|
||||
[Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative is " + nameof(ModelAttributes) + "." + nameof(GetAttributesForType) + ".")]
|
||||
public ModelAttributes(IEnumerable<object> typeAttributes)
|
||||
: this(typeAttributes, null, null)
|
||||
{
|
||||
if (typeAttributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeAttributes));
|
||||
}
|
||||
|
||||
Attributes = typeAttributes.ToArray();
|
||||
TypeAttributes = Attributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -35,21 +34,57 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <param name="typeAttributes">
|
||||
/// The set of attributes for the property's <see cref="Type"/>. See <see cref="PropertyInfo.PropertyType"/>.
|
||||
/// </param>
|
||||
[Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative is " + nameof(ModelAttributes) + "." + nameof(GetAttributesForProperty) + ".")]
|
||||
public ModelAttributes(IEnumerable<object> propertyAttributes, IEnumerable<object> typeAttributes)
|
||||
: this(typeAttributes, propertyAttributes, null)
|
||||
{
|
||||
if (propertyAttributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyAttributes));
|
||||
}
|
||||
}
|
||||
|
||||
if (typeAttributes == null)
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ModelAttributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="typeAttributes">
|
||||
/// If this instance represents a type, the set of attributes for that type.
|
||||
/// If this instance represents a property, the set of attributes for the property's <see cref="Type"/>.
|
||||
/// Otherwise, <c>null</c>.
|
||||
/// </param>
|
||||
/// <param name="propertyAttributes">
|
||||
/// If this instance represents a property, the set of attributes for that property.
|
||||
/// Otherwise, <c>null</c>.
|
||||
/// </param>
|
||||
/// <param name="parameterAttributes">
|
||||
/// If this instance represents a parameter, the set of attributes for that parameter.
|
||||
/// Otherwise, <c>null</c>.
|
||||
/// </param>
|
||||
public ModelAttributes(IEnumerable<object> typeAttributes, IEnumerable<object> propertyAttributes, IEnumerable<object> parameterAttributes)
|
||||
{
|
||||
if (propertyAttributes != null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeAttributes));
|
||||
}
|
||||
// Represents a property
|
||||
if (typeAttributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeAttributes));
|
||||
}
|
||||
|
||||
PropertyAttributes = propertyAttributes.ToArray();
|
||||
TypeAttributes = typeAttributes.ToArray();
|
||||
Attributes = PropertyAttributes.Concat(TypeAttributes).ToArray();
|
||||
PropertyAttributes = propertyAttributes.ToArray();
|
||||
TypeAttributes = typeAttributes.ToArray();
|
||||
Attributes = PropertyAttributes.Concat(TypeAttributes).ToArray();
|
||||
}
|
||||
else if (parameterAttributes != null)
|
||||
{
|
||||
// Represents a parameter
|
||||
Attributes = ParameterAttributes = parameterAttributes.ToArray();
|
||||
}
|
||||
else if (typeAttributes != null)
|
||||
{
|
||||
// Represents a type
|
||||
if (typeAttributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeAttributes));
|
||||
}
|
||||
|
||||
Attributes = TypeAttributes = typeAttributes.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -59,15 +94,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public IReadOnlyList<object> Attributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of attributes on the property, or <c>null</c> if this instance represents the attributes
|
||||
/// for a <see cref="Type"/>.
|
||||
/// Gets the set of attributes on the property, or <c>null</c> if this instance does not represent the attributes
|
||||
/// for a property.
|
||||
/// </summary>
|
||||
public IReadOnlyList<object> PropertyAttributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of attributes on the parameter, or <c>null</c> if this instance does not represent the attributes
|
||||
/// for a parameter.
|
||||
/// </summary>
|
||||
public IReadOnlyList<object> ParameterAttributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of attributes on the <see cref="Type"/>. If this instance represents a property,
|
||||
/// then <see cref="TypeAttributes"/> contains attributes retrieved from
|
||||
/// <see cref="PropertyInfo.PropertyType"/>.
|
||||
/// <see cref="PropertyInfo.PropertyType"/>. If this instance represents a parameter, then
|
||||
/// the value is <c>null</c>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<object> TypeAttributes { get; }
|
||||
|
||||
|
|
@ -104,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
return new ModelAttributes(propertyAttributes, typeAttributes);
|
||||
return new ModelAttributes(typeAttributes, propertyAttributes, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -128,7 +170,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
attributes = attributes.Concat(metadataType.GetTypeInfo().GetCustomAttributes());
|
||||
}
|
||||
|
||||
return new ModelAttributes(attributes);
|
||||
return new ModelAttributes(attributes, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attributes for the given <paramref name="parameterInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="parameterInfo">The <see cref="ParameterInfo"/> for which attributes need to be resolved.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="ModelAttributes"/> instance with the attributes of the <see cref="ParameterInfo"/>.</returns>
|
||||
public static ModelAttributes GetAttributesForParameter(ParameterInfo parameterInfo)
|
||||
{
|
||||
return new ModelAttributes(null, null, parameterInfo.GetCustomAttributes());
|
||||
}
|
||||
|
||||
private static Type GetMetadataType(Type type)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
|
|
@ -15,18 +16,55 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||
private readonly IModelBinderFactory _modelBinderFactory;
|
||||
private readonly IObjectModelValidator _validator;
|
||||
private readonly IObjectModelValidator _validatorForBackCompatOnly;
|
||||
private readonly IModelValidatorProvider _validatorProvider;
|
||||
private readonly ValidatorCache _validatorCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ParameterDescriptor"/>.
|
||||
/// Initializes a new instance of <see cref="ParameterBinder"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/>.</param>
|
||||
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param>
|
||||
public ParameterBinder(
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IModelValidatorProvider validatorProvider)
|
||||
: this(modelMetadataProvider, modelBinderFactory, validatorProvider, null)
|
||||
{
|
||||
if (validatorProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(validatorProvider));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ParameterBinder"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/>.</param>
|
||||
/// <param name="validator">The <see cref="IObjectModelValidator"/>.</param>
|
||||
[Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative is the overload that takes a " + nameof(IModelValidatorProvider) + " instead of a " + nameof(IObjectModelValidator) + ".")]
|
||||
public ParameterBinder(
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IObjectModelValidator validator)
|
||||
: this(modelMetadataProvider, modelBinderFactory, null, validator)
|
||||
{
|
||||
// Note: When this obsolete constructor overload is removed, also remember
|
||||
// to remove the validatorForBackCompatOnly field.
|
||||
|
||||
if (validator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(validator));
|
||||
}
|
||||
}
|
||||
|
||||
private ParameterBinder(
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IModelValidatorProvider validatorProvider,
|
||||
IObjectModelValidator validatorForBackCompatOnly)
|
||||
{
|
||||
if (modelMetadataProvider == null)
|
||||
{
|
||||
|
|
@ -38,14 +76,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||
}
|
||||
|
||||
if (validator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(validator));
|
||||
}
|
||||
|
||||
_modelMetadataProvider = modelMetadataProvider;
|
||||
_modelBinderFactory = modelBinderFactory;
|
||||
_validator = validator;
|
||||
_validatorProvider = validatorProvider;
|
||||
_validatorForBackCompatOnly = validatorForBackCompatOnly;
|
||||
_validatorCache = new ValidatorCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -185,16 +220,58 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
await modelBinder.BindModelAsync(modelBindingContext);
|
||||
|
||||
var modelBindingResult = modelBindingContext.Result;
|
||||
if (modelBindingResult.IsModelSet)
|
||||
|
||||
if (_validatorForBackCompatOnly != null)
|
||||
{
|
||||
_validator.Validate(
|
||||
// Since we don't have access to an IModelValidatorProvider, fall back
|
||||
// on back-compatibility logic. In this scenario, top-level validation
|
||||
// attributes will be ignored like they were historically.
|
||||
if (modelBindingResult.IsModelSet)
|
||||
{
|
||||
_validatorForBackCompatOnly.Validate(
|
||||
actionContext,
|
||||
modelBindingContext.ValidationState,
|
||||
modelBindingContext.ModelName,
|
||||
modelBindingResult.Model);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EnforceBindRequiredAndValidate(
|
||||
actionContext,
|
||||
modelBindingContext.ValidationState,
|
||||
modelBindingContext.ModelName,
|
||||
modelBindingResult.Model);
|
||||
metadata,
|
||||
modelBindingContext,
|
||||
modelBindingResult);
|
||||
}
|
||||
|
||||
return modelBindingResult;
|
||||
}
|
||||
|
||||
private void EnforceBindRequiredAndValidate(ActionContext actionContext, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult)
|
||||
{
|
||||
if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired)
|
||||
{
|
||||
// Enforce BindingBehavior.Required (e.g., [BindRequired])
|
||||
var modelName = modelBindingContext.FieldName;
|
||||
var message = metadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(modelName);
|
||||
actionContext.ModelState.TryAddModelError(modelName, message);
|
||||
}
|
||||
else if (modelBindingResult.IsModelSet || metadata.IsRequired)
|
||||
{
|
||||
// Enforce any other validation rules
|
||||
var visitor = new ValidationVisitor(
|
||||
actionContext,
|
||||
_validatorProvider,
|
||||
_validatorCache,
|
||||
_modelMetadataProvider,
|
||||
modelBindingContext.ValidationState);
|
||||
|
||||
visitor.Validate(
|
||||
metadata,
|
||||
modelBindingContext.ModelName,
|
||||
modelBindingResult.Model,
|
||||
alwaysValidateAtTopLevel: metadata.IsRequired);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
|||
/// <returns><c>true</c> if the object is valid, otherwise <c>false</c>.</returns>
|
||||
public bool Validate(ModelMetadata metadata, string key, object model)
|
||||
{
|
||||
if (model == null && key != null)
|
||||
return Validate(metadata, key, model, alwaysValidateAtTopLevel: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a object.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="ModelMetadata"/> associated with the model.</param>
|
||||
/// <param name="key">The model prefix key.</param>
|
||||
/// <param name="model">The model object.</param>
|
||||
/// <param name="alwaysValidateAtTopLevel">If <c>true</c>, applies validation rules even if the top-level value is <c>null</c>.</param>
|
||||
/// <returns><c>true</c> if the object is valid, otherwise <c>false</c>.</returns>
|
||||
public bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel)
|
||||
{
|
||||
if (model == null && key != null && !alwaysValidateAtTopLevel)
|
||||
{
|
||||
var entry = _modelState[key];
|
||||
if (entry != null && entry.ValidationState != ModelValidationState.Valid)
|
||||
|
|
@ -128,6 +141,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
|||
{
|
||||
var result = results[i];
|
||||
var key = ModelNames.CreatePropertyModelName(_key, result.MemberName);
|
||||
|
||||
// If this is a top-level parameter/property, the key would be empty,
|
||||
// so use the name of the top-level property
|
||||
if (string.IsNullOrEmpty(key) && _metadata.PropertyName != null)
|
||||
{
|
||||
key = _metadata.PropertyName;
|
||||
}
|
||||
|
||||
_modelState.TryAddModelError(key, result.Message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
/// </summary>
|
||||
public class DataAnnotationsModelValidator : IModelValidator
|
||||
{
|
||||
private static readonly object _emptyValidationContextInstance = new object();
|
||||
private readonly IStringLocalizer _stringLocalizer;
|
||||
private readonly IValidationAttributeAdapterProvider _validationAttributeAdapterProvider;
|
||||
|
||||
|
|
@ -83,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
var container = validationContext.Container;
|
||||
|
||||
var context = new ValidationContext(
|
||||
instance: container ?? validationContext.Model,
|
||||
instance: container ?? validationContext.Model ?? _emptyValidationContextInstance,
|
||||
serviceProvider: validationContext.ActionContext?.HttpContext?.RequestServices,
|
||||
items: null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
modelBinderFactory,
|
||||
Mock.Of<IObjectModelValidator>()),
|
||||
Mock.Of<IModelValidatorProvider>()),
|
||||
modelBinderFactory,
|
||||
modelMetadataProvider,
|
||||
filterProviders,
|
||||
|
|
|
|||
|
|
@ -13,15 +13,110 @@ using Microsoft.AspNetCore.Mvc.Formatters;
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class ControllerBinderDelegateProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_DoesNotAddActionArgumentsOrCallBinderOrValidator_IfBindingIsNotAllowed_OnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.Parameters.Add(
|
||||
new ControllerParameterDescriptor
|
||||
{
|
||||
Name = "foo",
|
||||
ParameterType = typeof(object),
|
||||
BindingInfo = new BindingInfo(),
|
||||
ParameterInfo = ParameterInfos.BindNeverParameterInfo
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Verifiable();
|
||||
|
||||
var mockValidator = CreateMockValidator();
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var controller = new TestController();
|
||||
var parameterBinder = GetParameterBinder(factory);
|
||||
|
||||
// Act
|
||||
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
|
||||
parameterBinder,
|
||||
factory,
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
actionDescriptor);
|
||||
|
||||
await binderDelegate(controllerContext, controller, arguments);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(arguments);
|
||||
binder
|
||||
.Verify(o => o.BindModelAsync(
|
||||
It.IsAny<DefaultModelBindingContext>()),
|
||||
Times.Never());
|
||||
mockValidator
|
||||
.Verify(o => o.Validate(
|
||||
It.IsAny<ModelValidationContext>()),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_DoesNotAddActionArgumentsOrCallBinderOrValidator_IfBindingIsNotAllowed_OnProperty()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.BoundProperties.Add(
|
||||
new ParameterDescriptor
|
||||
{
|
||||
Name = nameof(TestController.RequiredButBindNeverProperty),
|
||||
ParameterType = typeof(object)
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Verifiable();
|
||||
|
||||
var mockValidator = CreateMockValidator();
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var controller = new TestController();
|
||||
var parameterBinder = GetParameterBinder(factory);
|
||||
|
||||
// Act
|
||||
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
|
||||
parameterBinder,
|
||||
factory,
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
actionDescriptor);
|
||||
|
||||
await binderDelegate(controllerContext, controller, arguments);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(arguments);
|
||||
binder
|
||||
.Verify(o => o.BindModelAsync(
|
||||
It.IsAny<DefaultModelBindingContext>()),
|
||||
Times.Never());
|
||||
mockValidator
|
||||
.Verify(o => o.Validate(
|
||||
It.IsAny<ModelValidationContext>()),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsNull()
|
||||
{
|
||||
|
|
@ -100,7 +195,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public async Task BindActionArgumentsAsync_AddsActionArguments_IfBinderReturnsNotNull()
|
||||
{
|
||||
// Arrange
|
||||
Func<object, int> method = foo => 1;
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.Parameters.Add(
|
||||
new ParameterDescriptor
|
||||
|
|
@ -143,31 +237,120 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Equal(value, arguments["foo"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_GetsMetadataFromParameter()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.Parameters.Add(
|
||||
new ControllerParameterDescriptor
|
||||
{
|
||||
Name = "foo",
|
||||
ParameterType = typeof(object),
|
||||
ParameterInfo = ParameterInfos.NoAttributesParameterInfo
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var mockBinder = new Mock<IModelBinder>();
|
||||
var factory = GetModelBinderFactory(mockBinder.Object);
|
||||
|
||||
var parameterBinder = GetParameterBinder(factory, CreateMockValidator().Object);
|
||||
var controller = new TestController();
|
||||
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
var modelMetadata = new Mock<FakeModelMetadata>();
|
||||
modelMetadata.Setup(m => m.IsBindingAllowed).Returns(true);
|
||||
var mockMetadataProvider = new Mock<DefaultModelMetadataProvider>(
|
||||
Mock.Of<ICompositeMetadataDetailsProvider>());
|
||||
mockMetadataProvider
|
||||
.Setup(p => p.GetMetadataForParameter(ParameterInfos.NoAttributesParameterInfo))
|
||||
.Returns(modelMetadata.Object);
|
||||
|
||||
// Act
|
||||
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
|
||||
parameterBinder,
|
||||
factory,
|
||||
mockMetadataProvider.Object,
|
||||
actionDescriptor);
|
||||
|
||||
await binderDelegate(controllerContext, controller, arguments);
|
||||
|
||||
// Assert
|
||||
mockBinder
|
||||
.Verify(o => o.BindModelAsync(
|
||||
It.Is<ModelBindingContext>(context => context.ModelMetadata == modelMetadata.Object)),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_GetsMetadataFromType_IsMetadataProviderIsNotDefaultMetadataProvider()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.Parameters.Add(
|
||||
new ControllerParameterDescriptor
|
||||
{
|
||||
Name = "foo",
|
||||
ParameterType = typeof(Person)
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var mockBinder = new Mock<IModelBinder>();
|
||||
var factory = GetModelBinderFactory(mockBinder.Object);
|
||||
|
||||
var parameterBinder = GetParameterBinder(factory, CreateMockValidator().Object);
|
||||
var controller = new TestController();
|
||||
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
var modelMetadata = new Mock<FakeModelMetadata>();
|
||||
modelMetadata.Setup(m => m.IsBindingAllowed).Returns(true);
|
||||
var mockMetadataProvider = new Mock<IModelMetadataProvider>();
|
||||
mockMetadataProvider
|
||||
.Setup(p => p.GetMetadataForType(typeof(Person)))
|
||||
.Returns(modelMetadata.Object);
|
||||
|
||||
// Act
|
||||
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
|
||||
parameterBinder,
|
||||
factory,
|
||||
mockMetadataProvider.Object,
|
||||
actionDescriptor);
|
||||
|
||||
await binderDelegate(controllerContext, controller, arguments);
|
||||
|
||||
// Assert
|
||||
mockBinder
|
||||
.Verify(o => o.BindModelAsync(
|
||||
It.Is<ModelBindingContext>(context => context.ModelMetadata == modelMetadata.Object)),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_CallsValidator_IfModelBinderSucceeds()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.Parameters.Add(
|
||||
new ParameterDescriptor
|
||||
new ControllerParameterDescriptor
|
||||
{
|
||||
Name = "foo",
|
||||
ParameterType = typeof(object),
|
||||
ParameterInfo = ParameterInfos.CustomValidationParameterInfo
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var factory = GetModelBinderFactory("Hello");
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
var mockValidator = CreateMockValidator();
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||
.Returns(new[] { new ModelValidationResult("memberName", "some message") });
|
||||
|
||||
var parameterBinder = GetParameterBinder(factory, mockValidator.Object);
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var parameterBinder = GetParameterBinder(factory, mockValidator.Object, modelMetadataProvider);
|
||||
var controller = new TestController();
|
||||
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
|
|
@ -175,7 +358,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
|
||||
parameterBinder,
|
||||
factory,
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
modelMetadataProvider,
|
||||
actionDescriptor);
|
||||
|
||||
await binderDelegate(controllerContext, controller, arguments);
|
||||
|
|
@ -183,18 +366,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Assert
|
||||
mockValidator
|
||||
.Verify(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()),
|
||||
It.IsAny<ModelValidationContext>()),
|
||||
Times.Once());
|
||||
|
||||
Assert.False(controllerContext.ModelState.IsValid);
|
||||
Assert.Equal(
|
||||
"some message",
|
||||
controllerContext.ModelState["memberName"].Errors.Single().ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_DoesNotCallValidator_IfModelBinderFails()
|
||||
{
|
||||
// Arrange
|
||||
Func<object, int> method = foo => 1;
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.Parameters.Add(
|
||||
new ParameterDescriptor
|
||||
|
|
@ -212,14 +396,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
|
||||
var mockValidator = CreateMockValidator();
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var controller = new TestController();
|
||||
var parameterBinder = GetParameterBinder(factory, mockValidator.Object);
|
||||
|
|
@ -236,10 +413,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Assert
|
||||
mockValidator
|
||||
.Verify(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()),
|
||||
It.IsAny<ModelValidationContext>()),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +425,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionDescriptor.BoundProperties.Add(
|
||||
new ParameterDescriptor
|
||||
{
|
||||
Name = nameof(TestController.StringProperty),
|
||||
Name = nameof(TestController.ValidatedProperty),
|
||||
ParameterType = typeof(string),
|
||||
});
|
||||
|
||||
|
|
@ -259,22 +433,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var controller = new TestController();
|
||||
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
var mockValidator = CreateMockValidator();
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||
.Returns(new[] { new ModelValidationResult("memberName", "some message") });
|
||||
|
||||
var factory = GetModelBinderFactory("Hello");
|
||||
var parameterBinder = GetParameterBinder(factory, mockValidator.Object);
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var parameterBinder = GetParameterBinder(factory, mockValidator.Object, modelMetadataProvider);
|
||||
|
||||
// Act
|
||||
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
|
||||
parameterBinder,
|
||||
factory,
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
modelMetadataProvider,
|
||||
actionDescriptor);
|
||||
|
||||
await binderDelegate(controllerContext, controller, arguments);
|
||||
|
|
@ -282,18 +454,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Assert
|
||||
mockValidator
|
||||
.Verify(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()),
|
||||
It.IsAny<ModelValidationContext>()),
|
||||
Times.Once());
|
||||
Assert.False(controllerContext.ModelState.IsValid);
|
||||
Assert.Equal(
|
||||
"some message",
|
||||
controllerContext.ModelState["memberName"].Errors.Single().ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindActionArgumentsAsync_DoesNotCallValidator_ForControllerProperties_IfModelBinderFails()
|
||||
{
|
||||
// Arrange
|
||||
Func<object, int> method = foo => 1;
|
||||
var actionDescriptor = GetActionDescriptor();
|
||||
actionDescriptor.BoundProperties.Add(
|
||||
new ParameterDescriptor
|
||||
|
|
@ -311,14 +483,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
|
||||
var mockValidator = CreateMockValidator();
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var parameterBinder = GetParameterBinder(factory, mockValidator.Object);
|
||||
|
||||
|
|
@ -334,10 +499,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Assert
|
||||
mockValidator
|
||||
.Verify(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()),
|
||||
It.IsAny<ModelValidationContext>()),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
|
|
@ -845,10 +1007,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var modelMetadataProvider = new EmptyModelMetadataProvider();
|
||||
var modelBinderProvider = new BodyModelBinderProvider(new[] { Mock.Of<IInputFormatter>() }, Mock.Of<IHttpRequestStreamReaderFactory>());
|
||||
var factory = TestModelBinderFactory.CreateDefault(modelBinderProvider);
|
||||
var modelValidatorProvider = new Mock<IModelValidatorProvider>(MockBehavior.Strict).Object;
|
||||
var parameterBinder = new Mock<ParameterBinder>(
|
||||
new EmptyModelMetadataProvider(),
|
||||
factory,
|
||||
CreateMockValidator());
|
||||
modelValidatorProvider);
|
||||
parameterBinder.Setup(p => p.BindModelAsync(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<IModelBinder>(),
|
||||
|
|
@ -959,11 +1122,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
private static ParameterBinder GetParameterBinder(
|
||||
IModelBinderFactory factory = null,
|
||||
IObjectModelValidator validator = null)
|
||||
IModelValidator validator = null,
|
||||
IModelMetadataProvider modelMetadataProvider = null)
|
||||
{
|
||||
if (validator == null)
|
||||
{
|
||||
validator = CreateMockValidator();
|
||||
validator = CreateMockValidator().Object;
|
||||
}
|
||||
|
||||
if (factory == null)
|
||||
|
|
@ -971,22 +1135,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
factory = TestModelBinderFactory.CreateDefault();
|
||||
}
|
||||
|
||||
var validatorProvider = new Mock<IModelValidatorProvider>();
|
||||
validatorProvider
|
||||
.Setup(p => p.CreateValidators(It.IsAny<ModelValidatorProviderContext>()))
|
||||
.Callback<ModelValidatorProviderContext>(context =>
|
||||
{
|
||||
foreach (var result in context.Results)
|
||||
{
|
||||
result.Validator = validator;
|
||||
result.IsReusable = true;
|
||||
}
|
||||
});
|
||||
|
||||
return new ParameterBinder(
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
modelMetadataProvider ?? TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
factory,
|
||||
validator);
|
||||
validatorProvider.Object);
|
||||
}
|
||||
|
||||
private static IObjectModelValidator CreateMockValidator()
|
||||
private static Mock<IModelValidator> CreateMockValidator()
|
||||
{
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
return mockValidator.Object;
|
||||
It.IsAny<ModelValidationContext>()));
|
||||
return mockValidator;
|
||||
}
|
||||
|
||||
// No need for bind-related attributes on properties in this controller class. Properties are added directly
|
||||
|
|
@ -1008,6 +1181,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public int NonNullableProperty { get; set; }
|
||||
|
||||
public int? NullableProperty { get; set; }
|
||||
|
||||
[CustomValidation("Test message")] public string ValidatedProperty { get; set; }
|
||||
|
||||
// Despite being "required", the BindNever means this property won't be involved
|
||||
// in binding, so no validation will be performed
|
||||
[Required, BindNever] public string RequiredButBindNeverProperty { get; set; }
|
||||
}
|
||||
|
||||
private class Person : IEquatable<Person>, IEquatable<object>
|
||||
|
|
@ -1034,5 +1213,53 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public BindingSource BindingSource { get { return BindingSource.Query; } }
|
||||
}
|
||||
|
||||
private class CustomValidationAttribute : Attribute, IModelValidator
|
||||
{
|
||||
public string Message { get; }
|
||||
|
||||
public CustomValidationAttribute(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
|
||||
{
|
||||
yield return new ModelValidationResult(context.ModelMetadata.BinderModelName, Message);
|
||||
}
|
||||
}
|
||||
|
||||
private class ParameterInfos
|
||||
{
|
||||
public void Method(
|
||||
object param1,
|
||||
[BindNever] object param2,
|
||||
[CustomValidation("some message")] string param3)
|
||||
{
|
||||
}
|
||||
|
||||
public static ParameterInfo NoAttributesParameterInfo
|
||||
= typeof(ParameterInfos)
|
||||
.GetMethod(nameof(ParameterInfos.Method))
|
||||
.GetParameters()[0];
|
||||
|
||||
public static ParameterInfo BindNeverParameterInfo
|
||||
= typeof(ParameterInfos)
|
||||
.GetMethod(nameof(ParameterInfos.Method))
|
||||
.GetParameters()[1];
|
||||
|
||||
public static ParameterInfo CustomValidationParameterInfo
|
||||
= typeof(ParameterInfos)
|
||||
.GetMethod(nameof(ParameterInfos.Method))
|
||||
.GetParameters()[2];
|
||||
}
|
||||
|
||||
public abstract class FakeModelMetadata : ModelMetadata
|
||||
{
|
||||
public FakeModelMetadata()
|
||||
: base(ModelMetadataIdentity.ForType(typeof(string)))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
|
|
@ -22,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
new ModelAttributes(attributes, null, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
new ModelAttributes(attributes, null, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -69,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
new ModelAttributes(attributes, null, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -93,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
new ModelAttributes(attributes, null, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -116,7 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
new ModelAttributes(attributes, null, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -140,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
new ModelAttributes(attributes, null, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -162,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -185,7 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -208,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -231,7 +233,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -254,7 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -266,6 +268,147 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.True(context.BindingMetadata.IsBindingRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBindingDetails_FindsBindingBehaviorNever_OnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var parameterAttributes = new object[]
|
||||
{
|
||||
new BindingBehaviorAttribute(BindingBehavior.Never),
|
||||
};
|
||||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
|
||||
new ModelAttributes(null, null, parameterAttributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.BindingMetadata.IsBindingAllowed);
|
||||
Assert.False(context.BindingMetadata.IsBindingRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBindingDetails_FindsBindNever_OnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var parameterAttributes = new object[]
|
||||
{
|
||||
new BindNeverAttribute(),
|
||||
};
|
||||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
|
||||
new ModelAttributes(null, null, parameterAttributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.BindingMetadata.IsBindingAllowed);
|
||||
Assert.False(context.BindingMetadata.IsBindingRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBindingDetails_FindsBindingBehaviorOptional_OnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var parameterAttributes = new object[]
|
||||
{
|
||||
new BindingBehaviorAttribute(BindingBehavior.Optional),
|
||||
};
|
||||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
|
||||
new ModelAttributes(null, null, parameterAttributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.BindingMetadata.IsBindingAllowed);
|
||||
Assert.False(context.BindingMetadata.IsBindingRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBindingDetails_FindsBindingBehaviorRequired_OnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var parameterAttributes = new object[]
|
||||
{
|
||||
new BindingBehaviorAttribute(BindingBehavior.Required),
|
||||
};
|
||||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
|
||||
new ModelAttributes(null, null, parameterAttributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.BindingMetadata.IsBindingAllowed);
|
||||
Assert.True(context.BindingMetadata.IsBindingRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBindingDetails_FindsBindRequired_OnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var parameterAttributes = new object[]
|
||||
{
|
||||
new BindRequiredAttribute(),
|
||||
};
|
||||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
|
||||
new ModelAttributes(null, null, parameterAttributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.BindingMetadata.IsBindingAllowed);
|
||||
Assert.True(context.BindingMetadata.IsBindingRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBindingDetails_FindsCustomAttributes_OnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var parameterAttributes = new object[]
|
||||
{
|
||||
new CustomAttribute { Identifier = "Instance1" },
|
||||
new CustomAttribute { Identifier = "Instance2" }
|
||||
};
|
||||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
|
||||
new ModelAttributes(null, null, parameterAttributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Attributes,
|
||||
a => Assert.Equal("Instance1", ((CustomAttribute)a).Identifier),
|
||||
a => Assert.Equal("Instance2", ((CustomAttribute)a).Identifier));
|
||||
Assert.Equal(2, context.ParameterAttributes.Count);
|
||||
}
|
||||
|
||||
// These attributes have conflicting behavior - the 'required' behavior should be used because
|
||||
// of ordering.
|
||||
[Fact]
|
||||
|
|
@ -280,7 +423,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -298,7 +441,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Arrange
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -316,7 +459,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Arrange
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -334,7 +477,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Arrange
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -357,7 +500,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -380,7 +523,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -403,7 +546,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -426,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], propertyAttributes, null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -445,7 +588,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Arrange
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOverridesInheritedBindNever)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
|
|
@ -470,7 +613,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
new ModelAttributes(attributes, null, null));
|
||||
|
||||
// These values shouldn't be changed since this is a Type-Metadata
|
||||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||
|
|
@ -501,7 +644,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var context = new BindingMetadataProviderContext(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: typeAttributes));
|
||||
new ModelAttributes(typeAttributes, new object[0], null));
|
||||
|
||||
// These values shouldn't be changed since this is a Type-Metadata
|
||||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||
|
|
@ -545,5 +688,22 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
BindingSource = bindingSource;
|
||||
}
|
||||
}
|
||||
|
||||
private class ParameterInfos
|
||||
{
|
||||
public void Method(object param1)
|
||||
{
|
||||
}
|
||||
|
||||
public static ParameterInfo SampleParameterInfo
|
||||
= typeof(ParameterInfos)
|
||||
.GetMethod(nameof(ParameterInfos.Method))
|
||||
.GetParameters()[0];
|
||||
}
|
||||
|
||||
private class CustomAttribute : Attribute
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -448,7 +448,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
: base(
|
||||
new EmptyModelMetadataProvider(),
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>())
|
||||
Mock.Of<IModelValidatorProvider>())
|
||||
{
|
||||
_actionParameters = actionParameters;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(Test));
|
||||
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
|
|||
|
|
@ -197,6 +197,61 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForParameter_SuppliesEmptyAttributes_WhenParameterHasNoAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
var parameters = typeof(ModelType)
|
||||
.GetMethod(nameof(ModelType.Method1))
|
||||
.GetParameters();
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForParameter(parameters[0]);
|
||||
|
||||
// Assert
|
||||
var defaultMetadata = Assert.IsType<DefaultModelMetadata>(metadata);
|
||||
Assert.Empty(defaultMetadata.Attributes.Attributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForParameter_SuppliesAttributes_WhenParamHasAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
var parameters = typeof(ModelType)
|
||||
.GetMethod(nameof(ModelType.Method1))
|
||||
.GetParameters();
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForParameter(parameters[1]);
|
||||
|
||||
// Assert
|
||||
var defaultMetadata = Assert.IsType<DefaultModelMetadata>(metadata);
|
||||
Assert.Equal(2, defaultMetadata.Attributes.Attributes.Count);
|
||||
var attribute1 = Assert.IsType<ModelAttribute>(defaultMetadata.Attributes.Attributes[0]);
|
||||
Assert.Equal("ParamAttrib1", attribute1.Value);
|
||||
var attribute2 = Assert.IsType<ModelAttribute>(defaultMetadata.Attributes.Attributes[1]);
|
||||
Assert.Equal("ParamAttrib2", attribute2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForParameter_Cached()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
var parameter = typeof(ModelType)
|
||||
.GetMethod(nameof(ModelType.Method1))
|
||||
.GetParameters()[1];
|
||||
|
||||
// Act
|
||||
var metadata1 = provider.GetMetadataForParameter(parameter);
|
||||
var metadata2 = provider.GetMetadataForParameter(parameter);
|
||||
|
||||
// Assert
|
||||
Assert.Same(metadata1, metadata2);
|
||||
}
|
||||
|
||||
private static DefaultModelMetadataProvider CreateProvider()
|
||||
{
|
||||
return new DefaultModelMetadataProvider(
|
||||
|
|
@ -211,6 +266,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
public PropertyType Property1 { get; } = new PropertyType();
|
||||
|
||||
public PropertyType Property2 { get; set; }
|
||||
|
||||
public void Method1(
|
||||
object paramWithNoAttributes,
|
||||
[Model("ParamAttrib1"), Model("ParamAttrib2")] object paramWithTwoAttributes)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Model("OnPropertyType")]
|
||||
|
|
@ -218,6 +279,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
|
||||
private class ModelAttribute : Attribute
|
||||
{
|
||||
public ModelAttribute(string value)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
Enumerable.Empty<IMetadataDetailsProvider>());
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
// Act
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
Enumerable.Empty<IMetadataDetailsProvider>());
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(Exception));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
// Act
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(string), "Message", typeof(Exception));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
// Act
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
|
@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(modelType);
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(modelType);
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(modelType);
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
cache.BindingMetadata = new BindingMetadata()
|
||||
{
|
||||
IsBindingAllowed = false, // Will be ignored.
|
||||
|
|
@ -213,7 +213,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(modelType);
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
cache.BindingMetadata = new BindingMetadata()
|
||||
{
|
||||
IsBindingRequired = true, // Will be ignored.
|
||||
|
|
@ -239,7 +239,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(modelType);
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -260,7 +260,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(modelType);
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -285,13 +285,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
detailsProvider,
|
||||
new DefaultMetadataDetails(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)),
|
||||
attributes: new ModelAttributes(new object[0], new object[0]))),
|
||||
attributes: new ModelAttributes(new object[0], new object[0], null))),
|
||||
new DefaultModelMetadata(
|
||||
provider.Object,
|
||||
detailsProvider,
|
||||
new DefaultMetadataDetails(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), "Prop2", typeof(string)),
|
||||
attributes: new ModelAttributes(new object[0], new object[0]))),
|
||||
attributes: new ModelAttributes(new object[0], new object[0], null))),
|
||||
};
|
||||
|
||||
provider
|
||||
|
|
@ -299,7 +299,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
.Returns(expectedProperties);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
||||
|
||||
|
|
@ -361,7 +361,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
detailsProvider,
|
||||
new DefaultMetadataDetails(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), originalName, typeof(string)),
|
||||
attributes: new ModelAttributes(new object[0], new object[0]))));
|
||||
attributes: new ModelAttributes(new object[0], new object[0], null))));
|
||||
}
|
||||
|
||||
provider
|
||||
|
|
@ -369,7 +369,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
.Returns(expectedProperties);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
||||
|
||||
|
|
@ -461,7 +461,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
{
|
||||
var propertyCache = new DefaultMetadataDetails(
|
||||
ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)),
|
||||
attributes: new ModelAttributes(new object[0], new object[0]));
|
||||
attributes: new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
propertyCache.DisplayMetadata = new DisplayMetadata();
|
||||
propertyCache.DisplayMetadata.Order = kvp.Value;
|
||||
|
|
@ -477,7 +477,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
.Returns(expectedProperties);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
||||
|
||||
|
|
@ -497,7 +497,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -518,7 +518,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -539,7 +539,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(int[]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
cache.BindingMetadata = new BindingMetadata()
|
||||
{
|
||||
IsReadOnly = true, // Will be ignored.
|
||||
|
|
@ -562,7 +562,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -581,7 +581,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -600,7 +600,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -624,7 +624,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(modelType);
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -651,7 +651,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
|
||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||
|
||||
|
|
@ -683,7 +683,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(int));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
cache.ValidationMetadata = new ValidationMetadata
|
||||
{
|
||||
PropertyValidationFilter = value,
|
||||
|
|
@ -706,7 +706,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(int));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
cache.ValidationMetadata = new ValidationMetadata()
|
||||
{
|
||||
ValidateChildren = true,
|
||||
|
|
@ -729,7 +729,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var provider = new DefaultModelMetadataProvider(detailsProvider);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(XmlDocument));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
cache.ValidationMetadata = new ValidationMetadata()
|
||||
{
|
||||
ValidateChildren = false,
|
||||
|
|
@ -750,7 +750,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
// Arrange
|
||||
var detailsProvider = new Mock<ICompositeMetadataDetailsProvider>();
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||
metadataProvider
|
||||
.Setup(mp => mp.GetMetadataForType(typeof(string)))
|
||||
|
|
@ -770,7 +770,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
// Arrange
|
||||
var detailsProvider = new Mock<ICompositeMetadataDetailsProvider>();
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
|
||||
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||
metadataProvider
|
||||
.Setup(mp => mp.GetMetadataForProperties(typeof(Exception)))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
|
||||
var attributes = new Attribute[] { new ValidateNeverAttribute() };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
|
||||
var attributes = new Attribute[] { new ValidateNeverAttribute() };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0], null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
|
||||
var attributes = new Attribute[] { new ValidateNeverAttribute() };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(ValidateNeverClass));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
typeof(string),
|
||||
nameof(ValidateNeverClass.ClassName),
|
||||
typeof(ValidateNeverClass));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
typeof(string),
|
||||
nameof(ValidateNeverSubclass.SubclassName),
|
||||
typeof(ValidateNeverSubclass));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var attribute = new TestClientModelValidationAttribute();
|
||||
var attributes = new Attribute[] { attribute };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var attribute = new TestModelValidationAttribute();
|
||||
var attributes = new Attribute[] { attribute };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
var attribute = new TestValidationAttribute();
|
||||
var attributes = new Attribute[] { attribute };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
context.ValidationMetadata.ValidatorMetadata.Add(attribute);
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
nameof(Person.Age),
|
||||
typeof(Person));
|
||||
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
nameof(Person.Age),
|
||||
typeof(Person));
|
||||
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||
|
||||
|
|
|
|||
|
|
@ -178,6 +178,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.IsType<ClassValidator>(attribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributesForParameter_NoAttributes()
|
||||
{
|
||||
// Arrange & Act
|
||||
var attributes = ModelAttributes.GetAttributesForParameter(
|
||||
typeof(MethodWithParamAttributesType)
|
||||
.GetMethod(nameof(MethodWithParamAttributesType.Method))
|
||||
.GetParameters()[0]);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(attributes.Attributes);
|
||||
Assert.Empty(attributes.ParameterAttributes);
|
||||
Assert.Null(attributes.TypeAttributes);
|
||||
Assert.Null(attributes.PropertyAttributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributesForParameter_SomeAttributes()
|
||||
{
|
||||
// Arrange & Act
|
||||
var attributes = ModelAttributes.GetAttributesForParameter(
|
||||
typeof(MethodWithParamAttributesType)
|
||||
.GetMethod(nameof(MethodWithParamAttributesType.Method))
|
||||
.GetParameters()[1]);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<RequiredAttribute>(attributes.Attributes[0]);
|
||||
Assert.IsType<RangeAttribute>(attributes.Attributes[1]);
|
||||
Assert.IsType<RequiredAttribute>(attributes.ParameterAttributes[0]);
|
||||
Assert.IsType<RangeAttribute>(attributes.ParameterAttributes[1]);
|
||||
Assert.Null(attributes.TypeAttributes);
|
||||
Assert.Null(attributes.PropertyAttributes);
|
||||
}
|
||||
|
||||
[ClassValidator]
|
||||
private class BaseModel
|
||||
{
|
||||
|
|
@ -266,5 +300,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
private class MetadataPropertyType
|
||||
{
|
||||
}
|
||||
|
||||
[IrrelevantAttribute] // We verify this is ignored
|
||||
private class MethodWithParamAttributesType
|
||||
{
|
||||
[IrrelevantAttribute] // We verify this is ignored
|
||||
public void Method(object noAttribs, [Required, Range(1, 100)] int validationAttribs)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class IrrelevantAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,14 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -89,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var parameterBinder = new ParameterBinder(
|
||||
metadataProvider,
|
||||
factory.Object,
|
||||
CreateMockValidator());
|
||||
CreateMockValidatorProvider());
|
||||
|
||||
var controllerContext = new ControllerContext();
|
||||
|
||||
|
|
@ -139,7 +144,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var argumentBinder = new ParameterBinder(
|
||||
metadataProvider,
|
||||
factory.Object,
|
||||
CreateMockValidator());
|
||||
CreateMockValidatorProvider());
|
||||
|
||||
var valueProvider = new SimpleValueProvider
|
||||
{
|
||||
|
|
@ -154,15 +159,237 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.True(binderExecuted);
|
||||
}
|
||||
|
||||
private static IObjectModelValidator CreateMockValidator()
|
||||
[Fact]
|
||||
public async Task BindModelAsync_EnforcesTopLevelBindRequired()
|
||||
{
|
||||
var mockValidator = new Mock<IObjectModelValidator>();
|
||||
// Arrange
|
||||
var actionContext = new ControllerContext();
|
||||
|
||||
var mockModelMetadata = CreateMockModelMetadata();
|
||||
mockModelMetadata.Setup(o => o.IsBindingRequired).Returns(true);
|
||||
mockModelMetadata.Setup(o => o.DisplayName).Returns("Ignored Display Name"); // Bind attribute errors are phrased in terms of the model name, not display name
|
||||
|
||||
var parameterBinder = CreateParameterBinder(mockModelMetadata.Object, validator: null);
|
||||
var modelBindingResult = ModelBindingResult.Failed();
|
||||
|
||||
// Act
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
actionContext,
|
||||
CreateMockModelBinder(modelBindingResult),
|
||||
CreateMockValueProvider(),
|
||||
new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
|
||||
mockModelMetadata.Object,
|
||||
"ignoredvalue");
|
||||
|
||||
// Assert
|
||||
Assert.False(actionContext.ModelState.IsValid);
|
||||
Assert.Equal("myParam", actionContext.ModelState.Single().Key);
|
||||
Assert.Equal(
|
||||
new DefaultModelBindingMessageProvider().MissingBindRequiredValueAccessor("myParam"),
|
||||
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_EnforcesTopLevelRequired()
|
||||
{
|
||||
// Arrange
|
||||
var actionContext = new ControllerContext();
|
||||
var mockModelMetadata = CreateMockModelMetadata();
|
||||
mockModelMetadata.Setup(o => o.IsRequired).Returns(true);
|
||||
mockModelMetadata.Setup(o => o.DisplayName).Returns("My Display Name");
|
||||
mockModelMetadata.Setup(o => o.ValidatorMetadata).Returns(new[]
|
||||
{
|
||||
new RequiredAttribute()
|
||||
});
|
||||
|
||||
var validator = new DataAnnotationsModelValidator(
|
||||
new ValidationAttributeAdapterProvider(),
|
||||
new RequiredAttribute(),
|
||||
stringLocalizer: null);
|
||||
|
||||
var parameterBinder = CreateParameterBinder(
|
||||
mockModelMetadata.Object,
|
||||
validator);
|
||||
var modelBindingResult = ModelBindingResult.Success(null);
|
||||
|
||||
// Act
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
actionContext,
|
||||
CreateMockModelBinder(modelBindingResult),
|
||||
CreateMockValueProvider(),
|
||||
new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
|
||||
mockModelMetadata.Object,
|
||||
"ignoredvalue");
|
||||
|
||||
// Assert
|
||||
Assert.False(actionContext.ModelState.IsValid);
|
||||
Assert.Equal("myParam", actionContext.ModelState.Single().Key);
|
||||
Assert.Equal(
|
||||
new RequiredAttribute().FormatErrorMessage("My Display Name"),
|
||||
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_EnforcesTopLevelDataAnnotationsAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var actionContext = new ControllerContext();
|
||||
var mockModelMetadata = CreateMockModelMetadata();
|
||||
var validationAttribute = new RangeAttribute(1, 100);
|
||||
mockModelMetadata.Setup(o => o.DisplayName).Returns("My Display Name");
|
||||
mockModelMetadata.Setup(o => o.ValidatorMetadata).Returns(new[] {
|
||||
validationAttribute
|
||||
});
|
||||
|
||||
var validator = new DataAnnotationsModelValidator(
|
||||
new ValidationAttributeAdapterProvider(),
|
||||
validationAttribute,
|
||||
stringLocalizer: null);
|
||||
|
||||
var parameterBinder = CreateParameterBinder(
|
||||
mockModelMetadata.Object,
|
||||
validator);
|
||||
var modelBindingResult = ModelBindingResult.Success(123);
|
||||
|
||||
// Act
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
actionContext,
|
||||
CreateMockModelBinder(modelBindingResult),
|
||||
CreateMockValueProvider(),
|
||||
new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
|
||||
mockModelMetadata.Object,
|
||||
50); // This value is ignored, because test explicitly set the ModelBindingResult
|
||||
|
||||
// Assert
|
||||
Assert.False(actionContext.ModelState.IsValid);
|
||||
Assert.Equal("myParam", actionContext.ModelState.Single().Key);
|
||||
Assert.Equal(
|
||||
validationAttribute.FormatErrorMessage("My Display Name"),
|
||||
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_SupportsIObjectModelValidatorForBackCompat()
|
||||
{
|
||||
// Arrange
|
||||
var actionContext = new ControllerContext();
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
It.IsAny<object>()))
|
||||
.Callback((ActionContext context, ValidationStateDictionary validationState, string prefix, object model) =>
|
||||
{
|
||||
context.ModelState.AddModelError(prefix, "Test validation message");
|
||||
});
|
||||
|
||||
var modelMetadata = CreateMockModelMetadata().Object;
|
||||
var parameterBinder = CreateBackCompatParameterBinder(
|
||||
modelMetadata,
|
||||
mockValidator.Object);
|
||||
var modelBindingResult = ModelBindingResult.Success(123);
|
||||
|
||||
// Act
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
actionContext,
|
||||
CreateMockModelBinder(modelBindingResult),
|
||||
CreateMockValueProvider(),
|
||||
new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
|
||||
modelMetadata,
|
||||
"ignored");
|
||||
|
||||
// Assert
|
||||
Assert.False(actionContext.ModelState.IsValid);
|
||||
Assert.Equal("myParam", actionContext.ModelState.Single().Key);
|
||||
Assert.Equal(
|
||||
"Test validation message",
|
||||
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
|
||||
}
|
||||
|
||||
private static Mock<FakeModelMetadata> CreateMockModelMetadata()
|
||||
{
|
||||
var mockModelMetadata = new Mock<FakeModelMetadata>();
|
||||
mockModelMetadata
|
||||
.Setup(o => o.ModelBindingMessageProvider)
|
||||
.Returns(new DefaultModelBindingMessageProvider());
|
||||
return mockModelMetadata;
|
||||
}
|
||||
|
||||
private static IModelBinder CreateMockModelBinder(ModelBindingResult modelBinderResult)
|
||||
{
|
||||
var mockBinder = new Mock<IModelBinder>(MockBehavior.Strict);
|
||||
mockBinder
|
||||
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns<ModelBindingContext>(context =>
|
||||
{
|
||||
context.Result = modelBinderResult;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
return mockBinder.Object;
|
||||
}
|
||||
|
||||
private static ParameterBinder CreateParameterBinder(
|
||||
ModelMetadata modelMetadata,
|
||||
IModelValidator validator)
|
||||
{
|
||||
var mockModelMetadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
|
||||
mockModelMetadataProvider
|
||||
.Setup(o => o.GetMetadataForType(typeof(Person)))
|
||||
.Returns(modelMetadata);
|
||||
|
||||
var mockModelBinderFactory = new Mock<IModelBinderFactory>(MockBehavior.Strict);
|
||||
return new ParameterBinder(
|
||||
mockModelMetadataProvider.Object,
|
||||
mockModelBinderFactory.Object,
|
||||
CreateMockValidatorProvider(validator));
|
||||
}
|
||||
|
||||
private static ParameterBinder CreateBackCompatParameterBinder(
|
||||
ModelMetadata modelMetadata,
|
||||
IObjectModelValidator validator)
|
||||
{
|
||||
var mockModelMetadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
|
||||
mockModelMetadataProvider
|
||||
.Setup(o => o.GetMetadataForType(typeof(Person)))
|
||||
.Returns(modelMetadata);
|
||||
|
||||
var mockModelBinderFactory = new Mock<IModelBinderFactory>(MockBehavior.Strict);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return new ParameterBinder(
|
||||
mockModelMetadataProvider.Object,
|
||||
mockModelBinderFactory.Object,
|
||||
validator);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
private static IValueProvider CreateMockValueProvider()
|
||||
{
|
||||
var mockValueProvider = new Mock<IValueProvider>(MockBehavior.Strict);
|
||||
mockValueProvider
|
||||
.Setup(o => o.ContainsPrefix(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
return mockValueProvider.Object;
|
||||
}
|
||||
|
||||
private static IModelValidatorProvider CreateMockValidatorProvider(IModelValidator validator = null)
|
||||
{
|
||||
var mockValidator = new Mock<IModelValidatorProvider>();
|
||||
mockValidator
|
||||
.Setup(o => o.CreateValidators(
|
||||
It.IsAny<ModelValidatorProviderContext>()))
|
||||
.Callback<ModelValidatorProviderContext>(context =>
|
||||
{
|
||||
if (validator != null)
|
||||
{
|
||||
foreach (var result in context.Results)
|
||||
{
|
||||
result.Validator = validator;
|
||||
}
|
||||
}
|
||||
});
|
||||
return mockValidator.Object;
|
||||
}
|
||||
|
||||
|
|
@ -180,5 +407,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
return Equals(obj as Person);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class FakeModelMetadata : ModelMetadata
|
||||
{
|
||||
public FakeModelMetadata()
|
||||
: base(ModelMetadataIdentity.ForType(typeof(string)))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
stringLocalizerFactory: null);
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(new object[] { attribute }));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(new object[] { attribute }, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new[] { dataType, };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { dataType, displayFormat, };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { editable };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { editable };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display, displayName };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -213,7 +213,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display, displayName };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -240,7 +240,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display, displayName };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -277,7 +277,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { displayName };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -314,7 +314,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -346,7 +346,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -383,7 +383,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -420,7 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -451,7 +451,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -488,7 +488,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -519,7 +519,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -567,7 +567,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { display };
|
||||
var key = ModelMetadataIdentity.ForType(typeof(DataAnnotationsMetadataProviderTest));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -614,7 +614,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var key = ModelMetadataIdentity.ForType(type);
|
||||
var attributes = new object[0];
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -650,7 +650,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var key = ModelMetadataIdentity.ForType(type);
|
||||
var attributes = new object[0];
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -784,7 +784,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var key = ModelMetadataIdentity.ForType(type);
|
||||
var attributes = new object[0];
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -812,7 +812,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
var attributes = new object[0];
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(type);
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||
stringLocalizer
|
||||
|
|
@ -964,7 +964,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var key = ModelMetadataIdentity.ForType(type);
|
||||
var attributes = new object[0];
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -994,7 +994,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(EnumWithDisplayOrder));
|
||||
var attributes = new object[0];
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
|
||||
// Act
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
|
@ -1098,7 +1098,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { required };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -1120,7 +1120,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
context.ValidationMetadata.IsRequired = initialValue;
|
||||
|
||||
// Act
|
||||
|
|
@ -1143,7 +1143,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { new RequiredAttribute() };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||
|
||||
// Act
|
||||
|
|
@ -1166,7 +1166,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
|
||||
var attributes = new Attribute[] { };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
context.BindingMetadata.IsReadOnly = initialValue;
|
||||
|
||||
// Act
|
||||
|
|
@ -1187,7 +1187,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
var attribute = new TestValidationAttribute();
|
||||
var attributes = new Attribute[] { attribute };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
// Act
|
||||
provider.CreateValidationMetadata(context);
|
||||
|
|
@ -1208,7 +1208,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
var attribute = new TestValidationAttribute();
|
||||
var attributes = new Attribute[] { attribute };
|
||||
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
context.ValidationMetadata.ValidatorMetadata.Add(attribute);
|
||||
|
||||
// Act
|
||||
|
|
@ -1227,7 +1227,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
var key = ModelMetadataIdentity.ForType(typeof(EnumWithLocalizedDisplayNames));
|
||||
var attributes = new object[0];
|
||||
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
|
||||
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
|
||||
provider.CreateDisplayMetadata(context);
|
||||
|
||||
return context.DisplayMetadata.EnumGroupedDisplayNamesAndValues;
|
||||
|
|
|
|||
|
|
@ -196,6 +196,54 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_RequiredButNullAtTopLevel_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var metadata = _metadataProvider.GetMetadataForProperty(typeof(string), "Length");
|
||||
var validator = new DataAnnotationsModelValidator(
|
||||
new ValidationAttributeAdapterProvider(),
|
||||
new RequiredAttribute(),
|
||||
stringLocalizer: null);
|
||||
var validationContext = new ModelValidationContext(
|
||||
actionContext: new ActionContext(),
|
||||
modelMetadata: metadata,
|
||||
metadataProvider: _metadataProvider,
|
||||
container: null,
|
||||
model: null);
|
||||
|
||||
// Act
|
||||
var result = validator.Validate(validationContext);
|
||||
|
||||
// Assert
|
||||
var validationResult = result.Single();
|
||||
Assert.Empty(validationResult.MemberName);
|
||||
Assert.Equal(new RequiredAttribute().FormatErrorMessage("Length"), validationResult.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_RequiredAndNotNullAtTopLevel_Valid()
|
||||
{
|
||||
// Arrange
|
||||
var metadata = _metadataProvider.GetMetadataForProperty(typeof(string), "Length");
|
||||
var validator = new DataAnnotationsModelValidator(
|
||||
new ValidationAttributeAdapterProvider(),
|
||||
new RequiredAttribute(),
|
||||
stringLocalizer: null);
|
||||
var validationContext = new ModelValidationContext(
|
||||
actionContext: new ActionContext(),
|
||||
modelMetadata: metadata,
|
||||
metadataProvider: _metadataProvider,
|
||||
container: null,
|
||||
model: 123);
|
||||
|
||||
// Act
|
||||
var result = validator.Validate(validationContext);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
public static TheoryData<string, IEnumerable<string>, IEnumerable<ModelValidationResult>>
|
||||
Valdate_ReturnsExpectedResults_Data
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
typeof(string),
|
||||
nameof(ClassWithDataMemberIsRequiredTrue.StringProperty),
|
||||
typeof(ClassWithDataMemberIsRequiredTrue));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
typeof(string),
|
||||
nameof(ClassWithDataMemberIsRequiredFalse.StringProperty),
|
||||
typeof(ClassWithDataMemberIsRequiredFalse));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
};
|
||||
|
||||
var key = ModelMetadataIdentity.ForType(typeof(ClassWithDataMemberIsRequiredTrue));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
typeof(string),
|
||||
nameof(ClassWithoutAttributes.StringProperty),
|
||||
typeof(ClassWithoutAttributes));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
|
||||
|
||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
typeof(string),
|
||||
nameof(ClassWithDataMemberIsRequiredTrueWithoutDataContract.StringProperty),
|
||||
typeof(ClassWithDataMemberIsRequiredTrueWithoutDataContract));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, new object[0]));
|
||||
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
|
||||
|
||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||
|
||||
|
|
|
|||
|
|
@ -1066,7 +1066,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
{
|
||||
return new DefaultMetadataDetails(
|
||||
key,
|
||||
new ModelAttributes(_attributes.Concat(entry.ModelAttributes.TypeAttributes).ToArray()));
|
||||
new ModelAttributes(_attributes.Concat(entry.ModelAttributes.TypeAttributes).ToArray(), null, null));
|
||||
}
|
||||
|
||||
return entry;
|
||||
|
|
@ -1079,7 +1079,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
{
|
||||
return new DefaultMetadataDetails(
|
||||
e.Key,
|
||||
new ModelAttributes(_attributes.Concat(e.ModelAttributes.PropertyAttributes), e.ModelAttributes.TypeAttributes));
|
||||
new ModelAttributes(e.ModelAttributes.TypeAttributes, _attributes.Concat(e.ModelAttributes.PropertyAttributes), null));
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class InputValidationTests : IClassFixture<MvcTestFixture<FormatterWebSite.Startup>>
|
||||
{
|
||||
public InputValidationTests(MvcTestFixture<FormatterWebSite.Startup> fixture)
|
||||
{
|
||||
Client = fixture.Client;
|
||||
}
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task ValidRequest_IsAccepted()
|
||||
{
|
||||
// Arrange
|
||||
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "RequiredProp", "1" },
|
||||
{ "BindRequiredProp", "2" },
|
||||
{ "RequiredAndBindRequiredProp", "3" },
|
||||
{ "requiredParam", "4" },
|
||||
{ "bindRequiredParam", "5" },
|
||||
{ "requiredAndBindRequiredParam", "6" },
|
||||
{ "UnboundRequiredProp", "100" }, // Value should not be used
|
||||
{ "UnboundBindRequiredProp", "101" }, // Value should not be used
|
||||
{ "BindNeverRequiredProp", "ignoredValue" }, // Value should not be used
|
||||
});
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync("http://localhost/TopLevelValidation", content);
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Contains("[OptionalProp:0]", responseText);
|
||||
Assert.Contains("[RequiredProp:1]", responseText);
|
||||
Assert.Contains("[BindRequiredProp:2]", responseText);
|
||||
Assert.Contains("[RequiredAndBindRequiredProp:3]", responseText);
|
||||
Assert.Contains("[OptionalStringLengthProp:]", responseText);
|
||||
Assert.Contains("[OptionalRangeDisplayNameProp:0]", responseText);
|
||||
Assert.Contains("[UnboundRequiredProp:0]", responseText);
|
||||
Assert.Contains("[UnboundBindRequiredProp:0]", responseText);
|
||||
Assert.Contains("[BindNeverRequiredProp:]", responseText);
|
||||
Assert.Contains("[optionalParam:0]", responseText);
|
||||
Assert.Contains("[requiredParam:4]", responseText);
|
||||
Assert.Contains("[bindRequiredParam:5]", responseText);
|
||||
Assert.Contains("[requiredAndBindRequiredParam:6]", responseText);
|
||||
Assert.Contains("[optionalStringLengthParam:]", responseText);
|
||||
Assert.Contains("[optionalRangeDisplayNameParam:0]", responseText);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvalidRequest_IsRejected()
|
||||
{
|
||||
// Arrange
|
||||
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "OptionalStringLengthProp", "ThisStringIsTooLongForTheProperty" },
|
||||
{ "OptionalRangeDisplayNameProp", "123" },
|
||||
{ "optionalStringLengthParam", "ThisStringIsTooLongForTheParameter" },
|
||||
{ "optionalRangeDisplayNameParam", "456" },
|
||||
});
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync("http://localhost/TopLevelValidation", content);
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
var errors = JsonConvert.DeserializeObject<JObject>(responseText)
|
||||
.Properties()
|
||||
.ToDictionary(
|
||||
prop => prop.Name,
|
||||
prop => ((JArray)prop.Value).Single().Value<string>());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
Assert.Equal(10, errors.Count);
|
||||
Assert.Equal(
|
||||
"The RequiredProp field is required.",
|
||||
errors["RequiredProp"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'BindRequiredProp' property was not provided.",
|
||||
errors["BindRequiredProp"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'RequiredAndBindRequiredProp' property was not provided.",
|
||||
errors["RequiredAndBindRequiredProp"]);
|
||||
Assert.Equal(
|
||||
"The field OptionalStringLengthProp must be a string with a maximum length of 5.",
|
||||
errors["OptionalStringLengthProp"]);
|
||||
Assert.Equal(
|
||||
"The field Some Display Name For Prop must be between 1 and 100.",
|
||||
errors["OptionalRangeDisplayNameProp"]);
|
||||
Assert.Equal(
|
||||
"The requiredParam field is required.",
|
||||
errors["requiredParam"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'bindRequiredParam' property was not provided.",
|
||||
errors["bindRequiredParam"]);
|
||||
Assert.Equal(
|
||||
"A value for the 'requiredAndBindRequiredParam' property was not provided.",
|
||||
errors["requiredAndBindRequiredParam"]);
|
||||
Assert.Equal(
|
||||
"The field optionalStringLengthParam must be a string with a maximum length of 5.",
|
||||
errors["optionalStringLengthParam"]);
|
||||
Assert.Equal(
|
||||
"The field Some Display Name For Param must be between 1 and 100.",
|
||||
errors["optionalRangeDisplayNameParam"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
|
|
@ -489,6 +492,132 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionParameter_WithBindNever_DoesNotGetBound()
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = BindingAndValidationController.BindNeverParamInfo.Name,
|
||||
ParameterType = typeof(int)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.QueryString = QueryString.Create(parameter.Name, "123");
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelMetadata = modelMetadataProvider
|
||||
.GetMetadataForParameter(BindingAndValidationController.BindNeverParamInfo);
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await parameterBinder.BindModelAsync(
|
||||
parameter,
|
||||
testContext,
|
||||
modelMetadataProvider,
|
||||
modelMetadata);
|
||||
|
||||
// Assert
|
||||
Assert.False(modelBindingResult.IsModelSet);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(123, true)]
|
||||
[InlineData(null, false)]
|
||||
public async Task ActionParameter_EnforcesBindRequired(int? input, bool isValid)
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = BindingAndValidationController.BindRequiredParamInfo.Name,
|
||||
ParameterType = typeof(int)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
if (input.HasValue)
|
||||
{
|
||||
request.QueryString = QueryString.Create(parameter.Name, input.Value.ToString());
|
||||
}
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelMetadata = modelMetadataProvider
|
||||
.GetMetadataForParameter(BindingAndValidationController.BindRequiredParamInfo);
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await parameterBinder.BindModelAsync(
|
||||
parameter,
|
||||
testContext,
|
||||
modelMetadataProvider,
|
||||
modelMetadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(input.HasValue, modelBindingResult.IsModelSet);
|
||||
Assert.Equal(isValid, modelState.IsValid);
|
||||
if (isValid)
|
||||
{
|
||||
Assert.Equal(input.Value, Assert.IsType<int>(modelBindingResult.Model));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("requiredAndStringLengthParam", null, false)]
|
||||
[InlineData("requiredAndStringLengthParam", "", false)]
|
||||
[InlineData("requiredAndStringLengthParam", "abc", true)]
|
||||
[InlineData("requiredAndStringLengthParam", "abcTooLong", false)]
|
||||
[InlineData("displayNameStringLengthParam", null, true)]
|
||||
[InlineData("displayNameStringLengthParam", "", true)]
|
||||
[InlineData("displayNameStringLengthParam", "abc", true)]
|
||||
[InlineData("displayNameStringLengthParam", "abcTooLong", false, "My Display Name")]
|
||||
public async Task ActionParameter_EnforcesDataAnnotationsAttributes(
|
||||
string paramName, string input, bool isValid, string displayName = null)
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameterInfo = BindingAndValidationController.GetParameterInfo(paramName);
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = parameterInfo.Name,
|
||||
ParameterType = parameterInfo.ParameterType
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
if (input != null)
|
||||
{
|
||||
request.QueryString = QueryString.Create(parameter.Name, input);
|
||||
}
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelMetadata = modelMetadataProvider
|
||||
.GetMetadataForParameter(parameterInfo);
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await parameterBinder.BindModelAsync(
|
||||
parameter,
|
||||
testContext,
|
||||
modelMetadataProvider,
|
||||
modelMetadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(input != null, modelBindingResult.IsModelSet);
|
||||
Assert.Equal(isValid, modelState.IsValid);
|
||||
if (!isValid)
|
||||
{
|
||||
var message = modelState[paramName].Errors.Single().ErrorMessage;
|
||||
Assert.Contains(displayName ?? parameter.Name, message);
|
||||
}
|
||||
}
|
||||
|
||||
private struct PointStruct
|
||||
{
|
||||
public PointStruct(double x, double y)
|
||||
|
|
@ -533,6 +662,33 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
private class BindingAndValidationController
|
||||
{
|
||||
public void MyAction(
|
||||
[BindNever] int bindNeverParam,
|
||||
[BindRequired] int bindRequiredParam,
|
||||
[Required, StringLength(3)] string requiredAndStringLengthParam,
|
||||
[Display(Name = "My Display Name"), StringLength(3)] string displayNameStringLengthParam)
|
||||
{
|
||||
}
|
||||
|
||||
private static MethodInfo MyActionMethodInfo
|
||||
=> typeof(BindingAndValidationController).GetMethod(nameof(MyAction));
|
||||
|
||||
public static ParameterInfo BindNeverParamInfo
|
||||
=> MyActionMethodInfo.GetParameters()[0];
|
||||
|
||||
public static ParameterInfo BindRequiredParamInfo
|
||||
=> MyActionMethodInfo.GetParameters()[1];
|
||||
|
||||
public static ParameterInfo GetParameterInfo(string parameterName)
|
||||
{
|
||||
return MyActionMethodInfo
|
||||
.GetParameters()
|
||||
.Single(p => p.Name.Equals(parameterName, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomReadOnlyCollection<T> : ICollection<T>
|
||||
{
|
||||
private ICollection<T> _original;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
|
|
@ -97,5 +100,143 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// Assert
|
||||
Assert.False(result.IsModelSet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_WithBindProperty_BindNever_DoesNotBindModel()
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = nameof(TestController.BindNeverProp),
|
||||
ParameterType = typeof(string),
|
||||
BindingInfo = BindingInfo.GetBindingInfo(new[] { new BindPropertyAttribute() }),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.Method = "POST";
|
||||
request.QueryString = new QueryString($"?{parameter.Name}=Joey");
|
||||
});
|
||||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelMetadata = modelMetadataProvider
|
||||
.GetMetadataForProperty(typeof(TestController), parameter.Name);
|
||||
|
||||
// Act
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
parameter,
|
||||
testContext,
|
||||
modelMetadataProvider,
|
||||
modelMetadata);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.IsModelSet);
|
||||
Assert.True(testContext.ModelState.IsValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, false)]
|
||||
[InlineData(123, true)]
|
||||
public async Task BindModelAsync_WithBindProperty_EnforcesBindRequired(int? input, bool isValid)
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = nameof(TestController.BindRequiredProp),
|
||||
ParameterType = typeof(string),
|
||||
BindingInfo = BindingInfo.GetBindingInfo(new[] { new BindPropertyAttribute() }),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.Method = "POST";
|
||||
|
||||
if (input.HasValue)
|
||||
{
|
||||
request.QueryString = new QueryString($"?{parameter.Name}={input.Value}");
|
||||
}
|
||||
});
|
||||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelMetadata = modelMetadataProvider
|
||||
.GetMetadataForProperty(typeof(TestController), parameter.Name);
|
||||
|
||||
// Act
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
parameter,
|
||||
testContext,
|
||||
modelMetadataProvider,
|
||||
modelMetadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(input.HasValue, result.IsModelSet);
|
||||
Assert.Equal(isValid, testContext.ModelState.IsValid);
|
||||
if (isValid)
|
||||
{
|
||||
Assert.Equal(input.Value, Assert.IsType<int>(result.Model));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("RequiredAndStringLengthProp", null, false)]
|
||||
[InlineData("RequiredAndStringLengthProp", "", false)]
|
||||
[InlineData("RequiredAndStringLengthProp", "abc", true)]
|
||||
[InlineData("RequiredAndStringLengthProp", "abcTooLong", false)]
|
||||
[InlineData("DisplayNameStringLengthProp", null, true)]
|
||||
[InlineData("DisplayNameStringLengthProp", "", true)]
|
||||
[InlineData("DisplayNameStringLengthProp", "abc", true)]
|
||||
[InlineData("DisplayNameStringLengthProp", "abcTooLong", false, "My Display Name")]
|
||||
public async Task BindModelAsync_WithBindProperty_EnforcesDataAnnotationsAttributes(
|
||||
string propertyName, string input, bool isValid, string displayName = null)
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = propertyName,
|
||||
ParameterType = typeof(string),
|
||||
BindingInfo = BindingInfo.GetBindingInfo(new[] { new BindPropertyAttribute() }),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.Method = "POST";
|
||||
|
||||
if (input != null)
|
||||
{
|
||||
request.QueryString = new QueryString($"?{parameter.Name}={input}");
|
||||
}
|
||||
});
|
||||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelMetadata = modelMetadataProvider
|
||||
.GetMetadataForProperty(typeof(TestController), parameter.Name);
|
||||
|
||||
// Act
|
||||
var result = await parameterBinder.BindModelAsync(
|
||||
parameter,
|
||||
testContext,
|
||||
modelMetadataProvider,
|
||||
modelMetadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(input != null, result.IsModelSet);
|
||||
Assert.Equal(isValid, testContext.ModelState.IsValid);
|
||||
if (!isValid)
|
||||
{
|
||||
var message = testContext.ModelState[propertyName].Errors.Single().ErrorMessage;
|
||||
Assert.Contains(displayName ?? parameter.Name, message);
|
||||
}
|
||||
}
|
||||
|
||||
class TestController
|
||||
{
|
||||
[BindNever] public string BindNeverProp { get; set; }
|
||||
[BindRequired] public int BindRequiredProp { get; set; }
|
||||
[Required, StringLength(3)] public string RequiredAndStringLengthProp { get; set; }
|
||||
[DisplayName("My Display Name"), StringLength(3)] public string DisplayNameStringLengthProp { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,25 +76,41 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
|
||||
return new ParameterBinder(
|
||||
metadataProvider,
|
||||
new ModelBinderFactory(metadataProvider, options),
|
||||
GetObjectValidator(metadataProvider, options));
|
||||
GetModelBinderFactory(metadataProvider, options),
|
||||
new CompositeModelValidatorProvider(GetModelValidatorProviders(options)));
|
||||
}
|
||||
|
||||
public static IModelBinderFactory GetModelBinderFactory(
|
||||
IModelMetadataProvider metadataProvider,
|
||||
IOptions<MvcOptions> options = null)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
options = GetServices().GetRequiredService<IOptions<MvcOptions>>();
|
||||
}
|
||||
|
||||
return new ModelBinderFactory(metadataProvider, options);
|
||||
}
|
||||
|
||||
public static IObjectModelValidator GetObjectValidator(
|
||||
IModelMetadataProvider metadataProvider,
|
||||
IOptions<MvcOptions> options = null)
|
||||
{
|
||||
IList<IModelValidatorProvider> validatorProviders;
|
||||
return new DefaultObjectValidator(
|
||||
metadataProvider,
|
||||
GetModelValidatorProviders(options));
|
||||
}
|
||||
|
||||
private static IList<IModelValidatorProvider> GetModelValidatorProviders(IOptions<MvcOptions> options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
validatorProviders = TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders;
|
||||
return TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders;
|
||||
}
|
||||
else
|
||||
{
|
||||
validatorProviders = options.Value.ModelValidatorProviders;
|
||||
return options.Value.ModelValidatorProviders;
|
||||
}
|
||||
|
||||
return new DefaultObjectValidator(metadataProvider, validatorProviders);
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(
|
||||
|
|
|
|||
|
|
@ -18,5 +18,31 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
|
||||
return await parameterBinder.BindModelAsync(context, valueProvider, parameter);
|
||||
}
|
||||
public static async Task<ModelBindingResult> BindModelAsync(
|
||||
this ParameterBinder parameterBinder,
|
||||
ParameterDescriptor parameter,
|
||||
ControllerContext context,
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
ModelMetadata modelMetadata)
|
||||
{
|
||||
var valueProvider = await CompositeValueProvider.CreateAsync(context);
|
||||
|
||||
var modelBinderFactory = ModelBindingTestHelper.GetModelBinderFactory(modelMetadataProvider);
|
||||
|
||||
var modelBinder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
|
||||
{
|
||||
BindingInfo = parameter.BindingInfo,
|
||||
Metadata = modelMetadata,
|
||||
CacheToken = parameter,
|
||||
});
|
||||
|
||||
return await parameterBinder.BindModelAsync(
|
||||
context,
|
||||
modelBinder,
|
||||
valueProvider,
|
||||
parameter,
|
||||
modelMetadata,
|
||||
value: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var parameterBinder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>());
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
|
||||
return new PageActionInvokerProvider(
|
||||
loader,
|
||||
|
|
|
|||
|
|
@ -1249,11 +1249,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
private static ParameterBinder GetParameterBinder(
|
||||
IModelBinderFactory factory = null,
|
||||
IObjectModelValidator validator = null)
|
||||
IModelValidatorProvider validator = null)
|
||||
{
|
||||
if (validator == null)
|
||||
{
|
||||
validator = CreateMockValidator();
|
||||
validator = CreateMockValidatorProvider();
|
||||
}
|
||||
|
||||
if (factory == null)
|
||||
|
|
@ -1267,15 +1267,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
validator);
|
||||
}
|
||||
|
||||
private static IObjectModelValidator CreateMockValidator()
|
||||
private static IModelValidatorProvider CreateMockValidatorProvider()
|
||||
{
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
var mockValidator = new Mock<IModelValidatorProvider>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
It.IsAny<ActionContext>(),
|
||||
It.IsAny<ValidationStateDictionary>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
.Setup(o => o.CreateValidators(
|
||||
It.IsAny<ModelValidatorProviderContext>()));
|
||||
return mockValidator.Object;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>());
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
|
|
@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var binder = new ParameterBinder(
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>());
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>());
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>());
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>());
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
|
|
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var binder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>());
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
|
||||
// Act
|
||||
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
|
||||
|
|
@ -460,7 +460,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
: base(
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
TestModelBinderFactory.CreateDefault(),
|
||||
Mock.Of<IObjectModelValidator>())
|
||||
Mock.Of<IModelValidatorProvider>())
|
||||
{
|
||||
_args = args;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public class TestModelMetadataProvider : DefaultModelMetadataProvider
|
||||
{
|
||||
// Creates a provider with all the defaults - includes data annotations
|
||||
public static IModelMetadataProvider CreateDefaultProvider(IStringLocalizerFactory stringLocalizerFactory = null)
|
||||
public static ModelMetadataProvider CreateDefaultProvider(IStringLocalizerFactory stringLocalizerFactory = null)
|
||||
{
|
||||
var detailsProviders = new IMetadataDetailsProvider[]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace FormatterWebSite.Controllers
|
||||
{
|
||||
public class TopLevelValidationController : Controller
|
||||
{
|
||||
[BindProperty] public int OptionalProp { get; set; }
|
||||
[BindProperty, Required] public int RequiredProp { get; set; }
|
||||
[BindProperty, BindRequired] public int BindRequiredProp { get; set; }
|
||||
[BindProperty, Required, BindRequired] public int RequiredAndBindRequiredProp { get; set; }
|
||||
[BindProperty, StringLength(5)] public string OptionalStringLengthProp { get; set; }
|
||||
[BindProperty, Range(1, 100), DisplayName("Some Display Name For Prop")] public int OptionalRangeDisplayNameProp { get; set; }
|
||||
|
||||
// Despite the Required/BindRequired attributes, these properties won't be validated
|
||||
// because they aren't [BindProperty] properties (hence aren't involved in binding).
|
||||
[Required] public int UnboundRequiredProp { get; set; }
|
||||
[BindRequired] public int UnboundBindRequiredProp { get; set; }
|
||||
|
||||
// The [BindNever] overrides [BindProperty], meaning [Required] will not apply
|
||||
// (nor will any incoming value be used)
|
||||
[BindProperty, BindNever, Required] public string BindNeverRequiredProp { get; set; }
|
||||
|
||||
public IActionResult Index(
|
||||
int optionalParam,
|
||||
[Required] int requiredParam,
|
||||
[BindRequired] int bindRequiredParam,
|
||||
[Required, BindRequired] int requiredAndBindRequiredParam,
|
||||
[StringLength(5)] string optionalStringLengthParam,
|
||||
[Range(1, 100), Display(Name = "Some Display Name For Param")] int optionalRangeDisplayNameParam)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
return Content($@"
|
||||
[{ nameof(OptionalProp) }:{ OptionalProp }]
|
||||
[{ nameof(RequiredProp) }:{ RequiredProp }]
|
||||
[{ nameof(BindRequiredProp) }:{ BindRequiredProp }]
|
||||
[{ nameof(RequiredAndBindRequiredProp) }:{ RequiredAndBindRequiredProp }]
|
||||
[{ nameof(OptionalStringLengthProp) }:{ OptionalStringLengthProp }]
|
||||
[{ nameof(OptionalRangeDisplayNameProp) }:{ OptionalRangeDisplayNameProp }]
|
||||
[{ nameof(UnboundRequiredProp) }:{ UnboundRequiredProp }]
|
||||
[{ nameof(UnboundBindRequiredProp) }:{ UnboundBindRequiredProp }]
|
||||
[{ nameof(BindNeverRequiredProp) }:{ BindNeverRequiredProp }]
|
||||
[{ nameof(optionalParam) }:{ optionalParam }]
|
||||
[{ nameof(requiredParam) }:{ requiredParam }]
|
||||
[{ nameof(bindRequiredParam) }:{ bindRequiredParam }]
|
||||
[{ nameof(requiredAndBindRequiredParam) }:{ requiredAndBindRequiredParam }]
|
||||
[{ nameof(optionalStringLengthParam) }:{ optionalStringLengthParam }]
|
||||
[{ nameof(optionalRangeDisplayNameParam) }:{ optionalRangeDisplayNameParam }]");
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue