Simplify BindAttribute - rename PredicateProvider

This change renames IPropertyBindingPredicateProvider to
IPropertyFilterProvider. The changes here are mostly renames of
parameters/variables from predicate -> propertyFilter. I did a
find+replace and left the term 'predicate' in some of the docs because it
refers to a predicate in the abstract sense.

This change also simplifies BindAttribute and removes support for type
activation.
This commit is contained in:
Ryan Nowak 2016-03-24 15:10:35 -07:00
parent 1bd66ffda0
commit 144766f2e3
24 changed files with 222 additions and 463 deletions

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
BindingSource = other.BindingSource; BindingSource = other.BindingSource;
BinderModelName = other.BinderModelName; BinderModelName = other.BinderModelName;
BinderType = other.BinderType; BinderType = other.BinderType;
PropertyBindingPredicateProvider = other.PropertyBindingPredicateProvider; PropertyFilterProvider = other.PropertyFilterProvider;
} }
/// <summary> /// <summary>
@ -52,9 +52,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public Type BinderType { get; set; } public Type BinderType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ModelBinding.IPropertyBindingPredicateProvider"/>. /// Gets or sets the <see cref="ModelBinding.IPropertyFilterProvider"/>.
/// </summary> /// </summary>
public IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; set; } public IPropertyFilterProvider PropertyFilterProvider { get; set; }
/// <summary> /// <summary>
/// Constructs a new instance of <see cref="BindingInfo"/> from the given <paramref name="attributes"/>. /// Constructs a new instance of <see cref="BindingInfo"/> from the given <paramref name="attributes"/>.
@ -100,46 +100,50 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
} }
// PropertyBindingPredicateProvider // PropertyFilterProvider
var predicateProviders = attributes.OfType<IPropertyBindingPredicateProvider>().ToArray(); var propertyFilterProviders = attributes.OfType<IPropertyFilterProvider>().ToArray();
if (predicateProviders.Length > 0) if (propertyFilterProviders.Length == 1)
{ {
isBindingInfoPresent = true; isBindingInfoPresent = true;
bindingInfo.PropertyBindingPredicateProvider = new CompositePredicateProvider( bindingInfo.PropertyFilterProvider = propertyFilterProviders[0];
predicateProviders); }
else if (propertyFilterProviders.Length > 1)
{
isBindingInfoPresent = true;
bindingInfo.PropertyFilterProvider = new CompositePropertyFilterProvider(propertyFilterProviders);
} }
return isBindingInfoPresent ? bindingInfo : null; return isBindingInfoPresent ? bindingInfo : null;
} }
private class CompositePredicateProvider : IPropertyBindingPredicateProvider private class CompositePropertyFilterProvider : IPropertyFilterProvider
{ {
private readonly IEnumerable<IPropertyBindingPredicateProvider> _providers; private readonly IEnumerable<IPropertyFilterProvider> _providers;
public CompositePredicateProvider(IEnumerable<IPropertyBindingPredicateProvider> providers) public CompositePropertyFilterProvider(IEnumerable<IPropertyFilterProvider> providers)
{ {
_providers = providers; _providers = providers;
} }
public Func<ModelBindingContext, string, bool> PropertyFilter public Func<ModelMetadata, bool> PropertyFilter
{ {
get get
{ {
return CreatePredicate(); return CreatePropertyFilter();
} }
} }
private Func<ModelBindingContext, string, bool> CreatePredicate() private Func<ModelMetadata, bool> CreatePropertyFilter()
{ {
var predicates = _providers var propertyFilters = _providers
.Select(p => p.PropertyFilter) .Select(p => p.PropertyFilter)
.Where(p => p != null); .Where(p => p != null);
return (context, propertyName) => return (m) =>
{ {
foreach (var predicate in predicates) foreach (var propertyFilter in propertyFilters)
{ {
if (!predicate(context, propertyName)) if (!propertyFilter(m))
{ {
return false; return false;
} }

View File

@ -8,11 +8,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <summary> /// <summary>
/// Provides a predicate which can determines which model properties should be bound by model binding. /// Provides a predicate which can determines which model properties should be bound by model binding.
/// </summary> /// </summary>
public interface IPropertyBindingPredicateProvider public interface IPropertyFilterProvider
{ {
/// <summary> /// <summary>
/// Gets a predicate which can determines which model properties should be bound by model binding. /// Gets a predicate which can determines which model properties should be bound by model binding.
/// </summary> /// </summary>
Func<ModelBindingContext, string, bool> PropertyFilter { get; } Func<ModelMetadata, bool> PropertyFilter { get; }
} }
} }

View File

@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// Gets or sets a predicate which will be evaluated for each property to determine if the property /// Gets or sets a predicate which will be evaluated for each property to determine if the property
/// is eligible for model binding. /// is eligible for model binding.
/// </summary> /// </summary>
public abstract Func<ModelBindingContext, string, bool> PropertyFilter { get; set; } public abstract Func<ModelMetadata, bool> PropertyFilter { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ValidationStateDictionary"/>. Used for tracking validation state to /// Gets or sets the <see cref="ValidationStateDictionary"/>. Used for tracking validation state to

View File

@ -269,10 +269,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public abstract string NullDisplayText { get; } public abstract string NullDisplayText { get; }
/// <summary> /// <summary>
/// Gets the <see cref="IPropertyBindingPredicateProvider"/>, which can determine which properties /// Gets the <see cref="IPropertyFilterProvider"/>, which can determine which properties
/// should be model bound. /// should be model bound.
/// </summary> /// </summary>
public abstract IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; } public abstract IPropertyFilterProvider PropertyFilterProvider { get; }
/// <summary> /// <summary>
/// Gets a value that indicates whether the property should be displayed in read-only views. /// Gets a value that indicates whether the property should be displayed in read-only views.

View File

@ -7,9 +7,7 @@ using System.Linq;
#if NETSTANDARD1_5 #if NETSTANDARD1_5
using System.Reflection; using System.Reflection;
#endif #endif
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
{ {
@ -17,14 +15,11 @@ namespace Microsoft.AspNetCore.Mvc
/// This attribute can be used on action parameters and types, to indicate model level metadata. /// This attribute can be used on action parameters and types, to indicate model level metadata.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingPredicateProvider public class BindAttribute : Attribute, IModelNameProvider, IPropertyFilterProvider
{ {
private static readonly Func<ModelBindingContext, string, bool> _defaultFilter = private static readonly Func<ModelMetadata, bool> _default = (m) => true;
(context, propertyName) => true;
private ObjectFactory _factory; private Func<ModelMetadata, bool> _propertyFilter;
private Func<ModelBindingContext, string, bool> _predicateFromInclude;
/// <summary> /// <summary>
/// Creates a new instace of <see cref="BindAttribute"/>. /// Creates a new instace of <see cref="BindAttribute"/>.
@ -41,33 +36,6 @@ namespace Microsoft.AspNetCore.Mvc
Include = items.ToArray(); Include = items.ToArray();
} }
/// <summary>
/// Creates a new instance of <see cref="BindAttribute"/>.
/// </summary>
/// <param name="predicateProviderType">The type which implements
/// <see cref="IPropertyBindingPredicateProvider"/>.
/// </param>
public BindAttribute(Type predicateProviderType)
{
if (predicateProviderType == null)
{
throw new ArgumentNullException(nameof(predicateProviderType));
}
if (!typeof(IPropertyBindingPredicateProvider).IsAssignableFrom(predicateProviderType))
{
var message = Resources.FormatPropertyBindingPredicateProvider_WrongType(
predicateProviderType.FullName,
typeof(IPropertyBindingPredicateProvider).FullName);
throw new ArgumentException(message, nameof(predicateProviderType));
}
PredicateProviderType = predicateProviderType;
}
/// <inheritdoc />
public Type PredicateProviderType { get; }
/// <summary> /// <summary>
/// Gets the names of properties to include in model binding. /// Gets the names of properties to include in model binding.
/// </summary> /// </summary>
@ -91,64 +59,26 @@ namespace Microsoft.AspNetCore.Mvc
} }
/// <inheritdoc /> /// <inheritdoc />
public Func<ModelBindingContext, string, bool> PropertyFilter public Func<ModelMetadata, bool> PropertyFilter
{ {
get get
{ {
if (PredicateProviderType != null) if (Include != null && Include.Length > 0)
{ {
var factory = GetFactory(); if (_propertyFilter == null)
return CreatePredicateFromProviderType(factory);
}
else if (Include != null && Include.Length > 0)
{
if (_predicateFromInclude == null)
{ {
_predicateFromInclude = _propertyFilter = (m) => Include.Contains(m.PropertyName, StringComparer.Ordinal);
(context, propertyName) => Include.Contains(propertyName, StringComparer.Ordinal);
} }
return _predicateFromInclude; return _propertyFilter;
} }
else else
{ {
return _defaultFilter; return _default;
} }
} }
} }
private ObjectFactory GetFactory()
{
if (_factory == null)
{
_factory = ActivatorUtilities.CreateFactory(PredicateProviderType, Type.EmptyTypes);
}
return _factory;
}
private static Func<ModelBindingContext, string, bool> CreatePredicateFromProviderType(
ObjectFactory factory)
{
// Holding state to avoid execessive creation of the provider.
var initialized = false;
Func<ModelBindingContext, string, bool> predicate = null;
return (ModelBindingContext context, string propertyName) =>
{
if (!initialized)
{
var services = context.OperationBindingContext.HttpContext.RequestServices;
var provider = (IPropertyBindingPredicateProvider)factory(services, arguments: null);
initialized = true;
predicate = provider.PropertyFilter ?? _defaultFilter;
}
return predicate(context, propertyName);
};
}
private static IEnumerable<string> SplitString(string original) private static IEnumerable<string> SplitString(string original)
{ {
if (string.IsNullOrEmpty(original)) if (string.IsNullOrEmpty(original))

View File

@ -1222,13 +1222,13 @@ namespace Microsoft.AspNetCore.Mvc
/// <param name="model">The model instance to update.</param> /// <param name="model">The model instance to update.</param>
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>. /// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>.
/// </param> /// </param>
/// <param name="predicate">A predicate which can be used to filter properties at runtime.</param> /// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns> /// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
[NonAction] [NonAction]
public Task<bool> TryUpdateModelAsync<TModel>( public Task<bool> TryUpdateModelAsync<TModel>(
TModel model, TModel model,
string prefix, string prefix,
Func<ModelBindingContext, string, bool> predicate) Func<ModelMetadata, bool> propertyFilter)
where TModel : class where TModel : class
{ {
if (model == null) if (model == null)
@ -1236,9 +1236,9 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(model)); throw new ArgumentNullException(nameof(model));
} }
if (predicate == null) if (propertyFilter == null)
{ {
throw new ArgumentNullException(nameof(predicate)); throw new ArgumentNullException(nameof(propertyFilter));
} }
return ModelBindingHelper.TryUpdateModelAsync( return ModelBindingHelper.TryUpdateModelAsync(
@ -1251,7 +1251,7 @@ namespace Microsoft.AspNetCore.Mvc
ControllerContext.InputFormatters, ControllerContext.InputFormatters,
ObjectValidator, ObjectValidator,
new CompositeModelValidatorProvider(ControllerContext.ValidatorProviders), new CompositeModelValidatorProvider(ControllerContext.ValidatorProviders),
predicate); propertyFilter);
} }
/// <summary> /// <summary>
@ -1311,14 +1311,14 @@ namespace Microsoft.AspNetCore.Mvc
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>. /// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
/// </param> /// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param> /// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="predicate">A predicate which can be used to filter properties at runtime.</param> /// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns> /// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
[NonAction] [NonAction]
public Task<bool> TryUpdateModelAsync<TModel>( public Task<bool> TryUpdateModelAsync<TModel>(
TModel model, TModel model,
string prefix, string prefix,
IValueProvider valueProvider, IValueProvider valueProvider,
Func<ModelBindingContext, string, bool> predicate) Func<ModelMetadata, bool> propertyFilter)
where TModel : class where TModel : class
{ {
if (model == null) if (model == null)
@ -1331,9 +1331,9 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(valueProvider)); throw new ArgumentNullException(nameof(valueProvider));
} }
if (predicate == null) if (propertyFilter == null)
{ {
throw new ArgumentNullException(nameof(predicate)); throw new ArgumentNullException(nameof(propertyFilter));
} }
return ModelBindingHelper.TryUpdateModelAsync( return ModelBindingHelper.TryUpdateModelAsync(
@ -1346,7 +1346,7 @@ namespace Microsoft.AspNetCore.Mvc
ControllerContext.InputFormatters, ControllerContext.InputFormatters,
ObjectValidator, ObjectValidator,
new CompositeModelValidatorProvider(ControllerContext.ValidatorProviders), new CompositeModelValidatorProvider(ControllerContext.ValidatorProviders),
predicate); propertyFilter);
} }
/// <summary> /// <summary>
@ -1396,7 +1396,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>. /// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
/// </param> /// </param>
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param> /// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
/// <param name="predicate">A predicate which can be used to filter properties at runtime.</param> /// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns> /// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
[NonAction] [NonAction]
public Task<bool> TryUpdateModelAsync( public Task<bool> TryUpdateModelAsync(
@ -1404,7 +1404,7 @@ namespace Microsoft.AspNetCore.Mvc
Type modelType, Type modelType,
string prefix, string prefix,
IValueProvider valueProvider, IValueProvider valueProvider,
Func<ModelBindingContext, string, bool> predicate) Func<ModelMetadata, bool> propertyFilter)
{ {
if (model == null) if (model == null)
{ {
@ -1421,9 +1421,9 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(valueProvider)); throw new ArgumentNullException(nameof(valueProvider));
} }
if (predicate == null) if (propertyFilter == null)
{ {
throw new ArgumentNullException(nameof(predicate)); throw new ArgumentNullException(nameof(propertyFilter));
} }
return ModelBindingHelper.TryUpdateModelAsync( return ModelBindingHelper.TryUpdateModelAsync(
@ -1437,7 +1437,7 @@ namespace Microsoft.AspNetCore.Mvc
ControllerContext.InputFormatters, ControllerContext.InputFormatters,
ObjectValidator, ObjectValidator,
new CompositeModelValidatorProvider(ControllerContext.ValidatorProviders), new CompositeModelValidatorProvider(ControllerContext.ValidatorProviders),
predicate); propertyFilter);
} }
/// <summary> /// <summary>

View File

@ -69,20 +69,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Provide a unique instance based on one passed to the constructor. // Provide a unique instance based on one passed to the constructor.
context.BindingMetadata.ModelBindingMessageProvider = new ModelBindingMessageProvider(_messageProvider); context.BindingMetadata.ModelBindingMessageProvider = new ModelBindingMessageProvider(_messageProvider);
// PropertyBindingPredicateProvider // PropertyFilterProvider
var predicateProviders = context.Attributes.OfType<IPropertyBindingPredicateProvider>().ToArray(); var propertyFilterProviders = context.Attributes.OfType<IPropertyFilterProvider>().ToArray();
if (predicateProviders.Length == 0) if (propertyFilterProviders.Length == 0)
{ {
context.BindingMetadata.PropertyBindingPredicateProvider = null; context.BindingMetadata.PropertyFilterProvider = null;
} }
else if (predicateProviders.Length == 1) else if (propertyFilterProviders.Length == 1)
{ {
context.BindingMetadata.PropertyBindingPredicateProvider = predicateProviders[0]; context.BindingMetadata.PropertyFilterProvider = propertyFilterProviders[0];
} }
else else
{ {
context.BindingMetadata.PropertyBindingPredicateProvider = new CompositePredicateProvider( var composite = new CompositePropertyFilterProvider(propertyFilterProviders);
predicateProviders); context.BindingMetadata.PropertyFilterProvider = composite;
} }
if (context.Key.MetadataKind == ModelMetadataKind.Property) if (context.Key.MetadataKind == ModelMetadataKind.Property)
@ -107,34 +107,34 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
private class CompositePredicateProvider : IPropertyBindingPredicateProvider private class CompositePropertyFilterProvider : IPropertyFilterProvider
{ {
private readonly IEnumerable<IPropertyBindingPredicateProvider> _providers; private readonly IEnumerable<IPropertyFilterProvider> _providers;
public CompositePredicateProvider(IEnumerable<IPropertyBindingPredicateProvider> providers) public CompositePropertyFilterProvider(IEnumerable<IPropertyFilterProvider> providers)
{ {
_providers = providers; _providers = providers;
} }
public Func<ModelBindingContext, string, bool> PropertyFilter public Func<ModelMetadata, bool> PropertyFilter
{ {
get get
{ {
return CreatePredicate(); return CreatePropertyFilter();
} }
} }
private Func<ModelBindingContext, string, bool> CreatePredicate() private Func<ModelMetadata, bool> CreatePropertyFilter()
{ {
var predicates = _providers var propertyFilters = _providers
.Select(p => p.PropertyFilter) .Select(p => p.PropertyFilter)
.Where(p => p != null); .Where(p => p != null);
return (context, propertyName) => return (m) =>
{ {
foreach (var predicate in predicates) foreach (var propertyFilter in propertyFilters)
{ {
if (!predicate(context, propertyName)) if (!propertyFilter(m))
{ {
return false; return false;
} }

View File

@ -56,8 +56,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
var binderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName; var binderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName;
var propertyPredicateProvider = var propertyFilterProvider = bindingInfo?.PropertyFilterProvider ?? metadata.PropertyFilterProvider;
bindingInfo?.PropertyBindingPredicateProvider ?? metadata.PropertyBindingPredicateProvider;
var valueProvider = operationBindingContext.ValueProvider; var valueProvider = operationBindingContext.ValueProvider;
var bindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource; var bindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource;
@ -70,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
BinderModelName = binderModelName, BinderModelName = binderModelName,
BindingSource = bindingSource, BindingSource = bindingSource,
PropertyFilter = propertyPredicateProvider?.PropertyFilter, PropertyFilter = propertyFilterProvider?.PropertyFilter,
// Because this is the top-level context, FieldName and ModelName should be the same. // Because this is the top-level context, FieldName and ModelName should be the same.
FieldName = binderModelName ?? modelName, FieldName = binderModelName ?? modelName,
@ -123,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
FieldName = fieldName; FieldName = fieldName;
BinderModelName = modelMetadata.BinderModelName; BinderModelName = modelMetadata.BinderModelName;
BindingSource = modelMetadata.BindingSource; BindingSource = modelMetadata.BindingSource;
PropertyFilter = modelMetadata.PropertyBindingPredicateProvider?.PropertyFilter; PropertyFilter = modelMetadata.PropertyFilterProvider?.PropertyFilter;
IsTopLevelObject = false; IsTopLevelObject = false;
@ -262,7 +261,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
/// <inheritdoc /> /// <inheritdoc />
public override Func<ModelBindingContext, string, bool> PropertyFilter public override Func<ModelMetadata, bool> PropertyFilter
{ {
get { return _state.PropertyFilter; } get { return _state.PropertyFilter; }
set { _state.PropertyFilter = value; } set { _state.PropertyFilter = value; }
@ -317,7 +316,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public string ModelName; public string ModelName;
public IValueProvider ValueProvider; public IValueProvider ValueProvider;
public Func<ModelBindingContext, string, bool> PropertyFilter; public Func<ModelMetadata, bool> PropertyFilter;
public ValidationStateDictionary ValidationState; public ValidationStateDictionary ValidationState;
public ModelStateDictionary ModelState; public ModelStateDictionary ModelState;

View File

@ -114,13 +114,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// <returns><c>true</c> if the model property can be bound, otherwise <c>false</c>.</returns> /// <returns><c>true</c> if the model property can be bound, otherwise <c>false</c>.</returns>
protected virtual bool CanBindProperty(ModelBindingContext bindingContext, ModelMetadata propertyMetadata) protected virtual bool CanBindProperty(ModelBindingContext bindingContext, ModelMetadata propertyMetadata)
{ {
var modelMetadataPredicate = bindingContext.ModelMetadata.PropertyBindingPredicateProvider?.PropertyFilter; var metadataProviderFilter = bindingContext.ModelMetadata.PropertyFilterProvider?.PropertyFilter;
if (modelMetadataPredicate?.Invoke(bindingContext, propertyMetadata.PropertyName) == false) if (metadataProviderFilter?.Invoke(propertyMetadata) == false)
{ {
return false; return false;
} }
if (bindingContext.PropertyFilter?.Invoke(bindingContext, propertyMetadata.PropertyName) == false) if (bindingContext.PropertyFilter?.Invoke(propertyMetadata) == false)
{ {
return false; return false;
} }

View File

@ -5,20 +5,18 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
/// <summary> /// <summary>
/// Default implementation for <see cref="IPropertyBindingPredicateProvider"/>. /// Default implementation for <see cref="IPropertyFilterProvider"/>.
/// Provides a expression based way to provide include properties. /// Provides a expression based way to provide include properties.
/// </summary> /// </summary>
/// <typeparam name="TModel">The target model Type.</typeparam> /// <typeparam name="TModel">The target model Type.</typeparam>
public class DefaultPropertyBindingPredicateProvider<TModel> : IPropertyBindingPredicateProvider public class DefaultPropertyFilterProvider<TModel> : IPropertyFilterProvider
where TModel : class where TModel : class
{ {
private static readonly Func<ModelBindingContext, string, bool> _defaultFilter = private static readonly Func<ModelMetadata, bool> _default = (m) => true;
(context, propertyName) => true;
/// <summary> /// <summary>
/// The prefix which is used while generating the property filter. /// The prefix which is used while generating the property filter.
@ -44,24 +42,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual Func<ModelBindingContext, string, bool> PropertyFilter public virtual Func<ModelMetadata, bool> PropertyFilter
{ {
get get
{ {
if (PropertyIncludeExpressions == null) if (PropertyIncludeExpressions == null)
{ {
return _defaultFilter; return _default;
} }
// We do not cache by default. // We do not cache by default.
return GetPredicateFromExpression(PropertyIncludeExpressions); return GetPropertyFilterFromExpression(PropertyIncludeExpressions);
} }
} }
private Func<ModelBindingContext, string, bool> GetPredicateFromExpression( private Func<ModelMetadata, bool> GetPropertyFilterFromExpression(
IEnumerable<Expression<Func<TModel, object>>> includeExpressions) IEnumerable<Expression<Func<TModel, object>>> includeExpressions)
{ {
var expression = ModelBindingHelper.GetIncludePredicateExpression(Prefix, includeExpressions.ToArray()); var expression = ModelBindingHelper.GetPropertyFilterExpression(includeExpressions.ToArray());
return expression.Compile(); return expression.Compile();
} }
} }

View File

@ -74,9 +74,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
} }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ModelBinding.IPropertyBindingPredicateProvider"/>. /// Gets or sets the <see cref="ModelBinding.IPropertyFilterProvider"/>.
/// See <see cref="ModelMetadata.PropertyBindingPredicateProvider"/>. /// See <see cref="ModelMetadata.PropertyFilterProvider"/>.
/// </summary> /// </summary>
public IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; set; } public IPropertyFilterProvider PropertyFilterProvider { get; set; }
} }
} }

View File

@ -456,11 +456,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
} }
/// <inheritdoc /> /// <inheritdoc />
public override IPropertyBindingPredicateProvider PropertyBindingPredicateProvider public override IPropertyFilterProvider PropertyFilterProvider
{ {
get get
{ {
return BindingMetadata.PropertyBindingPredicateProvider; return BindingMetadata.PropertyFilterProvider;
} }
} }

View File

@ -168,7 +168,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
BinderModelName = metadata.BinderModelName, BinderModelName = metadata.BinderModelName,
BinderType = metadata.BinderType, BinderType = metadata.BinderType,
BindingSource = metadata.BindingSource, BindingSource = metadata.BindingSource,
PropertyBindingPredicateProvider = metadata.PropertyBindingPredicateProvider, PropertyFilterProvider = metadata.PropertyFilterProvider,
}; };
} }

View File

@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
inputFormatters, inputFormatters,
objectModelValidator, objectModelValidator,
validatorProvider, validatorProvider,
predicate: (context, propertyName) => true); propertyFilter: (m) => true);
} }
/// <summary> /// <summary>
@ -198,8 +198,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(includeExpressions)); throw new ArgumentNullException(nameof(includeExpressions));
} }
var includeExpression = GetIncludePredicateExpression(prefix, includeExpressions); var expression = GetPropertyFilterExpression(includeExpressions);
var predicate = includeExpression.Compile(); var propertyFilter = expression.Compile();
return TryUpdateModelAsync( return TryUpdateModelAsync(
model, model,
@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
inputFormatters, inputFormatters,
objectModelValidator, objectModelValidator,
validatorProvider, validatorProvider,
predicate: predicate); propertyFilter: propertyFilter);
} }
/// <summary> /// <summary>
@ -234,8 +234,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// bound values.</param> /// bound values.</param>
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation /// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
/// on the model instance.</param> /// on the model instance.</param>
/// <param name="predicate">A predicate which can be used to /// <param name="propertyFilter">
/// filter properties(for inclusion/exclusion) at runtime.</param> /// A predicate which can be used to filter properties(for inclusion/exclusion) at runtime.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns> /// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
public static Task<bool> TryUpdateModelAsync<TModel>( public static Task<bool> TryUpdateModelAsync<TModel>(
TModel model, TModel model,
@ -247,7 +248,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
IList<IInputFormatter> inputFormatters, IList<IInputFormatter> inputFormatters,
IObjectModelValidator objectModelValidator, IObjectModelValidator objectModelValidator,
IModelValidatorProvider validatorProvider, IModelValidatorProvider validatorProvider,
Func<ModelBindingContext, string, bool> predicate) Func<ModelMetadata, bool> propertyFilter)
where TModel : class where TModel : class
{ {
if (model == null) if (model == null)
@ -295,9 +296,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(validatorProvider)); throw new ArgumentNullException(nameof(validatorProvider));
} }
if (predicate == null) if (propertyFilter == null)
{ {
throw new ArgumentNullException(nameof(predicate)); throw new ArgumentNullException(nameof(propertyFilter));
} }
return TryUpdateModelAsync( return TryUpdateModelAsync(
@ -311,7 +312,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
inputFormatters, inputFormatters,
objectModelValidator, objectModelValidator,
validatorProvider, validatorProvider,
predicate: predicate); propertyFilter: propertyFilter);
} }
/// <summary> /// <summary>
@ -409,7 +410,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
inputFormatters, inputFormatters,
objectModelValidator, objectModelValidator,
validatorProvider, validatorProvider,
predicate: (context, propertyName) => true); propertyFilter: (m) => true);
} }
/// <summary> /// <summary>
@ -432,21 +433,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// bound values.</param> /// bound values.</param>
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation /// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
/// on the model instance.</param> /// on the model instance.</param>
/// <param name="predicate">A predicate which can be used to /// <param name="propertyFilter">A predicate which can be used to
/// filter properties(for inclusion/exclusion) at runtime.</param> /// filter properties(for inclusion/exclusion) at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns> /// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
public static async Task<bool> TryUpdateModelAsync( public static async Task<bool> TryUpdateModelAsync(
object model, object model,
Type modelType, Type modelType,
string prefix, string prefix,
ActionContext actionContext, ActionContext actionContext,
IModelMetadataProvider metadataProvider, IModelMetadataProvider metadataProvider,
IModelBinderFactory modelBinderFactory, IModelBinderFactory modelBinderFactory,
IValueProvider valueProvider, IValueProvider valueProvider,
IList<IInputFormatter> inputFormatters, IList<IInputFormatter> inputFormatters,
IObjectModelValidator objectModelValidator, IObjectModelValidator objectModelValidator,
IModelValidatorProvider validatorProvider, IModelValidatorProvider validatorProvider,
Func<ModelBindingContext, string, bool> predicate) Func<ModelMetadata, bool> propertyFilter)
{ {
if (model == null) if (model == null)
{ {
@ -498,9 +499,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(validatorProvider)); throw new ArgumentNullException(nameof(validatorProvider));
} }
if (predicate == null) if (propertyFilter == null)
{ {
throw new ArgumentNullException(nameof(predicate)); throw new ArgumentNullException(nameof(propertyFilter));
} }
if (!modelType.IsAssignableFrom(model.GetType())) if (!modelType.IsAssignableFrom(model.GetType()))
@ -529,7 +530,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
bindingInfo: null, bindingInfo: null,
modelName: prefix ?? string.Empty); modelName: prefix ?? string.Empty);
modelBindingContext.Model = model; modelBindingContext.Model = model;
modelBindingContext.PropertyFilter = predicate; modelBindingContext.PropertyFilter = propertyFilter;
var factoryContext = new ModelBinderFactoryContext() var factoryContext = new ModelBinderFactoryContext()
{ {
@ -539,7 +540,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
BinderModelName = modelMetadata.BinderModelName, BinderModelName = modelMetadata.BinderModelName,
BinderType = modelMetadata.BinderType, BinderType = modelMetadata.BinderType,
BindingSource = modelMetadata.BindingSource, BindingSource = modelMetadata.BindingSource,
PropertyBindingPredicateProvider = modelMetadata.PropertyBindingPredicateProvider, PropertyFilterProvider = modelMetadata.PropertyFilterProvider,
}, },
// We're using the model metadata as the cache token here so that TryUpdateModelAsync calls // We're using the model metadata as the cache token here so that TryUpdateModelAsync calls
@ -607,42 +608,36 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// Creates an expression for a predicate to limit the set of properties used in model binding. /// Creates an expression for a predicate to limit the set of properties used in model binding.
/// </summary> /// </summary>
/// <typeparam name="TModel">The model type.</typeparam> /// <typeparam name="TModel">The model type.</typeparam>
/// <param name="prefix">The model prefix.</param>
/// <param name="expressions">Expressions identifying the properties to allow for binding.</param> /// <param name="expressions">Expressions identifying the properties to allow for binding.</param>
/// <returns>An expression which can be used with <see cref="IPropertyBindingPredicateProvider"/>.</returns> /// <returns>An expression which can be used with <see cref="IPropertyFilterProvider"/>.</returns>
public static Expression<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel>( public static Expression<Func<ModelMetadata, bool>> GetPropertyFilterExpression<TModel>(
string prefix,
Expression<Func<TModel, object>>[] expressions) Expression<Func<TModel, object>>[] expressions)
{ {
if (expressions.Length == 0) if (expressions.Length == 0)
{ {
// If nothing is included explcitly, treat everything as included. // If nothing is included explicitly, treat everything as included.
return (context, propertyName) => true; return (m) => true;
} }
var firstExpression = GetPredicateExpression(prefix, expressions[0]); var firstExpression = GetPredicateExpression(expressions[0]);
var orWrapperExpression = firstExpression.Body; var orWrapperExpression = firstExpression.Body;
foreach (var expression in expressions.Skip(1)) foreach (var expression in expressions.Skip(1))
{ {
var predicate = GetPredicateExpression(prefix, expression); var predicate = GetPredicateExpression(expression);
orWrapperExpression = Expression.OrElse(orWrapperExpression, orWrapperExpression = Expression.OrElse(
Expression.Invoke(predicate, firstExpression.Parameters)); orWrapperExpression,
Expression.Invoke(predicate, firstExpression.Parameters));
} }
return Expression.Lambda<Func<ModelBindingContext, string, bool>>( return Expression.Lambda<Func<ModelMetadata, bool>>(orWrapperExpression, firstExpression.Parameters);
orWrapperExpression, firstExpression.Parameters);
} }
private static Expression<Func<ModelBindingContext, string, bool>> GetPredicateExpression<TModel> private static Expression<Func<ModelMetadata, bool>> GetPredicateExpression<TModel>(
(string prefix, Expression<Func<TModel, object>> expression) Expression<Func<TModel, object>> expression)
{ {
var propertyName = GetPropertyName(expression.Body); var propertyName = GetPropertyName(expression.Body);
var property = ModelNames.CreatePropertyModelName(prefix, propertyName);
return return (metadata) => string.Equals(metadata.PropertyName, propertyName, StringComparison.Ordinal);
(context, modelPropertyName) =>
property.Equals(ModelNames.CreatePropertyModelName(context.ModelName, modelPropertyName),
StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>

View File

@ -922,22 +922,6 @@ namespace Microsoft.AspNetCore.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0);
} }
/// <summary>
/// The type '{0}' does not implement the interface '{1}'.
/// </summary>
internal static string PropertyBindingPredicateProvider_WrongType
{
get { return GetString("PropertyBindingPredicateProvider_WrongType"); }
}
/// <summary>
/// The type '{0}' does not implement the interface '{1}'.
/// </summary>
internal static string FormatPropertyBindingPredicateProvider_WrongType(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyBindingPredicateProvider_WrongType"), p0, p1);
}
/// <summary> /// <summary>
/// The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types. /// The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.
/// </summary> /// </summary>
@ -1159,7 +1143,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
/// </summary> /// </summary>
internal static string FormatMustSpecifyAtLeastOneAuthenticationScheme() internal static string FormatMustSpecifyAtLeastOneAuthenticationScheme()
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("MustSpecifyAtLeastOneAuthenticationScheme")); return GetString("MustSpecifyAtLeastOneAuthenticationScheme");
} }
/// <summary> /// <summary>

View File

@ -297,9 +297,6 @@
<data name="ModelBinding_MissingBindRequiredMember" xml:space="preserve"> <data name="ModelBinding_MissingBindRequiredMember" xml:space="preserve">
<value>A value for the '{0}' property was not provided.</value> <value>A value for the '{0}' property was not provided.</value>
</data> </data>
<data name="PropertyBindingPredicateProvider_WrongType" xml:space="preserve">
<value>The type '{0}' does not implement the interface '{1}'.</value>
</data>
<data name="ValueProviderResult_NoConverterExists" xml:space="preserve"> <data name="ValueProviderResult_NoConverterExists" xml:space="preserve">
<value>The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.</value> <value>The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.</value>
</data> </data>

View File

@ -536,7 +536,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
} }
public override IPropertyBindingPredicateProvider PropertyBindingPredicateProvider public override IPropertyFilterProvider PropertyFilterProvider
{ {
get get
{ {

View File

@ -3,6 +3,7 @@
using System; using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Moq; using Moq;
using Xunit; using Xunit;
@ -10,34 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
public class BindAttributeTest public class BindAttributeTest
{ {
[Fact]
public void Constructor_Throws_IfTypeDoesNotImplement_IPropertyBindingPredicateProvider()
{
// Arrange
var expected =
"The type 'Microsoft.AspNetCore.Mvc.ModelBinding.BindAttributeTest+UnrelatedType' " +
"does not implement the interface " +
"'Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyBindingPredicateProvider'." +
Environment.NewLine +
"Parameter name: predicateProviderType";
// Act & Assert
var exception = Assert.Throws<ArgumentException>(() => new BindAttribute(typeof(UnrelatedType)));
Assert.Equal(expected, exception.Message);
}
[Theory]
[InlineData(typeof(DerivedProvider))]
[InlineData(typeof(BaseProvider))]
public void Constructor_SetsThe_PropertyFilterProviderType_ForValidTypes(Type type)
{
// Arrange
var attribute = new BindAttribute(type);
// Act & Assert
Assert.Equal(type, attribute.PredicateProviderType);
}
[Theory] [Theory]
[InlineData("UserName", true)] [InlineData("UserName", true)]
[InlineData("Username", false)] [InlineData("Username", false)]
@ -54,98 +27,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var context = new DefaultModelBindingContext(); var context = new DefaultModelBindingContext();
// Act var identity = ModelMetadataIdentity.ForProperty(typeof(int), property, typeof(string));
var predicate = bind.PropertyFilter; context.ModelMetadata = new Mock<ModelMetadata>(identity).Object;
// Assert
Assert.Equal(isIncluded, predicate(context, property));
}
[Theory]
[InlineData("UserName", true)]
[InlineData("Username", false)]
[InlineData("Password", false)]
public void BindAttribute_ProviderType(string property, bool isIncluded)
{
// Arrange
var bind = new BindAttribute(typeof(TestProvider));
var context = new DefaultModelBindingContext();
context.OperationBindingContext = new OperationBindingContext()
{
ActionContext = new ActionContext()
{
HttpContext = new DefaultHttpContext(),
},
};
var services = new Mock<IServiceProvider>();
context.OperationBindingContext.HttpContext.RequestServices = services.Object;
// Act // Act
var predicate = bind.PropertyFilter; var propertyFilter = bind.PropertyFilter;
// Assert // Assert
Assert.Equal(isIncluded, predicate(context, property)); Assert.Equal(isIncluded, propertyFilter(context.ModelMetadata));
}
// Each time .PropertyFilter is called, a since instance of the provider should
// be created and cached.
[Fact]
public void BindAttribute_ProviderType_Cached()
{
// Arrange
var bind = new BindAttribute(typeof(TestProvider));
var context = new DefaultModelBindingContext();
context.OperationBindingContext = new OperationBindingContext()
{
ActionContext = new ActionContext()
{
HttpContext = new DefaultHttpContext(),
},
};
var services = new Mock<IServiceProvider>(MockBehavior.Strict);
context.OperationBindingContext.HttpContext.RequestServices = services.Object;
// Act
var predicate = bind.PropertyFilter;
// Assert
Assert.True(predicate(context, "UserName"));
Assert.True(predicate(context, "UserName"));
}
private class TestProvider : IPropertyBindingPredicateProvider
{
public Func<ModelBindingContext, string, bool> PropertyFilter
{
get
{
return (context, property) => string.Equals(property, "UserName", StringComparison.Ordinal);
}
}
}
private class BaseProvider : IPropertyBindingPredicateProvider
{
public Func<ModelBindingContext, string, bool> PropertyFilter
{
get
{
throw new NotImplementedException();
}
}
}
private class DerivedProvider : BaseProvider
{
}
private class UnrelatedType
{
} }
} }
} }

View File

@ -1151,8 +1151,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Include and exclude should be null, resulting in property // Include and exclude should be null, resulting in property
// being included. // being included.
Assert.True(context.PropertyFilter(context, "Property1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property1"]));
Assert.True(context.PropertyFilter(context, "Property2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property2"]));
}); });
var controller = GetController(binder, valueProvider); var controller = GetController(binder, valueProvider);
@ -1179,8 +1179,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Include and exclude should be null, resulting in property // Include and exclude should be null, resulting in property
// being included. // being included.
Assert.True(context.PropertyFilter(context, "Property1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property1"]));
Assert.True(context.PropertyFilter(context, "Property2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property2"]));
}); });
var controller = GetController(binder, valueProvider); var controller = GetController(binder, valueProvider);
@ -1206,8 +1206,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Include and exclude should be null, resulting in property // Include and exclude should be null, resulting in property
// being included. // being included.
Assert.True(context.PropertyFilter(context, "Property1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property1"]));
Assert.True(context.PropertyFilter(context, "Property2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property2"]));
}); });
var controller = GetController(binder, valueProvider: null); var controller = GetController(binder, valueProvider: null);
@ -1221,64 +1221,65 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
} }
[Fact] [Fact]
public async Task TryUpdateModel_PredicateOverload_UsesPassedArguments() public async Task TryUpdateModel_PropertyFilterOverload_UsesPassedArguments()
{ {
// Arrange // Arrange
var modelName = "mymodel"; var modelName = "mymodel";
Func<ModelBindingContext, string, bool> includePredicate = (context, propertyName) => Func<ModelMetadata, bool> propertyFilter = (m) =>
string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) || string.Equals(m.PropertyName, "Include1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase); string.Equals(m.PropertyName, "Include2", StringComparison.OrdinalIgnoreCase);
var valueProvider = Mock.Of<IValueProvider>(); var valueProvider = Mock.Of<IValueProvider>();
var binder = new StubModelBinder(context => var binder = new StubModelBinder(context =>
{ {
Assert.Same(valueProvider, Assert.IsType<CompositeValueProvider>(context.ValueProvider)[0]); Assert.Same(valueProvider, Assert.IsType<CompositeValueProvider>(context.ValueProvider)[0]);
Assert.True(context.PropertyFilter(context, "include1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Include1"]));
Assert.True(context.PropertyFilter(context, "include2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Include2"]));
Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude1"]));
Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude2"]));
Assert.False(context.PropertyFilter(context, "exclude1"));
Assert.False(context.PropertyFilter(context, "exclude2"));
}); });
var controller = GetController(binder, valueProvider); var controller = GetController(binder, valueProvider);
var model = new MyModel(); var model = new MyModel();
// Act // Act
await controller.TryUpdateModelAsync(model, modelName, includePredicate); await controller.TryUpdateModelAsync(model, modelName, propertyFilter);
// Assert // Assert
Assert.NotEqual(0, binder.BindModelCount); Assert.NotEqual(0, binder.BindModelCount);
} }
[Fact] [Fact]
public async Task TryUpdateModel_PredicateWithValueProviderOverload_UsesPassedArguments() public async Task TryUpdateModel_PropertyFilterWithValueProviderOverload_UsesPassedArguments()
{ {
// Arrange // Arrange
var modelName = "mymodel"; var modelName = "mymodel";
Func<ModelBindingContext, string, bool> includePredicate = Func<ModelMetadata, bool> propertyFilter = (m) =>
(context, propertyName) => string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) || string.Equals(m.PropertyName, "Include1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase); string.Equals(m.PropertyName, "Include2", StringComparison.OrdinalIgnoreCase);
var valueProvider = Mock.Of<IValueProvider>(); var valueProvider = Mock.Of<IValueProvider>();
var binder = new StubModelBinder(context => var binder = new StubModelBinder(context =>
{ {
Assert.Same(valueProvider, context.ValueProvider); Assert.Same(valueProvider, context.ValueProvider);
Assert.True(context.PropertyFilter(context, "include1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Include1"]));
Assert.True(context.PropertyFilter(context, "include2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Include2"]));
Assert.False(context.PropertyFilter(context, "exclude1")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude1"]));
Assert.False(context.PropertyFilter(context, "exclude2")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude2"]));
}); });
var controller = GetController(binder, valueProvider: null); var controller = GetController(binder, valueProvider: null);
var model = new MyModel(); var model = new MyModel();
// Act // Act
await controller.TryUpdateModelAsync(model, modelName, valueProvider, includePredicate); await controller.TryUpdateModelAsync(model, modelName, valueProvider, propertyFilter);
// Assert // Assert
Assert.NotEqual(0, binder.BindModelCount); Assert.NotEqual(0, binder.BindModelCount);
@ -1298,14 +1299,14 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
var binder = new StubModelBinder(context => var binder = new StubModelBinder(context =>
{ {
Assert.Same( Assert.Same(
valueProvider.Object, valueProvider.Object,
Assert.IsType<CompositeValueProvider>(context.ValueProvider)[0]); Assert.IsType<CompositeValueProvider>(context.ValueProvider)[0]);
Assert.True(context.PropertyFilter(context, "Property1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property1"]));
Assert.True(context.PropertyFilter(context, "Property2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property2"]));
Assert.False(context.PropertyFilter(context, "exclude1")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude1"]));
Assert.False(context.PropertyFilter(context, "exclude2")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude2"]));
}); });
@ -1335,11 +1336,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
{ {
Assert.Same(valueProvider.Object, context.ValueProvider); Assert.Same(valueProvider.Object, context.ValueProvider);
Assert.True(context.PropertyFilter(context, "Property1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property1"]));
Assert.True(context.PropertyFilter(context, "Property2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property2"]));
Assert.False(context.PropertyFilter(context, "exclude1")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude1"]));
Assert.False(context.PropertyFilter(context, "exclude2")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude2"]));
}); });
var controller = GetController(binder, valueProvider: null); var controller = GetController(binder, valueProvider: null);
@ -1353,14 +1354,14 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
} }
[Fact] [Fact]
public async Task TryUpdateModelNonGeneric_PredicateWithValueProviderOverload_UsesPassedArguments() public async Task TryUpdateModelNonGeneric_PropertyFilterWithValueProviderOverload_UsesPassedArguments()
{ {
// Arrange // Arrange
var modelName = "mymodel"; var modelName = "mymodel";
Func<ModelBindingContext, string, bool> includePredicate = Func<ModelMetadata, bool> propertyFilter = (m) =>
(context, propertyName) => string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) || string.Equals(m.PropertyName, "Include1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase); string.Equals(m.PropertyName, "Include2", StringComparison.OrdinalIgnoreCase);
var valueProvider = Mock.Of<IValueProvider>(); var valueProvider = Mock.Of<IValueProvider>();
@ -1368,11 +1369,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
{ {
Assert.Same(valueProvider, context.ValueProvider); Assert.Same(valueProvider, context.ValueProvider);
Assert.True(context.PropertyFilter(context, "include1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Include1"]));
Assert.True(context.PropertyFilter(context, "include2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Include2"]));
Assert.False(context.PropertyFilter(context, "exclude1")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude1"]));
Assert.False(context.PropertyFilter(context, "exclude2")); Assert.False(context.PropertyFilter(context.ModelMetadata.Properties["Exclude2"]));
}); });
var controller = GetController(binder, valueProvider: null); var controller = GetController(binder, valueProvider: null);
@ -1380,7 +1381,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
var model = new MyModel(); var model = new MyModel();
// Act // Act
await controller.TryUpdateModelAsync(model, model.GetType(), modelName, valueProvider, includePredicate); await controller.TryUpdateModelAsync(model, model.GetType(), modelName, valueProvider, propertyFilter);
// Assert // Assert
Assert.NotEqual(0, binder.BindModelCount); Assert.NotEqual(0, binder.BindModelCount);
@ -1400,8 +1401,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Include and exclude should be null, resulting in property // Include and exclude should be null, resulting in property
// being included. // being included.
Assert.True(context.PropertyFilter(context, "Property1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property1"]));
Assert.True(context.PropertyFilter(context, "Property2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property2"]));
}); });
var controller = GetController(binder, valueProvider); var controller = GetController(binder, valueProvider);
@ -1428,8 +1429,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Include and exclude should be null, resulting in property // Include and exclude should be null, resulting in property
// being included. // being included.
Assert.True(context.PropertyFilter(context, "Property1")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property1"]));
Assert.True(context.PropertyFilter(context, "Property2")); Assert.True(context.PropertyFilter(context.ModelMetadata.Properties["Property2"]));
}); });
var controller = GetController(binder, valueProvider); var controller = GetController(binder, valueProvider);
@ -1667,6 +1668,12 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
{ {
public string Property1 { get; set; } public string Property1 { get; set; }
public string Property2 { get; set; } public string Property2 { get; set; }
public string Include1 { get; set; }
public string Include2 { get; set; }
public string Exclude1 { get; set; }
public string Exclude2 { get; set; }
} }
private class MyDerivedModel : MyModel private class MyDerivedModel : MyModel

