Updating Associated Metadata Provider to follow the existing pattern.

This commit is contained in:
Harsh Gupta 2014-11-19 14:13:18 -08:00
parent 680cdf4d57
commit b54c326ee6
23 changed files with 825 additions and 390 deletions

View File

@ -43,11 +43,11 @@ namespace Microsoft.AspNet.Mvc
var metadata = metadataProvider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: actionDescriptor.MethodInfo,
parameterName: parameter.Name,
binderMetadata: parameter.BinderMetadata);
parameterName: parameter.Name);
if (metadata != null)
{
UpdateParameterMetadata(metadata, parameter.BinderMetadata);
parameterMetadata.Add(metadata);
}
}
@ -57,6 +57,20 @@ namespace Microsoft.AspNet.Mvc
return actionArguments;
}
private void UpdateParameterMetadata(ModelMetadata metadata, IBinderMetadata binderMetadata)
{
if (binderMetadata != null)
{
metadata.BinderMetadata = binderMetadata;
}
var nameProvider = binderMetadata as IModelNameProvider;
if (nameProvider != null && nameProvider.Name != null)
{
metadata.BinderModelName = nameProvider.Name;
}
}
private async Task PopulateArgumentAsync(
ActionBindingContext actionBindingContext,
IDictionary<string, object> arguments,
@ -82,23 +96,23 @@ namespace Microsoft.AspNet.Mvc
}
}
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata,
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata,
ActionBindingContext actionBindingContext,
OperationBindingContext operationBindingContext)
{
Predicate<string> propertyFilter =
propertyName => BindAttribute.IsPropertyAllowed(propertyName,
modelMetadata.IncludedProperties,
modelMetadata.ExcludedProperties);
modelMetadata.BinderIncludeProperties,
modelMetadata.BinderExcludeProperties);
var modelBindingContext = new ModelBindingContext
{
ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName,
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.ModelName == null,
FallbackToEmptyPrefix = modelMetadata.BinderModelName == null,
ValueProvider = actionBindingContext.ValueProvider,
OperationBindingContext = operationBindingContext,
};

View File

@ -11,7 +11,7 @@ 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, IModelPropertyBindingInfo
public sealed class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingInfo
{
/// <summary>
/// Comma separated set of properties which are to be excluded during model binding.
@ -45,10 +45,8 @@ namespace Microsoft.AspNet.Mvc
IReadOnlyList<string> excludeProperties)
{
// We allow a property to be bound if its both in the include list AND not in the exclude list.
// An empty include list implies all properties are allowed.
// An empty exclude list implies no properties are disallowed.
var includeProperty = (includeProperties == null) ||
(includeProperties.Count == 0) ||
var includeProperty = (includeProperties != null) &&
includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
var excludeProperty = (excludeProperties != null) &&
excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);

View File

