Adding Support for TryUpdateModel using include expressions and predicate.
This commit is contained in:
parent
ca714c5149
commit
d7094fd32d
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
|
@ -96,21 +95,17 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata,
|
||||
ActionBindingContext actionBindingContext,
|
||||
OperationBindingContext operationBindingContext)
|
||||
internal static ModelBindingContext GetModelBindingContext(
|
||||
ModelMetadata modelMetadata,
|
||||
ActionBindingContext actionBindingContext,
|
||||
OperationBindingContext operationBindingContext)
|
||||
{
|
||||
Func<ModelBindingContext, string, bool> propertyFilter =
|
||||
(context, propertyName) => BindAttribute.IsPropertyAllowed(propertyName,
|
||||
modelMetadata.BinderIncludeProperties,
|
||||
modelMetadata.BinderExcludeProperties);
|
||||
|
||||
var modelBindingContext = new ModelBindingContext
|
||||
{
|
||||
ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName,
|
||||
ModelMetadata = modelMetadata,
|
||||
ModelState = actionBindingContext.ActionContext.ModelState,
|
||||
PropertyFilter = propertyFilter,
|
||||
|
||||
// Fallback only if there is no explicit model name set.
|
||||
FallbackToEmptyPrefix = modelMetadata.BinderModelName == null,
|
||||
ValueProvider = actionBindingContext.ValueProvider,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,6 +167,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
return false;
|
||||
}
|
||||
|
||||
// Internal for tests
|
||||
internal static string GetPropertyName(Expression expression)
|
||||
{
|
||||
if (expression.NodeType == ExpressionType.Convert ||
|
||||
|
|
@ -203,8 +204,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
private static Expression<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel>
|
||||
(string prefix, Expression<Func<TModel, object>>[] expressions)
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
// 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 Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
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.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingInfo
|
||||
public class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingPredicateProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Comma separated set of properties which are to be excluded during model binding.
|
||||
/// </summary>
|
||||
public string Exclude { get; set; } = string.Empty;
|
||||
private static readonly Func<ModelBindingContext, string, bool> _defaultFilter = (context, propertyName) => true;
|
||||
|
||||
private Func<ModelBindingContext, string, bool> _predicateFromInclude;
|
||||
|
||||
/// <summary>
|
||||
/// Comma separated set of properties which are to be included during model binding.
|
||||
/// Creates a new instace of <see cref="BindAttribute"/>.
|
||||
/// </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>
|
||||
/// Allows a user to specify a particular prefix to match during model binding.
|
||||
/// </summary>
|
||||
// This property is exposed for back compat reasons.
|
||||
public string Prefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -40,18 +71,56 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
public static bool IsPropertyAllowed(string propertyName,
|
||||
IReadOnlyList<string> includeProperties,
|
||||
IReadOnlyList<string> excludeProperties)
|
||||
/// <inheritdoc />
|
||||
public Func<ModelBindingContext, string, bool> PropertyFilter
|
||||
{
|
||||
// We allow a property to be bound if its both in the include list AND not in the exclude list.
|
||||
// An empty exclude list implies no properties are disallowed.
|
||||
var includeProperty = (includeProperties != null) &&
|
||||
includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
|
||||
var excludeProperty = (excludeProperties != null) &&
|
||||
excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
|
||||
get
|
||||
{
|
||||
if (PredicateProviderType != null)
|
||||
{
|
||||
return CreatePredicateFromProviderType(PredicateProviderType);
|
||||
}
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
|
|
@ -269,14 +270,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
||||
{
|
||||
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||
var newPropertyFilter = GetPropertyFilter();
|
||||
return bindingContext.ModelMetadata.Properties
|
||||
.Where(propertyMetadata =>
|
||||
bindingContext.PropertyFilter(bindingContext, propertyMetadata.PropertyName) &&
|
||||
newPropertyFilter(bindingContext, propertyMetadata.PropertyName) &&
|
||||
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
|
||||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
|
||||
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)
|
||||
{
|
||||
var attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Required = attributes.OfType<RequiredAttribute>().FirstOrDefault();
|
||||
ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
|
||||
BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
|
||||
PropertyBindingInfo = attributes.OfType<IPropertyBindingInfo>();
|
||||
PropertyBindingPredicateProviders = attributes.OfType<IPropertyBindingPredicateProvider>();
|
||||
BinderModelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
|
||||
BinderTypeProviders = attributes.OfType<IBinderTypeProviderMetadata>();
|
||||
|
||||
|
|
@ -82,11 +82,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public HiddenInputAttribute HiddenInput { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="IEnumerable{IPropertyBindingInfo}"/> found in collection
|
||||
/// passed to the <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor,
|
||||
/// if any.
|
||||
/// Gets (or sets in subclasses) <see cref="IEnumerable{IPropertyBindingPredicateProvider}"/> found in
|
||||
/// collection passed to the <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/>
|
||||
/// constructor, if any.
|
||||
/// </summary>
|
||||
public IEnumerable<IPropertyBindingInfo> PropertyBindingInfo { get; protected set; }
|
||||
public IEnumerable<IPropertyBindingPredicateProvider> PropertyBindingPredicateProviders { get; protected set; }
|
||||
|
||||
public RequiredAttribute Required { get; protected set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -70,56 +70,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
: base.ComputeBinderModelNamePrefix();
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<string> ComputeBinderIncludeProperties()
|
||||
protected override IPropertyBindingPredicateProvider ComputePropertyBindingPredicateProvider()
|
||||
{
|
||||
var propertyBindingInfo = PrototypeCache.PropertyBindingInfo?.ToList();
|
||||
if (propertyBindingInfo != null && propertyBindingInfo.Count != 0)
|
||||
{
|
||||
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();
|
||||
return PrototypeCache.PropertyBindingPredicateProviders.Any()
|
||||
? new CompositePredicateProvider(PrototypeCache.PropertyBindingPredicateProviders.ToArray())
|
||||
: null;
|
||||
}
|
||||
|
||||
protected override bool ComputeConvertEmptyStringToNull()
|
||||
|
|
@ -383,5 +338,44 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
.Where(trimmed => !string.IsNullOrEmpty(trimmed));
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private bool _showForEdit;
|
||||
private IBinderMetadata _binderMetadata;
|
||||
private string _binderModelName;
|
||||
private IReadOnlyList<string> _binderIncludeProperties;
|
||||
private IReadOnlyList<string> _binderExcludeProperties;
|
||||
private IPropertyBindingPredicateProvider _propertyBindingPredicateProvider;
|
||||
private Type _binderType;
|
||||
|
||||
private bool _convertEmptyStringToNullComputed;
|
||||
|
|
@ -53,10 +52,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private bool _showForDisplayComputed;
|
||||
private bool _showForEditComputed;
|
||||
private bool _isBinderMetadataComputed;
|
||||
private bool _isBinderIncludePropertiesComputed;
|
||||
private bool _isBinderModelNameComputed;
|
||||
private bool _isBinderExcludePropertiesComputed;
|
||||
private bool _isBinderTypeComputed;
|
||||
private bool _propertyBindingPredicateProviderComputed;
|
||||
|
||||
// Constructor for creating real instances of the metadata class based on a prototype
|
||||
protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor)
|
||||
|
|
@ -102,50 +100,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
_binderMetadata = value;
|
||||
_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 />
|
||||
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 />
|
||||
public sealed override bool ShowForDisplay
|
||||
{
|
||||
|
|
@ -506,14 +481,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return base.BinderMetadata;
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<string> ComputeBinderIncludeProperties()
|
||||
protected virtual IPropertyBindingPredicateProvider ComputePropertyBindingPredicateProvider()
|
||||
{
|
||||
return base.BinderIncludeProperties;
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<string> ComputeBinderExcludeProperties()
|
||||
{
|
||||
return base.BinderExcludeProperties;
|
||||
return base.PropertyBindingPredicateProvider;
|
||||
}
|
||||
|
||||
protected virtual string ComputeBinderModelNamePrefix()
|
||||
|
|
|
|||
|
|
@ -48,23 +48,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <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>
|
||||
public virtual string BinderModelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties which are to be included while binding this model.
|
||||
/// </summary>
|
||||
public virtual IReadOnlyList<string> BinderIncludeProperties { get; set; }
|
||||
|
||||
/// <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"/>.
|
||||
/// Gets or sets 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>
|
||||
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
|
||||
{
|
||||
get { return _propertyName; }
|
||||
|
|
|
|||
|
|
@ -251,19 +251,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' must derive from '{1}'.
|
||||
/// The type '{0}' does not implement the interface '{1}'.
|
||||
/// </summary>
|
||||
internal static string TypeMustDeriveFromType
|
||||
internal static string PropertyBindingPredicateProvider_WrongType
|
||||
{
|
||||
get { return GetString("TypeMustDeriveFromType"); }
|
||||
get { return GetString("PropertyBindingPredicateProvider_WrongType"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' must derive from '{1}'.
|
||||
/// The type '{0}' does not implement the interface '{1}'.
|
||||
/// </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>
|
||||
|
|
|
|||
|
|
@ -162,8 +162,8 @@
|
|||
<data name="ModelBindingContext_ModelMetadataMustBeSet" xml:space="preserve">
|
||||
<value>The ModelMetadata property must be set before accessing this property.</value>
|
||||
</data>
|
||||
<data name="TypeMustDeriveFromType" xml:space="preserve">
|
||||
<value>The type '{0}' must derive from '{1}'.</value>
|
||||
<data name="PropertyBindingPredicateProvider_WrongType" xml:space="preserve">
|
||||
<value>The type '{0}' does not implement the interface '{1}'.</value>
|
||||
</data>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -49,33 +49,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetModelBindingContext_DoesNotReturn_ExcludedProperties()
|
||||
{
|
||||
// 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()
|
||||
public void GetModelBindingContext_ReturnsOnlyIncludedProperties_UsingBindAttributeInclude()
|
||||
{
|
||||
// Arrange
|
||||
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))]
|
||||
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))]
|
||||
[Bind(new string[] { nameof(IncludedExplicitly1), nameof(IncludedExplicitly2) })]
|
||||
private class TypeWithIncludedPropertiesUsingBindAttribute
|
||||
{
|
||||
public int ExcludedByDefault1 { get; set; }
|
||||
|
|
|
|||
|
|
@ -707,6 +707,71 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
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¶m2.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]
|
||||
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_BlacklistedAtEitherLevelIsNotBound()
|
||||
{
|
||||
|
|
@ -716,18 +781,18 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_BlackListingAtEitherLevelDoesNotBind" +
|
||||
"?param1.IncludedExplicitlyAtTypeLevel=someValue¶m2.ExcludedExplicitlyAtTypeLevel=someValue");
|
||||
"UpdateUserId_BlackListingAtEitherLevelDoesNotBind" +
|
||||
"?param1.LastName=someValue¶m2.Id=123");
|
||||
|
||||
// Assert
|
||||
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||
Assert.Equal(2, json.Count);
|
||||
Assert.Null(json["param1.IncludedExplicitlyAtTypeLevel"]);
|
||||
Assert.Null(json["param2.ExcludedExplicitlyAtTypeLevel"]);
|
||||
Assert.Null(json["param1.LastName"]);
|
||||
Assert.Equal("0", json["param2.Id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_WhitelistedAtBothLevelsIsBound()
|
||||
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_IncludedAtBothLevelsIsBound()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
|
|
@ -735,17 +800,17 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtBothLevelBinds" +
|
||||
"?param1.IncludedExplicitlyAtTypeLevel=someValue¶m2.ExcludedExplicitlyAtTypeLevel=someValue");
|
||||
"UpdateFirstName_IncludingAtBothLevelBinds" +
|
||||
"?param1.FirstName=someValue¶m2.Id=123");
|
||||
|
||||
// Assert
|
||||
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||
Assert.Equal(1, json.Count);
|
||||
Assert.Equal("someValue", json["param1.IncludedExplicitlyAtTypeLevel"]);
|
||||
Assert.Equal("someValue", json["param1.FirstName"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_WhitelistingAtOneLevelIsNotBound()
|
||||
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_IncludingAtOneLevelIsNotBound()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
|
|
@ -753,14 +818,14 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtOnlyOneLevelDoesNotBind" +
|
||||
"?param1.IncludedExplicitlyAtTypeLevel=someValue¶m1.IncludedExplicitlyAtParameterLevel=someValue");
|
||||
"UpdateIsAdmin_IncludingAtOnlyOneLevelDoesNotBind" +
|
||||
"?param1.FirstName=someValue¶m1.IsAdmin=true");
|
||||
|
||||
// Assert
|
||||
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||
Assert.Equal(2, json.Count);
|
||||
Assert.Null(json["param1.IncludedExplicitlyAtParameterLevel"]);
|
||||
Assert.Null(json["param1.IncludedExplicitlyAtTypeLevel"]);
|
||||
Assert.Equal("False", json["param1.IsAdmin"]);
|
||||
Assert.Null(json["param1.FirstName"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -1,20 +1,169 @@
|
|||
// 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 Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
#if ASPNET50
|
||||
using Moq;
|
||||
#endif
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
public class BindAttributeTest
|
||||
{
|
||||
[Fact]
|
||||
public void PrefixPropertyDefaultsToNull()
|
||||
public void Constructor_Throws_IfTypeDoesNotImplement_IPropertyBindingPredicateProvider()
|
||||
{
|
||||
// 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
|
||||
Assert.Null(attr.Prefix);
|
||||
// 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]
|
||||
[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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ using System.ComponentModel.DataAnnotations;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
[InlineData(typeof(Person), false)]
|
||||
[InlineData(typeof(EmptyModel), true)]
|
||||
[InlineData(typeof(EmptyModel), false)]
|
||||
public async Task
|
||||
public async Task
|
||||
CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsExplicitPrefix(Type modelType, bool isPrefixProvided)
|
||||
{
|
||||
var mockValueProvider = new Mock<IValueProvider>();
|
||||
|
|
@ -247,12 +249,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
.Returns<IValueProviderMetadata>(
|
||||
valueProviderMetadata =>
|
||||
{
|
||||
if(valueProviderMetadata is ValueBinderMetadataAttribute)
|
||||
{
|
||||
return mockOriginalValueProvider.Object;
|
||||
}
|
||||
if (valueProviderMetadata is ValueBinderMetadataAttribute)
|
||||
{
|
||||
return mockOriginalValueProvider.Object;
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
});
|
||||
|
||||
var bindingContext = new MutableObjectBinderContext
|
||||
|
|
@ -590,6 +592,60 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
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_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; }
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
[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 virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Null(cache.ScaffoldColumn);
|
||||
Assert.Null(cache.BinderMetadata);
|
||||
Assert.Null(cache.BinderModelNameProvider);
|
||||
Assert.Empty(cache.PropertyBindingInfo);
|
||||
Assert.Empty(cache.PropertyBindingPredicateProviders);
|
||||
}
|
||||
|
||||
public static TheoryData<object, Func<CachedDataAnnotationsMetadataAttributes, object>>
|
||||
|
|
@ -78,18 +78,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void Constructor_FindsPropertyBindingInfo()
|
||||
{
|
||||
// Arrange
|
||||
var propertyBindingInfos =
|
||||
new[] { new TestPropertyBindingInfo(), new TestPropertyBindingInfo() };
|
||||
var providers = new[] { new TestPredicateProvider(), new TestPredicateProvider() };
|
||||
|
||||
// Act
|
||||
var cache = new CachedDataAnnotationsMetadataAttributes(propertyBindingInfos);
|
||||
var result = cache.PropertyBindingInfo.ToArray();
|
||||
var cache = new CachedDataAnnotationsMetadataAttributes(providers);
|
||||
var result = cache.PropertyBindingPredicateProviders.ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(propertyBindingInfos.Length, result.Length);
|
||||
for (var index = 0; index < propertyBindingInfos.Length; index++)
|
||||
Assert.Equal(providers.Length, result.Length);
|
||||
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; }
|
||||
}
|
||||
|
||||
private class TestPredicateProvider : IPropertyBindingPredicateProvider
|
||||
{
|
||||
public Func<ModelBindingContext, string, bool> PropertyFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
|
@ -11,57 +12,50 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
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]
|
||||
public void DataAnnotationsModelMetadataProvider_ReadsIncludedAndExcludedProperties_ForTypes()
|
||||
public void DataAnnotationsModelMetadataProvider_UsesPredicateOnType()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||
var type = typeof(User);
|
||||
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1", "IncludedExplicitly1" };
|
||||
var expectedExcludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
|
||||
var context = new ModelBindingContext();
|
||||
|
||||
var expected = new[] { "IsAdmin", "UserName" };
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForType(null, type);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties);
|
||||
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties);
|
||||
var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter;
|
||||
|
||||
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 ModelMetadataProvider_ReadsIncludedAndExcludedProperties_AtParameterAndType_ForParameters()
|
||||
public void DataAnnotationsModelMetadataProvider_UsesPredicateOnParameter()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||
var methodInfo = type.GetMethod("ActionWithBindAttribute");
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
var type = GetType();
|
||||
var methodInfo = type.GetMethod(
|
||||
"ActionWithoutBindAttribute",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
// Note it does an intersection for included and a union for excluded.
|
||||
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1" };
|
||||
var expectedExcludedPropertyNames = new[] {
|
||||
"Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
|
||||
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", "UserName" };
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForParameter(
|
||||
|
|
@ -70,16 +64,68 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
parameterName: "param");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties);
|
||||
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties);
|
||||
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 ModelMetadataProvider_ReadsPrefixProperty_OnlyAtParameterLevel_ForParameters()
|
||||
public void DataAnnotationsModelMetadataProvider_UsesPredicateOnParameter_Merge()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||
var methodInfo = type.GetMethod("ActionWithBindAttribute");
|
||||
var type = GetType();
|
||||
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();
|
||||
|
||||
// Act
|
||||
|
|
@ -96,7 +142,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForTypes()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||
var type = typeof(User);
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
|
|
@ -106,23 +152,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
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]
|
||||
public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay()
|
||||
|
|
@ -193,8 +222,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.NotNull(propertyMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<TypeBasedBinderAttribute>(propertyMetadata.BinderMetadata);
|
||||
Assert.Equal("PersonType", propertyMetadata.BinderModelName);
|
||||
Assert.Equal(new[] { "IncludeAtType" }, propertyMetadata.BinderIncludeProperties.ToArray());
|
||||
Assert.Equal(new[] { "ExcludeAtType" }, propertyMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -210,12 +237,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.NotNull(propertyMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(propertyMetadata.BinderMetadata);
|
||||
Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName);
|
||||
Assert.Empty(propertyMetadata.BinderIncludeProperties);
|
||||
Assert.Equal(new[] { "ExcludeAtProperty", "ExcludeAtType" },
|
||||
propertyMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
|
||||
#if ASPNET50
|
||||
[Fact]
|
||||
public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType()
|
||||
{
|
||||
|
|
@ -223,16 +246,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var parameterMetadata = provider.GetMetadataForParameter(null,
|
||||
typeof(Person).GetMethod("Update"),
|
||||
"person");
|
||||
var parameterMetadata = provider.GetMetadataForParameter(
|
||||
null,
|
||||
typeof(Person).GetMethod("Update"),
|
||||
"person");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(parameterMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<TypeBasedBinderAttribute>(parameterMetadata.BinderMetadata);
|
||||
Assert.Equal("PersonType", parameterMetadata.BinderModelName);
|
||||
Assert.Equal(new[] { "IncludeAtType" }, parameterMetadata.BinderIncludeProperties.ToArray());
|
||||
Assert.Equal(new[] { "ExcludeAtType" }, parameterMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -242,53 +264,48 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var parameterMetadata = provider.GetMetadataForParameter(null,
|
||||
typeof(Person).GetMethod("Save"),
|
||||
"person");
|
||||
var parameterMetadata = provider.GetMetadataForParameter(
|
||||
null,
|
||||
typeof(Person).GetMethod("Save"),
|
||||
"person");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(parameterMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(parameterMetadata.BinderMetadata);
|
||||
Assert.Equal("PersonParameter", parameterMetadata.BinderModelName);
|
||||
Assert.Empty(parameterMetadata.BinderIncludeProperties);
|
||||
Assert.Equal(new[] { "ExcludeAtParameter", "ExcludeAtType" },
|
||||
parameterMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
#endif
|
||||
public class TypeBasedBinderAttribute : Attribute,
|
||||
IBinderMetadata, IModelNameProvider, IPropertyBindingInfo
|
||||
|
||||
private void ActionWithoutBindAttribute(User param)
|
||||
{
|
||||
}
|
||||
|
||||
private void ActionWithBindAttribute([Bind(new string[] { "IsAdmin" }, Prefix = "ParameterPrefix")] User param)
|
||||
{
|
||||
}
|
||||
|
||||
public class TypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Exclude { get; set; }
|
||||
|
||||
public string Include { get; set; }
|
||||
}
|
||||
|
||||
public class NonTypeBasedBinderAttribute : Attribute,
|
||||
IBinderMetadata, IModelNameProvider, IPropertyBindingInfo
|
||||
public class NonTypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider
|
||||
{
|
||||
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 Person Parent { get; set; }
|
||||
|
||||
[NonTypeBasedBinder(Name = "GrandParentProperty", Include = "IncludeAtProperty", Exclude = "ExcludeAtProperty")]
|
||||
[NonTypeBasedBinder(Name = "GrandParentProperty")]
|
||||
public Person GrandParent { get; set; }
|
||||
|
||||
public void Update(Person person)
|
||||
{
|
||||
}
|
||||
|
||||
public void Save([NonTypeBasedBinder(Name = "PersonParameter",
|
||||
Include = "IncludeAtParameter", Exclude = "ExcludeAtParameter")] Person person)
|
||||
public void Save([NonTypeBasedBinder(Name = "PersonParameter")] Person person)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -317,5 +334,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -52,9 +52,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
Assert.Null(metadata.BinderModelName);
|
||||
Assert.Null(metadata.BinderMetadata);
|
||||
Assert.Null(metadata.PropertyBindingPredicateProvider);
|
||||
Assert.Null(metadata.BinderType);
|
||||
Assert.Empty(metadata.BinderIncludeProperties);
|
||||
Assert.Null(metadata.BinderExcludeProperties);
|
||||
}
|
||||
|
||||
public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeDataStrings
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var nonEmptycontainerModel = new DummyModelContainer { Model = contactModel };
|
||||
|
||||
var binderMetadata = new TestBinderMetadata();
|
||||
var predicateProvider = new DummyPropertyBindingPredicateProvider();
|
||||
var emptyPropertyList = new List<string>();
|
||||
var nonEmptyPropertyList = new List<string>() { "SomeProperty" };
|
||||
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.BinderType = null, m => m.BinderType, null },
|
||||
{ m => m.BinderType = typeof(string), m => m.BinderType, typeof(string) },
|
||||
{ m => m.BinderIncludeProperties = null, m => m.BinderIncludeProperties, null },
|
||||
{
|
||||
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
|
||||
},
|
||||
{ m => m.PropertyBindingPredicateProvider = null, m => m.PropertyBindingPredicateProvider, null },
|
||||
{ m => m.PropertyBindingPredicateProvider = predicateProvider, m => m.PropertyBindingPredicateProvider, predicateProvider },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -128,8 +109,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Null(metadata.BinderModelName);
|
||||
Assert.Null(metadata.BinderType);
|
||||
Assert.Null(metadata.BinderMetadata);
|
||||
Assert.Null(metadata.BinderIncludeProperties);
|
||||
Assert.Null(metadata.BinderExcludeProperties);
|
||||
Assert.Null(metadata.PropertyBindingPredicateProvider);
|
||||
}
|
||||
|
||||
// IsComplexType
|
||||
|
|
@ -514,5 +494,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
public DummyContactModel Model { get; set; }
|
||||
}
|
||||
|
||||
private class DummyPropertyBindingPredicateProvider : IPropertyBindingPredicateProvider
|
||||
{
|
||||
public Func<ModelBindingContext, string, bool> PropertyFilter { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,61 @@
|
|||
// 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.Expressions;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace ModelBindingWebSite.Controllers
|
||||
{
|
||||
public class BindAttributeController : Controller
|
||||
{
|
||||
public Dictionary<string, string>
|
||||
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_BlackListingAtEitherLevelDoesNotBind(
|
||||
[Bind(Exclude = "IncludedExplicitlyAtTypeLevel")] TypeWithIncludedPropertyAtBindAttribute param1,
|
||||
[Bind(Include = "ExcludedExplicitlyAtTypeLevel")] TypeWithExcludedPropertyAtBindAttribute param2)
|
||||
public User EchoUser([Bind(typeof(ExcludeUserPropertiesAtParameter))] User user)
|
||||
{
|
||||
return new Dictionary<string, string>()
|
||||
{
|
||||
// The first one should not be included because the parameter level bind attribute filters it out.
|
||||
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel },
|
||||
return user;
|
||||
}
|
||||
|
||||
// The second one should not be included because the type level bind attribute filters it out.
|
||||
{ "param2.ExcludedExplicitlyAtTypeLevel", param2.ExcludedExplicitlyAtTypeLevel },
|
||||
};
|
||||
public User EchoUserUsingServices([Bind(typeof(ExcludeUserPropertiesUsingService))] User user)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
|
||||
public Dictionary<string, string>
|
||||
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtBothLevelBinds(
|
||||
[Bind(Include = "IncludedExplicitlyAtTypeLevel")] TypeWithIncludedPropertyAtBindAttribute param1)
|
||||
UpdateUserId_BlackListingAtEitherLevelDoesNotBind(
|
||||
[Bind(typeof(ExcludeLastName))] User2 param1,
|
||||
[Bind("Id")] User2 param2)
|
||||
{
|
||||
return new Dictionary<string, string>()
|
||||
{
|
||||
// The since this is included at both level it is bound.
|
||||
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel },
|
||||
// LastName is excluded at parameter level.
|
||||
{ "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>
|
||||
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtOnlyOneLevelDoesNotBind(
|
||||
[Bind(Include = "IncludedExplicitlyAtParameterLevel")]
|
||||
TypeWithIncludedPropertyAtParameterAndTypeUsingBindAttribute param1)
|
||||
public Dictionary<string, string> UpdateFirstName_IncludingAtBothLevelBinds(
|
||||
[Bind("FirstName")] User2 param1)
|
||||
{
|
||||
return new Dictionary<string, string>()
|
||||
{
|
||||
// The since this is included at only type level it is not bound.
|
||||
{ "param1.IncludedExplicitlyAtParameterLevel", param1.IncludedExplicitlyAtParameterLevel },
|
||||
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel },
|
||||
// The since FirstName is included at both level it is bound.
|
||||
{ "param1.FirstName", param1.FirstName },
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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")]
|
||||
|
|
@ -70,22 +125,27 @@ namespace ModelBindingWebSite.Controllers
|
|||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
[Bind(Include = nameof(IncludedExplicitlyAtTypeLevel))]
|
||||
public class TypeWithIncludedPropertyAtParameterAndTypeUsingBindAttribute
|
||||
[Bind(nameof(FirstName), nameof(LastName))]
|
||||
public class User2
|
||||
{
|
||||
public string IncludedExplicitlyAtTypeLevel { get; set; }
|
||||
public string IncludedExplicitlyAtParameterLevel { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
public string FirstName { get; set; }
|
||||
|
||||
public string LastName { get; set; }
|
||||
|
||||
public bool IsAdmin { get; set; }
|
||||
}
|
||||
|
||||
[Bind(Include = nameof(IncludedExplicitlyAtTypeLevel))]
|
||||
public class TypeWithIncludedPropertyAtBindAttribute
|
||||
public class ExcludeLastName : IPropertyBindingPredicateProvider
|
||||
{
|
||||
public string IncludedExplicitlyAtTypeLevel { get; set; }
|
||||
}
|
||||
|
||||
[Bind(Exclude = nameof(ExcludedExplicitlyAtTypeLevel))]
|
||||
public class TypeWithExcludedPropertyAtBindAttribute
|
||||
{
|
||||
public string ExcludedExplicitlyAtTypeLevel { get; set; }
|
||||
public Func<ModelBindingContext, string, bool> PropertyFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
return (context, propertyName) =>
|
||||
!string.Equals("LastName", propertyName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
// 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.ModelBinding
|
||||
namespace ModelBindingWebSite
|
||||
{
|
||||
public class TestPropertyBindingInfo : IPropertyBindingInfo
|
||||
public interface ITestService
|
||||
{
|
||||
public string Exclude { get; set; }
|
||||
|
||||
public string Include { get; set; }
|
||||
bool Test();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ namespace ModelBindingWebSite
|
|||
});
|
||||
|
||||
services.AddSingleton<ICalculator, DefaultCalculator>();
|
||||
services.AddSingleton<ITestService, TestService>();
|
||||
});
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue