diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs index 0b8771682d..111a13e380 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs @@ -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 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 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, }; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs index 101f683a53..b8fccfa509 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc /// This attribute can be used on action parameters and types, to indicate model level metadata. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] - public sealed class BindAttribute : Attribute, IModelNameProvider, IModelPropertyBindingInfo + public sealed class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingInfo { /// /// Comma separated set of properties which are to be excluded during model binding. @@ -45,10 +45,8 @@ namespace Microsoft.AspNet.Mvc IReadOnlyList 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); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index 5d405bf1c4..204582e3f8 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -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 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 GetMetadataForProperties(ModelBindingContext bindingContext) { var validationInfo = GetPropertyValidationInfo(bindingContext); - var propertyTypeMetadata = bindingContext.OperationBindingContext - .MetadataProvider - .GetMetadataForType(null, bindingContext.ModelType); - Predicate 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)); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/IModelPropertyBindingInfo.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/IPropertyBindingInfo.cs similarity index 93% rename from src/Microsoft.AspNet.Mvc.ModelBinding/IModelPropertyBindingInfo.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/IPropertyBindingInfo.cs index 349716a0ef..875af16631 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/IModelPropertyBindingInfo.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/IPropertyBindingInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc /// /// Represents an entity which has binding information for a model. /// - public interface IModelPropertyBindingInfo + public interface IPropertyBindingInfo { /// /// Comma separated set of properties which are to be excluded during model binding. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs index 5f6f89e0c7..c1ae24737a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs @@ -49,9 +49,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public ModelMetadata GetMetadataForParameter( Func 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 attributes, + protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable 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 modelAccessor); + private ModelMetadata GetMetadataForParameterCore(Func 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 attributes, - string parameterName, - IBinderMetadata binderMetadata) + IEnumerable 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 diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs index b41134a60f..8e6e003179 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public class CachedDataAnnotationsMetadataAttributes { - public CachedDataAnnotationsMetadataAttributes(IEnumerable attributes) + public CachedDataAnnotationsMetadataAttributes(IEnumerable attributes) { DataType = attributes.OfType().FirstOrDefault(); Display = attributes.OfType().FirstOrDefault(); @@ -20,6 +20,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding HiddenInput = attributes.OfType().FirstOrDefault(); Required = attributes.OfType().FirstOrDefault(); ScaffoldColumn = attributes.OfType().FirstOrDefault(); + BinderMetadata = attributes.OfType().FirstOrDefault(); + PropertyBindingInfo = attributes.OfType(); + BinderModelNameProvider = attributes.OfType().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 } } + /// + /// Gets (or sets in subclasses) found in collection passed to the + /// constructor, if any. + /// + public IBinderMetadata BinderMetadata { get; protected set; } + + /// + /// Gets (or sets in subclasses) found in collection passed to the + /// constructor, if any. + /// + public IModelNameProvider BinderModelNameProvider { get; protected set; } + /// /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. + /// constructor, if any. /// public DataTypeAttribute DataType { get; protected set; } @@ -44,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. + /// constructor, if any. /// If no such attribute was found but a was, gets the /// value. /// @@ -54,10 +69,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. + /// constructor, if any. /// public HiddenInputAttribute HiddenInput { get; protected set; } + /// + /// Gets (or sets in subclasses) found in collection + /// passed to the constructor, + /// if any. + /// + public IEnumerable PropertyBindingInfo { get; protected set; } + public RequiredAttribute Required { get; protected set; } public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs index 4736a5b4be..7095d76b7a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs @@ -26,20 +26,79 @@ namespace Microsoft.AspNet.Mvc.ModelBinding Type containerType, Type modelType, string propertyName, - IEnumerable attributes) + IEnumerable attributes) : base(provider, containerType, modelType, propertyName, new CachedDataAnnotationsMetadataAttributes(attributes)) { - BinderMetadata = attributes.OfType().FirstOrDefault(); + } - var modelNameProvider = attributes.OfType().FirstOrDefault(); - ModelName = modelNameProvider?.Name; + protected override IBinderMetadata ComputeBinderMetadata() + { + return PrototypeCache.BinderMetadata != null + ? PrototypeCache.BinderMetadata + : base.ComputeBinderMetadata(); + } - var bindAttribute = attributes.OfType().FirstOrDefault(); - ReadSettingsFromBindAttribute(bindAttribute); + protected override string ComputeBinderModelNamePrefix() + { + return PrototypeCache.BinderModelNameProvider != null + ? PrototypeCache.BinderModelNameProvider.Name + : base.ComputeBinderModelNamePrefix(); + } + + protected override IReadOnlyList 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 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 SplitString(string original) { if (string.IsNullOrEmpty(original)) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs index 257c278cb0..6be9adfef4 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs @@ -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 _binderIncludeProperties; + private IReadOnlyList _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 prototype, Func 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; } + /// + public sealed override IBinderMetadata BinderMetadata + { + get + { + if (!_isBinderMetadataComputed) + { + _binderMetadata = ComputeBinderMetadata(); + _isBinderMetadataComputed = true; + } + + return _binderMetadata; + } + + set + { + _binderMetadata = value; + _isBinderMetadataComputed = true; + } + } + + /// + public sealed override IReadOnlyList BinderIncludeProperties + { + get + { + if (!_isBinderIncludePropertiesComputed) + { + _binderIncludeProperties = ComputeBinderIncludeProperties(); + _isBinderIncludePropertiesComputed = true; + } + + return _binderIncludeProperties; + } + + set + { + _binderIncludeProperties = value; + _isBinderIncludePropertiesComputed = true; + } + } + + /// + public sealed override IReadOnlyList BinderExcludeProperties + { + get + { + if (!_isBinderExcludePropertiesComputed) + { + _binderExcludeProperties = ComputeBinderExcludeProperties(); + _isBinderExcludePropertiesComputed = true; + } + + return _binderExcludeProperties; + } + + set + { + _binderExcludeProperties = value; + _isBinderExcludePropertiesComputed = true; + } + } + + /// + public sealed override string BinderModelName + { + get + { + if (!_isBinderModelNameComputed) + { + _binderModelName = ComputeBinderModelNamePrefix(); + _isBinderModelNameComputed = true; + } + + return _binderModelName; + } + + set + { + _binderModelName = value; + _isBinderModelNameComputed = true; + } + } + + /// 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; - } - } - /// public sealed override string DataTypeName { @@ -133,6 +205,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// public sealed override string Description { get @@ -172,6 +245,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// public sealed override string DisplayName { get @@ -275,6 +349,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// public sealed override bool IsReadOnly { get @@ -293,6 +368,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// public sealed override bool IsRequired { get @@ -311,6 +387,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// public sealed override bool IsComplexType { get @@ -324,6 +401,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// + public sealed override string NullDisplayText + { + get + { + if (!_nullDisplayTextComputed) + { + _nullDisplayText = ComputeNullDisplayText(); + _nullDisplayTextComputed = true; + } + return _nullDisplayText; + } + set + { + _nullDisplayText = value; + _nullDisplayTextComputed = true; + } + } + + /// public sealed override bool ShowForDisplay { get @@ -342,6 +439,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// 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 ComputeBinderIncludeProperties() + { + return base.BinderIncludeProperties; + } + + protected virtual IReadOnlyList 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; - } - /// /// Calculate the value. /// @@ -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; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs index c92d29ece5..3a7acfcd85 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider { protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype( - IEnumerable attributes, + IEnumerable attributes, Type containerType, Type modelType, string propertyName) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs index 8d3ec200bc..eb9b989688 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public class EmptyModelMetadataProvider : AssociatedMetadataProvider { - protected override ModelMetadata CreateMetadataPrototype(IEnumerable attributes, + protected override ModelMetadata CreateMetadataPrototype(IEnumerable attributes, Type containerType, Type modelType, string propertyName) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs index 73f35ff87a..648e0a02b1 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs @@ -18,7 +18,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelMetadata GetMetadataForParameter( Func modelAccessor, [NotNull] MethodInfo methodInfo, - [NotNull] string parameterName, - IBinderMetadata binderMetadata); + [NotNull] string parameterName); } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelAttributes.cs new file mode 100644 index 0000000000..57f3d25f23 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelAttributes.cs @@ -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 +{ + /// + /// Provides static methods which can be used to get a combined list of attributes associated + /// with a parameter or property. + /// + public static class ModelAttributes + { + /// + /// Gets the attributes for the given . + /// + /// A for which attributes need to be resolved. + /// + /// An containing the attributes on the + /// before the attributes on the type. + public static IEnumerable GetAttributesForParameter(ParameterInfo parameter) + { + // Return the parameter attributes first. + var parameterAttributes = parameter.GetCustomAttributes(); + var typeAttributes = parameter.ParameterType.GetTypeInfo().GetCustomAttributes(); + + return parameterAttributes.Concat(typeAttributes); + } + + /// + /// Gets the attributes for the given . + /// + /// A for which attributes need to be resolved. + /// + /// An containing the attributes on the + /// before the attributes on the type. + public static IEnumerable GetAttributesForProperty(PropertyInfo property) + { + // Return the property attributes first. + var propertyAttributes = property.GetCustomAttributes(); + var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes(); + + return propertyAttributes.Concat(typeAttributes); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index b6174ea7ee..d551fdd477 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -50,22 +50,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// Represents the name of a model if specified explicitly using . /// - public string ModelName { get; set; } + public virtual string BinderModelName { get; set; } /// - /// Properties which are marked as Included for this model. + /// Properties which are to be included while binding this model. /// - public IReadOnlyList IncludedProperties { get; set; } + public virtual IReadOnlyList BinderIncludeProperties { get; set; } /// - /// Properties which are marked as Excluded for this model. + /// Properties which are to be excluded while binding this model. /// - public IReadOnlyList ExcludedProperties { get; set; } + public virtual IReadOnlyList BinderExcludeProperties { get; set; } /// /// Gets or sets a binder metadata for this model. /// - public IBinderMetadata BinderMetadata { get; set; } + public virtual IBinderMetadata BinderMetadata { get; set; } /// /// A reference to the model's container . diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs index febc538292..a6bc2644d2 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -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()), + Mock.Of()); + + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadata = metadataProvider.GetMetadataForType( + modelAccessor: null, modelType: typeof(TypeWithExcludedPropertiesUsingBindAttribute)); + + var actionBindingContext = new ActionBindingContext(actionContext, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); + // Act + var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( + modelMetadata, actionBindingContext, Mock.Of()); + + // 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()), + Mock.Of()); + + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadata = metadataProvider.GetMetadataForType( + modelAccessor: null, modelType: typeof(TypeWithIncludedPropertiesUsingBindAttribute)); + + var actionBindingContext = new ActionBindingContext(actionContext, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); + // Act + var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( + modelMetadata, actionBindingContext, Mock.Of()); + + // 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()), + Mock.Of()); + + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null, + methodInfo: methodInfo, + parameterName: "parameter"); + + var actionBindingContext = new ActionBindingContext(actionContext, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); + // Act + var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( + modelMetadata, actionBindingContext, Mock.Of()); + + // 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(), Mock.Of()); // Act - var context = DefaultControllerActionArgumentBinder - .GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of()); + var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( + modelMetadata, actionBindingContext, Mock.Of()); // 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(), Mock.Of()); // Act - var context = DefaultControllerActionArgumentBinder - .GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of()); + var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( + modelMetadata, actionBindingContext, Mock.Of()); // 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; } + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs index c545e37801..4c1e2a0d81 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs @@ -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(), - 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(), - 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 diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs index 000c3048d5..895b85aad3 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs @@ -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(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(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 attributes, Type containerType, Type modelType, string propertyName) + protected override ModelMetadata CreateMetadataPrototype(IEnumerable 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 Attributes { get; set; } + public IEnumerable Attributes { get; set; } public Type ContainerType { get; set; } public Type ModelType { get; set; } public string PropertyName { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs index 2e2c36dac8..0775379dc1 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs @@ -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(); @@ -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> + public static TheoryData> ExpectedAttributeData { get { - return new TheoryData> + return new TheoryData> { { 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 accessor) + object attribute, + Func 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() { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs index 119211465b..259eee03b3 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs @@ -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(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(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(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(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; } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs index 6d3e219c76..d90fc065c6 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs @@ -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> ExpectedAttributeDataStrings + public static TheoryData> ExpectedAttributeDataStrings { get { - return new TheoryData> + return new TheoryData> { { 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 accessor) + public void AttributesOverrideMetadataStrings(object attribute, Func 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() { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs index 5300a0c308..6fc4f7c1a0 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs @@ -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(); + var nonEmptyPropertyList = new List() { "SomeProperty" }; return new TheoryData, Func, 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(); + 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))] @@ -108,10 +142,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void IsComplexTypeTestsReturnsFalseForSimpleTypes(Type type) { // Arrange - var provider = new Mock(); + 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(); + 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(); + 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), 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), 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(); - 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(); - 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 setter, + Func 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 setter, + Func 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 setter, - Func 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(); - 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(); - 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 setter, - Func 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; diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestBinderMetadata.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestBinderMetadata.cs new file mode 100644 index 0000000000..3fc8528321 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestBinderMetadata.cs @@ -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 + { + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestModelNameProvider.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestModelNameProvider.cs new file mode 100644 index 0000000000..f0f695e33c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestModelNameProvider.cs @@ -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; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestPropertyBindingInfo.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestPropertyBindingInfo.cs new file mode 100644 index 0000000000..29e6d2f4e7 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestPropertyBindingInfo.cs @@ -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; } + } +} \ No newline at end of file