@ -51,14 +51,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
var bindingContext = context.ModelBindingContext;
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
var isThereAnExplicitAlias = bindingContext.ModelMetadata.ModelName != null;
// The fact that this has reached here,
// it is a complex object which was not directly bound by any previous model binders.
var hasExplicitAlias = bindingContext.ModelMetadata.BinderModelName != null;
// The fact that this has reached here,
// it is a complex object which was not directly bound by any previous model binders.
// Check if this was supposed to be handled by a non value provider based binder.
// if it was then it should be not be bound using mutable object binder.
// This check would prevent it from recursing in if a model contains a property of its own type.
// We skip this check if it is a top level object because we want to always evaluate
// We skip this check if it is a top level object because we want to always evaluate
// the creation of top level object (this is also required for ModelBinderAttribute to work.)
if (!isTopLevelObject &&
bindingContext.ModelMetadata.BinderMetadata != null &&
@ -68,14 +68,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
// Create the object if :
// 1. It is a top level model with an explicit user supplied prefix.
// 1. It is a top level model with an explicit user supplied prefix.
// In this case since it will never fallback to empty prefix, we need to create the model here.
if (isTopLevelObject && isThereAnExplicitAlias)
if (isTopLevelObject && hasExplicitAlias)
{
return true;
}
// 2. It is a top level object and there is no model name ( Fallback to empty prefix case ).
// 2. It is a top level object and there is no model name ( Fallback to empty prefix case ).
// This is necessary as we do not want to depend on a value provider to contain an empty prefix.
if (isTopLevelObject && bindingContext.ModelName == string.Empty)
{
@ -83,9 +83,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
// 3. The model name is not prefixed and a value provider can directly provide a value for the model name.
// The fact that it is not prefixed means that the containsPrefixAsync call checks for the exact model name
// instead of doing a prefix match.
if (!bindingContext.ModelName.Contains(".") &&
// The fact that it is not prefixed means that the containsPrefixAsync call checks for the exact
// model name instead of doing a prefix match.
if (!bindingContext.ModelName.Contains(".") &&
await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
{
return true;
@ -103,11 +103,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private async Task<bool> CanValueBindAnyModelProperties(MutableObjectBinderContext context)
{
// We need to enumerate the non marked properties and properties marked with IValueProviderMetadata
// instead of checking bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName)
// because there can be a case
// where a value provider might be willing to provide a marked property, which might never be bound.
// For example if person.Name is marked with FromQuery, and FormValueProvider has a key person.Name, and the
// QueryValueProvider does not, we do not want to create Person.
// instead of checking bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName)
// because there can be a case where a value provider might be willing to provide a marked property,
// which might never be bound.
// For example if person.Name is marked with FromQuery, and FormValueProvider has a key person.Name,
// and the QueryValueProvider does not, we do not want to create Person.
var isAnyPropertyEnabledForValueProviderBasedBinding = false;
foreach (var propertyMetadata in context.PropertyMetadata)
{
@ -144,7 +144,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (valueProviderMetadata != null)
{
// if there is a binder metadata and since the property can be bound using a value provider.
var metadataAwareValueProvider = bindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
var metadataAwareValueProvider =
bindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
if (metadataAwareValueProvider != null)
{
valueProvider = metadataAwareValueProvider.Filter(valueProviderMetadata);
@ -268,19 +269,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
{
var validationInfo = GetPropertyValidationInfo(bindingContext);
var propertyTypeMetadata = bindingContext.OperationBindingContext
.MetadataProvider
.GetMetadataForType(null, bindingContext.ModelType);
Predicate<string> newPropertyFilter =
propertyName => bindingContext.PropertyFilter(propertyName) &&
BindAttribute.IsPropertyAllowed(
propertyName,
propertyTypeMetadata.IncludedProperties,
propertyTypeMetadata.ExcludedProperties);
return bindingContext.ModelMetadata.Properties
.Where(propertyMetadata =>
newPropertyFilter(propertyMetadata.PropertyName) &&
bindingContext.PropertyFilter(propertyMetadata.PropertyName) &&
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
CanUpdateProperty(propertyMetadata));

View File

@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc
/// <summary>
/// Represents an entity which has binding information for a model.
/// </summary>
public interface IModelPropertyBindingInfo
public interface IPropertyBindingInfo
{
/// <summary>
/// Comma separated set of properties which are to be excluded during model binding.

View File

@ -49,9 +49,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public ModelMetadata GetMetadataForParameter(
Func<object> modelAccessor,
[NotNull] MethodInfo methodInfo,
[NotNull] string parameterName,
IBinderMetadata binderMetadata)
[NotNull] string parameterName)
{
if (string.IsNullOrEmpty(parameterName))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "parameterName");
}
var parameter = methodInfo.GetParameters().FirstOrDefault(
param => StringComparer.Ordinal.Equals(param.Name, parameterName));
if (parameter == null)
@ -60,11 +64,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
throw new ArgumentException(message, nameof(parameterName));
}
return GetMetadataForParameterCore(modelAccessor, parameterName, parameter, binderMetadata);
return GetMetadataForParameterCore(modelAccessor, parameterName, parameter);
}
// Override for creating the prototype metadata (without the accessor)
protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes,
protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable<object> attributes,
Type containerType,
Type modelType,
string propertyName);
@ -72,27 +76,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Override for applying the prototype + modelAccess to yield the final metadata
protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype,
Func<object> modelAccessor);
private ModelMetadata GetMetadataForParameterCore(Func<object> modelAccessor,
string parameterName,
ParameterInfo parameter,
IBinderMetadata binderMetadata)
ParameterInfo parameter)
{
var parameterInfo =
CreateParameterInfo(parameter.ParameterType,
parameter.GetCustomAttributes(),
parameterName,
binderMetadata);
ModelAttributes.GetAttributesForParameter(parameter),
parameterName);
var metadata = CreateMetadataFromPrototype(parameterInfo.Prototype, modelAccessor);
// If there is no metadata associated with the parameter itself get it from the type.
if (metadata != null && metadata.BinderMetadata == null)
{
var typeInfo = GetTypeInformation(parameter.ParameterType);
metadata.BinderMetadata = typeInfo.Prototype.BinderMetadata;
}
return metadata;
}
@ -125,18 +119,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
metadata.IsReadOnly = true;
}
// We need to update the property after the prototype creation because otherwise
// if the property type is same as the containing type, it would cause infinite recursion.
// If there is no metadata associated with the property itself get it from the type.
if (metadata != null && metadata.BinderMetadata == null)
{
if (propertyInfo.Prototype != null)
{
var typeInfo = GetTypeInformation(propertyInfo.Prototype.ModelType);
metadata.BinderMetadata = typeInfo.Prototype.BinderMetadata;
}
}
return metadata;
}
@ -189,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return new PropertyInformation
{
PropertyHelper = helper,
Prototype = CreateMetadataPrototype(property.GetCustomAttributes(),
Prototype = CreateMetadataPrototype(ModelAttributes.GetAttributesForProperty(property),
containerType,
property.PropertyType,
property.Name),
@ -199,26 +181,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private ParameterInformation CreateParameterInfo(
Type parameterType,
IEnumerable<Attribute> attributes,
string parameterName,
IBinderMetadata binderMetadata)
IEnumerable<object> attributes,
string parameterName)
{
var metadataProtoType = CreateMetadataPrototype(attributes: attributes,
containerType: null,
modelType: parameterType,
propertyName: parameterName);
if (binderMetadata != null)
{
metadataProtoType.BinderMetadata = binderMetadata;
}
var nameProvider = binderMetadata as IModelNameProvider;
if (nameProvider != null && nameProvider.Name != null)
{
metadataProtoType.ModelName = nameProvider.Name;
}
return new ParameterInformation
{
Prototype = metadataProtoType

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class CachedDataAnnotationsMetadataAttributes
{
public CachedDataAnnotationsMetadataAttributes(IEnumerable<Attribute> attributes)
public CachedDataAnnotationsMetadataAttributes(IEnumerable<object> attributes)
{
DataType = attributes.OfType<DataTypeAttribute>().FirstOrDefault();
Display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
@ -20,6 +20,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
HiddenInput = attributes.OfType<HiddenInputAttribute>().FirstOrDefault();
Required = attributes.OfType<RequiredAttribute>().FirstOrDefault();
ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
PropertyBindingInfo = attributes.OfType<IPropertyBindingInfo>();
BinderModelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
// Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is
// non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType]
@ -32,9 +35,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <summary>
/// Gets (or sets in subclasses) <see cref="IBinderMetadata"/> found in collection passed to the
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
/// </summary>
public IBinderMetadata BinderMetadata { get; protected set; }
/// <summary>
/// Gets (or sets in subclasses) <see cref="IModelNameProvider"/> found in collection passed to the
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
/// </summary>
public IModelNameProvider BinderModelNameProvider { get; protected set; }
/// <summary>
/// Gets (or sets in subclasses) <see cref="DataTypeAttribute"/> found in collection passed to the
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{Attribute})"/> constructor, if any.
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
/// </summary>
public DataTypeAttribute DataType { get; protected set; }
@ -44,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// Gets (or sets in subclasses) <see cref="DisplayFormatAttribute"/> found in collection passed to the
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{Attribute})"/> constructor, if any.
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
/// If no such attribute was found but a <see cref="DataTypeAttribute"/> was, gets the
/// <see cref="DataTypeAttribute.DisplayFormat"/> value.
/// </summary>
@ -54,10 +69,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// Gets (or sets in subclasses) <see cref="HiddenInputAttribute"/> found in collection passed to the
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{Attribute})"/> constructor, if any.
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
/// </summary>
public HiddenInputAttribute HiddenInput { get; protected set; }
/// <summary>
/// Gets (or sets in subclasses) <see cref="IEnumerable{IModelPropertyBindingInfo}"/> found in collection
/// passed to the <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor,
/// if any.
/// </summary>
public IEnumerable<IPropertyBindingInfo> PropertyBindingInfo { get; protected set; }
public RequiredAttribute Required { get; protected set; }
public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; }

View File

@ -26,20 +26,79 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Type containerType,
Type modelType,
string propertyName,
IEnumerable<Attribute> attributes)
IEnumerable<object> attributes)
: base(provider,
containerType,
modelType,
propertyName,
new CachedDataAnnotationsMetadataAttributes(attributes))
{
BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
}
var modelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
ModelName = modelNameProvider?.Name;
protected override IBinderMetadata ComputeBinderMetadata()
{
return PrototypeCache.BinderMetadata != null
? PrototypeCache.BinderMetadata
: base.ComputeBinderMetadata();
}
var bindAttribute = attributes.OfType<BindAttribute>().FirstOrDefault();
ReadSettingsFromBindAttribute(bindAttribute);
protected override string ComputeBinderModelNamePrefix()
{
return PrototypeCache.BinderModelNameProvider != null
? PrototypeCache.BinderModelNameProvider.Name
: base.ComputeBinderModelNamePrefix();
}
protected override IReadOnlyList<string> ComputeBinderIncludeProperties()
{
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();
}
protected override bool ComputeConvertEmptyStringToNull()
@ -291,17 +350,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
private void ReadSettingsFromBindAttribute(BindAttribute bindAttribute)
{
if (bindAttribute == null)
{
return;
}
ExcludedProperties = SplitString(bindAttribute.Exclude).ToList();
IncludedProperties = SplitString(bindAttribute.Include).ToList();
}
private static IEnumerable<string> SplitString(string original)
{
if (string.IsNullOrEmpty(original))

View File

@ -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;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -30,6 +31,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private bool _isRequired;
private bool _showForDisplay;
private bool _showForEdit;
private IBinderMetadata _binderMetadata;
private string _binderModelName;
private IReadOnlyList<string> _binderIncludeProperties;
private IReadOnlyList<string> _binderExcludeProperties;
private bool _convertEmptyStringToNullComputed;
private bool _nullDisplayTextComputed;
@ -46,6 +51,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private bool _isRequiredComputed;
private bool _showForDisplayComputed;
private bool _showForEditComputed;
private bool _isBinderMetadataComputed;
private bool _isBinderIncludePropertiesComputed;
private bool _isBinderModelNameComputed;
private bool _isBinderExcludePropertiesComputed;
// Constructor for creating real instances of the metadata class based on a prototype
protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor)
@ -57,10 +66,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
CacheKey = prototype.CacheKey;
PrototypeCache = prototype.PrototypeCache;
BinderMetadata = prototype.BinderMetadata;
IncludedProperties = prototype.IncludedProperties;
ExcludedProperties = prototype.ExcludedProperties;
ModelName = prototype.ModelName;
_isComplexType = prototype.IsComplexType;
_isComplexTypeComputed = true;
}
@ -76,6 +81,91 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
PrototypeCache = prototypeCache;
}
/// <inheritdoc />
public sealed override IBinderMetadata BinderMetadata
{
get
{
if (!_isBinderMetadataComputed)
{
_binderMetadata = ComputeBinderMetadata();
_isBinderMetadataComputed = true;
}
return _binderMetadata;
}
set
{
_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
{
get
{
if (!_isBinderModelNameComputed)
{
_binderModelName = ComputeBinderModelNamePrefix();
_isBinderModelNameComputed = true;
}
return _binderModelName;
}
set
{
_binderModelName = value;
_isBinderModelNameComputed = true;
}
}
/// <inheritdoc />
public sealed override bool ConvertEmptyStringToNull
{
get
@ -94,24 +184,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
public sealed override string NullDisplayText
{
get
{
if (!_nullDisplayTextComputed)
{
_nullDisplayText = ComputeNullDisplayText();
_nullDisplayTextComputed = true;
}
return _nullDisplayText;
}
set
{
_nullDisplayText = value;
_nullDisplayTextComputed = true;
}
}
/// <inheritdoc />
public sealed override string DataTypeName
{
@ -133,6 +205,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override string Description
{
get
@ -172,6 +245,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override string DisplayName
{
get
@ -275,6 +349,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override bool IsReadOnly
{
get
@ -293,6 +368,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override bool IsRequired
{
get
@ -311,6 +387,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override bool IsComplexType
{
get
@ -324,6 +401,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override string NullDisplayText
{
get
{
if (!_nullDisplayTextComputed)
{
_nullDisplayText = ComputeNullDisplayText();
_nullDisplayTextComputed = true;
}
return _nullDisplayText;
}
set
{
_nullDisplayText = value;
_nullDisplayTextComputed = true;
}
}
/// <inheritdoc />
public sealed override bool ShowForDisplay
{
get
@ -342,6 +439,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override bool ShowForEdit
{
get
@ -377,16 +475,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
protected TPrototypeCache PrototypeCache { get; set; }
protected virtual IBinderMetadata ComputeBinderMetadata()
{
return base.BinderMetadata;
}
protected virtual IReadOnlyList<string> ComputeBinderIncludeProperties()
{
return base.BinderIncludeProperties;
}
protected virtual IReadOnlyList<string> ComputeBinderExcludeProperties()
{
return base.BinderExcludeProperties;
}
protected virtual string ComputeBinderModelNamePrefix()
{
return base.BinderModelName;
}
protected virtual bool ComputeConvertEmptyStringToNull()
{
return base.ConvertEmptyStringToNull;
}
protected virtual string ComputeNullDisplayText()
{
return base.NullDisplayText;
}
/// <summary>
/// Calculate the <see cref="DataTypeName"/> value.
/// </summary>
@ -466,6 +579,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return base.IsComplexType;
}
protected virtual string ComputeNullDisplayText()
{
return base.NullDisplayText;
}
protected virtual bool ComputeShowForDisplay()
{
return base.ShowForDisplay;

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
{
protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(
IEnumerable<Attribute> attributes,
IEnumerable<object> attributes,
Type containerType,
Type modelType,
string propertyName)

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class EmptyModelMetadataProvider : AssociatedMetadataProvider<ModelMetadata>
{
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes,
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<object> attributes,
Type containerType,
Type modelType,
string propertyName)

View File

@ -18,7 +18,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelMetadata GetMetadataForParameter(
Func<object> modelAccessor,
[NotNull] MethodInfo methodInfo,
[NotNull] string parameterName,
IBinderMetadata binderMetadata);
[NotNull] string parameterName);
}
}

View File

@ -0,0 +1,48 @@
// 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.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// Provides static methods which can be used to get a combined list of attributes associated
/// with a parameter or property.
/// </summary>
public static class ModelAttributes
{
/// <summary>
/// Gets the attributes for the given <paramref name="parameter"/>.
/// </summary>
/// <param name="parameter">A <see cref="ParameterInfo"/> for which attributes need to be resolved.
/// </param>
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
/// <paramref name="parameter"/> before the attributes on the <paramref name="parameter"/> type.</returns>
public static IEnumerable<object> GetAttributesForParameter(ParameterInfo parameter)
{
// Return the parameter attributes first.
var parameterAttributes = parameter.GetCustomAttributes();
var typeAttributes = parameter.ParameterType.GetTypeInfo().GetCustomAttributes();
return parameterAttributes.Concat(typeAttributes);
}
/// <summary>
/// Gets the attributes for the given <paramref name="property"/>.
/// </summary>
/// <param name="property">A <see cref="ParameterInfo"/> for which attributes need to be resolved.
/// </param>
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
/// <paramref name="property"/> before the attributes on the <paramref name="property"/> type.</returns>
public static IEnumerable<object> GetAttributesForProperty(PropertyInfo property)
{
// Return the property attributes first.
var propertyAttributes = property.GetCustomAttributes();
var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes();
return propertyAttributes.Concat(typeAttributes);
}
}
}

View File

@ -50,22 +50,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// Represents the name of a model if specified explicitly using <see cref="IModelNameProvider"/>.
/// </summary>
public string ModelName { get; set; }
public virtual string BinderModelName { get; set; }
/// <summary>
/// Properties which are marked as Included for this model.
/// Properties which are to be included while binding this model.
/// </summary>
public IReadOnlyList<string> IncludedProperties { get; set; }
public virtual IReadOnlyList<string> BinderIncludeProperties { get; set; }
/// <summary>
/// Properties which are marked as Excluded for this model.
/// Properties which are to be excluded while binding this model.
/// </summary>
public IReadOnlyList<string> ExcludedProperties { get; set; }
public virtual IReadOnlyList<string> BinderExcludeProperties { get; set; }
/// <summary>
/// Gets or sets a binder metadata for this model.
/// </summary>
public IBinderMetadata BinderMetadata { get; set; }
public virtual IBinderMetadata BinderMetadata { get; set; }
/// <summary>
/// A reference to the model's container <see cref="object"/>.

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
@ -23,30 +24,115 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
}
public void ParameterHasFieldPrefix([Bind(Prefix = "bar")] string foo)
public void ParameterWithNoBindAttribute(MySimpleModelWithTypeBasedBind parameter)
{
}
public void ParameterHasEmptyFieldPrefix([Bind(Prefix = "")] MySimpleModel foo,
[Bind(Prefix = "")] MySimpleModelWithTypeBasedBind foo1)
public void ParameterHasFieldPrefix([Bind(Prefix = "simpleModelPrefix")] string parameter)
{
}
public void ParameterHasPrefixAndComplexType([Bind(Prefix = "bar")] MySimpleModel foo,
[Bind(Prefix = "bar")] MySimpleModelWithTypeBasedBind foo1)
public void ParameterHasEmptyFieldPrefix([Bind(Prefix = "")] MySimpleModel parameter,
[Bind(Prefix = "")] MySimpleModelWithTypeBasedBind parameter1)
{
}
public void ParameterHasEmptyBindAttribute([Bind] MySimpleModel foo,
[Bind] MySimpleModelWithTypeBasedBind foo1)
public void ParameterHasPrefixAndComplexType(
[Bind(Prefix = "simpleModelPrefix")] MySimpleModel parameter,
[Bind(Prefix = "simpleModelPrefix")] MySimpleModelWithTypeBasedBind parameter1)
{
}
public void ParameterHasEmptyBindAttribute([Bind] MySimpleModel parameter,
[Bind] MySimpleModelWithTypeBasedBind parameter1)
{
}
[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("Excluded1"));
Assert.False(context.PropertyFilter("Excluded2"));
}
[Fact]
public void GetModelBindingContext_ReturnsOnlyWhiteListedProperties_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(TypeWithIncludedPropertiesUsingBindAttribute));
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.True(context.PropertyFilter("IncludedExplicitly1"));
Assert.True(context.PropertyFilter("IncludedExplicitly2"));
}
[Fact]
public void GetModelBindingContext_UsesBindAttributeOnType_IfNoBindAttributeOnParameter_ForPrefix()
{
// Arrange
var type = typeof(ControllerActionArgumentBinderTests);
var methodInfo = type.GetMethod("ParameterWithNoBindAttribute");
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
Mock.Of<ActionDescriptor>());
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
methodInfo: methodInfo,
parameterName: "parameter");
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.Equal("TypePrefix", context.ModelName);
}
[Theory]
[InlineData("ParameterHasFieldPrefix", false, "bar")]
[InlineData("ParameterHasFieldPrefix", false, "simpleModelPrefix")]
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
[InlineData("ParameterHasPrefixAndComplexType", false, "bar")]
[InlineData("ParameterHasEmptyBindAttribute", true, "foo")]
[InlineData("ParameterHasPrefixAndComplexType", false, "simpleModelPrefix")]
[InlineData("ParameterHasEmptyBindAttribute", true, "parameter")]
public void GetModelBindingContext_ModelBindingContextIsSetWithModelName_ForParameters(
string actionMethodName, bool expectedFallToEmptyPrefix, string expectedModelName)
{
@ -59,8 +145,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
methodInfo: methodInfo,
parameterName: "foo",
binderMetadata: null);
parameterName: "parameter");
var actionBindingContext = new ActionBindingContext(actionContext,
@ -70,8 +155,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
// Act
var context = DefaultControllerActionArgumentBinder
.GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
// Assert
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
@ -80,8 +165,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
[Theory]
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
[InlineData("ParameterHasPrefixAndComplexType", false, "bar")]
[InlineData("ParameterHasEmptyBindAttribute", true, "foo1")]
[InlineData("ParameterHasPrefixAndComplexType", false, "simpleModelPrefix")]
[InlineData("ParameterHasEmptyBindAttribute", true, "parameter1")]
public void GetModelBindingContext_ModelBindingContextIsNotSet_ForTypes(
string actionMethodName, bool expectedFallToEmptyPrefix, string expectedModelName)
{
@ -94,8 +179,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
methodInfo: methodInfo,
parameterName: "foo1",
binderMetadata: null);
parameterName: "parameter1");
var actionBindingContext = new ActionBindingContext(actionContext,
@ -105,8 +189,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
// Act
var context = DefaultControllerActionArgumentBinder
.GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
// Assert
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
@ -233,7 +317,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test
}
}
private class NonValueProviderBinderMetadataAttribute : Attribute, IBinderMetadata
{
}
@ -241,5 +324,29 @@ namespace Microsoft.AspNet.Mvc.Core.Test
private class ValueProviderMetadataAttribute : Attribute, IValueProviderMetadata
{
}
[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))]
private class TypeWithIncludedPropertiesUsingBindAttribute
{
public int ExcludedByDefault1 { get; set; }
public int ExcludedByDefault2 { get; set; }
public int IncludedExplicitly1 { get; set; }
public int IncludedExplicitly2 { get; set; }
}
}
}

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
};
bindingContext.ModelBindingContext.ModelMetadata.ModelName = isPrefixProvided ? "prefix" : null;
bindingContext.ModelBindingContext.ModelMetadata.BinderModelName = isPrefixProvided ? "prefix" : null;
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext);
@ -603,79 +603,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
}
[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; }
}
[Fact]
public void GetMetadataForProperties_DoesNotReturn_ExcludedProperties()
{
// Arrange
var expectedPropertyNames = new[] { "IncludedByDefault1", "IncludedByDefault2" };
var bindingContext = new ModelBindingContext
{
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
OperationBindingContext = new OperationBindingContext
{
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);
}
[Bind(Include = 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; }
}
[Fact]
public void GetMetadataForProperties_ReturnsOnlyWhiteListedProperties_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()
}
};
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 GetRequiredPropertiesCollection_MixedAttributes()
{
@ -1274,8 +1201,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return metadataProvider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: methodInfo,
parameterName: parameterName,
binderMetadata: null);
parameterName: parameterName);
}
private class EmptyModel

View File

@ -96,49 +96,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
[Fact]
public void GetMetadataForProperty_WithNoBinderMetadata_GetsItFromType()
public void GetMetadataForParameterNullOrEmptyPropertyNameThrows()
{
// Arrange
var provider = new DataAnnotationsModelMetadataProvider();
var provider = new TestableAssociatedMetadataProvider();
// Act
var propertyMetadata = provider.GetMetadataForProperty(null, typeof(Person), nameof(Person.Parent));
// Assert
Assert.NotNull(propertyMetadata.BinderMetadata);
Assert.IsType<TestBinderMetadataAttribute>(propertyMetadata.BinderMetadata);
}
#if ASPNET50
[Fact]
public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType()
{
// Arrange
var provider = new DataAnnotationsModelMetadataProvider();
// Act
var parameterMetadata = provider.GetMetadataForParameter(null,
typeof(Person).GetMethod("Update"),
"person",
null);
// Assert
Assert.NotNull(parameterMetadata.BinderMetadata);
Assert.IsType<TestBinderMetadataAttribute>(parameterMetadata.BinderMetadata);
}
#endif
public class TestBinderMetadataAttribute : Attribute, IBinderMetadata
{
}
[TestBinderMetadata]
public class Person
{
public Person Parent { get; set; }
public void Update(Person person)
{
}
// Act & Assert
ExceptionAssert.ThrowsArgumentNullOrEmpty(
() => provider.GetMetadataForParameter(modelAccessor: null, methodInfo: null, parameterName: null),
"parameterName");
ExceptionAssert.ThrowsArgumentNullOrEmpty(
() => provider.GetMetadataForParameter(modelAccessor: null, methodInfo: null, parameterName: null),
"parameterName");
}
// GetMetadataForProperty
@ -310,7 +279,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public ModelMetadata CreateMetadataPrototypeReturnValue = null;
public ModelMetadata CreateMetadataFromPrototypeReturnValue = null;
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<object> attributes, Type containerType, Type modelType, string propertyName)
{
CreateMetadataPrototypeLog.Add(new CreateMetadataPrototypeParams
{
@ -337,7 +306,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private class CreateMetadataPrototypeParams
{
public IEnumerable<Attribute> Attributes { get; set; }
public IEnumerable<object> Attributes { get; set; }
public Type ContainerType { get; set; }
public Type ModelType { get; set; }
public string PropertyName { get; set; }

View File

@ -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 Xunit;
@ -14,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class CachedDataAnnotationsMetadataAttributesTest
{
[Fact]
public void Constructor_DefaultsAllPropertiesToNull()
public void Constructor_SetsDefaultValuesForAllProperties()
{
// Arrange
var attributes = Enumerable.Empty<Attribute>();
@ -31,14 +32,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(cache.HiddenInput);
Assert.Null(cache.Required);
Assert.Null(cache.ScaffoldColumn);
Assert.Null(cache.BinderMetadata);
Assert.Null(cache.BinderModelNameProvider);
Assert.Empty(cache.PropertyBindingInfo);
}
public static TheoryData<Attribute, Func<CachedDataAnnotationsMetadataAttributes, Attribute>>
public static TheoryData<object, Func<CachedDataAnnotationsMetadataAttributes, object>>
ExpectedAttributeData
{
get
{
return new TheoryData<Attribute, Func<CachedDataAnnotationsMetadataAttributes, Attribute>>
return new TheoryData<object, Func<CachedDataAnnotationsMetadataAttributes, object>>
{
{ new DataTypeAttribute(DataType.Duration), cache => cache.DataType },
{ new DisplayAttribute(), cache => cache.Display },
@ -47,7 +51,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ new EditableAttribute(allowEdit: false), cache => cache.Editable },
{ new HiddenInputAttribute(), cache => cache.HiddenInput },
{ new RequiredAttribute(), cache => cache.Required },
{ new ScaffoldColumnAttribute(scaffold: false), cache => cache.ScaffoldColumn },
{ new TestBinderMetadata(), cache => cache.BinderMetadata },
{ new TestModelNameProvider(), cache => cache.BinderModelNameProvider },
};
}
}
@ -55,8 +60,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Theory]
[MemberData(nameof(ExpectedAttributeData))]
public void Constructor_FindsExpectedAttribute(
Attribute attribute,
Func<CachedDataAnnotationsMetadataAttributes, Attribute> accessor)
object attribute,
Func<CachedDataAnnotationsMetadataAttributes, object> accessor)
{
// Arrange
var attributes = new[] { attribute };
@ -69,6 +74,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Same(attribute, result);
}
[Fact]
public void Constructor_FindsPropertyBindingInfo()
{
// Arrange
var propertyBindingInfos =
new[] { new TestPropertyBindingInfo(), new TestPropertyBindingInfo() };
// Act
var cache = new CachedDataAnnotationsMetadataAttributes(propertyBindingInfos);
var result = cache.PropertyBindingInfo.ToArray();
// Assert
for (var index = 0; index < propertyBindingInfos.Length; index++)
{
Assert.Same(propertyBindingInfos[index], result[index]);
}
}
[Fact]
public void Constructor_FindsDisplayFormat_FromDataType()
{

View File

@ -1,6 +1,7 @@
// 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.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
@ -45,12 +46,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadata = provider.GetMetadataForType(null, type);
// Assert
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.IncludedProperties);
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.ExcludedProperties);
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties);
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties);
}
[Fact]
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_OnlyAtParameterLevel_ForParameters()
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_AtParameterAndType_ForParameters()
{
// Arrange
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
@ -58,20 +59,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var provider = new DataAnnotationsModelMetadataProvider();
// Note it does an intersection for included and a union for excluded.
var expectedIncludedPropertyNames = new[] { "Property1", "Property2", "IncludedAndExcludedExplicitly1" };
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1" };
var expectedExcludedPropertyNames = new[] {
"Property3", "Property4", "IncludedAndExcludedExplicitly1" };
"Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
// Act
var metadata = provider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: methodInfo,
parameterName: "param",
binderMetadata: null);
parameterName: "param");
// Assert
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.IncludedProperties);
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.ExcludedProperties);
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties);
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties);
}
[Fact]
@ -86,11 +86,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadata = provider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: methodInfo,
parameterName: "param",
binderMetadata: null);
parameterName: "param");
// Assert
Assert.Equal("ParameterPrefix", metadata.ModelName);
Assert.Equal("ParameterPrefix", metadata.BinderModelName);
}
[Fact]
@ -104,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadata = provider.GetMetadataForType(null, type);
// Assert
Assert.Equal("TypePrefix", metadata.ModelName);
Assert.Equal("TypePrefix", metadata.BinderModelName);
}
[Fact]
@ -119,11 +118,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadata = provider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: methodInfo,
parameterName: "param",
binderMetadata: null);
parameterName: "param");
// Assert
Assert.Equal("ParameterPrefix", metadata.ModelName);
Assert.Equal("ParameterPrefix", metadata.BinderModelName);
}
[Fact]
@ -167,8 +165,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(result);
}
// TODO https://github.com/aspnet/Mvc/issues/1000
// Enable test once we detect attributes on the property's type
[Fact]
public void HiddenInputWorksOnPropertyType()
{
// Arrange
@ -183,6 +180,119 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(result);
}
[Fact]
public void GetMetadataForProperty_WithNoBinderMetadata_GetsItFromType()
{
// Arrange
var provider = new DataAnnotationsModelMetadataProvider();
// Act
var propertyMetadata = provider.GetMetadataForProperty(null, typeof(Person), nameof(Person.Parent));
// Assert
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]
public void GetMetadataForProperty_WithBinderMetadataOnPropertyAndType_GetsMetadataFromProperty()
{
// Arrange
var provider = new DataAnnotationsModelMetadataProvider();
// Act
var propertyMetadata = provider.GetMetadataForProperty(null, typeof(Person), nameof(Person.GrandParent));
// Assert
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()
{
// Arrange
var provider = new DataAnnotationsModelMetadataProvider();
// Act
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]
public void GetMetadataForParameter_WithBinderDataOnParameterAndType_GetsMetadataFromParameter()
{
// Arrange
var provider = new DataAnnotationsModelMetadataProvider();
// Act
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
{
public string Name { get; set; }
public string Exclude { get; set; }
public string Include { get; set; }
}
public class NonTypeBasedBinderAttribute : Attribute,
IBinderMetadata, IModelNameProvider, IPropertyBindingInfo
{
public string Name { get; set; }
public string Exclude { get; set; }
public string Include { get; set; }
}
[TypeBasedBinder(Name = "PersonType", Include = "IncludeAtType", Exclude = "ExcludeAtType")]
public class Person
{
public Person Parent { get; set; }
[NonTypeBasedBinder(Name = "GrandParentProperty", Include = "IncludeAtProperty", Exclude = "ExcludeAtProperty")]
public Person GrandParent { get; set; }
public void Update(Person person)
{
}
public void Save([NonTypeBasedBinder(Name = "PersonParameter",
Include = "IncludeAtParameter", Exclude = "ExcludeAtParameter")] Person person)
{
}
}
private class ScaffoldColumnModel
{
public int NoAttribute { get; set; }

View File

@ -49,13 +49,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(metadata.TemplateHint);
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);
Assert.Null(metadata.BinderModelName);
Assert.Null(metadata.BinderMetadata);
Assert.Empty(metadata.BinderIncludeProperties);
Assert.Null(metadata.BinderExcludeProperties);
}
public static TheoryData<Attribute, Func<ModelMetadata, string>> ExpectedAttributeDataStrings
public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeDataStrings
{
get
{
return new TheoryData<Attribute, Func<ModelMetadata, string>>
return new TheoryData<object, Func<ModelMetadata, string>>
{
{
new DataTypeAttribute("value"), metadata => metadata.DataTypeName
@ -91,13 +96,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
new DisplayFormatAttribute { NullDisplayText = "value" }, metadata => metadata.NullDisplayText
},
{
new TestModelNameProvider() { Name = "value" }, metadata => metadata.BinderModelName
},
};
}
}
[Theory]
[MemberData(nameof(ExpectedAttributeDataStrings))]
public void AttributesOverrideMetadataStrings(Attribute attribute, Func<ModelMetadata, string> accessor)
public void AttributesOverrideMetadataStrings(object attribute, Func<ModelMetadata, string> accessor)
{
// Arrange
var attributes = new[] { attribute };
@ -221,6 +229,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(expectedResult, result);
}
[Fact]
public void BinderMetadataIfPresent_Overrides_DefaultBinderMetadata()
{
// Arrange
var firstBinderMetadata = new TestBinderMetadata();
var secondBinderMetadata = new TestBinderMetadata();
var provider = new DataAnnotationsModelMetadataProvider();
var metadata = new CachedDataAnnotationsModelMetadata(
provider,
containerType: null,
modelType: typeof(object),
propertyName: null,
attributes: new object[] { firstBinderMetadata, secondBinderMetadata });
// Act
var result = metadata.BinderMetadata;
// Assert
Assert.Same(firstBinderMetadata, result);
}
[Fact]
public void DataTypeName_Null_IfHtmlEncodeTrue()
{

View File

@ -21,6 +21,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var contactModel = new DummyContactModel { FirstName = "test" };
var nonEmptycontainerModel = new DummyModelContainer { Model = contactModel };
var binderMetadata = new TestBinderMetadata();
var emptyPropertyList = new List<string>();
var nonEmptyPropertyList = new List<string>() { "SomeProperty" };
return new TheoryData<Action<ModelMetadata>, Func<ModelMetadata, object>, object>
{
{ m => m.ConvertEmptyStringToNull = false, m => m.ConvertEmptyStringToNull, false },
@ -45,21 +48,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ m => m.Container = null, m => m.Container, null },
{ m => m.Container = emptycontainerModel, m => m.Container, emptycontainerModel },
{ m => m.Container = nonEmptycontainerModel, m => m.Container, nonEmptycontainerModel },
{ m => m.BinderMetadata = null, m => m.BinderMetadata, null },
{ m => m.BinderMetadata = binderMetadata, m => m.BinderMetadata, binderMetadata },
{ m => m.BinderModelName = null, m => m.BinderModelName, null },
{ m => m.BinderModelName = "newModelName", m => m.BinderModelName, "newModelName" },
{ m => m.BinderModelName = string.Empty, m => m.BinderModelName, string.Empty },
{ 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
},
};
}
}
#if ASPNET50
// Constructor
[Fact]
public void DefaultValues()
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var provider = new EmptyModelMetadataProvider();
// Act
var metadata = new ModelMetadata(provider.Object, typeof(Exception), () => "model", typeof(string), "propertyName");
var metadata =
new ModelMetadata(provider, typeof(Exception), () => "model", typeof(string), "propertyName");
// Assert
Assert.Equal(typeof(Exception), metadata.ContainerType);
@ -91,8 +122,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal("propertyName", metadata.PropertyName);
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);
Assert.Null(metadata.BinderModelName);
Assert.Null(metadata.BinderMetadata);
Assert.Null(metadata.BinderIncludeProperties);
Assert.Null(metadata.BinderExcludeProperties);
}
#endif
// IsComplexType
@ -100,7 +135,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
}
#if ASPNET50
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(Nullable<int>))]
@ -108,10 +142,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void IsComplexTypeTestsReturnsFalseForSimpleTypes(Type type)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var provider = new EmptyModelMetadataProvider();
// Act
var modelMetadata = new ModelMetadata(provider.Object, null, null, type, null);
var modelMetadata = new ModelMetadata(provider, null, null, type, null);
// Assert
Assert.False(modelMetadata.IsComplexType);
@ -125,10 +159,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void IsComplexTypeTestsReturnsTrueForComplexTypes(Type type)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var provider = new EmptyModelMetadataProvider();
// Act
var modelMetadata = new ModelMetadata(provider.Object, null, null, type, null);
var modelMetadata = new ModelMetadata(provider, null, null, type, null);
// Assert
Assert.True(modelMetadata.IsComplexType);
@ -140,13 +174,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void IsNullableValueTypeTests()
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var provider = new EmptyModelMetadataProvider();
// Act & Assert
Assert.False(new ModelMetadata(provider.Object, null, null, typeof(string), null).IsNullableValueType);
Assert.False(new ModelMetadata(provider.Object, null, null, typeof(IDisposable), null).IsNullableValueType);
Assert.True(new ModelMetadata(provider.Object, null, null, typeof(Nullable<int>), null).IsNullableValueType);
Assert.False(new ModelMetadata(provider.Object, null, null, typeof(int), null).IsNullableValueType);
Assert.False(new ModelMetadata(provider, null, null, typeof(string), null).IsNullableValueType);
Assert.False(new ModelMetadata(provider, null, null, typeof(IDisposable), null).IsNullableValueType);
Assert.True(new ModelMetadata(provider, null, null, typeof(Nullable<int>), null).IsNullableValueType);
Assert.False(new ModelMetadata(provider, null, null, typeof(int), null).IsNullableValueType);
}
// IsRequired
@ -158,8 +192,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void IsRequired_ReturnsFalse_ForNullableTypes(Type modelType)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var metadata = new ModelMetadata(provider.Object,
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(provider,
containerType: null,
modelAccessor: null,
modelType: modelType,
@ -178,8 +212,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void IsRequired_ReturnsTrue_ForNonNullableTypes(Type modelType)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var metadata = new ModelMetadata(provider.Object,
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(provider,
containerType: null,
modelAccessor: null,
modelType: modelType,
@ -193,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
// Properties
#if ASPNET50
[Fact]
public void PropertiesCallsProvider()
{
@ -215,6 +249,61 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
#endif
[Theory]
[MemberData(nameof(MetadataModifierData))]
public void PropertiesPropertyChangesPersist(
Action<ModelMetadata> setter,
Func<ModelMetadata, object> getter,
object expected)
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(
provider,
containerType: null,
modelAccessor: () => new Class1(),
modelType: typeof(Class1),
propertyName: null);
// Act
foreach (var property in metadata.Properties)
{
setter(property);
}
// Assert
foreach (var property in metadata.Properties)
{
// Due to boxing of structs, can't Assert.Same().
Assert.Equal(expected, getter(property));
}
}
[Theory]
[MemberData(nameof(MetadataModifierData))]
public void PropertyChangesPersist(
Action<ModelMetadata> setter,
Func<ModelMetadata, object> getter,
object expected)
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(
provider,
containerType: null,
modelAccessor: () => new Class1(),
modelType: typeof(Class1),
propertyName: null);
// Act
setter(metadata);
var result = getter(metadata);
// Assert
// Due to boxing of structs, can't Assert.Same().
Assert.Equal(expected, result);
}
[Fact]
public void PropertiesListGetsResetWhenModelGetsReset()
{
@ -278,36 +367,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(firstPropertiesEvaluation, secondPropertiesEvaluation);
}
[Theory]
[MemberData(nameof(MetadataModifierData))]
public void PropertiesPropertyChangesPersist(
Action<ModelMetadata> setter,
Func<ModelMetadata, object> getter,
object expected)
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(
provider,
containerType: null,
modelAccessor: () => new Class1(),
modelType: typeof(Class1),
propertyName: null);
// Act
foreach (var property in metadata.Properties)
{
setter(property);
}
// Assert
foreach (var property in metadata.Properties)
{
// Due to boxing of structs, can't Assert.Same().
Assert.Equal(expected, getter(property));
}
}
private class Class1
{
public string Prop1 { get; set; }
@ -341,13 +400,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal("displayName", result);
}
#if ASPNET50
[Fact]
public void ReturnsPropertyNameWhenSetAndDisplayNameIsNull()
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var metadata = new ModelMetadata(provider.Object, null, null, typeof(object), "PropertyName");
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(provider, null, null, typeof(object), "PropertyName");
// Act
var result = metadata.GetDisplayName();
@ -360,8 +418,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ReturnsTypeNameWhenPropertyNameAndDisplayNameAreNull()
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var metadata = new ModelMetadata(provider.Object, null, null, typeof(object), null);
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(provider, null, null, typeof(object), null);
// Act
var result = metadata.GetDisplayName();
@ -369,7 +427,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
Assert.Equal("Object", result);
}
#endif
// SimpleDisplayText
@ -435,33 +492,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public Class1 Prop1 { get; set; }
}
[Theory]
[MemberData(nameof(MetadataModifierData))]
public void PropertyChangesPersist(
Action<ModelMetadata> setter,
Func<ModelMetadata, object> getter,
object expected)
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var metadata = new ModelMetadata(
provider,
containerType: null,
modelAccessor: () => new Class1(),
modelType: typeof(Class1),
propertyName: null);
// Act
setter(metadata);
var result = getter(metadata);
// Assert
// Due to boxing of structs, can't Assert.Same().
Assert.Equal(expected, result);
}
// Helpers
private class DummyContactModel
{
public int IntField = 0;

View File

@ -0,0 +1,9 @@
// 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
{
public class TestBinderMetadata : IBinderMetadata
{
}
}

View File

@ -0,0 +1,11 @@
// 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
{
public class TestModelNameProvider : IModelNameProvider
{
public string Name { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// 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
{
public class TestPropertyBindingInfo : IPropertyBindingInfo
{
public string Exclude { get; set; }
public string Include { get; set; }
}
}