Adding PropertyFilter instead of depending on model metadata to concatenate Metadata for parameter/property and type metadata
This commit is contained in:
parent
77c4391e47
commit
d1c0213a10
|
|
@ -85,6 +85,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata, ActionBindingContext actionBindingContext)
|
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata, ActionBindingContext actionBindingContext)
|
||||||
{
|
{
|
||||||
|
Predicate<string> propertyFilter =
|
||||||
|
propertyName => BindAttribute.IsPropertyAllowed(propertyName,
|
||||||
|
modelMetadata.IncludedProperties,
|
||||||
|
modelMetadata.ExcludedProperties);
|
||||||
|
|
||||||
var modelBindingContext = new ModelBindingContext
|
var modelBindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName,
|
ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName,
|
||||||
|
|
@ -94,7 +99,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
ValidatorProvider = actionBindingContext.ValidatorProvider,
|
ValidatorProvider = actionBindingContext.ValidatorProvider,
|
||||||
MetadataProvider = actionBindingContext.MetadataProvider,
|
MetadataProvider = actionBindingContext.MetadataProvider,
|
||||||
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
||||||
|
PropertyFilter = propertyFilter,
|
||||||
// Fallback only if there is no explicit model name set.
|
// Fallback only if there is no explicit model name set.
|
||||||
FallbackToEmptyPrefix = modelMetadata.ModelName == null,
|
FallbackToEmptyPrefix = modelMetadata.ModelName == null,
|
||||||
ValueProvider = actionBindingContext.ValueProvider,
|
ValueProvider = actionBindingContext.ValueProvider,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
|
@ -37,5 +39,21 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return Prefix;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
MetadataProvider = oldBindingContext.MetadataProvider,
|
MetadataProvider = oldBindingContext.MetadataProvider,
|
||||||
ModelBinder = oldBindingContext.ModelBinder,
|
ModelBinder = oldBindingContext.ModelBinder,
|
||||||
HttpContext = oldBindingContext.HttpContext,
|
HttpContext = oldBindingContext.HttpContext,
|
||||||
|
PropertyFilter = oldBindingContext.PropertyFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
// validation is expensive to create, so copy it over if we can
|
// validation is expensive to create, so copy it over if we can
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureModel(bindingContext);
|
EnsureModel(bindingContext);
|
||||||
var propertyMetadatas = GetMetadataForProperties(bindingContext);
|
var propertyMetadatas = GetMetadataForProperties(bindingContext).ToArray();
|
||||||
var dto = CreateAndPopulateDto(bindingContext, propertyMetadatas);
|
var dto = CreateAndPopulateDto(bindingContext, propertyMetadatas);
|
||||||
|
|
||||||
// post-processing, e.g. property setters and hooking up validation
|
// 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)
|
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var validationInfo = GetPropertyValidationInfo(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
|
return bindingContext.ModelMetadata.Properties
|
||||||
.Where(propertyMetadata =>
|
.Where(propertyMetadata =>
|
||||||
IsPropertyAllowed(propertyMetadata.PropertyName,
|
newPropertyFilter(propertyMetadata.PropertyName) &&
|
||||||
bindingContext.ModelMetadata.IncludedProperties,
|
|
||||||
bindingContext.ModelMetadata.ExcludedProperties) &&
|
|
||||||
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
|
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
|
||||||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
|
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
|
||||||
CanUpdateProperty(propertyMetadata));
|
CanUpdateProperty(propertyMetadata));
|
||||||
|
|
|
||||||
|
|
@ -82,61 +82,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
parameter.GetCustomAttributes(),
|
parameter.GetCustomAttributes(),
|
||||||
parameterName,
|
parameterName,
|
||||||
binderMarker);
|
binderMarker);
|
||||||
|
|
||||||
var typeInfo = GetTypeInformation(parameter.ParameterType);
|
|
||||||
UpdateMetadataWithTypeInfo(parameterInfo.Prototype, typeInfo);
|
|
||||||
|
|
||||||
return CreateMetadataFromPrototype(parameterInfo.Prototype, modelAccessor);
|
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)
|
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
|
||||||
{
|
{
|
||||||
var typeInfo = GetTypeInformation(containerType);
|
var typeInfo = GetTypeInformation(containerType);
|
||||||
|
|
@ -203,19 +151,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
info.Properties = properties;
|
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;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModelBindingContext
|
public class ModelBindingContext
|
||||||
{
|
{
|
||||||
|
private static readonly Predicate<string> _defaultPropertyFilter = _ => true;
|
||||||
private string _modelName;
|
private string _modelName;
|
||||||
private ModelStateDictionary _modelState;
|
private ModelStateDictionary _modelState;
|
||||||
private Dictionary<string, ModelMetadata> _propertyMetadata;
|
private Dictionary<string, ModelMetadata> _propertyMetadata;
|
||||||
private ModelValidationNode _validationNode;
|
private ModelValidationNode _validationNode;
|
||||||
|
private Predicate<string> _propertyFilter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ModelBindingContext"/> class.
|
/// 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>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="ModelValidationNode"/> instance used as a container for
|
/// Gets or sets the <see cref="ModelValidationNode"/> instance used as a container for
|
||||||
/// validation information.
|
/// validation information.
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_BothAtParameterAndTypeLevel_ForParameters()
|
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_OnlyAtParameterLevel_ForParameters()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||||
|
|
@ -58,9 +58,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var provider = new DataAnnotationsModelMetadataProvider();
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
// Note it does an intersection for included and a union for excluded.
|
// 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[] {
|
var expectedExcludedPropertyNames = new[] {
|
||||||
"Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
|
"Property3", "Property4", "IncludedAndExcludedExplicitly1" };
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = provider.GetMetadataForParameter(
|
var metadata = provider.GetMetadataForParameter(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue