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;
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
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; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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¶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]
|
[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¶m2.ExcludedExplicitlyAtTypeLevel=someValue");
|
"?param1.LastName=someValue¶m2.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¶m2.ExcludedExplicitlyAtTypeLevel=someValue");
|
"?param1.FirstName=someValue¶m2.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¶m1.IncludedExplicitlyAtParameterLevel=someValue");
|
"?param1.FirstName=someValue¶m1.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]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -22,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
[InlineData(typeof(Person), false)]
|
[InlineData(typeof(Person), false)]
|
||||||
[InlineData(typeof(EmptyModel), true)]
|
[InlineData(typeof(EmptyModel), true)]
|
||||||
[InlineData(typeof(EmptyModel), false)]
|
[InlineData(typeof(EmptyModel), false)]
|
||||||
public async Task
|
public async Task
|
||||||
CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsExplicitPrefix(Type modelType, bool isPrefixProvided)
|
CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsExplicitPrefix(Type modelType, bool isPrefixProvided)
|
||||||
{
|
{
|
||||||
var mockValueProvider = new Mock<IValueProvider>();
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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