View File

@ -488,39 +488,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.False(result); Assert.False(result);
} }
[Theory]
[InlineData(nameof(TypeWithExcludedPropertiesUsingBindAttribute.IncludedByDefault1), true)]
[InlineData(nameof(TypeWithExcludedPropertiesUsingBindAttribute.IncludedByDefault2), true)]
[InlineData(nameof(TypeWithExcludedPropertiesUsingBindAttribute.Excluded1), false)]
[InlineData(nameof(TypeWithExcludedPropertiesUsingBindAttribute.Excluded2), false)]
public void CanBindProperty_WithPredicate(string property, bool expected)
{
// Arrange
var metadata = GetMetadataForProperty(typeof(TypeWithExcludedPropertiesUsingBindAttribute), property);
var bindingContext = new DefaultModelBindingContext()
{
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
OperationBindingContext = new OperationBindingContext()
{
ActionContext = new ActionContext()
{
HttpContext = new DefaultHttpContext()
{
RequestServices = new ServiceCollection().BuildServiceProvider(),
},
},
},
};
var binder = CreateBinder(bindingContext.ModelMetadata);
// Act
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
// Assert
Assert.Equal(expected, result);
}
[Theory] [Theory]
[InlineData(nameof(TypeWithIncludedPropertiesUsingBindAttribute.IncludedExplicitly1), true)] [InlineData(nameof(TypeWithIncludedPropertiesUsingBindAttribute.IncludedExplicitly1), true)]
[InlineData(nameof(TypeWithIncludedPropertiesUsingBindAttribute.IncludedExplicitly2), true)] [InlineData(nameof(TypeWithIncludedPropertiesUsingBindAttribute.IncludedExplicitly2), true)]
@ -1084,7 +1051,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
BinderModelName = metadata.BinderModelName, BinderModelName = metadata.BinderModelName,
BinderType = metadata.BinderType, BinderType = metadata.BinderType,
BindingSource = metadata.BindingSource, BindingSource = metadata.BindingSource,
PropertyBindingPredicateProvider = metadata.PropertyBindingPredicateProvider, PropertyFilterProvider = metadata.PropertyFilterProvider,
}, },
}); });
} }
@ -1284,17 +1251,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
public int IncludedExplicitly2 { get; set; } public int IncludedExplicitly2 { get; set; }
} }
[Bind(typeof(ExcludedProvider))]
private class TypeWithExcludedPropertiesUsingBindAttribute
{
public int Excluded1 { get; set; }
public int Excluded2 { get; set; }
public int IncludedByDefault1 { get; set; }
public int IncludedByDefault2 { get; set; }
}
private class Document private class Document
{ {
[NonValueBinderMetadata] [NonValueBinderMetadata]
@ -1317,15 +1273,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
public BindingSource BindingSource { get { return BindingSource.Query; } } public BindingSource BindingSource { get { return BindingSource.Query; } }
} }
private class ExcludedProvider : IPropertyBindingPredicateProvider private class ExcludedProvider : IPropertyFilterProvider
{ {
public Func<ModelBindingContext, string, bool> PropertyFilter public Func<ModelMetadata, bool> PropertyFilter
{ {
get get
{ {
return (context, propertyName) => return (m) =>
!string.Equals("Excluded1", propertyName, StringComparison.OrdinalIgnoreCase) && !string.Equals("Excluded1", m.PropertyName, StringComparison.OrdinalIgnoreCase) &&
!string.Equals("Excluded2", propertyName, StringComparison.OrdinalIgnoreCase); !string.Equals("Excluded2", m.PropertyName, StringComparison.OrdinalIgnoreCase);
} }
} }
} }

