Adding support for bind attribute.
This commit is contained in:
parent
c0d8ca8aed
commit
75405e3b76
|
|
@ -16,14 +16,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultControllerActionArgumentBinder : IControllerActionArgumentBinder
|
public class DefaultControllerActionArgumentBinder : IControllerActionArgumentBinder
|
||||||
{
|
{
|
||||||
private readonly IBodyModelValidator _modelValidator;
|
|
||||||
private readonly IActionBindingContextProvider _bindingContextProvider;
|
private readonly IActionBindingContextProvider _bindingContextProvider;
|
||||||
|
|
||||||
public DefaultControllerActionArgumentBinder(IActionBindingContextProvider bindingContextProvider,
|
public DefaultControllerActionArgumentBinder(IActionBindingContextProvider bindingContextProvider)
|
||||||
IBodyModelValidator modelValidator)
|
|
||||||
{
|
{
|
||||||
_bindingContextProvider = bindingContextProvider;
|
_bindingContextProvider = bindingContextProvider;
|
||||||
_modelValidator = modelValidator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IDictionary<string, object>> GetActionArgumentsAsync(ActionContext actionContext)
|
public async Task<IDictionary<string, object>> GetActionArgumentsAsync(ActionContext actionContext)
|
||||||
|
|
@ -49,8 +46,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PopulateActionArgumentsAsync(IEnumerable<ModelMetadata> modelMetadatas,
|
private async Task PopulateActionArgumentsAsync(IEnumerable<ModelMetadata> modelMetadatas,
|
||||||
ActionBindingContext actionBindingContext,
|
ActionBindingContext actionBindingContext,
|
||||||
IDictionary<string, object> invocationInfo)
|
IDictionary<string, object> invocationInfo)
|
||||||
{
|
{
|
||||||
var bodyBoundParameterCount = modelMetadatas.Count(
|
var bodyBoundParameterCount = modelMetadatas.Count(
|
||||||
modelMetadata => modelMetadata.Marker is IBodyBinderMarker);
|
modelMetadata => modelMetadata.Marker is IBodyBinderMarker);
|
||||||
|
|
@ -61,19 +58,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
foreach (var modelMetadata in modelMetadatas)
|
foreach (var modelMetadata in modelMetadatas)
|
||||||
{
|
{
|
||||||
var parameterType = modelMetadata.ModelType;
|
var modelBindingContext = GetModelBindingContext(modelMetadata, actionBindingContext);
|
||||||
var modelBindingContext = new ModelBindingContext
|
|
||||||
{
|
|
||||||
ModelName = modelMetadata.PropertyName,
|
|
||||||
ModelMetadata = modelMetadata,
|
|
||||||
ModelState = actionBindingContext.ActionContext.ModelState,
|
|
||||||
ModelBinder = actionBindingContext.ModelBinder,
|
|
||||||
ValidatorProvider = actionBindingContext.ValidatorProvider,
|
|
||||||
MetadataProvider = actionBindingContext.MetadataProvider,
|
|
||||||
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
|
||||||
FallbackToEmptyPrefix = true,
|
|
||||||
ValueProvider = actionBindingContext.ValueProvider,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
|
if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
|
||||||
{
|
{
|
||||||
|
|
@ -81,5 +66,25 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata, ActionBindingContext actionBindingContext)
|
||||||
|
{
|
||||||
|
var modelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName,
|
||||||
|
ModelMetadata = modelMetadata,
|
||||||
|
ModelState = actionBindingContext.ActionContext.ModelState,
|
||||||
|
ModelBinder = actionBindingContext.ModelBinder,
|
||||||
|
ValidatorProvider = actionBindingContext.ValidatorProvider,
|
||||||
|
MetadataProvider = actionBindingContext.MetadataProvider,
|
||||||
|
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
||||||
|
|
||||||
|
// Fallback only if there is no explicit model name set.
|
||||||
|
FallbackToEmptyPrefix = modelMetadata.ModelName == null,
|
||||||
|
ValueProvider = actionBindingContext.ValueProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
return modelBindingContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Comma separated set of properties which are to be excluded during model binding.
|
||||||
|
/// </summary>
|
||||||
|
public string Exclude { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Comma separated set of properties which are to be included during model binding.
|
||||||
|
/// </summary>
|
||||||
|
public string Include { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// This property is exposed for back compat reasons.
|
||||||
|
/// <summary>
|
||||||
|
/// Allows a user to specify a particular prefix to match during model binding.
|
||||||
|
/// </summary>
|
||||||
|
public string Prefix { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the model name used during model binding.
|
||||||
|
/// </summary>
|
||||||
|
string IModelNameProvider.Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Prefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,8 +17,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||||
|
|
||||||
if (!CanBindType(bindingContext.ModelType) ||
|
if (!CanBindType(bindingContext.ModelType))
|
||||||
!await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var topLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||||
|
var isThereAnExplicitAlias = bindingContext.ModelMetadata.ModelName != null;
|
||||||
|
|
||||||
|
|
||||||
|
// The first check is necessary because if we fallback to empty prefix, we do not want to depend
|
||||||
|
// on a value provider to provide a value for empty prefix.
|
||||||
|
var containsPrefix = (bindingContext.ModelName == string.Empty && topLevelObject) ||
|
||||||
|
await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName);
|
||||||
|
|
||||||
|
// Always create the model if
|
||||||
|
// 1. It is a top level object and the model name is empty.
|
||||||
|
// 2. There is a value provider which can provide value for the model name.
|
||||||
|
// 3. There is an explicit alias provided by the user and it is a top level object.
|
||||||
|
// The reson we depend on explicit alias is that otherwise we want the FallToEmptyPrefix codepath
|
||||||
|
// to kick in so that empty prefix values could be bound.
|
||||||
|
if (!containsPrefix && !(isThereAnExplicitAlias && topLevelObject))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +168,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||||
return bindingContext.ModelMetadata.Properties
|
return bindingContext.ModelMetadata.Properties
|
||||||
.Where(propertyMetadata =>
|
.Where(propertyMetadata =>
|
||||||
|
IsPropertyAllowed(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));
|
||||||
|
|
@ -349,6 +371,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return addedError;
|
return addedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private 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 exclude list implies no properties are disallowed.
|
||||||
|
var includeProperty = (includeProperties != null) &&
|
||||||
|
includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var excludeProperty = (excludeProperties != null) &&
|
||||||
|
excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return includeProperty && !excludeProperty;
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class PropertyValidationInfo
|
internal sealed class PropertyValidationInfo
|
||||||
{
|
{
|
||||||
public PropertyValidationInfo()
|
public PropertyValidationInfo()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an entity which can provide model name as metadata.
|
||||||
|
/// </summary>
|
||||||
|
public interface IModelNameProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Model name.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an entity which has binding information for a model.
|
||||||
|
/// </summary>
|
||||||
|
public interface IModelPropertyBindingInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Comma separated set of properties which are to be excluded during model binding.
|
||||||
|
/// </summary>
|
||||||
|
string Exclude { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Comma separated set of properties which are to be included during model binding.
|
||||||
|
/// </summary>
|
||||||
|
string Include { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,7 +85,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// Override for applying the prototype + modelAccess to yield the final metadata
|
// Override for applying the prototype + modelAccess to yield the final metadata
|
||||||
protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype,
|
protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype,
|
||||||
Func<object> modelAccessor);
|
Func<object> modelAccessor);
|
||||||
|
|
||||||
private ModelMetadata GetMetadataForParameterCore(Func<object> modelAccessor,
|
private ModelMetadata GetMetadataForParameterCore(Func<object> modelAccessor,
|
||||||
string parameterName,
|
string parameterName,
|
||||||
ParameterInfo parameter)
|
ParameterInfo parameter)
|
||||||
|
|
@ -94,10 +93,61 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
CreateParameterInfo(parameter.ParameterType,
|
CreateParameterInfo(parameter.ParameterType,
|
||||||
parameter.GetCustomAttributes(),
|
parameter.GetCustomAttributes(),
|
||||||
parameterName);
|
parameterName);
|
||||||
var typePrototype = GetTypeInformation(parameter.ParameterType).Prototype;
|
|
||||||
|
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);
|
||||||
|
|
@ -162,8 +212,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
properties.Add(propertyHelper.Name, CreatePropertyInformation(type, propertyHelper));
|
properties.Add(propertyHelper.Name, CreatePropertyInformation(type, propertyHelper));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
new CachedDataAnnotationsMetadataAttributes(attributes))
|
new CachedDataAnnotationsMetadataAttributes(attributes))
|
||||||
{
|
{
|
||||||
Marker = attributes.OfType<IBinderMarker>().FirstOrDefault();
|
Marker = attributes.OfType<IBinderMarker>().FirstOrDefault();
|
||||||
|
|
||||||
|
var modelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
|
||||||
|
ModelName = modelNameProvider?.Name;
|
||||||
|
|
||||||
|
var bindAttribute = attributes.OfType<BindAttribute>().FirstOrDefault();
|
||||||
|
ReadSettingsFromBindAttribute(bindAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool ComputeConvertEmptyStringToNull()
|
protected override bool ComputeConvertEmptyStringToNull()
|
||||||
|
|
@ -265,5 +271,29 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
modelType.FullName, displayColumnAttribute.DisplayColumn));
|
modelType.FullName, displayColumnAttribute.DisplayColumn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
return new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = original.Split(',')
|
||||||
|
.Select(piece => piece.Trim())
|
||||||
|
.Where(trimmed => !string.IsNullOrEmpty(trimmed));
|
||||||
|
return split;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
CacheKey = prototype.CacheKey;
|
CacheKey = prototype.CacheKey;
|
||||||
PrototypeCache = prototype.PrototypeCache;
|
PrototypeCache = prototype.PrototypeCache;
|
||||||
Marker = prototype.Marker;
|
Marker = prototype.Marker;
|
||||||
|
IncludedProperties = prototype.IncludedProperties;
|
||||||
|
ExcludedProperties = prototype.ExcludedProperties;
|
||||||
|
ModelName = prototype.ModelName;
|
||||||
_isComplexType = prototype.IsComplexType;
|
_isComplexType = prototype.IsComplexType;
|
||||||
_isComplexTypeComputed = true;
|
_isComplexTypeComputed = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
_modelAccessor = modelAccessor;
|
_modelAccessor = modelAccessor;
|
||||||
_modelType = modelType;
|
_modelType = modelType;
|
||||||
_propertyName = propertyName;
|
_propertyName = propertyName;
|
||||||
|
|
||||||
_convertEmptyStringToNull = true;
|
_convertEmptyStringToNull = true;
|
||||||
_isRequired = !modelType.AllowsNullValue();
|
_isRequired = !modelType.AllowsNullValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the name of a model if specified explicitly using <see cref="IModelNameProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string ModelName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Properties which are marked as Included for this model.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> IncludedProperties { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Properties which are marked as Excluded for this model.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> ExcludedProperties { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a binder marker for this model.
|
/// Gets or sets a binder marker for this model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1402,8 +1402,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
actionDescriptor,
|
actionDescriptor,
|
||||||
inputFormattersProvider.Object,
|
inputFormattersProvider.Object,
|
||||||
new DefaultControllerActionArgumentBinder(
|
new DefaultControllerActionArgumentBinder(
|
||||||
actionBindingContextProvider.Object,
|
actionBindingContextProvider.Object));
|
||||||
new DefaultBodyModelValidator()));
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,103 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
{
|
{
|
||||||
public class ControllerActionArgumentBinderTests
|
public class ControllerActionArgumentBinderTests
|
||||||
{
|
{
|
||||||
|
public class MySimpleModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Bind(Prefix = "TypePrefix")]
|
||||||
|
public class MySimpleModelWithTypeBasedBind
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParameterHasFieldPrefix([Bind(Prefix = "bar")] string foo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParameterHasEmptyFieldPrefix([Bind(Prefix = "")] MySimpleModel foo,
|
||||||
|
[Bind(Prefix = "")] MySimpleModelWithTypeBasedBind foo1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParameterHasPrefixAndComplexType([Bind(Prefix = "bar")] MySimpleModel foo,
|
||||||
|
[Bind(Prefix = "bar")] MySimpleModelWithTypeBasedBind foo1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParameterHasEmptyBindAttribute([Bind] MySimpleModel foo,
|
||||||
|
[Bind] MySimpleModelWithTypeBasedBind foo1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("ParameterHasFieldPrefix", false, "bar")]
|
||||||
|
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
|
||||||
|
[InlineData("ParameterHasPrefixAndComplexType", false, "bar")]
|
||||||
|
[InlineData("ParameterHasEmptyBindAttribute", true, "foo")]
|
||||||
|
public void GetModelBindingContext_ModelBindingContextIsSetWithModelName_ForParameters(
|
||||||
|
string actionMethodName, bool expectedFallToEmptyPrefix, string expectedModelName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(ControllerActionArgumentBinderTests);
|
||||||
|
var methodInfo = type.GetMethod(actionMethodName);
|
||||||
|
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: "foo");
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
||||||
|
Assert.Equal(expectedModelName, context.ModelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
|
||||||
|
[InlineData("ParameterHasPrefixAndComplexType", false, "bar")]
|
||||||
|
[InlineData("ParameterHasEmptyBindAttribute", true, "foo1")]
|
||||||
|
public void GetModelBindingContext_ModelBindingContextIsNotSet_ForTypes(
|
||||||
|
string actionMethodName, bool expectedFallToEmptyPrefix, string expectedModelName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(ControllerActionArgumentBinderTests);
|
||||||
|
var methodInfo = type.GetMethod(actionMethodName);
|
||||||
|
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: "foo1");
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
||||||
|
Assert.Equal(expectedModelName, context.ModelName);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Parameters_WithMultipleFromBody_Throw()
|
public async Task Parameters_WithMultipleFromBody_Throw()
|
||||||
{
|
{
|
||||||
|
|
@ -53,7 +150,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
.Returns(Task.FromResult(bindingContext));
|
.Returns(Task.FromResult(bindingContext));
|
||||||
|
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
actionBindingContextProvider.Object, Mock.Of<IBodyModelValidator>());
|
actionBindingContextProvider.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
|
@ -101,7 +198,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
.Returns(Task.FromResult(bindingContext));
|
.Returns(Task.FromResult(bindingContext));
|
||||||
|
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
actionBindingContextProvider.Object, Mock.Of<IBodyModelValidator>());
|
actionBindingContextProvider.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await invoker.GetActionArgumentsAsync(actionContext);
|
var result = await invoker.GetActionArgumentsAsync(actionContext);
|
||||||
|
|
@ -153,7 +250,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
.Returns(Task.FromResult(bindingContext));
|
.Returns(Task.FromResult(bindingContext));
|
||||||
|
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
actionBindingContextProvider.Object, Mock.Of<IBodyModelValidator>());
|
actionBindingContextProvider.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await invoker.GetActionArgumentsAsync(actionContext);
|
var result = await invoker.GetActionArgumentsAsync(actionContext);
|
||||||
|
|
|
||||||
|
|
@ -235,5 +235,126 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal("The Field2 field is required.", json["model.Field2"]);
|
Assert.Equal("The Field2 field is required.", json["model.Field2"]);
|
||||||
Assert.Equal("The Field3 field is required.", json["model.Field3"]);
|
Assert.Equal("The Field3 field is required.", json["model.Field3"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_BlacklistedAtEitherLevelIsNotBound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||||
|
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_BlackListingAtEitherLevelDoesNotBind" +
|
||||||
|
"?param1.IncludedExplicitlyAtTypeLevel=someValue¶m2.ExcludedExplicitlyAtTypeLevel=someValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||||
|
Assert.Equal(2, json.Count);
|
||||||
|
Assert.Null(json["param1.IncludedExplicitlyAtTypeLevel"]);
|
||||||
|
Assert.Null(json["param2.ExcludedExplicitlyAtTypeLevel"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_WhitelistedAtBothLevelsIsBound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||||
|
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtBothLevelBinds" +
|
||||||
|
"?param1.IncludedExplicitlyAtTypeLevel=someValue¶m2.ExcludedExplicitlyAtTypeLevel=someValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||||
|
Assert.Equal(1, json.Count);
|
||||||
|
Assert.Equal("someValue", json["param1.IncludedExplicitlyAtTypeLevel"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_WhitelistingAtOneLevelIsNotBound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||||
|
"BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtOnlyOneLevelDoesNotBind" +
|
||||||
|
"?param1.IncludedExplicitlyAtTypeLevel=someValue¶m1.IncludedExplicitlyAtParameterLevel=someValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||||
|
Assert.Equal(2, json.Count);
|
||||||
|
Assert.Null(json["param1.IncludedExplicitlyAtParameterLevel"]);
|
||||||
|
Assert.Null(json["param1.IncludedExplicitlyAtTypeLevel"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindAttribute_BindsUsingParameterPrefix()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||||
|
"BindParameterUsingParameterPrefix" +
|
||||||
|
"?randomPrefix.Value=someValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("someValue", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindAttribute_DoesNotUseTypePrefix()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||||
|
"TypePrefixIsNeverUsed" +
|
||||||
|
"?param.Value=someValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("someValue", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindAttribute_FallsBackOnEmptyPrefixIfNoParameterPrefixIsProvided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
|
||||||
|
"TypePrefixIsNeverUsed" +
|
||||||
|
"?Value=someValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("someValue", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindAttribute_DoesNotFallBackOnEmptyPrefixIfParameterPrefixIsProvided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/BindAttribute/" +
|
||||||
|
"BindParameterUsingParameterPrefix" +
|
||||||
|
"?Value=someValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||||
|
Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
{
|
||||||
|
public class BindAttributeTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void PrefixPropertyDefaultsToNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
BindAttribute attr = new BindAttribute();
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Null(attr.Prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Testing;
|
using Microsoft.AspNet.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -58,6 +59,48 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
testableBinder.Verify();
|
testableBinder.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModel_InitsInstance_ForEmptyModelName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(false));
|
||||||
|
|
||||||
|
var mockDtoBinder = new Mock<IModelBinder>();
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForObject(new Person()),
|
||||||
|
ModelName = "",
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
ModelBinder = mockDtoBinder.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
|
};
|
||||||
|
|
||||||
|
mockDtoBinder
|
||||||
|
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
|
.Returns((ModelBindingContext mbc) =>
|
||||||
|
{
|
||||||
|
// just return the DTO unchanged
|
||||||
|
return Task.FromResult(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
|
testableBinder.Setup(o => o.EnsureModelPublic(bindingContext)).Verifiable();
|
||||||
|
testableBinder.Setup(o => o.GetMetadataForPropertiesPublic(bindingContext))
|
||||||
|
.Returns(new ModelMetadata[0]).Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(retValue);
|
||||||
|
Assert.IsType<Person>(bindingContext.Model);
|
||||||
|
Assert.True(bindingContext.ValidationNode.ValidateAllProperties);
|
||||||
|
testableBinder.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanUpdateProperty_HasPublicSetter_ReturnsTrue()
|
public void CanUpdateProperty_HasPublicSetter_ReturnsTrue()
|
||||||
{
|
{
|
||||||
|
|
@ -194,7 +237,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
};
|
};
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
@ -215,7 +259,75 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(Person)),
|
ModelMetadata = GetMetadataForType(typeof(Person)),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
|
};
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(bindingContext);
|
||||||
|
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
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)),
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
|
};
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(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)),
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
};
|
};
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
@ -812,6 +924,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return metadataProvider.GetMetadataForType(null, t);
|
return metadataProvider.GetMetadataForType(null, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ModelMetadata GetMetadataForParameter(MethodInfo methodInfo, string parameterName)
|
||||||
|
{
|
||||||
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
return metadataProvider.GetMetadataForParameter(null, methodInfo, parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
private class Person
|
private class Person
|
||||||
{
|
{
|
||||||
private DateTime? _dateOfDeath;
|
private DateTime? _dateOfDeath;
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,117 @@
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class CachedDataAnnotationsModelMetadataProviderTest
|
public class CachedDataAnnotationsModelMetadataProviderTest
|
||||||
{
|
{
|
||||||
|
[Bind(Include = nameof(IncludedAndExcludedExplicitly1) + "," + nameof(IncludedExplicitly1),
|
||||||
|
Exclude = nameof(IncludedAndExcludedExplicitly1) + "," + nameof(ExcludedExplicitly1),
|
||||||
|
Prefix = "TypePrefix")]
|
||||||
|
private class TypeWithExludedAndIncludedPropertiesUsingBindAttribute
|
||||||
|
{
|
||||||
|
public int ExcludedExplicitly1 { get; set; }
|
||||||
|
|
||||||
|
public int IncludedAndExcludedExplicitly1 { get; set; }
|
||||||
|
|
||||||
|
public int IncludedExplicitly1 { get; set; }
|
||||||
|
|
||||||
|
public int NotIncludedOrExcluded { get; set; }
|
||||||
|
|
||||||
|
public void ActionWithBindAttribute(
|
||||||
|
[Bind(Include = "Property1, Property2,IncludedAndExcludedExplicitly1",
|
||||||
|
Exclude ="Property3, Property4, IncludedAndExcludedExplicitly1",
|
||||||
|
Prefix = "ParameterPrefix")]
|
||||||
|
TypeWithExludedAndIncludedPropertiesUsingBindAttribute param)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataAnnotationsModelMetadataProvider_ReadsIncludedAndExcludedProperties_ForTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||||
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1", "IncludedExplicitly1" };
|
||||||
|
var expectedExcludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata = provider.GetMetadataForType(null, type);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.IncludedProperties);
|
||||||
|
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.ExcludedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelMetadataProvider_ReadsIncludedAndExcludedProperties_BothAtParameterAndTypeLevel_ForParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||||
|
var methodInfo = type.GetMethod("ActionWithBindAttribute");
|
||||||
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
|
// Note it does an intersection for included and a union for excluded.
|
||||||
|
var expectedIncludedPropertyNames = new[] { "IncludedAndExcludedExplicitly1" };
|
||||||
|
var expectedExcludedPropertyNames = new[] {
|
||||||
|
"Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata = provider.GetMetadataForParameter(null, methodInfo, "param");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.IncludedProperties);
|
||||||
|
Assert.Equal(expectedExcludedPropertyNames.ToList(), metadata.ExcludedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelMetadataProvider_ReadsPrefixProperty_OnlyAtParameterLevel_ForParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||||
|
var methodInfo = type.GetMethod("ActionWithBindAttribute");
|
||||||
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata = provider.GetMetadataForParameter(null, methodInfo, "param");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("ParameterPrefix", metadata.ModelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||||
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata = provider.GetMetadataForType(null, type);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("TypePrefix", metadata.ModelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(TypeWithExludedAndIncludedPropertiesUsingBindAttribute);
|
||||||
|
var methodInfo = type.GetMethod("ActionWithBindAttribute");
|
||||||
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata = provider.GetMetadataForParameter(null, methodInfo, "param");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("ParameterPrefix", metadata.ModelName);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay()
|
public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
// 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 Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class BindAttributeController : Controller
|
||||||
|
{
|
||||||
|
public Dictionary<string, string>
|
||||||
|
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_BlackListingAtEitherLevelDoesNotBind(
|
||||||
|
[Bind(Exclude = "IncludedExplicitlyAtTypeLevel")] TypeWithIncludedPropertyAtBindAttribute param1,
|
||||||
|
[Bind(Include = "ExcludedExplicitlyAtTypeLevel")] TypeWithExcludedPropertyAtBindAttribute param2)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
// The first one should not be included because the parameter level bind attribute filters it out.
|
||||||
|
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel },
|
||||||
|
|
||||||
|
// The second one should not be included because the type level bind attribute filters it out.
|
||||||
|
{ "param2.ExcludedExplicitlyAtTypeLevel", param2.ExcludedExplicitlyAtTypeLevel },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string>
|
||||||
|
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtBothLevelBinds(
|
||||||
|
[Bind(Include = "IncludedExplicitlyAtTypeLevel")] TypeWithIncludedPropertyAtBindAttribute param1)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
// The since this is included at both level it is bound.
|
||||||
|
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string>
|
||||||
|
BindAtParamterLevelAndBindAtTypeLevelAreBothEvaluated_WhiteListingAtOnlyOneLevelDoesNotBind(
|
||||||
|
[Bind(Include = "IncludedExplicitlyAtParameterLevel")]
|
||||||
|
TypeWithIncludedPropertyAtParameterAndTypeUsingBindAttribute param1)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
// The since this is included at only type level it is not bound.
|
||||||
|
{ "param1.IncludedExplicitlyAtParameterLevel", param1.IncludedExplicitlyAtParameterLevel },
|
||||||
|
{ "param1.IncludedExplicitlyAtTypeLevel", param1.IncludedExplicitlyAtTypeLevel },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BindParameterUsingParameterPrefix([Bind(Prefix = "randomPrefix")] ParameterPrefix param)
|
||||||
|
{
|
||||||
|
return param.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will use param to try to bind and not the value specified at TypePrefix.
|
||||||
|
public string TypePrefixIsNeverUsed([Bind] TypePrefix param)
|
||||||
|
{
|
||||||
|
return param.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Bind(Prefix = "TypePrefix")]
|
||||||
|
public class TypePrefix
|
||||||
|
{
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ParameterPrefix
|
||||||
|
{
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Bind(Include = nameof(IncludedExplicitlyAtTypeLevel))]
|
||||||
|
public class TypeWithIncludedPropertyAtParameterAndTypeUsingBindAttribute
|
||||||
|
{
|
||||||
|
public string IncludedExplicitlyAtTypeLevel { get; set; }
|
||||||
|
public string IncludedExplicitlyAtParameterLevel { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Bind(Include = nameof(IncludedExplicitlyAtTypeLevel))]
|
||||||
|
public class TypeWithIncludedPropertyAtBindAttribute
|
||||||
|
{
|
||||||
|
public string IncludedExplicitlyAtTypeLevel { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Bind(Exclude = nameof(ExcludedExplicitlyAtTypeLevel))]
|
||||||
|
public class TypeWithExcludedPropertyAtBindAttribute
|
||||||
|
{
|
||||||
|
public string ExcludedExplicitlyAtTypeLevel { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue