Updating Associated Metadata Provider to follow the existing pattern.
This commit is contained in:
parent
680cdf4d57
commit
b54c326ee6
|
|
@ -43,11 +43,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
var metadata = metadataProvider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: actionDescriptor.MethodInfo,
|
||||
parameterName: parameter.Name,
|
||||
binderMetadata: parameter.BinderMetadata);
|
||||
parameterName: parameter.Name);
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
UpdateParameterMetadata(metadata, parameter.BinderMetadata);
|
||||
parameterMetadata.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,20 @@ namespace Microsoft.AspNet.Mvc
|
|||
return actionArguments;
|
||||
}
|
||||
|
||||
private void UpdateParameterMetadata(ModelMetadata metadata, IBinderMetadata binderMetadata)
|
||||
{
|
||||
if (binderMetadata != null)
|
||||
{
|
||||
metadata.BinderMetadata = binderMetadata;
|
||||
}
|
||||
|
||||
var nameProvider = binderMetadata as IModelNameProvider;
|
||||
if (nameProvider != null && nameProvider.Name != null)
|
||||
{
|
||||
metadata.BinderModelName = nameProvider.Name;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PopulateArgumentAsync(
|
||||
ActionBindingContext actionBindingContext,
|
||||
IDictionary<string, object> arguments,
|
||||
|
|
@ -82,23 +96,23 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata,
|
||||
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata,
|
||||
ActionBindingContext actionBindingContext,
|
||||
OperationBindingContext operationBindingContext)
|
||||
{
|
||||
Predicate<string> propertyFilter =
|
||||
propertyName => BindAttribute.IsPropertyAllowed(propertyName,
|
||||
modelMetadata.IncludedProperties,
|
||||
modelMetadata.ExcludedProperties);
|
||||
modelMetadata.BinderIncludeProperties,
|
||||
modelMetadata.BinderExcludeProperties);
|
||||
|
||||
var modelBindingContext = new ModelBindingContext
|
||||
{
|
||||
ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName,
|
||||
ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName,
|
||||
ModelMetadata = modelMetadata,
|
||||
ModelState = actionBindingContext.ActionContext.ModelState,
|
||||
PropertyFilter = propertyFilter,
|
||||
// Fallback only if there is no explicit model name set.
|
||||
FallbackToEmptyPrefix = modelMetadata.ModelName == null,
|
||||
FallbackToEmptyPrefix = modelMetadata.BinderModelName == null,
|
||||
ValueProvider = actionBindingContext.ValueProvider,
|
||||
OperationBindingContext = operationBindingContext,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// This attribute can be used on action parameters and types, to indicate model level metadata.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class BindAttribute : Attribute, IModelNameProvider, IModelPropertyBindingInfo
|
||||
public sealed class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Comma separated set of properties which are to be excluded during model binding.
|
||||
|
|
@ -45,10 +45,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
IReadOnlyList<string> excludeProperties)
|
||||
{
|
||||
// We allow a property to be bound if its both in the include list AND not in the exclude list.
|
||||
// An empty include list implies all properties are allowed.
|
||||
// An empty exclude list implies no properties are disallowed.
|
||||
var includeProperty = (includeProperties == null) ||
|
||||
(includeProperties.Count == 0) ||
|
||||
var includeProperty = (includeProperties != null) &&
|
||||
includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
|
||||
var excludeProperty = (excludeProperties != null) &&
|
||||
excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
var bindingContext = context.ModelBindingContext;
|
||||
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||
var isThereAnExplicitAlias = bindingContext.ModelMetadata.ModelName != null;
|
||||
|
||||
// The fact that this has reached here,
|
||||
// it is a complex object which was not directly bound by any previous model binders.
|
||||
var hasExplicitAlias = bindingContext.ModelMetadata.BinderModelName != null;
|
||||
|
||||
// The fact that this has reached here,
|
||||
// it is a complex object which was not directly bound by any previous model binders.
|
||||
// Check if this was supposed to be handled by a non value provider based binder.
|
||||
// if it was then it should be not be bound using mutable object binder.
|
||||
// This check would prevent it from recursing in if a model contains a property of its own type.
|
||||
// We skip this check if it is a top level object because we want to always evaluate
|
||||
// We skip this check if it is a top level object because we want to always evaluate
|
||||
// the creation of top level object (this is also required for ModelBinderAttribute to work.)
|
||||
if (!isTopLevelObject &&
|
||||
bindingContext.ModelMetadata.BinderMetadata != null &&
|
||||
|
|
@ -68,14 +68,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
// Create the object if :
|
||||
// 1. It is a top level model with an explicit user supplied prefix.
|
||||
// 1. It is a top level model with an explicit user supplied prefix.
|
||||
// In this case since it will never fallback to empty prefix, we need to create the model here.
|
||||
if (isTopLevelObject && isThereAnExplicitAlias)
|
||||
if (isTopLevelObject && hasExplicitAlias)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. It is a top level object and there is no model name ( Fallback to empty prefix case ).
|
||||
// 2. It is a top level object and there is no model name ( Fallback to empty prefix case ).
|
||||
// This is necessary as we do not want to depend on a value provider to contain an empty prefix.
|
||||
if (isTopLevelObject && bindingContext.ModelName == string.Empty)
|
||||
{
|
||||
|
|
@ -83,9 +83,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
// 3. The model name is not prefixed and a value provider can directly provide a value for the model name.
|
||||
// The fact that it is not prefixed means that the containsPrefixAsync call checks for the exact model name
|
||||
// instead of doing a prefix match.
|
||||
if (!bindingContext.ModelName.Contains(".") &&
|
||||
// The fact that it is not prefixed means that the containsPrefixAsync call checks for the exact
|
||||
// model name instead of doing a prefix match.
|
||||
if (!bindingContext.ModelName.Contains(".") &&
|
||||
await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
|
||||
{
|
||||
return true;
|
||||
|
|
@ -103,11 +103,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private async Task<bool> CanValueBindAnyModelProperties(MutableObjectBinderContext context)
|
||||
{
|
||||
// We need to enumerate the non marked properties and properties marked with IValueProviderMetadata
|
||||
// instead of checking bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName)
|
||||
// because there can be a case
|
||||
// where a value provider might be willing to provide a marked property, which might never be bound.
|
||||
// For example if person.Name is marked with FromQuery, and FormValueProvider has a key person.Name, and the
|
||||
// QueryValueProvider does not, we do not want to create Person.
|
||||
// instead of checking bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName)
|
||||
// because there can be a case where a value provider might be willing to provide a marked property,
|
||||
// which might never be bound.
|
||||
// For example if person.Name is marked with FromQuery, and FormValueProvider has a key person.Name,
|
||||
// and the QueryValueProvider does not, we do not want to create Person.
|
||||
var isAnyPropertyEnabledForValueProviderBasedBinding = false;
|
||||
foreach (var propertyMetadata in context.PropertyMetadata)
|
||||
{
|
||||
|
|
@ -144,7 +144,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
if (valueProviderMetadata != null)
|
||||
{
|
||||
// if there is a binder metadata and since the property can be bound using a value provider.
|
||||
var metadataAwareValueProvider = bindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
|
||||
var metadataAwareValueProvider =
|
||||
bindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
|
||||
if (metadataAwareValueProvider != null)
|
||||
{
|
||||
valueProvider = metadataAwareValueProvider.Filter(valueProviderMetadata);
|
||||
|
|
@ -268,19 +269,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
||||
{
|
||||
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||
var propertyTypeMetadata = bindingContext.OperationBindingContext
|
||||
.MetadataProvider
|
||||
.GetMetadataForType(null, bindingContext.ModelType);
|
||||
Predicate<string> newPropertyFilter =
|
||||
propertyName => bindingContext.PropertyFilter(propertyName) &&
|
||||
BindAttribute.IsPropertyAllowed(
|
||||
propertyName,
|
||||
propertyTypeMetadata.IncludedProperties,
|
||||
propertyTypeMetadata.ExcludedProperties);
|
||||
|
||||
return bindingContext.ModelMetadata.Properties
|
||||
.Where(propertyMetadata =>
|
||||
newPropertyFilter(propertyMetadata.PropertyName) &&
|
||||
bindingContext.PropertyFilter(propertyMetadata.PropertyName) &&
|
||||
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
|
||||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
|
||||
CanUpdateProperty(propertyMetadata));
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// <summary>
|
||||
/// Represents an entity which has binding information for a model.
|
||||
/// </summary>
|
||||
public interface IModelPropertyBindingInfo
|
||||
public interface IPropertyBindingInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Comma separated set of properties which are to be excluded during model binding.
|
||||
|
|
@ -49,9 +49,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public ModelMetadata GetMetadataForParameter(
|
||||
Func<object> modelAccessor,
|
||||
[NotNull] MethodInfo methodInfo,
|
||||
[NotNull] string parameterName,
|
||||
IBinderMetadata binderMetadata)
|
||||
[NotNull] string parameterName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parameterName))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "parameterName");
|
||||
}
|
||||
|
||||
var parameter = methodInfo.GetParameters().FirstOrDefault(
|
||||
param => StringComparer.Ordinal.Equals(param.Name, parameterName));
|
||||
if (parameter == null)
|
||||
|
|
@ -60,11 +64,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
throw new ArgumentException(message, nameof(parameterName));
|
||||
}
|
||||
|
||||
return GetMetadataForParameterCore(modelAccessor, parameterName, parameter, binderMetadata);
|
||||
return GetMetadataForParameterCore(modelAccessor, parameterName, parameter);
|
||||
}
|
||||
|
||||
// Override for creating the prototype metadata (without the accessor)
|
||||
protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes,
|
||||
protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable<object> attributes,
|
||||
Type containerType,
|
||||
Type modelType,
|
||||
string propertyName);
|
||||
|
|
@ -72,27 +76,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Override for applying the prototype + modelAccess to yield the final metadata
|
||||
protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype,
|
||||
Func<object> modelAccessor);
|
||||
|
||||
private ModelMetadata GetMetadataForParameterCore(Func<object> modelAccessor,
|
||||
string parameterName,
|
||||
ParameterInfo parameter,
|
||||
IBinderMetadata binderMetadata)
|
||||
ParameterInfo parameter)
|
||||
{
|
||||
var parameterInfo =
|
||||
CreateParameterInfo(parameter.ParameterType,
|
||||
parameter.GetCustomAttributes(),
|
||||
parameterName,
|
||||
binderMetadata);
|
||||
ModelAttributes.GetAttributesForParameter(parameter),
|
||||
parameterName);
|
||||
|
||||
var metadata = CreateMetadataFromPrototype(parameterInfo.Prototype, modelAccessor);
|
||||
|
||||
|
||||
// If there is no metadata associated with the parameter itself get it from the type.
|
||||
if (metadata != null && metadata.BinderMetadata == null)
|
||||
{
|
||||
var typeInfo = GetTypeInformation(parameter.ParameterType);
|
||||
metadata.BinderMetadata = typeInfo.Prototype.BinderMetadata;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
|
@ -125,18 +119,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
metadata.IsReadOnly = true;
|
||||
}
|
||||
|
||||
// We need to update the property after the prototype creation because otherwise
|
||||
// if the property type is same as the containing type, it would cause infinite recursion.
|
||||
// If there is no metadata associated with the property itself get it from the type.
|
||||
if (metadata != null && metadata.BinderMetadata == null)
|
||||
{
|
||||
if (propertyInfo.Prototype != null)
|
||||
{
|
||||
var typeInfo = GetTypeInformation(propertyInfo.Prototype.ModelType);
|
||||
metadata.BinderMetadata = typeInfo.Prototype.BinderMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return new PropertyInformation
|
||||
{
|
||||
PropertyHelper = helper,
|
||||
Prototype = CreateMetadataPrototype(property.GetCustomAttributes(),
|
||||
Prototype = CreateMetadataPrototype(ModelAttributes.GetAttributesForProperty(property),
|
||||
containerType,
|
||||
property.PropertyType,
|
||||
property.Name),
|
||||
|
|
@ -199,26 +181,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
private ParameterInformation CreateParameterInfo(
|
||||
Type parameterType,
|
||||
IEnumerable<Attribute> attributes,
|
||||
string parameterName,
|
||||
IBinderMetadata binderMetadata)
|
||||
IEnumerable<object> attributes,
|
||||
string parameterName)
|
||||
{
|
||||
var metadataProtoType = CreateMetadataPrototype(attributes: attributes,
|
||||
containerType: null,
|
||||
modelType: parameterType,
|
||||
propertyName: parameterName);
|
||||
|
||||
if (binderMetadata != null)
|
||||
{
|
||||
metadataProtoType.BinderMetadata = binderMetadata;
|
||||
}
|
||||
|
||||
var nameProvider = binderMetadata as IModelNameProvider;
|
||||
if (nameProvider != null && nameProvider.Name != null)
|
||||
{
|
||||
metadataProtoType.ModelName = nameProvider.Name;
|
||||
}
|
||||
|
||||
return new ParameterInformation
|
||||
{
|
||||
Prototype = metadataProtoType
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
public class CachedDataAnnotationsMetadataAttributes
|
||||
{
|
||||
public CachedDataAnnotationsMetadataAttributes(IEnumerable<Attribute> attributes)
|
||||
public CachedDataAnnotationsMetadataAttributes(IEnumerable<object> attributes)
|
||||
{
|
||||
DataType = attributes.OfType<DataTypeAttribute>().FirstOrDefault();
|
||||
Display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
|
||||
|
|
@ -20,6 +20,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
HiddenInput = attributes.OfType<HiddenInputAttribute>().FirstOrDefault();
|
||||
Required = attributes.OfType<RequiredAttribute>().FirstOrDefault();
|
||||
ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
|
||||
BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
|
||||
PropertyBindingInfo = attributes.OfType<IPropertyBindingInfo>();
|
||||
BinderModelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
|
||||
|
||||
// Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is
|
||||
// non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType]
|
||||
|
|
@ -32,9 +35,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="IBinderMetadata"/> found in collection passed to the
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
|
||||
/// </summary>
|
||||
public IBinderMetadata BinderMetadata { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="IModelNameProvider"/> found in collection passed to the
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
|
||||
/// </summary>
|
||||
public IModelNameProvider BinderModelNameProvider { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="DataTypeAttribute"/> found in collection passed to the
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{Attribute})"/> constructor, if any.
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
|
||||
/// </summary>
|
||||
public DataTypeAttribute DataType { get; protected set; }
|
||||
|
||||
|
|
@ -44,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="DisplayFormatAttribute"/> found in collection passed to the
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{Attribute})"/> constructor, if any.
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
|
||||
/// If no such attribute was found but a <see cref="DataTypeAttribute"/> was, gets the
|
||||
/// <see cref="DataTypeAttribute.DisplayFormat"/> value.
|
||||
/// </summary>
|
||||
|
|
@ -54,10 +69,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="HiddenInputAttribute"/> found in collection passed to the
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{Attribute})"/> constructor, if any.
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
|
||||
/// </summary>
|
||||
public HiddenInputAttribute HiddenInput { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="IEnumerable{IModelPropertyBindingInfo}"/> found in collection
|
||||
/// passed to the <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor,
|
||||
/// if any.
|
||||
/// </summary>
|
||||
public IEnumerable<IPropertyBindingInfo> PropertyBindingInfo { get; protected set; }
|
||||
|
||||
public RequiredAttribute Required { get; protected set; }
|
||||
|
||||
public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; }
|
||||
|
|
|
|||
|
|
@ -26,20 +26,79 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Type containerType,
|
||||
Type modelType,
|
||||
string propertyName,
|
||||
IEnumerable<Attribute> attributes)
|
||||
IEnumerable<object> attributes)
|
||||
: base(provider,
|
||||
containerType,
|
||||
modelType,
|
||||
propertyName,
|
||||
new CachedDataAnnotationsMetadataAttributes(attributes))
|
||||
{
|
||||
BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
|
||||
}
|
||||
|
||||
var modelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
|
||||
ModelName = modelNameProvider?.Name;
|
||||
protected override IBinderMetadata ComputeBinderMetadata()
|
||||
{
|
||||
return PrototypeCache.BinderMetadata != null
|
||||
? PrototypeCache.BinderMetadata
|
||||
: base.ComputeBinderMetadata();
|
||||
}
|
||||
|
||||
var bindAttribute = attributes.OfType<BindAttribute>().FirstOrDefault();
|
||||
ReadSettingsFromBindAttribute(bindAttribute);
|
||||
protected override string ComputeBinderModelNamePrefix()
|
||||
{
|
||||
return PrototypeCache.BinderModelNameProvider != null
|
||||
? PrototypeCache.BinderModelNameProvider.Name
|
||||
: base.ComputeBinderModelNamePrefix();
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<string> ComputeBinderIncludeProperties()
|
||||
{
|
||||
var propertyBindingInfo = PrototypeCache.PropertyBindingInfo?.ToList();
|
||||
if (propertyBindingInfo != null && propertyBindingInfo.Count != 0)
|
||||
{
|
||||
if (string.IsNullOrEmpty(propertyBindingInfo[0].Include))
|
||||
{
|
||||
return Properties.Select(property => property.PropertyName).ToList();
|
||||
}
|
||||
|
||||
var includeFirst = SplitString(propertyBindingInfo[0].Include).ToList();
|
||||
if (propertyBindingInfo.Count != 2)
|
||||
{
|
||||
return includeFirst;
|
||||
}
|
||||
|
||||
var includedAtType = SplitString(propertyBindingInfo[1].Include).ToList();
|
||||
|
||||
if (includeFirst.Count == 0 && includedAtType.Count == 0)
|
||||
{
|
||||
// Need to include everything by default.
|
||||
return Properties.Select(property => property.PropertyName).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return includeFirst.Intersect(includedAtType).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
// Need to include everything by default.
|
||||
return Properties.Select(property => property.PropertyName).ToList();
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<string> ComputeBinderExcludeProperties()
|
||||
{
|
||||
var propertyBindingInfo = PrototypeCache.PropertyBindingInfo?.ToList();
|
||||
if (propertyBindingInfo != null && propertyBindingInfo.Count != 0)
|
||||
{
|
||||
var excludeFirst = SplitString(propertyBindingInfo[0].Exclude).ToList();
|
||||
|
||||
if (propertyBindingInfo.Count != 2)
|
||||
{
|
||||
return excludeFirst;
|
||||
}
|
||||
|
||||
var excludedAtType = SplitString(propertyBindingInfo[1].Exclude).ToList();
|
||||
return excludeFirst.Union(excludedAtType).ToList();
|
||||
}
|
||||
|
||||
return base.ComputeBinderExcludeProperties();
|
||||
}
|
||||
|
||||
protected override bool ComputeConvertEmptyStringToNull()
|
||||
|
|
@ -291,17 +350,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
private void ReadSettingsFromBindAttribute(BindAttribute bindAttribute)
|
||||
{
|
||||
if (bindAttribute == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExcludedProperties = SplitString(bindAttribute.Exclude).ToList();
|
||||
IncludedProperties = SplitString(bindAttribute.Include).ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> SplitString(string original)
|
||||
{
|
||||
if (string.IsNullOrEmpty(original))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
|
|
@ -30,6 +31,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private bool _isRequired;
|
||||
private bool _showForDisplay;
|
||||
private bool _showForEdit;
|
||||
private IBinderMetadata _binderMetadata;
|
||||
private string _binderModelName;
|
||||
private IReadOnlyList<string> _binderIncludeProperties;
|
||||
private IReadOnlyList<string> _binderExcludeProperties;
|
||||
|
||||
private bool _convertEmptyStringToNullComputed;
|
||||
private bool _nullDisplayTextComputed;
|
||||
|
|
@ -46,6 +51,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private bool _isRequiredComputed;
|
||||
private bool _showForDisplayComputed;
|
||||
private bool _showForEditComputed;
|
||||
private bool _isBinderMetadataComputed;
|
||||
private bool _isBinderIncludePropertiesComputed;
|
||||
private bool _isBinderModelNameComputed;
|
||||
private bool _isBinderExcludePropertiesComputed;
|
||||
|
||||
// Constructor for creating real instances of the metadata class based on a prototype
|
||||
protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor)
|
||||
|
|
@ -57,10 +66,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
CacheKey = prototype.CacheKey;
|
||||
PrototypeCache = prototype.PrototypeCache;
|
||||
BinderMetadata = prototype.BinderMetadata;
|
||||
IncludedProperties = prototype.IncludedProperties;
|
||||
ExcludedProperties = prototype.ExcludedProperties;
|
||||
ModelName = prototype.ModelName;
|
||||
_isComplexType = prototype.IsComplexType;
|
||||
_isComplexTypeComputed = true;
|
||||
}
|
||||
|
|
@ -76,6 +81,91 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
PrototypeCache = prototypeCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override IBinderMetadata BinderMetadata
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isBinderMetadataComputed)
|
||||
{
|
||||
_binderMetadata = ComputeBinderMetadata();
|
||||
_isBinderMetadataComputed = true;
|
||||
}
|
||||
|
||||
return _binderMetadata;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_binderMetadata = value;
|
||||
_isBinderMetadataComputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override IReadOnlyList<string> BinderIncludeProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isBinderIncludePropertiesComputed)
|
||||
{
|
||||
_binderIncludeProperties = ComputeBinderIncludeProperties();
|
||||
_isBinderIncludePropertiesComputed = true;
|
||||
}
|
||||
|
||||
return _binderIncludeProperties;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_binderIncludeProperties = value;
|
||||
_isBinderIncludePropertiesComputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override IReadOnlyList<string> BinderExcludeProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isBinderExcludePropertiesComputed)
|
||||
{
|
||||
_binderExcludeProperties = ComputeBinderExcludeProperties();
|
||||
_isBinderExcludePropertiesComputed = true;
|
||||
}
|
||||
|
||||
return _binderExcludeProperties;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_binderExcludeProperties = value;
|
||||
_isBinderExcludePropertiesComputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string BinderModelName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isBinderModelNameComputed)
|
||||
{
|
||||
_binderModelName = ComputeBinderModelNamePrefix();
|
||||
_isBinderModelNameComputed = true;
|
||||
}
|
||||
|
||||
return _binderModelName;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_binderModelName = value;
|
||||
_isBinderModelNameComputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool ConvertEmptyStringToNull
|
||||
{
|
||||
get
|
||||
|
|
@ -94,24 +184,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
public sealed override string NullDisplayText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_nullDisplayTextComputed)
|
||||
{
|
||||
_nullDisplayText = ComputeNullDisplayText();
|
||||
_nullDisplayTextComputed = true;
|
||||
}
|
||||
return _nullDisplayText;
|
||||
}
|
||||
set
|
||||
{
|
||||
_nullDisplayText = value;
|
||||
_nullDisplayTextComputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string DataTypeName
|
||||
{
|
||||
|
|
@ -133,6 +205,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string Description
|
||||
{
|
||||
get
|
||||
|
|
@ -172,6 +245,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string DisplayName
|
||||
{
|
||||
get
|
||||
|
|
@ -275,6 +349,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsReadOnly
|
||||
{
|
||||
get
|
||||
|
|
@ -293,6 +368,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsRequired
|
||||
{
|
||||
get
|
||||
|
|
@ -311,6 +387,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsComplexType
|
||||
{
|
||||
get
|
||||
|
|
@ -324,6 +401,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string NullDisplayText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_nullDisplayTextComputed)
|
||||
{
|
||||
_nullDisplayText = ComputeNullDisplayText();
|
||||
_nullDisplayTextComputed = true;
|
||||
}
|
||||
return _nullDisplayText;
|
||||
}
|
||||
set
|
||||
{
|
||||
_nullDisplayText = value;
|
||||
_nullDisplayTextComputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool ShowForDisplay
|
||||
{
|
||||
get
|
||||
|
|
@ -342,6 +439,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool ShowForEdit
|
||||
{
|
||||
get
|
||||
|
|
@ -377,16 +475,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
protected TPrototypeCache PrototypeCache { get; set; }
|
||||
|
||||
protected virtual IBinderMetadata ComputeBinderMetadata()
|
||||
{
|
||||
return base.BinderMetadata;
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<string> ComputeBinderIncludeProperties()
|
||||
{
|
||||
return base.BinderIncludeProperties;
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<string> ComputeBinderExcludeProperties()
|
||||
{
|
||||
return base.BinderExcludeProperties;
|
||||
}
|
||||
|
||||
protected virtual string ComputeBinderModelNamePrefix()
|
||||
{
|
||||
return base.BinderModelName;
|
||||
}
|
||||
|
||||
protected virtual bool ComputeConvertEmptyStringToNull()
|
||||
{
|
||||
return base.ConvertEmptyStringToNull;
|
||||
}
|
||||
|
||||
protected virtual string ComputeNullDisplayText()
|
||||
{
|
||||
return base.NullDisplayText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the <see cref="DataTypeName"/> value.
|
||||
/// </summary>
|
||||
|
|
@ -466,6 +579,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return base.IsComplexType;
|
||||
}
|
||||
|
||||
protected virtual string ComputeNullDisplayText()
|
||||
{
|
||||
return base.NullDisplayText;
|
||||
}
|
||||
|
||||
protected virtual bool ComputeShowForDisplay()
|
||||
{
|
||||
return base.ShowForDisplay;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
|
||||
{
|
||||
protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(
|
||||
IEnumerable<Attribute> attributes,
|
||||
IEnumerable<object> attributes,
|
||||
Type containerType,
|
||||
Type modelType,
|
||||
string propertyName)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
public class EmptyModelMetadataProvider : AssociatedMetadataProvider<ModelMetadata>
|
||||
{
|
||||
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes,
|
||||
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<object> attributes,
|
||||
Type containerType,
|
||||
Type modelType,
|
||||
string propertyName)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
ModelMetadata GetMetadataForParameter(
|
||||
Func<object> modelAccessor,
|
||||
[NotNull] MethodInfo methodInfo,
|
||||
[NotNull] string parameterName,
|
||||
IBinderMetadata binderMetadata);
|
||||
[NotNull] string parameterName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides static methods which can be used to get a combined list of attributes associated
|
||||
/// with a parameter or property.
|
||||
/// </summary>
|
||||
public static class ModelAttributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the attributes for the given <paramref name="parameter"/>.
|
||||
/// </summary>
|
||||
/// <param name="parameter">A <see cref="ParameterInfo"/> for which attributes need to be resolved.
|
||||
/// </param>
|
||||
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
|
||||
/// <paramref name="parameter"/> before the attributes on the <paramref name="parameter"/> type.</returns>
|
||||
public static IEnumerable<object> GetAttributesForParameter(ParameterInfo parameter)
|
||||
{
|
||||
// Return the parameter attributes first.
|
||||
var parameterAttributes = parameter.GetCustomAttributes();
|
||||
var typeAttributes = parameter.ParameterType.GetTypeInfo().GetCustomAttributes();
|
||||
|
||||
return parameterAttributes.Concat(typeAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attributes for the given <paramref name="property"/>.
|
||||
/// </summary>
|
||||
/// <param name="property">A <see cref="ParameterInfo"/> for which attributes need to be resolved.
|
||||
/// </param>
|
||||
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
|
||||
/// <paramref name="property"/> before the attributes on the <paramref name="property"/> type.</returns>
|
||||
public static IEnumerable<object> GetAttributesForProperty(PropertyInfo property)
|
||||
{
|
||||
// Return the property attributes first.
|
||||
var propertyAttributes = property.GetCustomAttributes();
|
||||
var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes();
|
||||
|
||||
return propertyAttributes.Concat(typeAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,22 +50,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// <summary>
|
||||
/// Represents the name of a model if specified explicitly using <see cref="IModelNameProvider"/>.
|
||||
/// </summary>
|
||||
public string ModelName { get; set; }
|
||||
public virtual string BinderModelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties which are marked as Included for this model.
|
||||
/// Properties which are to be included while binding this model.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> IncludedProperties { get; set; }
|
||||
public virtual IReadOnlyList<string> BinderIncludeProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties which are marked as Excluded for this model.
|
||||
/// Properties which are to be excluded while binding this model.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ExcludedProperties { get; set; }
|
||||
public virtual IReadOnlyList<string> BinderExcludeProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a binder metadata for this model.
|
||||
/// </summary>
|
||||
public IBinderMetadata BinderMetadata { get; set; }
|
||||
public virtual IBinderMetadata BinderMetadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the model's container <see cref="object"/>.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
|
@ -23,30 +24,115 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
{
|
||||
}
|
||||
|
||||
public void ParameterHasFieldPrefix([Bind(Prefix = "bar")] string foo)
|
||||
public void ParameterWithNoBindAttribute(MySimpleModelWithTypeBasedBind parameter)
|
||||
{
|
||||
}
|
||||
|
||||
public void ParameterHasEmptyFieldPrefix([Bind(Prefix = "")] MySimpleModel foo,
|
||||
[Bind(Prefix = "")] MySimpleModelWithTypeBasedBind foo1)
|
||||
public void ParameterHasFieldPrefix([Bind(Prefix = "simpleModelPrefix")] string parameter)
|
||||
{
|
||||
}
|
||||
|
||||
public void ParameterHasPrefixAndComplexType([Bind(Prefix = "bar")] MySimpleModel foo,
|
||||
[Bind(Prefix = "bar")] MySimpleModelWithTypeBasedBind foo1)
|
||||
public void ParameterHasEmptyFieldPrefix([Bind(Prefix = "")] MySimpleModel parameter,
|
||||
[Bind(Prefix = "")] MySimpleModelWithTypeBasedBind parameter1)
|
||||
{
|
||||
}
|
||||
|
||||
public void ParameterHasEmptyBindAttribute([Bind] MySimpleModel foo,
|
||||
[Bind] MySimpleModelWithTypeBasedBind foo1)
|
||||
public void ParameterHasPrefixAndComplexType(
|
||||
[Bind(Prefix = "simpleModelPrefix")] MySimpleModel parameter,
|
||||
[Bind(Prefix = "simpleModelPrefix")] MySimpleModelWithTypeBasedBind parameter1)
|
||||
{
|
||||
}
|
||||
|
||||
public void ParameterHasEmptyBindAttribute([Bind] MySimpleModel parameter,
|
||||
[Bind] MySimpleModelWithTypeBasedBind parameter1)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetModelBindingContext_DoesNotReturn_ExcludedProperties()
|
||||
{
|
||||
// Arrange
|
||||
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
|
||||
Mock.Of<ActionDescriptor>());
|
||||
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var modelMetadata = metadataProvider.GetMetadataForType(
|
||||
modelAccessor: null, modelType: typeof(TypeWithExcludedPropertiesUsingBindAttribute));
|
||||
|
||||
var actionBindingContext = new ActionBindingContext(actionContext,
|
||||
Mock.Of<IModelMetadataProvider>(),
|
||||
Mock.Of<IModelBinder>(),
|
||||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
// Act
|
||||
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
|
||||
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
|
||||
// Assert
|
||||
Assert.False(context.PropertyFilter("Excluded1"));
|
||||
Assert.False(context.PropertyFilter("Excluded2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetModelBindingContext_ReturnsOnlyWhiteListedProperties_UsingBindAttributeInclude()
|
||||
{
|
||||
// Arrange
|
||||
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
|
||||
Mock.Of<ActionDescriptor>());
|
||||
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var modelMetadata = metadataProvider.GetMetadataForType(
|
||||
modelAccessor: null, modelType: typeof(TypeWithIncludedPropertiesUsingBindAttribute));
|
||||
|
||||
var actionBindingContext = new ActionBindingContext(actionContext,
|
||||
Mock.Of<IModelMetadataProvider>(),
|
||||
Mock.Of<IModelBinder>(),
|
||||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
// Act
|
||||
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
|
||||
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
|
||||
// Assert
|
||||
Assert.True(context.PropertyFilter("IncludedExplicitly1"));
|
||||
Assert.True(context.PropertyFilter("IncludedExplicitly2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetModelBindingContext_UsesBindAttributeOnType_IfNoBindAttributeOnParameter_ForPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(ControllerActionArgumentBinderTests);
|
||||
var methodInfo = type.GetMethod("ParameterWithNoBindAttribute");
|
||||
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
|
||||
Mock.Of<ActionDescriptor>());
|
||||
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "parameter");
|
||||
|
||||
var actionBindingContext = new ActionBindingContext(actionContext,
|
||||
Mock.Of<IModelMetadataProvider>(),
|
||||
Mock.Of<IModelBinder>(),
|
||||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
// Act
|
||||
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
|
||||
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TypePrefix", context.ModelName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("ParameterHasFieldPrefix", false, "bar")]
|
||||
[InlineData("ParameterHasFieldPrefix", false, "simpleModelPrefix")]
|
||||
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
|
||||
[InlineData("ParameterHasPrefixAndComplexType", false, "bar")]
|
||||
[InlineData("ParameterHasEmptyBindAttribute", true, "foo")]
|
||||
[InlineData("ParameterHasPrefixAndComplexType", false, "simpleModelPrefix")]
|
||||
[InlineData("ParameterHasEmptyBindAttribute", true, "parameter")]
|
||||
public void GetModelBindingContext_ModelBindingContextIsSetWithModelName_ForParameters(
|
||||
string actionMethodName, bool expectedFallToEmptyPrefix, string expectedModelName)
|
||||
{
|
||||
|
|
@ -59,8 +145,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "foo",
|
||||
binderMetadata: null);
|
||||
parameterName: "parameter");
|
||||
|
||||
|
||||
var actionBindingContext = new ActionBindingContext(actionContext,
|
||||
|
|
@ -70,8 +155,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
// Act
|
||||
var context = DefaultControllerActionArgumentBinder
|
||||
.GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
|
||||
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
||||
|
|
@ -80,8 +165,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
|
||||
[Theory]
|
||||
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
|
||||
[InlineData("ParameterHasPrefixAndComplexType", false, "bar")]
|
||||
[InlineData("ParameterHasEmptyBindAttribute", true, "foo1")]
|
||||
[InlineData("ParameterHasPrefixAndComplexType", false, "simpleModelPrefix")]
|
||||
[InlineData("ParameterHasEmptyBindAttribute", true, "parameter1")]
|
||||
public void GetModelBindingContext_ModelBindingContextIsNotSet_ForTypes(
|
||||
string actionMethodName, bool expectedFallToEmptyPrefix, string expectedModelName)
|
||||
{
|
||||
|
|
@ -94,8 +179,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "foo1",
|
||||
binderMetadata: null);
|
||||
parameterName: "parameter1");
|
||||
|
||||
|
||||
var actionBindingContext = new ActionBindingContext(actionContext,
|
||||
|
|
@ -105,8 +189,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
// Act
|
||||
var context = DefaultControllerActionArgumentBinder
|
||||
.GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
|
||||
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
||||
|
|
@ -233,7 +317,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private class NonValueProviderBinderMetadataAttribute : Attribute, IBinderMetadata
|
||||
{
|
||||
}
|
||||
|
|
@ -241,5 +324,29 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
private class ValueProviderMetadataAttribute : Attribute, IValueProviderMetadata
|
||||
{
|
||||
}
|
||||
|
||||
[Bind(Exclude = nameof(Excluded1) + "," + nameof(Excluded2))]
|
||||
private class TypeWithExcludedPropertiesUsingBindAttribute
|
||||
{
|
||||
public int Excluded1 { get; set; }
|
||||
|
||||
public int Excluded2 { get; set; }
|
||||
|
||||
public int IncludedByDefault1 { get; set; }
|
||||
|
||||
public int IncludedByDefault2 { get; set; }
|
||||
}
|
||||
|
||||
[Bind(Include = nameof(IncludedExplicitly1) + "," + nameof(IncludedExplicitly2))]
|
||||
private class TypeWithIncludedPropertiesUsingBindAttribute
|
||||
{
|
||||
public int ExcludedByDefault1 { get; set; }
|
||||
|
||||
public int ExcludedByDefault2 { get; set; }
|
||||
|
||||
public int IncludedExplicitly1 { get; set; }
|
||||
|
||||
public int IncludedExplicitly2 { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
};
|
||||
|
||||
bindingContext.ModelBindingContext.ModelMetadata.ModelName = isPrefixProvided ? "prefix" : null;
|
||||
bindingContext.ModelBindingContext.ModelMetadata.BinderModelName = isPrefixProvided ? "prefix" : null;
|
||||
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||
bindingContext.ModelBindingContext);
|
||||
|
|
@ -603,79 +603,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
|
||||
}
|
||||
|
||||
[Bind(Exclude = nameof(Excluded1) + "," + nameof(Excluded2))]
|
||||
private class TypeWithExcludedPropertiesUsingBindAttribute
|
||||
{
|
||||
public int Excluded1 { get; set; }
|
||||
|
||||
public int Excluded2 { get; set; }
|
||||
|
||||
public int IncludedByDefault1 { get; set; }
|
||||
public int IncludedByDefault2 { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForProperties_DoesNotReturn_ExcludedProperties()
|
||||
{
|
||||
// Arrange
|
||||
var expectedPropertyNames = new[] { "IncludedByDefault1", "IncludedByDefault2" };
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||
}
|
||||
};
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
|
||||
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
|
||||
}
|
||||
|
||||
[Bind(Include = nameof(IncludedExplicitly1) + "," + nameof(IncludedExplicitly2))]
|
||||
private class TypeWithIncludedPropertiesUsingBindAttribute
|
||||
{
|
||||
public int ExcludedByDefault1 { get; set; }
|
||||
|
||||
public int ExcludedByDefault2 { get; set; }
|
||||
|
||||
public int IncludedExplicitly1 { get; set; }
|
||||
|
||||
public int IncludedExplicitly2 { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForProperties_ReturnsOnlyWhiteListedProperties_UsingBindAttributeInclude()
|
||||
{
|
||||
// Arrange
|
||||
var expectedPropertyNames = new[] { "IncludedExplicitly1", "IncludedExplicitly2" };
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(TypeWithIncludedPropertiesUsingBindAttribute)),
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||
}
|
||||
};
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
|
||||
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRequiredPropertiesCollection_MixedAttributes()
|
||||
{
|
||||
|
|
@ -1274,8 +1201,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return metadataProvider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: parameterName,
|
||||
binderMetadata: null);
|
||||
parameterName: parameterName);
|
||||
}
|
||||
|
||||
private class EmptyModel
|
||||
|
|
|
|||
|
|
@ -96,49 +96,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForProperty_WithNoBinderMetadata_GetsItFromType()
|
||||
public void GetMetadataForParameterNullOrEmptyPropertyNameThrows()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
var provider = new TestableAssociatedMetadataProvider();
|
||||
|
||||
// Act
|
||||
var propertyMetadata = provider.GetMetadataForProperty(null, typeof(Person), nameof(Person.Parent));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(propertyMetadata.BinderMetadata);
|
||||
Assert.IsType<TestBinderMetadataAttribute>(propertyMetadata.BinderMetadata);
|
||||
}
|
||||
|
||||
#if ASPNET50
|
||||
[Fact]
|
||||
public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var parameterMetadata = provider.GetMetadataForParameter(null,
|
||||
typeof(Person).GetMethod("Update"),
|
||||
"person",
|
||||
null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(parameterMetadata.BinderMetadata);
|
||||
Assert.IsType<TestBinderMetadataAttribute>(parameterMetadata.BinderMetadata);
|
||||
}
|
||||
#endif
|
||||
public class TestBinderMetadataAttribute : Attribute, IBinderMetadata
|
||||
{
|
||||
}
|
||||
|
||||
[TestBinderMetadata]
|
||||
public class Person
|
||||
{
|
||||
public Person Parent { get; set; }
|
||||
|
||||
public void Update(Person person)
|
||||
{
|
||||
}
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgumentNullOrEmpty(
|
||||
() => provider.GetMetadataForParameter(modelAccessor: null, methodInfo: null, parameterName: null),
|
||||
"parameterName");
|
||||
ExceptionAssert.ThrowsArgumentNullOrEmpty(
|
||||
() => provider.GetMetadataForParameter(modelAccessor: null, methodInfo: null, parameterName: null),
|
||||
"parameterName");
|
||||
}
|
||||
|
||||
// GetMetadataForProperty
|
||||
|
|
@ -310,7 +279,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public ModelMetadata CreateMetadataPrototypeReturnValue = null;
|
||||
public ModelMetadata CreateMetadataFromPrototypeReturnValue = null;
|
||||
|
||||
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
|
||||
protected override ModelMetadata CreateMetadataPrototype(IEnumerable<object> attributes, Type containerType, Type modelType, string propertyName)
|
||||
{
|
||||
CreateMetadataPrototypeLog.Add(new CreateMetadataPrototypeParams
|
||||
{
|
||||
|
|
@ -337,7 +306,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
private class CreateMetadataPrototypeParams
|
||||
{
|
||||
public IEnumerable<Attribute> Attributes { get; set; }
|
||||
public IEnumerable<object> Attributes { get; set; }
|
||||
public Type ContainerType { get; set; }
|
||||
public Type ModelType { get; set; }
|
||||
public string PropertyName { get; set; }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
|
@ -14,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public class CachedDataAnnotationsMetadataAttributesTest
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_DefaultsAllPropertiesToNull()
|
||||
public void Constructor_SetsDefaultValuesForAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var attributes = Enumerable.Empty<Attribute>();
|
||||
|
|
@ -31,14 +32,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Null(cache.HiddenInput);
|
||||
Assert.Null(cache.Required);
|
||||
Assert.Null(cache.ScaffoldColumn);
|
||||
Assert.Null(cache.BinderMetadata);
|
||||
Assert.Null(cache.BinderModelNameProvider);
|
||||
Assert.Empty(cache.PropertyBindingInfo);
|
||||
}
|
||||
|
||||
public static TheoryData<Attribute, Func<CachedDataAnnotationsMetadataAttributes, Attribute>>
|
||||
public static TheoryData<object, Func<CachedDataAnnotationsMetadataAttributes, object>>
|
||||
ExpectedAttributeData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<Attribute, Func<CachedDataAnnotationsMetadataAttributes, Attribute>>
|
||||
return new TheoryData<object, Func<CachedDataAnnotationsMetadataAttributes, object>>
|
||||
{
|
||||
{ new DataTypeAttribute(DataType.Duration), cache => cache.DataType },
|
||||
{ new DisplayAttribute(), cache => cache.Display },
|
||||
|
|
@ -47,7 +51,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{ new EditableAttribute(allowEdit: false), cache => cache.Editable },
|
||||
{ new HiddenInputAttribute(), cache => cache.HiddenInput },
|
||||
{ new RequiredAttribute(), cache => cache.Required },
|
||||
{ new ScaffoldColumnAttribute(scaffold: false), cache => cache.ScaffoldColumn },
|
||||
{ new TestBinderMetadata(), cache => cache.BinderMetadata },
|
||||
{ new TestModelNameProvider(), cache => cache.BinderModelNameProvider },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -55,8 +60,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
[Theory]
|
||||
[MemberData(nameof(ExpectedAttributeData))]
|
||||
public void Constructor_FindsExpectedAttribute(
|
||||
Attribute attribute,
|
||||
Func<CachedDataAnnotationsMetadataAttributes, Attribute> accessor)
|
||||
object attribute,
|
||||
Func<CachedDataAnnotationsMetadataAttributes, object> accessor)
|
||||
{
|
||||
// Arrange
|
||||
var attributes = new[] { attribute };
|
||||
|
|
@ -69,6 +74,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Same(attribute, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_FindsPropertyBindingInfo()
|
||||
{
|
||||
// Arrange
|
||||
var propertyBindingInfos =
|
||||
new[] { new TestPropertyBindingInfo(), new TestPropertyBindingInfo() };
|
||||
|
||||
// Act
|
||||
var cache = new CachedDataAnnotationsMetadataAttributes(propertyBindingInfos);
|
||||
var result = cache.PropertyBindingInfo.ToArray();
|
||||
|
||||
// Assert
|
||||
for (var index = 0; index < propertyBindingInfos.Length; index++)
|
||||
{
|
||||
Assert.Same(propertyBindingInfos[index], result[index]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_FindsDisplayFormat_FromDataType()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
|
@ -45,12 +46,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var metadata = provider.GetMetadataForType(null, type);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.IncludedProperties);
|
||||
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.ExcludedProperties);
|
||||
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties);
|
||||
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_OnlyAtParameterLevel_ForParameters()
|
||||
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_AtParameterAndType_ForParameters()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||
|
|
@ -58,20 +59,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Note it does an intersection for included and a union for excluded.
|
||||
var expectedIncludedPropertyNames = new[] { "Property1", "Property2", "IncludedAndExcludedExplicitly1" };
|
||||
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1" };
|
||||
var expectedExcludedPropertyNames = new[] {
|
||||
"Property3", "Property4", "IncludedAndExcludedExplicitly1" };
|
||||
"Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "param",
|
||||
binderMetadata: null);
|
||||
parameterName: "param");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.IncludedProperties);
|
||||
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.ExcludedProperties);
|
||||
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.BinderIncludeProperties);
|
||||
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.BinderExcludeProperties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -86,11 +86,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var metadata = provider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "param",
|
||||
binderMetadata: null);
|
||||
parameterName: "param");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ParameterPrefix", metadata.ModelName);
|
||||
Assert.Equal("ParameterPrefix", metadata.BinderModelName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -104,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var metadata = provider.GetMetadataForType(null, type);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TypePrefix", metadata.ModelName);
|
||||
Assert.Equal("TypePrefix", metadata.BinderModelName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -119,11 +118,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var metadata = provider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "param",
|
||||
binderMetadata: null);
|
||||
parameterName: "param");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ParameterPrefix", metadata.ModelName);
|
||||
Assert.Equal("ParameterPrefix", metadata.BinderModelName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -167,8 +165,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.True(result);
|
||||
}
|
||||
|
||||
// TODO https://github.com/aspnet/Mvc/issues/1000
|
||||
// Enable test once we detect attributes on the property's type
|
||||
[Fact]
|
||||
public void HiddenInputWorksOnPropertyType()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -183,6 +180,119 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForProperty_WithNoBinderMetadata_GetsItFromType()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var propertyMetadata = provider.GetMetadataForProperty(null, typeof(Person), nameof(Person.Parent));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(propertyMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<TypeBasedBinderAttribute>(propertyMetadata.BinderMetadata);
|
||||
Assert.Equal("PersonType", propertyMetadata.BinderModelName);
|
||||
Assert.Equal(new[] { "IncludeAtType" }, propertyMetadata.BinderIncludeProperties.ToArray());
|
||||
Assert.Equal(new[] { "ExcludeAtType" }, propertyMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForProperty_WithBinderMetadataOnPropertyAndType_GetsMetadataFromProperty()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var propertyMetadata = provider.GetMetadataForProperty(null, typeof(Person), nameof(Person.GrandParent));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(propertyMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(propertyMetadata.BinderMetadata);
|
||||
Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName);
|
||||
Assert.Empty(propertyMetadata.BinderIncludeProperties);
|
||||
Assert.Equal(new[] { "ExcludeAtProperty", "ExcludeAtType" },
|
||||
propertyMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
|
||||
#if ASPNET50
|
||||
[Fact]
|
||||
public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var parameterMetadata = provider.GetMetadataForParameter(null,
|
||||
typeof(Person).GetMethod("Update"),
|
||||
"person");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(parameterMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<TypeBasedBinderAttribute>(parameterMetadata.BinderMetadata);
|
||||
Assert.Equal("PersonType", parameterMetadata.BinderModelName);
|
||||
Assert.Equal(new[] { "IncludeAtType" }, parameterMetadata.BinderIncludeProperties.ToArray());
|
||||
Assert.Equal(new[] { "ExcludeAtType" }, parameterMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForParameter_WithBinderDataOnParameterAndType_GetsMetadataFromParameter()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var parameterMetadata = provider.GetMetadataForParameter(null,
|
||||
typeof(Person).GetMethod("Save"),
|
||||
"person");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(parameterMetadata.BinderMetadata);
|
||||
var attribute = Assert.IsType<NonTypeBasedBinderAttribute>(parameterMetadata.BinderMetadata);
|
||||
Assert.Equal("PersonParameter", parameterMetadata.BinderModelName);
|
||||
Assert.Empty(parameterMetadata.BinderIncludeProperties);
|
||||
Assert.Equal(new[] { "ExcludeAtParameter", "ExcludeAtType" },
|
||||
parameterMetadata.BinderExcludeProperties.ToArray());
|
||||
}
|
||||
#endif
|
||||
public class TypeBasedBinderAttribute : Attribute,
|
||||
IBinderMetadata, IModelNameProvider, IPropertyBindingInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Exclude { get; set; }
|
||||
|
||||
public string Include { get; set; }
|
||||
}
|
||||
|
||||
public class NonTypeBasedBinderAttribute : Attribute,
|
||||
IBinderMetadata, IModelNameProvider, IPropertyBindingInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Exclude { get; set; }
|
||||
|
||||
public string Include { get; set; }
|
||||
}
|
||||
|
||||
[TypeBasedBinder(Name = "PersonType", Include = "IncludeAtType", Exclude = "ExcludeAtType")]
|
||||
public class Person
|
||||
{
|
||||
public Person Parent { get; set; }
|
||||
|
||||
[NonTypeBasedBinder(Name = "GrandParentProperty", Include = "IncludeAtProperty", Exclude = "ExcludeAtProperty")]
|
||||
public Person GrandParent { get; set; }
|
||||
|
||||
public void Update(Person person)
|
||||
{
|
||||
}
|
||||
|
||||
public void Save([NonTypeBasedBinder(Name = "PersonParameter",
|
||||
Include = "IncludeAtParameter", Exclude = "ExcludeAtParameter")] Person person)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class ScaffoldColumnModel
|
||||
{
|
||||
public int NoAttribute { get; set; }
|
||||
|
|
|
|||
|
|
@ -49,13 +49,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Null(metadata.TemplateHint);
|
||||
|
||||
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);
|
||||
|
||||
Assert.Null(metadata.BinderModelName);
|
||||
Assert.Null(metadata.BinderMetadata);
|
||||
Assert.Empty(metadata.BinderIncludeProperties);
|
||||
Assert.Null(metadata.BinderExcludeProperties);
|
||||
}
|
||||
|
||||
public static TheoryData<Attribute, Func<ModelMetadata, string>> ExpectedAttributeDataStrings
|
||||
public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeDataStrings
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<Attribute, Func<ModelMetadata, string>>
|
||||
return new TheoryData<object, Func<ModelMetadata, string>>
|
||||
{
|
||||
{
|
||||
new DataTypeAttribute("value"), metadata => metadata.DataTypeName
|
||||
|
|
@ -91,13 +96,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
new DisplayFormatAttribute { NullDisplayText = "value" }, metadata => metadata.NullDisplayText
|
||||
},
|
||||
{
|
||||
new TestModelNameProvider() { Name = "value" }, metadata => metadata.BinderModelName
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ExpectedAttributeDataStrings))]
|
||||
public void AttributesOverrideMetadataStrings(Attribute attribute, Func<ModelMetadata, string> accessor)
|
||||
public void AttributesOverrideMetadataStrings(object attribute, Func<ModelMetadata, string> accessor)
|
||||
{
|
||||
// Arrange
|
||||
var attributes = new[] { attribute };
|
||||
|
|
@ -221,6 +229,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinderMetadataIfPresent_Overrides_DefaultBinderMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var firstBinderMetadata = new TestBinderMetadata();
|
||||
var secondBinderMetadata = new TestBinderMetadata();
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
var metadata = new CachedDataAnnotationsModelMetadata(
|
||||
provider,
|
||||
containerType: null,
|
||||
modelType: typeof(object),
|
||||
propertyName: null,
|
||||
attributes: new object[] { firstBinderMetadata, secondBinderMetadata });
|
||||
|
||||
// Act
|
||||
var result = metadata.BinderMetadata;
|
||||
|
||||
// Assert
|
||||
Assert.Same(firstBinderMetadata, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataTypeName_Null_IfHtmlEncodeTrue()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var contactModel = new DummyContactModel { FirstName = "test" };
|
||||
var nonEmptycontainerModel = new DummyModelContainer { Model = contactModel };
|
||||
|
||||
var binderMetadata = new TestBinderMetadata();
|
||||
var emptyPropertyList = new List<string>();
|
||||
var nonEmptyPropertyList = new List<string>() { "SomeProperty" };
|
||||
return new TheoryData<Action<ModelMetadata>, Func<ModelMetadata, object>, object>
|
||||
{
|
||||
{ m => m.ConvertEmptyStringToNull = false, m => m.ConvertEmptyStringToNull, false },
|
||||
|
|
@ -45,21 +48,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{ m => m.Container = null, m => m.Container, null },
|
||||
{ m => m.Container = emptycontainerModel, m => m.Container, emptycontainerModel },
|
||||
{ m => m.Container = nonEmptycontainerModel, m => m.Container, nonEmptycontainerModel },
|
||||
|
||||
{ m => m.BinderMetadata = null, m => m.BinderMetadata, null },
|
||||
{ m => m.BinderMetadata = binderMetadata, m => m.BinderMetadata, binderMetadata },
|
||||
{ m => m.BinderModelName = null, m => m.BinderModelName, null },
|
||||
{ m => m.BinderModelName = "newModelName", m => m.BinderModelName, "newModelName" },
|
||||
{ m => m.BinderModelName = string.Empty, m => m.BinderModelName, string.Empty },
|
||||
{ m => m.BinderIncludeProperties = null, m => m.BinderIncludeProperties, null },
|
||||
{
|
||||
m => m.BinderIncludeProperties = emptyPropertyList,
|
||||
m => m.BinderIncludeProperties,
|
||||
emptyPropertyList
|
||||
},
|
||||
{
|
||||
m => m.BinderIncludeProperties = nonEmptyPropertyList,
|
||||
m => m.BinderIncludeProperties,
|
||||
nonEmptyPropertyList
|
||||
},
|
||||
{ m => m.BinderExcludeProperties = null, m => m.BinderExcludeProperties, null },
|
||||
{
|
||||
m => m.BinderExcludeProperties = emptyPropertyList,
|
||||
m => m.BinderExcludeProperties,
|
||||
emptyPropertyList
|
||||
},
|
||||
{
|
||||
m => m.BinderExcludeProperties = nonEmptyPropertyList,
|
||||
m => m.BinderExcludeProperties,
|
||||
nonEmptyPropertyList
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#if ASPNET50
|
||||
// Constructor
|
||||
|
||||
[Fact]
|
||||
public void DefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var metadata = new ModelMetadata(provider.Object, typeof(Exception), () => "model", typeof(string), "propertyName");
|
||||
var metadata =
|
||||
new ModelMetadata(provider, typeof(Exception), () => "model", typeof(string), "propertyName");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(Exception), metadata.ContainerType);
|
||||
|
|
@ -91,8 +122,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal("propertyName", metadata.PropertyName);
|
||||
|
||||
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);
|
||||
|
||||
Assert.Null(metadata.BinderModelName);
|
||||
Assert.Null(metadata.BinderMetadata);
|
||||
Assert.Null(metadata.BinderIncludeProperties);
|
||||
Assert.Null(metadata.BinderExcludeProperties);
|
||||
}
|
||||
#endif
|
||||
|
||||
// IsComplexType
|
||||
|
||||
|
|
@ -100,7 +135,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
}
|
||||
|
||||
#if ASPNET50
|
||||
[Theory]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(Nullable<int>))]
|
||||
|
|
@ -108,10 +142,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void IsComplexTypeTestsReturnsFalseForSimpleTypes(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var modelMetadata = new ModelMetadata(provider.Object, null, null, type, null);
|
||||
var modelMetadata = new ModelMetadata(provider, null, null, type, null);
|
||||
|
||||
// Assert
|
||||
Assert.False(modelMetadata.IsComplexType);
|
||||
|
|
@ -125,10 +159,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void IsComplexTypeTestsReturnsTrueForComplexTypes(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var modelMetadata = new ModelMetadata(provider.Object, null, null, type, null);
|
||||
var modelMetadata = new ModelMetadata(provider, null, null, type, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelMetadata.IsComplexType);
|
||||
|
|
@ -140,13 +174,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void IsNullableValueTypeTests()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(new ModelMetadata(provider.Object, null, null, typeof(string), null).IsNullableValueType);
|
||||
Assert.False(new ModelMetadata(provider.Object, null, null, typeof(IDisposable), null).IsNullableValueType);
|
||||
Assert.True(new ModelMetadata(provider.Object, null, null, typeof(Nullable<int>), null).IsNullableValueType);
|
||||
Assert.False(new ModelMetadata(provider.Object, null, null, typeof(int), null).IsNullableValueType);
|
||||
Assert.False(new ModelMetadata(provider, null, null, typeof(string), null).IsNullableValueType);
|
||||
Assert.False(new ModelMetadata(provider, null, null, typeof(IDisposable), null).IsNullableValueType);
|
||||
Assert.True(new ModelMetadata(provider, null, null, typeof(Nullable<int>), null).IsNullableValueType);
|
||||
Assert.False(new ModelMetadata(provider, null, null, typeof(int), null).IsNullableValueType);
|
||||
}
|
||||
|
||||
// IsRequired
|
||||
|
|
@ -158,8 +192,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void IsRequired_ReturnsFalse_ForNullableTypes(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var metadata = new ModelMetadata(provider.Object,
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(provider,
|
||||
containerType: null,
|
||||
modelAccessor: null,
|
||||
modelType: modelType,
|
||||
|
|
@ -178,8 +212,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void IsRequired_ReturnsTrue_ForNonNullableTypes(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var metadata = new ModelMetadata(provider.Object,
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(provider,
|
||||
containerType: null,
|
||||
modelAccessor: null,
|
||||
modelType: modelType,
|
||||
|
|
@ -193,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
// Properties
|
||||
|
||||
#if ASPNET50
|
||||
[Fact]
|
||||
public void PropertiesCallsProvider()
|
||||
{
|
||||
|
|
@ -215,6 +249,61 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
#endif
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MetadataModifierData))]
|
||||
public void PropertiesPropertyChangesPersist(
|
||||
Action<ModelMetadata> setter,
|
||||
Func<ModelMetadata, object> getter,
|
||||
object expected)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(
|
||||
provider,
|
||||
containerType: null,
|
||||
modelAccessor: () => new Class1(),
|
||||
modelType: typeof(Class1),
|
||||
propertyName: null);
|
||||
|
||||
// Act
|
||||
foreach (var property in metadata.Properties)
|
||||
{
|
||||
setter(property);
|
||||
}
|
||||
|
||||
// Assert
|
||||
foreach (var property in metadata.Properties)
|
||||
{
|
||||
// Due to boxing of structs, can't Assert.Same().
|
||||
Assert.Equal(expected, getter(property));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MetadataModifierData))]
|
||||
public void PropertyChangesPersist(
|
||||
Action<ModelMetadata> setter,
|
||||
Func<ModelMetadata, object> getter,
|
||||
object expected)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(
|
||||
provider,
|
||||
containerType: null,
|
||||
modelAccessor: () => new Class1(),
|
||||
modelType: typeof(Class1),
|
||||
propertyName: null);
|
||||
|
||||
// Act
|
||||
setter(metadata);
|
||||
var result = getter(metadata);
|
||||
|
||||
// Assert
|
||||
// Due to boxing of structs, can't Assert.Same().
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertiesListGetsResetWhenModelGetsReset()
|
||||
{
|
||||
|
|
@ -278,36 +367,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal(firstPropertiesEvaluation, secondPropertiesEvaluation);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MetadataModifierData))]
|
||||
public void PropertiesPropertyChangesPersist(
|
||||
Action<ModelMetadata> setter,
|
||||
Func<ModelMetadata, object> getter,
|
||||
object expected)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(
|
||||
provider,
|
||||
containerType: null,
|
||||
modelAccessor: () => new Class1(),
|
||||
modelType: typeof(Class1),
|
||||
propertyName: null);
|
||||
|
||||
// Act
|
||||
foreach (var property in metadata.Properties)
|
||||
{
|
||||
setter(property);
|
||||
}
|
||||
|
||||
// Assert
|
||||
foreach (var property in metadata.Properties)
|
||||
{
|
||||
// Due to boxing of structs, can't Assert.Same().
|
||||
Assert.Equal(expected, getter(property));
|
||||
}
|
||||
}
|
||||
|
||||
private class Class1
|
||||
{
|
||||
public string Prop1 { get; set; }
|
||||
|
|
@ -341,13 +400,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal("displayName", result);
|
||||
}
|
||||
|
||||
#if ASPNET50
|
||||
[Fact]
|
||||
public void ReturnsPropertyNameWhenSetAndDisplayNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var metadata = new ModelMetadata(provider.Object, null, null, typeof(object), "PropertyName");
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(provider, null, null, typeof(object), "PropertyName");
|
||||
|
||||
// Act
|
||||
var result = metadata.GetDisplayName();
|
||||
|
|
@ -360,8 +418,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public void ReturnsTypeNameWhenPropertyNameAndDisplayNameAreNull()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new Mock<IModelMetadataProvider>();
|
||||
var metadata = new ModelMetadata(provider.Object, null, null, typeof(object), null);
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(provider, null, null, typeof(object), null);
|
||||
|
||||
// Act
|
||||
var result = metadata.GetDisplayName();
|
||||
|
|
@ -369,7 +427,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Assert
|
||||
Assert.Equal("Object", result);
|
||||
}
|
||||
#endif
|
||||
|
||||
// SimpleDisplayText
|
||||
|
||||
|
|
@ -435,33 +492,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public Class1 Prop1 { get; set; }
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MetadataModifierData))]
|
||||
public void PropertyChangesPersist(
|
||||
Action<ModelMetadata> setter,
|
||||
Func<ModelMetadata, object> getter,
|
||||
object expected)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = new ModelMetadata(
|
||||
provider,
|
||||
containerType: null,
|
||||
modelAccessor: () => new Class1(),
|
||||
modelType: typeof(Class1),
|
||||
propertyName: null);
|
||||
|
||||
// Act
|
||||
setter(metadata);
|
||||
var result = getter(metadata);
|
||||
|
||||
// Assert
|
||||
// Due to boxing of structs, can't Assert.Same().
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
private class DummyContactModel
|
||||
{
|
||||
public int IntField = 0;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue