Adding Support for TryUpdateModel using include expressions and predicate.

This commit is contained in:
Harsh Gupta 2014-11-10 13:24:51 -08:00 committed by Ryan Nowak
parent ca714c5149
commit d7094fd32d
25 changed files with 903 additions and 413 deletions

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
@ -96,21 +95,17 @@ namespace Microsoft.AspNet.Mvc
} }
} }
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata, internal static ModelBindingContext GetModelBindingContext(
ActionBindingContext actionBindingContext, ModelMetadata modelMetadata,
OperationBindingContext operationBindingContext) ActionBindingContext actionBindingContext,
OperationBindingContext operationBindingContext)
{ {
Func<ModelBindingContext, string, bool> propertyFilter =
(context, propertyName) => BindAttribute.IsPropertyAllowed(propertyName,
modelMetadata.BinderIncludeProperties,
modelMetadata.BinderExcludeProperties);
var modelBindingContext = new ModelBindingContext var modelBindingContext = new ModelBindingContext
{ {
ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName, ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName,
ModelMetadata = modelMetadata, ModelMetadata = modelMetadata,
ModelState = actionBindingContext.ActionContext.ModelState, ModelState = actionBindingContext.ActionContext.ModelState,
PropertyFilter = propertyFilter,
// Fallback only if there is no explicit model name set. // Fallback only if there is no explicit model name set.
FallbackToEmptyPrefix = modelMetadata.BinderModelName == null, FallbackToEmptyPrefix = modelMetadata.BinderModelName == null,
ValueProvider = actionBindingContext.ValueProvider, ValueProvider = actionBindingContext.ValueProvider,

View File

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
using System.Linq.Expressions;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Default implementation for <see cref="IPropertyBindingPredicateProvider"/>.
/// Provides a expression based way to provide include properties.
/// </summary>
/// <typeparam name="TModel">The target model Type.</typeparam>
public class DefaultPropertyBindingPredicateProvider<TModel> : IPropertyBindingPredicateProvider
where TModel : class
{
private static readonly Func<ModelBindingContext, string, bool> _defaultFilter = (context, propertyName) => true;
/// <summary>
/// The prefix which is used while generating the property filter.
/// </summary>
public virtual string Prefix
{
get
{
return string.Empty;
}
}
/// <summary>
/// Expressions which can be used to generate property filter which can filter model
/// properties.
/// </summary>
public virtual IEnumerable<Expression<Func<TModel, object>>> PropertyIncludeExpressions
{
get
{
return null;
}
}
/// <inheritdoc />
public virtual Func<ModelBindingContext, string, bool> PropertyFilter
{
get
{
if (PropertyIncludeExpressions == null)
{
return _defaultFilter;
}
// We do not cache by default.
return GetPredicateFromExpression(PropertyIncludeExpressions);
}
}
private Func<ModelBindingContext, string, bool> GetPredicateFromExpression(IEnumerable<Expression<Func<TModel, object>>> includeExpressions)
{
var expression = ModelBindingHelper.GetIncludePredicateExpression(Prefix, includeExpressions.ToArray());
return expression.Compile();
}
}
}

View File

@ -167,6 +167,7 @@ namespace Microsoft.AspNet.Mvc
return false; return false;
} }
// Internal for tests
internal static string GetPropertyName(Expression expression) internal static string GetPropertyName(Expression expression)
{ {
if (expression.NodeType == ExpressionType.Convert || if (expression.NodeType == ExpressionType.Convert ||
@ -203,8 +204,16 @@ namespace Microsoft.AspNet.Mvc
} }
} }
private static Expression<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel> /// <summary>
(string prefix, Expression<Func<TModel, object>>[] expressions) /// Creates an expression for a predicate to limit the set of properties used in model binding.
/// </summary>
/// <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>
/// <returns>An expression which can be used with <see cref="IPropertyBindingPredicateProvider"/>.</returns>
public static Expression<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel>(
string prefix,
Expression<Func<TModel, object>>[] expressions)
{ {
if (expressions.Length == 0) if (expressions.Length == 0)
{ {

View File

@ -2,8 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -11,22 +12,52 @@ namespace Microsoft.AspNet.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 sealed class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingInfo public class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingPredicateProvider
{ {
/// <summary> private static readonly Func<ModelBindingContext, string, bool> _defaultFilter = (context, propertyName) => true;
/// Comma separated set of properties which are to be excluded during model binding.
/// </summary> private Func<ModelBindingContext, string, bool> _predicateFromInclude;
public string Exclude { get; set; } = string.Empty;
/// <summary> /// <summary>
/// Comma separated set of properties which are to be included during model binding. /// Creates a new instace of <see cref="BindAttribute"/>.
/// </summary> /// </summary>
public string Include { get; set; } = string.Empty; /// <param name="include">Names of parameters to include in binding.</param>
public BindAttribute(params string[] include)
{
Include = include;
}
/// <summary>
/// Creates a new instance of <see cref="BindAttribute"/>.
/// </summary>
/// <param name="predicateProviderType">The type which implements
/// <see cref="IPropertyBindingPredicateProvider"/>.
/// </param>
public BindAttribute([NotNull] Type 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>
/// Gets the names of properties to include in model binding.
/// </summary>
public string[] Include { get; }
// This property is exposed for back compat reasons.
/// <summary> /// <summary>
/// Allows a user to specify a particular prefix to match during model binding. /// Allows a user to specify a particular prefix to match during model binding.
/// </summary> /// </summary>
// This property is exposed for back compat reasons.
public string Prefix { get; set; } public string Prefix { get; set; }
/// <summary> /// <summary>
@ -40,18 +71,56 @@ namespace Microsoft.AspNet.Mvc
} }
} }
public static bool IsPropertyAllowed(string propertyName, /// <inheritdoc />
IReadOnlyList<string> includeProperties, public Func<ModelBindingContext, string, bool> PropertyFilter
IReadOnlyList<string> excludeProperties)
{ {
// We allow a property to be bound if its both in the include list AND not in the exclude list. get
// An empty exclude list implies no properties are disallowed. {
var includeProperty = (includeProperties != null) && if (PredicateProviderType != null)
includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase); {
var excludeProperty = (excludeProperties != null) && return CreatePredicateFromProviderType(PredicateProviderType);
excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase); }
else if (Include != null && Include.Length > 0)
{
if (_predicateFromInclude == null)
{
_predicateFromInclude =
(context, propertyName) => Include.Contains(propertyName, StringComparer.Ordinal);
}
return includeProperty && !excludeProperty; return _predicateFromInclude;
}
else
{
return _defaultFilter;
}
}
}
private static Func<ModelBindingContext, string, bool> CreatePredicateFromProviderType(
Type predicateProviderType)
{
// 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 activator = services.GetService<ITypeActivator>();
var provider = (IPropertyBindingPredicateProvider)activator.CreateInstance(
services,
predicateProviderType);
initialized = true;
predicate = provider.PropertyFilter ?? _defaultFilter;
}
return predicate(context, propertyName);
};
} }
} }
} }

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal; using Microsoft.AspNet.Mvc.ModelBinding.Internal;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
@ -269,14 +270,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext) protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
{ {
var validationInfo = GetPropertyValidationInfo(bindingContext); var validationInfo = GetPropertyValidationInfo(bindingContext);
var newPropertyFilter = GetPropertyFilter();
return bindingContext.ModelMetadata.Properties return bindingContext.ModelMetadata.Properties
.Where(propertyMetadata => .Where(propertyMetadata =>
bindingContext.PropertyFilter(bindingContext, propertyMetadata.PropertyName) && newPropertyFilter(bindingContext, propertyMetadata.PropertyName) &&
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) || (validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) && !validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
CanUpdateProperty(propertyMetadata)); CanUpdateProperty(propertyMetadata));
} }
private static Func<ModelBindingContext, string, bool> GetPropertyFilter()
{
return (ModelBindingContext context, string propertyName) =>
{
var modelMetadataPredicate = context.ModelMetadata.PropertyBindingPredicateProvider?.PropertyFilter;
return
context.PropertyFilter(context, propertyName) &&
(modelMetadataPredicate == null || modelMetadataPredicate(context, propertyName));
};
}
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo) private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
{ {
var attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>(); var attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();

View File

@ -1,21 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Represents an entity which has binding information for a model.
/// </summary>
public interface IPropertyBindingInfo
{
/// <summary>
/// Comma separated set of properties which are to be excluded during model binding.
/// </summary>
string Exclude { get; }
/// <summary>
/// Comma separated set of properties which are to be included during model binding.
/// </summary>
string Include { get; }
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// Provides a predicate which can determines which model properties should be bound by model binding.
/// </summary>
public interface IPropertyBindingPredicateProvider
{
/// <summary>
/// Gets a predicate which can determines which model properties should be bound by model binding.
/// </summary>
Func<ModelBindingContext, string, bool> PropertyFilter { get; }
}
}

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Required = attributes.OfType<RequiredAttribute>().FirstOrDefault(); Required = attributes.OfType<RequiredAttribute>().FirstOrDefault();
ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault(); ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault(); BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
PropertyBindingInfo = attributes.OfType<IPropertyBindingInfo>(); PropertyBindingPredicateProviders = attributes.OfType<IPropertyBindingPredicateProvider>();
BinderModelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault(); BinderModelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
BinderTypeProviders = attributes.OfType<IBinderTypeProviderMetadata>(); BinderTypeProviders = attributes.OfType<IBinderTypeProviderMetadata>();
@ -82,11 +82,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public HiddenInputAttribute HiddenInput { get; protected set; } public HiddenInputAttribute HiddenInput { get; protected set; }
/// <summary> /// <summary>
/// Gets (or sets in subclasses) <see cref="IEnumerable{IPropertyBindingInfo}"/> found in collection /// Gets (or sets in subclasses) <see cref="IEnumerable{IPropertyBindingPredicateProvider}"/> found in
/// passed to the <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, /// collection passed to the <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/>
/// if any. /// constructor, if any.
/// </summary> /// </summary>
public IEnumerable<IPropertyBindingInfo> PropertyBindingInfo { get; protected set; } public IEnumerable<IPropertyBindingPredicateProvider> PropertyBindingPredicateProviders { get; protected set; }
public RequiredAttribute Required { get; protected set; } public RequiredAttribute Required { get; protected set; }

View File

@ -70,56 +70,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
: base.ComputeBinderModelNamePrefix(); : base.ComputeBinderModelNamePrefix();
} }
protected override IReadOnlyList<string> ComputeBinderIncludeProperties() protected override IPropertyBindingPredicateProvider ComputePropertyBindingPredicateProvider()
{ {
var propertyBindingInfo = PrototypeCache.PropertyBindingInfo?.ToList(); return PrototypeCache.PropertyBindingPredicateProviders.Any()
if (propertyBindingInfo != null && propertyBindingInfo.Count != 0) ? new CompositePredicateProvider(PrototypeCache.PropertyBindingPredicateProviders.ToArray())
{ : null;
if (string.IsNullOrEmpty(propertyBindingInfo[0].Include))
{
return Properties.Select(property => property.PropertyName).ToList();
}
var includeFirst = SplitString(propertyBindingInfo[0].Include).ToList();
if (propertyBindingInfo.Count != 2)
{
return includeFirst;
}
var includedAtType = SplitString(propertyBindingInfo[1].Include).ToList();
if (includeFirst.Count == 0 && includedAtType.Count == 0)
{
// Need to include everything by default.
return Properties.Select(property => property.PropertyName).ToList();
}
else
{
return includeFirst.Intersect(includedAtType).ToList();
}
}
// Need to include everything by default.
return Properties.Select(property => property.PropertyName).ToList();
}
protected override IReadOnlyList<string> ComputeBinderExcludeProperties()
{
var propertyBindingInfo = PrototypeCache.PropertyBindingInfo?.ToList();
if (propertyBindingInfo != null && propertyBindingInfo.Count != 0)
{
var excludeFirst = SplitString(propertyBindingInfo[0].Exclude).ToList();
if (propertyBindingInfo.Count != 2)
{
return excludeFirst;
}
var excludedAtType = SplitString(propertyBindingInfo[1].Exclude).ToList();
return excludeFirst.Union(excludedAtType).ToList();
}
return base.ComputeBinderExcludeProperties();
} }
protected override bool ComputeConvertEmptyStringToNull() protected override bool ComputeConvertEmptyStringToNull()
@ -383,5 +338,44 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
.Where(trimmed => !string.IsNullOrEmpty(trimmed)); .Where(trimmed => !string.IsNullOrEmpty(trimmed));
return split; return split;
} }
private class CompositePredicateProvider : IPropertyBindingPredicateProvider
{
private readonly IPropertyBindingPredicateProvider[] _providers;
public CompositePredicateProvider(IPropertyBindingPredicateProvider[] providers)
{
_providers = providers;
}
public Func<ModelBindingContext, string, bool> PropertyFilter
{
get
{
return CreatePredicate();
}
}
private Func<ModelBindingContext, string, bool> CreatePredicate()
{
var predicates = _providers
.Select(p => p.PropertyFilter)
.Where(p => p != null)
.ToArray();
return (context, propertyName) =>
{
foreach (var predicate in predicates)
{
if (!predicate(context, propertyName))
{
return false;
}
}
return true;
};
}
}
} }
} }

View File

@ -33,8 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private bool _showForEdit; private bool _showForEdit;
private IBinderMetadata _binderMetadata; private IBinderMetadata _binderMetadata;
private string _binderModelName; private string _binderModelName;
private IReadOnlyList<string> _binderIncludeProperties; private IPropertyBindingPredicateProvider _propertyBindingPredicateProvider;
private IReadOnlyList<string> _binderExcludeProperties;
private Type _binderType; private Type _binderType;
private bool _convertEmptyStringToNullComputed; private bool _convertEmptyStringToNullComputed;
@ -53,10 +52,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private bool _showForDisplayComputed; private bool _showForDisplayComputed;
private bool _showForEditComputed; private bool _showForEditComputed;
private bool _isBinderMetadataComputed; private bool _isBinderMetadataComputed;
private bool _isBinderIncludePropertiesComputed;
private bool _isBinderModelNameComputed; private bool _isBinderModelNameComputed;
private bool _isBinderExcludePropertiesComputed;
private bool _isBinderTypeComputed; private bool _isBinderTypeComputed;
private bool _propertyBindingPredicateProviderComputed;
// Constructor for creating real instances of the metadata class based on a prototype // Constructor for creating real instances of the metadata class based on a prototype
protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor) protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor)
@ -102,50 +100,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_binderMetadata = value; _binderMetadata = value;
_isBinderMetadataComputed = true; _isBinderMetadataComputed = true;
} }
}
/// <inheritdoc />
public sealed override IReadOnlyList<string> BinderIncludeProperties
{
get
{
if (!_isBinderIncludePropertiesComputed)
{
_binderIncludeProperties = ComputeBinderIncludeProperties();
_isBinderIncludePropertiesComputed = true;
}
return _binderIncludeProperties;
} }
set
{
_binderIncludeProperties = value;
_isBinderIncludePropertiesComputed = true;
}
}
/// <inheritdoc />
public sealed override IReadOnlyList<string> BinderExcludeProperties
{
get
{
if (!_isBinderExcludePropertiesComputed)
{
_binderExcludeProperties = ComputeBinderExcludeProperties();
_isBinderExcludePropertiesComputed = true;
}
return _binderExcludeProperties;
}
set
{
_binderExcludeProperties = value;
_isBinderExcludePropertiesComputed = true;
}
}
/// <inheritdoc /> /// <inheritdoc />
public sealed override string BinderModelName public sealed override string BinderModelName
{ {
@ -422,6 +378,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
} }
public sealed override IPropertyBindingPredicateProvider PropertyBindingPredicateProvider
{
get
{
if (!_propertyBindingPredicateProviderComputed)
{
_propertyBindingPredicateProvider = ComputePropertyBindingPredicateProvider();
_propertyBindingPredicateProviderComputed = true;
}
return _propertyBindingPredicateProvider;
}
set
{
_propertyBindingPredicateProvider = value;
_propertyBindingPredicateProviderComputed = true;
}
}
/// <inheritdoc /> /// <inheritdoc />
public sealed override bool ShowForDisplay public sealed override bool ShowForDisplay
{ {
@ -506,14 +481,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return base.BinderMetadata; return base.BinderMetadata;
} }
protected virtual IReadOnlyList<string> ComputeBinderIncludeProperties() protected virtual IPropertyBindingPredicateProvider ComputePropertyBindingPredicateProvider()
{ {
return base.BinderIncludeProperties; return base.PropertyBindingPredicateProvider;
}
protected virtual IReadOnlyList<string> ComputeBinderExcludeProperties()
{
return base.BinderExcludeProperties;
} }
protected virtual string ComputeBinderModelNamePrefix() protected virtual string ComputeBinderModelNamePrefix()

