Adding PropertyFilter instead of depending on model metadata to concatenate Metadata for parameter/property and type metadata

This commit is contained in:
Harsh Gupta 2014-10-17 19:53:34 -07:00
parent 77c4391e47
commit d1c0213a10
7 changed files with 54 additions and 73 deletions

View File

@ -85,6 +85,11 @@ namespace Microsoft.AspNet.Mvc
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata, ActionBindingContext actionBindingContext)
{
Predicate<string> 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,

View File

@ -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<string> includeProperties,
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) ||
includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
var excludeProperty = (excludeProperties != null) &&
excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
return includeProperty && !excludeProperty;
}
}
}

View File

@ -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

View File

@ -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<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
{
var validationInfo = GetPropertyValidationInfo(bindingContext);
var propertyTypeMetadata = bindingContext.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 =>
IsPropertyAllowed(propertyMetadata.PropertyName,
bindingContext.ModelMetadata.IncludedProperties,
bindingContext.ModelMetadata.ExcludedProperties) &&
newPropertyFilter(propertyMetadata.PropertyName) &&
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
CanUpdateProperty(propertyMetadata));

View File

@ -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<ModelMetadata> 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;
}

View File

@ -13,10 +13,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public class ModelBindingContext
{
private static readonly Predicate<string> _defaultPropertyFilter = _ => true;
private string _modelName;
private ModelStateDictionary _modelState;
private Dictionary<string, ModelMetadata> _propertyMetadata;
private ModelValidationNode _validationNode;
private Predicate<string> _propertyFilter;
/// <summary>
/// Initializes a new instance of the <see cref="ModelBindingContext"/> class.
@ -172,6 +174,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
public Predicate<string> PropertyFilter
{
get
{
if (_propertyFilter == null)
{
_propertyFilter = _defaultPropertyFilter;
}
return _propertyFilter;
}
set { _propertyFilter = value; }
}
/// <summary>
/// Gets or sets the <see cref="ModelValidationNode"/> instance used as a container for
/// validation information.

View File

@ -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(