View File

@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
Assert.Null(metadata.BinderModelName); Assert.Null(metadata.BinderModelName);
Assert.Null(metadata.BinderType); Assert.Null(metadata.BinderType);
Assert.Null(metadata.BindingSource); Assert.Null(metadata.BindingSource);
Assert.Null(metadata.PropertyBindingPredicateProvider); Assert.Null(metadata.PropertyFilterProvider);
} }
[Fact] [Fact]

View File

@ -150,14 +150,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[Theory] [Theory]
[MemberData(nameof(UnsuccessfulModelBindingData))] [MemberData(nameof(UnsuccessfulModelBindingData))]
public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsFalse_IfBinderIsUnsuccessful( public async Task TryUpdateModel_UsingPropertyFilterOverload_ReturnsFalse_IfBinderIsUnsuccessful(
ModelBindingResult? binderResult) ModelBindingResult? binderResult)
{ {
// Arrange // Arrange
var metadataProvider = new EmptyModelMetadataProvider(); var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder(binderResult); var binder = new StubModelBinder(binderResult);
var model = new MyModel(); var model = new MyModel();
Func<ModelBindingContext, string, bool> includePredicate = (context, propertyName) => true; Func<ModelMetadata, bool> propertyFilter = (m) => true;
// Act // Act
var result = await ModelBindingHelper.TryUpdateModelAsync( var result = await ModelBindingHelper.TryUpdateModelAsync(
@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
new List<IInputFormatter>(), new List<IInputFormatter>(),
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object, new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
Mock.Of<IModelValidatorProvider>(), Mock.Of<IModelValidatorProvider>(),
includePredicate); propertyFilter);
// Assert // Assert
Assert.False(result); Assert.False(result);
@ -180,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
[Fact] [Fact]
public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully() public async Task TryUpdateModel_UsingPropertyFilterOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
{ {
// Arrange // Arrange
var binderProviders = new IModelBinderProvider[] var binderProviders = new IModelBinderProvider[]
@ -208,9 +208,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ "ExcludedProperty", "ExcludedPropertyValue" } { "ExcludedProperty", "ExcludedPropertyValue" }
}; };
Func<ModelBindingContext, string, bool> includePredicate = (context, propertyName) => Func<ModelMetadata, bool> propertyFilter = (m) =>
string.Equals(propertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) || string.Equals(m.PropertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) ||
string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase); string.Equals(m.PropertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
var valueProvider = new TestValueProvider(values); var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(metadataProvider, new ValidatorCache()), new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
validator, validator,
includePredicate); propertyFilter);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -491,14 +491,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[Theory] [Theory]
[MemberData(nameof(UnsuccessfulModelBindingData))] [MemberData(nameof(UnsuccessfulModelBindingData))]
public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsFalse_IfBinderIsUnsuccessful( public async Task TryUpdateModelNonGeneric_PropertyFilterOverload_ReturnsFalse_IfBinderIsUnsuccessful(
ModelBindingResult? binderResult) ModelBindingResult? binderResult)
{ {
// Arrange // Arrange
var metadataProvider = new EmptyModelMetadataProvider(); var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder(binderResult); var binder = new StubModelBinder(binderResult);
var model = new MyModel(); var model = new MyModel();
Func<ModelBindingContext, string, bool> includePredicate = (context, propertyName) => true; Func<ModelMetadata, bool> propertyFilter = (m) => true;
// Act // Act
var result = await ModelBindingHelper.TryUpdateModelAsync( var result = await ModelBindingHelper.TryUpdateModelAsync(
@ -512,7 +512,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
inputFormatters: new List<IInputFormatter>(), inputFormatters: new List<IInputFormatter>(),
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object, objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
validatorProvider: Mock.Of<IModelValidatorProvider>(), validatorProvider: Mock.Of<IModelValidatorProvider>(),
predicate: includePredicate); propertyFilter: propertyFilter);
// Assert // Assert
Assert.False(result); Assert.False(result);
@ -522,7 +522,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
[Fact] [Fact]
public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully() public async Task TryUpdateModelNonGeneric_PropertyFilterOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
{ {
// Arrange // Arrange
var binderProviders = new IModelBinderProvider[] var binderProviders = new IModelBinderProvider[]
@ -550,9 +550,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ "ExcludedProperty", "ExcludedPropertyValue" } { "ExcludedProperty", "ExcludedPropertyValue" }
}; };
Func<ModelBindingContext, string, bool> includePredicate = (context, propertyName) => Func<ModelMetadata, bool> propertyFilter = (m) =>
string.Equals(propertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) || string.Equals(m.PropertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) ||
string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase); string.Equals(m.PropertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
var valueProvider = new TestValueProvider(values); var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(metadataProvider, new ValidatorCache()), new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
validator, validator,
includePredicate); propertyFilter);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -657,7 +657,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var binder = new StubModelBinder(); var binder = new StubModelBinder();
var model = new MyModel(); var model = new MyModel();
Func<ModelBindingContext, string, bool> includePredicate = (context, propertyName) => true; Func<ModelMetadata, bool> propertyFilter = (m) => true;
// Act & Assert // Act & Assert
var exception = await Assert.ThrowsAsync<ArgumentException>( var exception = await Assert.ThrowsAsync<ArgumentException>(
@ -672,7 +672,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(metadataProvider, new ValidatorCache()), new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
Mock.Of<IModelValidatorProvider>(), Mock.Of<IModelValidatorProvider>(),
includePredicate)); propertyFilter));
var expectedMessage = string.Format("The model's runtime type '{0}' is not assignable to the type '{1}'." + var expectedMessage = string.Format("The model's runtime type '{0}' is not assignable to the type '{1}'." +
Environment.NewLine + Environment.NewLine +

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
BinderModelName = Metadata.BinderModelName, BinderModelName = Metadata.BinderModelName,
BinderType = Metadata.BinderType, BinderType = Metadata.BinderType,
BindingSource = Metadata.BindingSource, BindingSource = Metadata.BindingSource,
PropertyBindingPredicateProvider = Metadata.PropertyBindingPredicateProvider, PropertyFilterProvider = Metadata.PropertyFilterProvider,
}; };
} }

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public class ModelMetadataProviderTest public class ModelMetadataProviderTest
{ {
[Fact] [Fact]
public void ModelMetadataProvider_UsesPredicateOnType() public void ModelMetadataProvider_UsesPropertyFilterProviderOnType()
{ {
// Arrange // Arrange
var type = typeof(User); var type = typeof(User);
@ -32,12 +32,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var metadata = provider.GetMetadataForType(type); var metadata = provider.GetMetadataForType(type);
// Assert // Assert
var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter; var propertyFilter = metadata.PropertyFilterProvider.PropertyFilter;
var matched = new HashSet<string>(); var matched = new HashSet<string>();
foreach (var property in metadata.Properties) foreach (var property in metadata.Properties)
{ {
if (predicate(context, property.PropertyName)) if (propertyFilter(property))
{ {
matched.Add(property.PropertyName); matched.Add(property.PropertyName);
} }