diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionArgumentBinder.cs index a409109f55..da63241c4f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionArgumentBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionArgumentBinder.cs @@ -85,6 +85,11 @@ namespace Microsoft.AspNet.Mvc internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata, ActionBindingContext actionBindingContext) { + Predicate propertyFilter = + propertyName => BindAttribute.IsPropertyAllowed(propertyName, + modelMetadata.IncludedProperties, + modelMetadata.ExcludedProperties); + var modelBindingContext = new ModelBindingContext { ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName, @@ -94,7 +99,7 @@ namespace Microsoft.AspNet.Mvc ValidatorProvider = actionBindingContext.ValidatorProvider, MetadataProvider = actionBindingContext.MetadataProvider, HttpContext = actionBindingContext.ActionContext.HttpContext, - + PropertyFilter = propertyFilter, // Fallback only if there is no explicit model name set. FallbackToEmptyPrefix = modelMetadata.ModelName == null, ValueProvider = actionBindingContext.ValueProvider, diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs index 0d2dc2ef29..e83ff6feb0 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq; namespace Microsoft.AspNet.Mvc { @@ -37,5 +39,21 @@ namespace Microsoft.AspNet.Mvc return Prefix; } } + + public static bool IsPropertyAllowed(string propertyName, + IReadOnlyList includeProperties, + 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) || + includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase); + var excludeProperty = (excludeProperties != null) && + excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase); + + return includeProperty && !excludeProperty; + } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs index 15c312eb29..4fa1621039 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs @@ -135,6 +135,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding MetadataProvider = oldBindingContext.MetadataProvider, ModelBinder = oldBindingContext.ModelBinder, HttpContext = oldBindingContext.HttpContext, + PropertyFilter = oldBindingContext.PropertyFilter, }; // validation is expensive to create, so copy it over if we can diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index 021ed83fe3..47dc259ea9 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } EnsureModel(bindingContext); - var propertyMetadatas = GetMetadataForProperties(bindingContext); + var propertyMetadatas = GetMetadataForProperties(bindingContext).ToArray(); var dto = CreateAndPopulateDto(bindingContext, propertyMetadatas); // post-processing, e.g. property setters and hooking up validation @@ -166,11 +166,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding protected virtual IEnumerable GetMetadataForProperties(ModelBindingContext bindingContext) { var validationInfo = GetPropertyValidationInfo(bindingContext); + var propertyTypeMetadata = bindingContext.MetadataProvider + .GetMetadataForType(null, bindingContext.ModelType); + Predicate newPropertyFilter = + propertyName => bindingContext.PropertyFilter(propertyName) && + BindAttribute.IsPropertyAllowed( + propertyName, + propertyTypeMetadata.IncludedProperties, + propertyTypeMetadata.ExcludedProperties); + return bindingContext.ModelMetadata.Properties .Where(propertyMetadata => - IsPropertyAllowed(propertyMetadata.PropertyName, - bindingContext.ModelMetadata.IncludedProperties, - bindingContext.ModelMetadata.ExcludedProperties) && + newPropertyFilter(propertyMetadata.PropertyName) && (validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) || !validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) && CanUpdateProperty(propertyMetadata)); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs index 3adf4aac38..590bfe279f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs @@ -82,61 +82,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding parameter.GetCustomAttributes(), parameterName, binderMarker); - - var typeInfo = GetTypeInformation(parameter.ParameterType); - UpdateMetadataWithTypeInfo(parameterInfo.Prototype, typeInfo); - return CreateMetadataFromPrototype(parameterInfo.Prototype, modelAccessor); } - private void UpdateMetadataWithTypeInfo(ModelMetadata parameterPrototype, TypeInformation typeInfo) - { - // If both are empty - // Include everything. - // If none are empty - // Include common. - // If nothing common - // Dont include anything. - if (typeInfo.Prototype.IncludedProperties == null || typeInfo.Prototype.IncludedProperties.Count == 0) - { - if (parameterPrototype.IncludedProperties == null || parameterPrototype.IncludedProperties.Count == 0) - { - parameterPrototype.IncludedProperties = typeInfo.Properties - .Select(property => property.Key) - .ToList(); - } - } - else - { - if (parameterPrototype.IncludedProperties == null || parameterPrototype.IncludedProperties.Count == 0) - { - parameterPrototype.IncludedProperties = typeInfo.Prototype.IncludedProperties; - } - else - { - parameterPrototype.IncludedProperties = parameterPrototype.IncludedProperties - .Intersect(typeInfo.Prototype.IncludedProperties, - StringComparer.OrdinalIgnoreCase).ToList(); - } - } - - if (typeInfo.Prototype.ExcludedProperties != null) - { - if (parameterPrototype.ExcludedProperties == null || parameterPrototype.ExcludedProperties.Count == 0) - { - parameterPrototype.ExcludedProperties = typeInfo.Prototype.ExcludedProperties; - } - else - { - parameterPrototype.ExcludedProperties = parameterPrototype.ExcludedProperties - .Union(typeInfo.Prototype.ExcludedProperties, - StringComparer.OrdinalIgnoreCase).ToList(); - } - } - - // Ignore the ModelName specified at Type level. (This is to be compatible with MVC). - } - private IEnumerable GetMetadataForPropertiesCore(object container, Type containerType) { var typeInfo = GetTypeInformation(containerType); @@ -203,19 +151,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } info.Properties = properties; - - if (info.Prototype != null) - { - // Update the included properties so that the properties are not ignored while binding. - if (info.Prototype.IncludedProperties == null || - info.Prototype.IncludedProperties.Count == 0) - { - // Mark all properties as included. - info.Prototype.IncludedProperties = - info.Properties.Select(property => property.Key).ToList(); - } - } - return info; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs index 3424266c93..e67f35fbc1 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs @@ -13,10 +13,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public class ModelBindingContext { + private static readonly Predicate _defaultPropertyFilter = _ => true; private string _modelName; private ModelStateDictionary _modelState; private Dictionary _propertyMetadata; private ModelValidationNode _validationNode; + private Predicate _propertyFilter; /// /// Initializes a new instance of the class. @@ -172,6 +174,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + public Predicate PropertyFilter + { + get + { + if (_propertyFilter == null) + { + _propertyFilter = _defaultPropertyFilter; + } + return _propertyFilter; + } + set { _propertyFilter = value; } + } + /// /// Gets or sets the instance used as a container for /// validation information. diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs index d1838da78a..a2a442344e 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } [Fact] - public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_BothAtParameterAndTypeLevel_ForParameters() + public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_OnlyAtParameterLevel_ForParameters() { // Arrange var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute); @@ -58,9 +58,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var provider = new DataAnnotationsModelMetadataProvider(); // Note it does an intersection for included and a union for excluded. - var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1" }; + var expectedIncludedPropertyNames = new[] { "Property1", "Property2", "IncludedAndExcludedExplicitly1" }; var expectedExcludedPropertyNames = new[] { - "Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" }; + "Property3", "Property4", "IncludedAndExcludedExplicitly1" }; // Act var metadata = provider.GetMetadataForParameter(