View File

@ -48,23 +48,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
/// <summary> /// <summary>
/// The name of the model if specified explicitly using <see cref="IModelNameProvider"/>. /// Gets or sets the name of a model if specified explicitly using <see cref="IModelNameProvider"/>.
/// </summary> /// </summary>
public virtual string BinderModelName { get; set; } public virtual string BinderModelName { get; set; }
/// <summary> /// <summary>
/// Properties which are to be included while binding this model. /// Gets or sets the <see cref="Type"/> of an <see cref="IModelBinder"/> or an
/// </summary> /// <see cref="IModelBinderProvider"/> of a model if specified explicitly using
public virtual IReadOnlyList<string> BinderIncludeProperties { get; set; } /// <see cref="IBinderTypeProviderMetadata"/>.
/// <summary>
/// Properties which are to be excluded while binding this model.
/// </summary>
public virtual IReadOnlyList<string> BinderExcludeProperties { get; set; }
/// <summary>
/// The <see cref="Type"/> of an <see cref="IModelBinder"/> or an <see cref="IModelBinderProvider"/>
/// of a model if specified explicitly using <see cref="IBinderTypeProviderMetadata"/>.
/// </summary> /// </summary>
public virtual Type BinderType { get; set; } public virtual Type BinderType { get; set; }
@ -211,6 +202,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
} }
/// <summary>
/// Gets or sets the <see cref="IPropertyBindingPredicateProvider"/>, which can determine which properties
/// should be model bound.
/// </summary>
public virtual IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; set; }
public string PropertyName public string PropertyName
{ {
get { return _propertyName; } get { return _propertyName; }

View File

@ -251,19 +251,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
/// <summary> /// <summary>
/// The type '{0}' must derive from '{1}'. /// The type '{0}' does not implement the interface '{1}'.
/// </summary> /// </summary>
internal static string TypeMustDeriveFromType internal static string PropertyBindingPredicateProvider_WrongType
{ {
get { return GetString("TypeMustDeriveFromType"); } get { return GetString("PropertyBindingPredicateProvider_WrongType"); }
} }
/// <summary> /// <summary>
/// The type '{0}' must derive from '{1}'. /// The type '{0}' does not implement the interface '{1}'.
/// </summary> /// </summary>
internal static string FormatTypeMustDeriveFromType(object p0, object p1) internal static string FormatPropertyBindingPredicateProvider_WrongType(object p0, object p1)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("TypeMustDeriveFromType"), p0, p1); return string.Format(CultureInfo.CurrentCulture, GetString("PropertyBindingPredicateProvider_WrongType"), p0, p1);
} }
/// <summary> /// <summary>

View File

@ -162,8 +162,8 @@
<data name="ModelBindingContext_ModelMetadataMustBeSet" xml:space="preserve"> <data name="ModelBindingContext_ModelMetadataMustBeSet" xml:space="preserve">
<value>The ModelMetadata property must be set before accessing this property.</value> <value>The ModelMetadata property must be set before accessing this property.</value>
</data> </data>
<data name="TypeMustDeriveFromType" xml:space="preserve"> <data name="PropertyBindingPredicateProvider_WrongType" xml:space="preserve">
<value>The type '{0}' must derive from '{1}'.</value> <value>The type '{0}' does not implement the interface '{1}'.</value>
</data> </data>
<data name="ValidatableObjectAdapter_IncompatibleType" xml:space="preserve"> <data name="ValidatableObjectAdapter_IncompatibleType" xml:space="preserve">
<value>The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'.</value> <value>The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'.</value>

View File

@ -49,33 +49,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
} }
[Fact] [Fact]
public void GetModelBindingContext_DoesNotReturn_ExcludedProperties() public void GetModelBindingContext_ReturnsOnlyIncludedProperties_UsingBindAttributeInclude()
{
// Arrange
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
Mock.Of<ActionDescriptor>());
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForType(
modelAccessor: null, modelType: typeof(TypeWithExcludedPropertiesUsingBindAttribute));
var actionBindingContext = new ActionBindingContext(actionContext,
Mock.Of<IModelMetadataProvider>(),
Mock.Of<IModelBinder>(),
Mock.Of<IValueProvider>(),
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
// Act
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
// Assert
Assert.False(context.PropertyFilter(context, "Excluded1"));
Assert.False(context.PropertyFilter(context, "Excluded2"));
}
[Fact]
public void GetModelBindingContext_ReturnsOnlyWhiteListedProperties_UsingBindAttributeInclude()
{ {
// Arrange // Arrange
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()), var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
@ -325,19 +299,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
} }
[Bind(Exclude = nameof(Excluded1) + "," + nameof(Excluded2))] [Bind(new string[] { nameof(IncludedExplicitly1), nameof(IncludedExplicitly2) })]
private class TypeWithExcludedPropertiesUsingBindAttribute
{
public int Excluded1 { get; set; }
public int Excluded2 { get; set; }
public int IncludedByDefault1 { get; set; }
public int IncludedByDefault2 { get; set; }
}
[Bind(Include = nameof(IncludedExplicitly1) + "," + nameof(IncludedExplicitly2))]
private class TypeWithIncludedPropertiesUsingBindAttribute private class TypeWithIncludedPropertiesUsingBindAttribute
{ {
public int ExcludedByDefault1 { get; set; } public int ExcludedByDefault1 { get; set; }

View File

@ -707,6 +707,71 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("The Field3 field is required.", json["model.Field3"]); Assert.Equal("The Field3 field is required.", json["model.Field3"]);
} }
[Fact]
public async Task BindAttribute_Filters_UsingDefaultPropertyFilterProvider_WithExpressions()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"EchoUser" +
"?user.UserName=someValue&user.RegisterationMonth=March&user.Id=123");
// Assert
var json = JsonConvert.DeserializeObject<User>(response);
// Does not touch what is not in the included expression.
Assert.Equal(0, json.Id);
// Updates the included properties.
Assert.Equal("someValue", json.UserName);
Assert.Equal("March", json.RegisterationMonth);
}
[Fact]
public async Task BindAttribute_Filters_UsingPropertyFilterProvider_UsingServices()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"EchoUserUsingServices" +
"?user.UserName=someValue&user.RegisterationMonth=March&user.Id=123");
// Assert
var json = JsonConvert.DeserializeObject<User>(response);
// Does not touch what is not in the included expression.
Assert.Equal(0, json.Id);
// Updates the included properties.
Assert.Equal("someValue", json.UserName);
Assert.Equal("March", json.RegisterationMonth);
}
[Fact]
public async Task BindAttribute_Filters_UsingDefaultPropertyFilterProvider_WithPredicate()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"UpdateUserId_BlackListingAtEitherLevelDoesNotBind" +
"?param1.LastName=someValue&param2.Id=123");
// Assert
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
Assert.Equal(2, json.Count);
Assert.Null(json["param1.LastName"]);
Assert.Equal("0", json["param2.Id"]);
}
[Fact] [Fact]
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_BlacklistedAtEitherLevelIsNotBound() public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_BlacklistedAtEitherLevelIsNotBound()
{ {
@ -716,18 +781,18 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Act // Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" + var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_BlackListingAtEitherLevelDoesNotBind" + "UpdateUserId_BlackListingAtEitherLevelDoesNotBind" +
"?param1.IncludedExplicitlyAtTypeLevel=someValue&param2.ExcludedExplicitlyAtTypeLevel=someValue"); "?param1.LastName=someValue&param2.Id=123");
// Assert // Assert
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response); var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
Assert.Equal(2, json.Count); Assert.Equal(2, json.Count);
Assert.Null(json["param1.IncludedExplicitlyAtTypeLevel"]); Assert.Null(json["param1.LastName"]);
Assert.Null(json["param2.ExcludedExplicitlyAtTypeLevel"]); Assert.Equal("0", json["param2.Id"]);
} }
[Fact] [Fact]
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_WhitelistedAtBothLevelsIsBound() public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_IncludedAtBothLevelsIsBound()
{ {
// Arrange // Arrange
var server = TestServer.Create(_services, _app); var server = TestServer.Create(_services, _app);
@ -735,17 +800,17 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Act // Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" + var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtBothLevelBinds" + "UpdateFirstName_IncludingAtBothLevelBinds" +
"?param1.IncludedExplicitlyAtTypeLevel=someValue&param2.ExcludedExplicitlyAtTypeLevel=someValue"); "?param1.FirstName=someValue&param2.Id=123");
// Assert // Assert
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response); var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
Assert.Equal(1, json.Count); Assert.Equal(1, json.Count);
Assert.Equal("someValue", json["param1.IncludedExplicitlyAtTypeLevel"]); Assert.Equal("someValue", json["param1.FirstName"]);
} }
[Fact] [Fact]
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_WhitelistingAtOneLevelIsNotBound() public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_IncludingAtOneLevelIsNotBound()
{ {
// Arrange // Arrange
var server = TestServer.Create(_services, _app); var server = TestServer.Create(_services, _app);
@ -753,14 +818,14 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Act // Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" + var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtOnlyOneLevelDoesNotBind" + "UpdateIsAdmin_IncludingAtOnlyOneLevelDoesNotBind" +
"?param1.IncludedExplicitlyAtTypeLevel=someValue&param1.IncludedExplicitlyAtParameterLevel=someValue"); "?param1.FirstName=someValue&param1.IsAdmin=true");
// Assert // Assert
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response); var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
Assert.Equal(2, json.Count); Assert.Equal(2, json.Count);
Assert.Null(json["param1.IncludedExplicitlyAtParameterLevel"]); Assert.Equal("False", json["param1.IsAdmin"]);
Assert.Null(json["param1.IncludedExplicitlyAtTypeLevel"]); Assert.Null(json["param1.FirstName"]);
} }
[Fact] [Fact]

View File

@ -1,20 +1,169 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.PipelineCore;
using Microsoft.Framework.DependencyInjection;
#if ASPNET50
using Moq;
#endif
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public class BindAttributeTest public class BindAttributeTest
{ {
[Fact] [Fact]
public void PrefixPropertyDefaultsToNull() public void Constructor_Throws_IfTypeDoesNotImplement_IPropertyBindingPredicateProvider()
{ {
// Arrange // Arrange
BindAttribute attr = new BindAttribute(); var expected =
"The type 'Microsoft.AspNet.Mvc.ModelBinding.BindAttributeTest+UnrelatedType' " +
"does not implement the interface " +
"'Microsoft.AspNet.Mvc.ModelBinding.IPropertyBindingPredicateProvider'." +
Environment.NewLine +
"Parameter name: predicateProviderType";
// Act & assert // Act & Assert
Assert.Null(attr.Prefix); 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]
[InlineData("UserName", true)]
[InlineData("Username", false)]
[InlineData("Password", false)]
public void BindAttribute_Include(string property, bool isIncluded)
{
// Arrange
var bind = new BindAttribute(new string[] { "UserName", "FirstName" });
var context = new ModelBindingContext();
// Act
var predicate = bind.PropertyFilter;
// Assert
Assert.Equal(isIncluded, predicate(context, property));
}
#if ASPNET50
[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 ModelBindingContext();
context.OperationBindingContext = new OperationBindingContext()
{
HttpContext = new DefaultHttpContext(),
};
var activator = new Mock<ITypeActivator>(MockBehavior.Strict);
activator
.Setup(a => a.CreateInstance(It.IsAny<IServiceProvider>(), typeof(TestProvider), It.IsAny<object[]>()))
.Returns(new TestProvider())
.Verifiable();
var services = new Mock<IServiceProvider>(MockBehavior.Strict);
services
.Setup(s => s.GetService(typeof(ITypeActivator)))
.Returns(activator.Object);
context.OperationBindingContext.HttpContext.RequestServices = services.Object;
// Act
var predicate = bind.PropertyFilter;
// Assert
Assert.Equal(isIncluded, predicate(context, property));
}
// 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 ModelBindingContext();
context.OperationBindingContext = new OperationBindingContext()
{
HttpContext = new DefaultHttpContext(),
};
var activator = new Mock<ITypeActivator>(MockBehavior.Strict);
activator
.Setup(a => a.CreateInstance(It.IsAny<IServiceProvider>(), typeof(TestProvider), It.IsAny<object[]>()))
.Returns(new TestProvider())
.Verifiable();
var services = new Mock<IServiceProvider>(MockBehavior.Strict);
services
.Setup(s => s.GetService(typeof(ITypeActivator)))
.Returns(activator.Object);
context.OperationBindingContext.HttpContext.RequestServices = services.Object;
// Act
var predicate = bind.PropertyFilter;
// Assert
Assert.True(predicate(context, "UserName"));
Assert.True(predicate(context, "UserName"));
activator
.Verify(
a => a.CreateInstance(It.IsAny<IServiceProvider>(), typeof(TestProvider), It.IsAny<object[]>()),
Times.Once());
}
#endif
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

@ -9,7 +9,9 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.PipelineCore;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Microsoft.Framework.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
@ -247,12 +249,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
.Returns<IValueProviderMetadata>( .Returns<IValueProviderMetadata>(
valueProviderMetadata => valueProviderMetadata =>
{ {
if(valueProviderMetadata is ValueBinderMetadataAttribute) if (valueProviderMetadata is ValueBinderMetadataAttribute)
{ {
return mockOriginalValueProvider.Object; return mockOriginalValueProvider.Object;
} }
return null; return null;
}); });
var bindingContext = new MutableObjectBinderContext var bindingContext = new MutableObjectBinderContext
@ -590,6 +592,60 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
ValidatorProvider = Mock.Of<IModelValidatorProvider>(), ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
MetadataProvider = new DataAnnotationsModelMetadataProvider() MetadataProvider = new DataAnnotationsModelMetadataProvider()
},
};
var testableBinder = new TestableMutableObjectModelBinder();
// Act
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
// Assert
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
}
[Fact]
public void GetMetadataForProperties_DoesNotReturn_ExcludedProperties()
{
// Arrange
var expectedPropertyNames = new[] { "IncludedByDefault1", "IncludedByDefault2" };
var bindingContext = new ModelBindingContext
{
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
OperationBindingContext = new OperationBindingContext
{
HttpContext = new DefaultHttpContext
{
RequestServices = CreateServices()
},
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
}
};
var testableBinder = new TestableMutableObjectModelBinder();
// Act
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
// Assert
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
}
[Fact]
public void GetMetadataForProperties_ReturnsOnlyIncludedProperties_UsingBindAttributeInclude()
{
// Arrange
var expectedPropertyNames = new[] { "IncludedExplicitly1", "IncludedExplicitly2" };
var bindingContext = new ModelBindingContext
{
ModelMetadata = GetMetadataForType(typeof(TypeWithIncludedPropertiesUsingBindAttribute)),
OperationBindingContext = new OperationBindingContext
{
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
} }
}; };
@ -1337,6 +1393,29 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public string MarkedWithABinderMetadata { get; set; } public string MarkedWithABinderMetadata { get; set; }
} }
[Bind(new[] { nameof(IncludedExplicitly1), nameof(IncludedExplicitly2) })]
private class TypeWithIncludedPropertiesUsingBindAttribute
{
public int ExcludedByDefault1 { get; set; }
public int ExcludedByDefault2 { get; set; }
public int IncludedExplicitly1 { 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; }
}
public class Document public class Document
{ {
[NonValueBinderMetadata] [NonValueBinderMetadata]
@ -1354,6 +1433,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
} }
public class ExcludedProvider : IPropertyBindingPredicateProvider
{
public Func<ModelBindingContext, string, bool> PropertyFilter
{
get
{
return (context, propertyName) =>
!string.Equals("Excluded1", propertyName, StringComparison.OrdinalIgnoreCase) &&
!string.Equals("Excluded2", propertyName, StringComparison.OrdinalIgnoreCase);
}
}
}
private IServiceProvider CreateServices()
{
var services = new Mock<IServiceProvider>(MockBehavior.Strict);
var typeActivator = new Mock<ITypeActivator>(MockBehavior.Strict);
typeActivator
.Setup(f => f.CreateInstance(It.IsAny<IServiceProvider>(), typeof(ExcludedProvider)))
.Returns(new ExcludedProvider());
services
.Setup(s => s.GetService(typeof(ITypeActivator)))
.Returns(typeActivator.Object);
return services.Object;
}
public class TestableMutableObjectModelBinder : MutableObjectModelBinder public class TestableMutableObjectModelBinder : MutableObjectModelBinder
{ {
public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata) public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(cache.ScaffoldColumn); Assert.Null(cache.ScaffoldColumn);
Assert.Null(cache.BinderMetadata); Assert.Null(cache.BinderMetadata);
Assert.Null(cache.BinderModelNameProvider); Assert.Null(cache.BinderModelNameProvider);
Assert.Empty(cache.PropertyBindingInfo); Assert.Empty(cache.PropertyBindingPredicateProviders);
} }
public static TheoryData<object, Func<CachedDataAnnotationsMetadataAttributes, object>> public static TheoryData<object, Func<CachedDataAnnotationsMetadataAttributes, object>>
@ -78,18 +78,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void Constructor_FindsPropertyBindingInfo() public void Constructor_FindsPropertyBindingInfo()
{ {
// Arrange // Arrange
var propertyBindingInfos = var providers = new[] { new TestPredicateProvider(), new TestPredicateProvider() };
new[] { new TestPropertyBindingInfo(), new TestPropertyBindingInfo() };
// Act // Act
var cache = new CachedDataAnnotationsMetadataAttributes(propertyBindingInfos); var cache = new CachedDataAnnotationsMetadataAttributes(providers);
var result = cache.PropertyBindingInfo.ToArray(); var result = cache.PropertyBindingPredicateProviders.ToArray();
// Assert // Assert
Assert.Equal(propertyBindingInfos.Length, result.Length); Assert.Equal(providers.Length, result.Length);
for (var index = 0; index < propertyBindingInfos.Length; index++) for (var index = 0; index < providers.Length; index++)
{ {
Assert.Same(propertyBindingInfos[index], result[index]); Assert.Same(providers[index], result[index]);
} }
} }
@ -147,5 +146,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public Type BinderType { get; set; } public Type BinderType { get; set; }
} }
private class TestPredicateProvider : IPropertyBindingPredicateProvider
{
public Func<ModelBindingContext, string, bool> PropertyFilter
{
get
{
throw new NotImplementedException();
}
}
}
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -11,57 +12,50 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public class CachedDataAnnotationsModelMetadataProviderTest public class CachedDataAnnotationsModelMetadataProviderTest
{ {
[Bind(Include = nameof(IncludedAndExcludedExplicitly1) + "," + nameof(IncludedExplicitly1),
Exclude = nameof(IncludedAndExcludedExplicitly1) + "," + nameof(ExcludedExplicitly1),
Prefix = "TypePrefix")]
private class TypeWithExludedAndIncludedPropertiesUsingBindAttribute
{
public int ExcludedExplicitly1 { get; set; }
public int IncludedAndExcludedExplicitly1 { get; set; }
public int IncludedExplicitly1 { get; set; }
public int NotIncludedOrExcluded { get; set; }
public void ActionWithBindAttribute(
[Bind(Include = "Property1, Property2,IncludedAndExcludedExplicitly1",
Exclude ="Property3, Property4, IncludedAndExcludedExplicitly1",
Prefix = "ParameterPrefix")]
TypeWithExludedAndIncludedPropertiesUsingBindAttribute param)
{
}
}
[Fact] [Fact]
public void DataAnnotationsModelMetadataProvider_ReadsIncludedAndExcludedProperties_ForTypes() public void DataAnnotationsModelMetadataProvider_UsesPredicateOnType()
{ {
// Arrange // Arrange
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute); var type = typeof(User);
var provider = new DataAnnotationsModelMetadataProvider(); var provider = new DataAnnotationsModelMetadataProvider();
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1", "IncludedExplicitly1" }; var context = new ModelBindingContext();
var expectedExcludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
var expected = new[] { "IsAdmin", "UserName" };
// Act // Act
var metadata = provider.GetMetadataForType(null, type); var metadata = provider.GetMetadataForType(null, type);
// Assert // Assert
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties); var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter;
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties);
var matched = new HashSet<string>();
foreach (var property in metadata.Properties)
{
if (predicate(context, property.PropertyName))
{
matched.Add(property.PropertyName);
}
}
Assert.Equal<string>(expected, matched);
} }
[Fact] [Fact]
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_AtParameterAndType_ForParameters() public void DataAnnotationsModelMetadataProvider_UsesPredicateOnParameter()
{ {
// Arrange // Arrange
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute); var type = GetType();
var methodInfo = type.GetMethod("ActionWithBindAttribute"); var methodInfo = type.GetMethod(
var provider = new DataAnnotationsModelMetadataProvider(); "ActionWithoutBindAttribute",
BindingFlags.Instance | BindingFlags.NonPublic);
// Note it does an intersection for included and a union for excluded. var provider = new DataAnnotationsModelMetadataProvider();
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1" }; var context = new ModelBindingContext();
var expectedExcludedPropertyNames = new[] {
"Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" }; // Note it does an intersection for included -- only properties that
// pass both predicates will be bound.
var expected = new[] { "IsAdmin", "UserName" };
// Act // Act
var metadata = provider.GetMetadataForParameter( var metadata = provider.GetMetadataForParameter(
@ -70,16 +64,68 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
parameterName: "param"); parameterName: "param");
// Assert // Assert
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties); var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter;
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties); Assert.NotNull(predicate);
var matched = new HashSet<string>();
foreach (var property in metadata.Properties)
{
if (predicate(context, property.PropertyName))
{
matched.Add(property.PropertyName);
}
}
Assert.Equal<string>(expected, matched);
} }
[Fact] [Fact]
public void ModelMetadataProvider_ReadsPrefixProperty_OnlyAtParameterLevel_ForParameters() public void DataAnnotationsModelMetadataProvider_UsesPredicateOnParameter_Merge()
{ {
// Arrange // Arrange
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute); var type = GetType();
var methodInfo = type.GetMethod("ActionWithBindAttribute"); var methodInfo = type.GetMethod(
"ActionWithBindAttribute",
BindingFlags.Instance | BindingFlags.NonPublic);
var provider = new DataAnnotationsModelMetadataProvider();
var context = new ModelBindingContext();
// Note it does an intersection for included -- only properties that
// pass both predicates will be bound.
var expected = new[] { "IsAdmin" };
// Act
var metadata = provider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: methodInfo,
parameterName: "param");
// Assert
var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter;
Assert.NotNull(predicate);
var matched = new HashSet<string>();
foreach (var property in metadata.Properties)
{
if (predicate(context, property.PropertyName))
{
matched.Add(property.PropertyName);
}
}
Assert.Equal<string>(expected, matched);
}
[Fact]
public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForParameters()
{
// Arrange
var type = GetType();
var methodInfo = type.GetMethod(
"ActionWithBindAttribute",
BindingFlags.Instance | BindingFlags.NonPublic);
var provider = new DataAnnotationsModelMetadataProvider(); var provider = new DataAnnotationsModelMetadataProvider();
// Act // Act
@ -96,7 +142,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForTypes() public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForTypes()
{ {
// Arrange // Arrange
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute); var type = typeof(User);
var provider = new DataAnnotationsModelMetadataProvider(); var provider = new DataAnnotationsModelMetadataProvider();
// Act // Act
@ -106,23 +152,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal("TypePrefix", metadata.BinderModelName); Assert.Equal("TypePrefix", metadata.BinderModelName);
} }
[Fact]
public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForParameters()
{
// Arrange
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
var methodInfo = type.GetMethod("ActionWithBindAttribute");
var provider = new DataAnnotationsModelMetadataProvider();
// Act
var metadata = provider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: methodInfo,
parameterName: "param");
// Assert
Assert.Equal("ParameterPrefix", metadata.BinderModelName);
}
[Fact] [Fact]
public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay() public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay()
@ -193,8 +222,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.NotNull(propertyMetadata.BinderMetadata); Assert.NotNull(propertyMetadata.BinderMetadata);
var attribute = Assert.IsType<TypeBasedBinderAttribute>(propertyMetadata.BinderMetadata); var attribute = Assert.IsType<TypeBasedBinderAttribute>(propertyMetadata.BinderMetadata);
Assert.Equal("PersonType", propertyMetadata.BinderModelName); Assert.Equal("PersonType", propertyMetadata.BinderModelName);
Assert.Equal(new[] { "IncludeAtType" }, propertyMetadata.BinderIncludeProperties.ToArray());
Assert.Equal(new[] { "ExcludeAtType" }, propertyMetadata.BinderExcludeProperties.ToArray());
} }
[Fact] [Fact]
@ -210,12 +237,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.NotNull(propertyMetadata.BinderMetadata); Assert.NotNull(propertyMetadata.BinderMetadata);
var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(propertyMetadata.BinderMetadata); var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(propertyMetadata.BinderMetadata);
Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName); Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName);
Assert.Empty(propertyMetadata.BinderIncludeProperties);
Assert.Equal(new[] { "ExcludeAtProperty", "ExcludeAtType" },
propertyMetadata.BinderExcludeProperties.ToArray());
} }
#if ASPNET50
[Fact] [Fact]
public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType() public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType()
{ {
@ -223,16 +246,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var provider = new DataAnnotationsModelMetadataProvider(); var provider = new DataAnnotationsModelMetadataProvider();
// Act // Act
var parameterMetadata = provider.GetMetadataForParameter(null, var parameterMetadata = provider.GetMetadataForParameter(
typeof(Person).GetMethod("Update"), null,
"person"); typeof(Person).GetMethod("Update"),
"person");
// Assert // Assert
Assert.NotNull(parameterMetadata.BinderMetadata); Assert.NotNull(parameterMetadata.BinderMetadata);
var attribute = Assert.IsType<TypeBasedBinderAttribute>(parameterMetadata.BinderMetadata); var attribute = Assert.IsType<TypeBasedBinderAttribute>(parameterMetadata.BinderMetadata);
Assert.Equal("PersonType", parameterMetadata.BinderModelName); Assert.Equal("PersonType", parameterMetadata.BinderModelName);
Assert.Equal(new[] { "IncludeAtType" }, parameterMetadata.BinderIncludeProperties.ToArray());
Assert.Equal(new[] { "ExcludeAtType" }, parameterMetadata.BinderExcludeProperties.ToArray());
} }
[Fact] [Fact]
@ -242,53 +264,48 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var provider = new DataAnnotationsModelMetadataProvider(); var provider = new DataAnnotationsModelMetadataProvider();
// Act // Act
var parameterMetadata = provider.GetMetadataForParameter(null, var parameterMetadata = provider.GetMetadataForParameter(
typeof(Person).GetMethod("Save"), null,
"person"); typeof(Person).GetMethod("Save"),
"person");
// Assert // Assert
Assert.NotNull(parameterMetadata.BinderMetadata); Assert.NotNull(parameterMetadata.BinderMetadata);
var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(parameterMetadata.BinderMetadata); var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(parameterMetadata.BinderMetadata);
Assert.Equal("PersonParameter", parameterMetadata.BinderModelName); Assert.Equal("PersonParameter", parameterMetadata.BinderModelName);
Assert.Empty(parameterMetadata.BinderIncludeProperties);
Assert.Equal(new[] { "ExcludeAtParameter", "ExcludeAtType" },
parameterMetadata.BinderExcludeProperties.ToArray());
} }
#endif
public class TypeBasedBinderAttribute : Attribute, private void ActionWithoutBindAttribute(User param)
IBinderMetadata, IModelNameProvider, IPropertyBindingInfo {
}
private void ActionWithBindAttribute([Bind(new string[] { "IsAdmin" }, Prefix = "ParameterPrefix")] User param)
{
}
public class TypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider
{ {
public string Name { get; set; } public string Name { get; set; }
public string Exclude { get; set; }
public string Include { get; set; }
} }
public class NonTypeBasedBinderAttribute : Attribute, public class NonTypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider
IBinderMetadata, IModelNameProvider, IPropertyBindingInfo
{ {
public string Name { get; set; } public string Name { get; set; }
public string Exclude { get; set; }
public string Include { get; set; }
} }
[TypeBasedBinder(Name = "PersonType", Include = "IncludeAtType", Exclude = "ExcludeAtType")] [TypeBasedBinder(Name = "PersonType")]
public class Person public class Person
{ {
public Person Parent { get; set; } public Person Parent { get; set; }
[NonTypeBasedBinder(Name = "GrandParentProperty", Include = "IncludeAtProperty", Exclude = "ExcludeAtProperty")] [NonTypeBasedBinder(Name = "GrandParentProperty")]
public Person GrandParent { get; set; } public Person GrandParent { get; set; }
public void Update(Person person) public void Update(Person person)
{ {
} }
public void Save([NonTypeBasedBinder(Name = "PersonParameter", public void Save([NonTypeBasedBinder(Name = "PersonParameter")] Person person)
Include = "IncludeAtParameter", Exclude = "ExcludeAtParameter")] Person person)
{ {
} }
} }
@ -317,5 +334,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public HiddenClass OfHiddenType { get; set; } public HiddenClass OfHiddenType { get; set; }
} }
[Bind(new[] { nameof(IsAdmin), nameof(UserName) }, Prefix = "TypePrefix")]
private class User
{
public int Id { get; set; }
public bool IsAdmin { get; set; }
public int UserName { get; set; }
public int NotIncludedOrExcluded { get; set; }
}
} }
} }

View File

@ -52,9 +52,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(metadata.BinderModelName); Assert.Null(metadata.BinderModelName);
Assert.Null(metadata.BinderMetadata); Assert.Null(metadata.BinderMetadata);
Assert.Null(metadata.PropertyBindingPredicateProvider);
Assert.Null(metadata.BinderType); Assert.Null(metadata.BinderType);
Assert.Empty(metadata.BinderIncludeProperties);
Assert.Null(metadata.BinderExcludeProperties);
} }
public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeDataStrings public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeDataStrings

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var nonEmptycontainerModel = new DummyModelContainer { Model = contactModel }; var nonEmptycontainerModel = new DummyModelContainer { Model = contactModel };
var binderMetadata = new TestBinderMetadata(); var binderMetadata = new TestBinderMetadata();
var predicateProvider = new DummyPropertyBindingPredicateProvider();
var emptyPropertyList = new List<string>(); var emptyPropertyList = new List<string>();
var nonEmptyPropertyList = new List<string>() { "SomeProperty" }; var nonEmptyPropertyList = new List<string>() { "SomeProperty" };
return new TheoryData<Action<ModelMetadata>, Func<ModelMetadata, object>, object> return new TheoryData<Action<ModelMetadata>, Func<ModelMetadata, object>, object>
@ -56,28 +57,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ m => m.BinderModelName = string.Empty, m => m.BinderModelName, string.Empty }, { m => m.BinderModelName = string.Empty, m => m.BinderModelName, string.Empty },
{ m => m.BinderType = null, m => m.BinderType, null }, { m => m.BinderType = null, m => m.BinderType, null },
{ m => m.BinderType = typeof(string), m => m.BinderType, typeof(string) }, { m => m.BinderType = typeof(string), m => m.BinderType, typeof(string) },
{ m => m.BinderIncludeProperties = null, m => m.BinderIncludeProperties, null }, { m => m.PropertyBindingPredicateProvider = null, m => m.PropertyBindingPredicateProvider, null },
{ { m => m.PropertyBindingPredicateProvider = predicateProvider, m => m.PropertyBindingPredicateProvider, predicateProvider },
m => m.BinderIncludeProperties = emptyPropertyList,
m => m.BinderIncludeProperties,
emptyPropertyList
},
{
m => m.BinderIncludeProperties = nonEmptyPropertyList,
m => m.BinderIncludeProperties,
nonEmptyPropertyList
},
{ m => m.BinderExcludeProperties = null, m => m.BinderExcludeProperties, null },
{
m => m.BinderExcludeProperties = emptyPropertyList,
m => m.BinderExcludeProperties,
emptyPropertyList
},
{
m => m.BinderExcludeProperties = nonEmptyPropertyList,
m => m.BinderExcludeProperties,
nonEmptyPropertyList
},
}; };
} }
} }
@ -128,8 +109,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(metadata.BinderModelName); Assert.Null(metadata.BinderModelName);
Assert.Null(metadata.BinderType); Assert.Null(metadata.BinderType);
Assert.Null(metadata.BinderMetadata); Assert.Null(metadata.BinderMetadata);
Assert.Null(metadata.BinderIncludeProperties); Assert.Null(metadata.PropertyBindingPredicateProvider);
Assert.Null(metadata.BinderExcludeProperties);
} }
// IsComplexType // IsComplexType
@ -514,5 +494,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public DummyContactModel Model { get; set; } public DummyContactModel Model { get; set; }
} }
private class DummyPropertyBindingPredicateProvider : IPropertyBindingPredicateProvider
{
public Func<ModelBindingContext, string, bool> PropertyFilter { get; set; }
}
} }
} }

View File

@ -1,49 +1,61 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace ModelBindingWebSite.Controllers namespace ModelBindingWebSite.Controllers
{ {
public class BindAttributeController : Controller public class BindAttributeController : Controller
{ {
public Dictionary<string, string> public User EchoUser([Bind(typeof(ExcludeUserPropertiesAtParameter))] User user)
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_BlackListingAtEitherLevelDoesNotBind(
[Bind(Exclude = "IncludedExplicitlyAtTypeLevel")] TypeWithIncludedPropertyAtBindAttribute param1,
[Bind(Include = "ExcludedExplicitlyAtTypeLevel")] TypeWithExcludedPropertyAtBindAttribute param2)
{ {
return new Dictionary<string, string>() return user;
{ }
// The first one should not be included because the parameter level bind attribute filters it out.
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel },
// The second one should not be included because the type level bind attribute filters it out. public User EchoUserUsingServices([Bind(typeof(ExcludeUserPropertiesUsingService))] User user)
{ "param2.ExcludedExplicitlyAtTypeLevel", param2.ExcludedExplicitlyAtTypeLevel }, {
}; return user;
} }
public Dictionary<string, string> public Dictionary<string, string>
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtBothLevelBinds( UpdateUserId_BlackListingAtEitherLevelDoesNotBind(
[Bind(Include = "IncludedExplicitlyAtTypeLevel")] TypeWithIncludedPropertyAtBindAttribute param1) [Bind(typeof(ExcludeLastName))] User2 param1,
[Bind("Id")] User2 param2)
{ {
return new Dictionary<string, string>() return new Dictionary<string, string>()
{ {
// The since this is included at both level it is bound. // LastName is excluded at parameter level.
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel }, { "param1.LastName", param1.LastName },
// Id is excluded because it is not explicitly included by the bind attribute at type level.
{ "param2.Id", param2.Id.ToString() },
}; };
} }
public Dictionary<string, string> public Dictionary<string, string> UpdateFirstName_IncludingAtBothLevelBinds(
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtOnlyOneLevelDoesNotBind( [Bind("FirstName")] User2 param1)
[Bind(Include = "IncludedExplicitlyAtParameterLevel")]
TypeWithIncludedPropertyAtParameterAndTypeUsingBindAttribute param1)
{ {
return new Dictionary<string, string>() return new Dictionary<string, string>()
{ {
// The since this is included at only type level it is not bound. // The since FirstName is included at both level it is bound.
{ "param1.IncludedExplicitlyAtParameterLevel", param1.IncludedExplicitlyAtParameterLevel }, { "param1.FirstName", param1.FirstName },
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel }, };
}
public Dictionary<string, string> UpdateIsAdmin_IncludingAtOnlyOneLevelDoesNotBind(
[Bind("IsAdmin" )] User2 param1)
{
return new Dictionary<string, string>()
{
// IsAdmin is not included because it is not explicitly included at type level.
{ "param1.IsAdmin", param1.IsAdmin.ToString() },
// FirstName is not included because it is not explicitly included at parameter level.
{ "param1.FirstName", param1.FirstName },
}; };
} }
@ -57,6 +69,49 @@ namespace ModelBindingWebSite.Controllers
{ {
return param.Value; return param.Value;
} }
private class ExcludeUserPropertiesAtParameter : DefaultPropertyBindingPredicateProvider<User>
{
public override string Prefix
{
get
{
return "user";
}
}
public override IEnumerable<Expression<Func<User, object>>> PropertyIncludeExpressions
{
get
{
yield return m => m.RegisterationMonth;
yield return m => m.UserName;
}
}
}
private class ExcludeUserPropertiesUsingService : ExcludeUserPropertiesAtParameter
{
private ITestService _testService;
public ExcludeUserPropertiesUsingService(ITestService testService)
{
_testService = testService;
}
public override IEnumerable<Expression<Func<User, object>>> PropertyIncludeExpressions
{
get
{
if (_testService.Test())
{
return base.PropertyIncludeExpressions;
}
return null;
}
}
}
} }
[Bind(Prefix = "TypePrefix")] [Bind(Prefix = "TypePrefix")]
@ -70,22 +125,27 @@ namespace ModelBindingWebSite.Controllers
public string Value { get; set; } public string Value { get; set; }
} }
[Bind(Include = nameof(IncludedExplicitlyAtTypeLevel))] [Bind(nameof(FirstName), nameof(LastName))]
public class TypeWithIncludedPropertyAtParameterAndTypeUsingBindAttribute public class User2
{ {
public string IncludedExplicitlyAtTypeLevel { get; set; } public int Id { get; set; }
public string IncludedExplicitlyAtParameterLevel { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsAdmin { get; set; }
} }
[Bind(Include = nameof(IncludedExplicitlyAtTypeLevel))] public class ExcludeLastName : IPropertyBindingPredicateProvider
public class TypeWithIncludedPropertyAtBindAttribute
{ {
public string IncludedExplicitlyAtTypeLevel { get; set; } public Func<ModelBindingContext, string, bool> PropertyFilter
} {
get
[Bind(Exclude = nameof(ExcludedExplicitlyAtTypeLevel))] {
public class TypeWithExcludedPropertyAtBindAttribute return (context, propertyName) =>
{ !string.Equals("LastName", propertyName, StringComparison.OrdinalIgnoreCase);
public string ExcludedExplicitlyAtTypeLevel { get; set; } }
}
} }
} }

View File

@ -1,12 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc.ModelBinding namespace ModelBindingWebSite
{ {
public class TestPropertyBindingInfo : IPropertyBindingInfo public interface ITestService
{ {
public string Exclude { get; set; } bool Test();
public string Include { get; set; }
} }
} }

View File

@ -27,6 +27,7 @@ namespace ModelBindingWebSite
}); });
services.AddSingleton<ICalculator, DefaultCalculator>(); services.AddSingleton<ICalculator, DefaultCalculator>();
services.AddSingleton<ITestService, TestService>();
}); });
// Add MVC to the request pipeline // Add MVC to the request pipeline

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace ModelBindingWebSite
{
public class TestService : ITestService
{
public bool Test()
{
return true;
}
}
}