Reintroducing BindingBehaviorAttribute attribute
* Porting MutableObjectModelBinder tests * Fix issues in MutableObjectModelBinder introduced by changing from TypeDescriptors to reflection
This commit is contained in:
parent
9cd99a42a7
commit
f8dd52dfe3
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
|
public sealed class BindNeverAttribute : BindingBehaviorAttribute
|
||||||
|
{
|
||||||
|
public BindNeverAttribute()
|
||||||
|
: base(BindingBehavior.Never)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
|
public sealed class BindRequiredAttribute : BindingBehaviorAttribute
|
||||||
|
{
|
||||||
|
public BindRequiredAttribute()
|
||||||
|
: base(BindingBehavior.Required)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public enum BindingBehavior
|
||||||
|
{
|
||||||
|
Optional = 0,
|
||||||
|
Never,
|
||||||
|
Required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class BindingBehaviorAttribute : Attribute
|
||||||
|
{
|
||||||
|
public BindingBehaviorAttribute(BindingBehavior behavior)
|
||||||
|
{
|
||||||
|
Behavior = behavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindingBehavior Behavior { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -57,10 +57,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
|
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var validationContext = new ModelValidationContext(bindingContext.ModelMetadata,
|
var validationContext = new ModelValidationContext(bindingContext.MetadataProvider,
|
||||||
bindingContext.ModelState,
|
bindingContext.ValidatorProviders,
|
||||||
bindingContext.MetadataProvider,
|
bindingContext.ModelState,
|
||||||
bindingContext.ValidatorProviders);
|
bindingContext.ModelMetadata,
|
||||||
|
containerMetadata: null);
|
||||||
|
|
||||||
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
|
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
|
||||||
bindingContext.Model = newBindingContext.Model;
|
bindingContext.Model = newBindingContext.Model;
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
// TODO: Revive ModelBinderConfig
|
// TODO: Revive ModelBinderConfig
|
||||||
// string errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ValidationContext, modelMetadata, incomingValue);
|
// string errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ValidationContext, modelMetadata, incomingValue);
|
||||||
var errorMessage = e.ValidationContext.ModelMetadata.PropertyName + " is required";
|
var errorMessage = "A value is required.";
|
||||||
if (errorMessage != null)
|
if (errorMessage != null)
|
||||||
{
|
{
|
||||||
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
|
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
|
||||||
|
|
@ -136,17 +136,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
// keep a set of the required properties so that we can cross-reference bound properties later
|
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||||
HashSet<string> requiredProperties;
|
return bindingContext.ModelMetadata.Properties
|
||||||
Dictionary<string, IModelValidator> requiredValidators;
|
.Where(propertyMetadata =>
|
||||||
HashSet<string> skipProperties;
|
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
|
||||||
GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
|
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
|
||||||
|
CanUpdateProperty(propertyMetadata));
|
||||||
return from propertyMetadata in bindingContext.ModelMetadata.Properties
|
|
||||||
let propertyName = propertyMetadata.PropertyName
|
|
||||||
let shouldUpdateProperty = requiredProperties.Contains(propertyName) || !skipProperties.Contains(propertyName)
|
|
||||||
where shouldUpdateProperty && CanUpdateProperty(propertyMetadata)
|
|
||||||
select propertyMetadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
|
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
|
||||||
|
|
@ -155,43 +150,55 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return (attr != null) ? attr.Value : null;
|
return (attr != null) ? attr.Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void GetRequiredPropertiesCollection(ModelBindingContext bindingContext,
|
internal static PropertyValidationInfo GetPropertyValidationInfo(ModelBindingContext bindingContext)
|
||||||
out HashSet<string> requiredProperties,
|
|
||||||
out Dictionary<string, IModelValidator> requiredValidators,
|
|
||||||
out HashSet<string> skipProperties)
|
|
||||||
{
|
{
|
||||||
requiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var validationInfo = new PropertyValidationInfo();
|
||||||
requiredValidators = new Dictionary<string, IModelValidator>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
skipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// TODO: HttpBindingBehaviorAttribute
|
|
||||||
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
|
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
|
||||||
foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties)
|
var typeAttribute = modelTypeInfo.GetCustomAttribute<BindingBehaviorAttribute>();
|
||||||
|
var properties = bindingContext.ModelType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
foreach (var property in properties)
|
||||||
{
|
{
|
||||||
var propertyName = propertyMetadata.PropertyName;
|
var propertyName = property.Name;
|
||||||
|
var propertyMetadata = bindingContext.PropertyMetadata[propertyName];
|
||||||
var requiredValidator = bindingContext.GetValidators(propertyMetadata)
|
var requiredValidator = bindingContext.GetValidators(propertyMetadata)
|
||||||
.FirstOrDefault(v => v.IsRequired);
|
.FirstOrDefault(v => v.IsRequired);
|
||||||
// TODO: Revive HttpBindingBehaviorAttribute
|
|
||||||
|
|
||||||
if (requiredValidator != null)
|
if (requiredValidator != null)
|
||||||
{
|
{
|
||||||
requiredValidators[propertyName] = requiredValidator;
|
validationInfo.RequiredValidators[propertyName] = requiredValidator;
|
||||||
requiredProperties.Add(propertyName);
|
}
|
||||||
|
|
||||||
|
var propertyAttribute = property.GetCustomAttribute<BindingBehaviorAttribute>();
|
||||||
|
var bindingBehaviorAttribute = propertyAttribute ?? typeAttribute;
|
||||||
|
if (bindingBehaviorAttribute != null)
|
||||||
|
{
|
||||||
|
switch (bindingBehaviorAttribute.Behavior)
|
||||||
|
{
|
||||||
|
case BindingBehavior.Required:
|
||||||
|
validationInfo.RequiredProperties.Add(propertyName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BindingBehavior.Never:
|
||||||
|
validationInfo.SkipProperties.Add(propertyName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (requiredValidator != null)
|
||||||
|
{
|
||||||
|
validationInfo.RequiredProperties.Add(propertyName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return validationInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto)
|
internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto)
|
||||||
{
|
{
|
||||||
HashSet<string> requiredProperties;
|
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||||
Dictionary<string, IModelValidator> requiredValidators;
|
|
||||||
HashSet<string> skipProperties;
|
|
||||||
GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
|
|
||||||
|
|
||||||
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
||||||
requiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName));
|
validationInfo.RequiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName));
|
||||||
|
|
||||||
foreach (var missingRequiredProperty in requiredProperties)
|
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
|
||||||
{
|
{
|
||||||
var addedError = false;
|
var addedError = false;
|
||||||
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
||||||
|
|
@ -205,12 +212,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
// Execute validator (if any) to get custom error message.
|
// Execute validator (if any) to get custom error message.
|
||||||
IModelValidator validator;
|
IModelValidator validator;
|
||||||
if (requiredValidators.TryGetValue(missingRequiredProperty, out validator))
|
if (validationInfo.RequiredValidators.TryGetValue(missingRequiredProperty, out validator))
|
||||||
{
|
{
|
||||||
addedError = RunValidator(validator, bindingContext, propertyMetadata, modelStateKey);
|
addedError = RunValidator(validator, bindingContext, propertyMetadata, modelStateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to default message if HttpBindingBehaviorAttribute required this property or validator
|
// Fall back to default message if BindingBehaviorAttribute required this property or validator
|
||||||
// (oddly) succeeded.
|
// (oddly) succeeded.
|
||||||
if (!addedError)
|
if (!addedError)
|
||||||
{
|
{
|
||||||
|
|
@ -227,7 +234,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var dtoResult = entry.Value;
|
var dtoResult = entry.Value;
|
||||||
if (dtoResult != null)
|
if (dtoResult != null)
|
||||||
{
|
{
|
||||||
SetProperty(bindingContext, propertyMetadata, dtoResult);
|
IModelValidator requiredValidator;
|
||||||
|
validationInfo.RequiredValidators.TryGetValue(propertyMetadata.PropertyName, out requiredValidator);
|
||||||
|
SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
|
bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -236,7 +245,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
|
||||||
protected virtual void SetProperty(ModelBindingContext bindingContext,
|
protected virtual void SetProperty(ModelBindingContext bindingContext,
|
||||||
ModelMetadata propertyMetadata,
|
ModelMetadata propertyMetadata,
|
||||||
ComplexModelDtoResult dtoResult)
|
ComplexModelDtoResult dtoResult,
|
||||||
|
IModelValidator requiredValidator)
|
||||||
{
|
{
|
||||||
var property = bindingContext.ModelType
|
var property = bindingContext.ModelType
|
||||||
.GetProperty(propertyMetadata.PropertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
.GetProperty(propertyMetadata.PropertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
||||||
|
|
@ -258,10 +268,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
||||||
if (bindingContext.ModelState.IsValidField(modelStateKey))
|
if (bindingContext.ModelState.IsValidField(modelStateKey))
|
||||||
{
|
{
|
||||||
var requiredValidator = bindingContext.GetValidators(propertyMetadata).FirstOrDefault(v => v.IsRequired);
|
|
||||||
if (requiredValidator != null)
|
if (requiredValidator != null)
|
||||||
{
|
{
|
||||||
var validationContext = bindingContext.CreateValidationContext(propertyMetadata);
|
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||||
foreach (var validationResult in requiredValidator.Validate(validationContext))
|
foreach (var validationResult in requiredValidator.Validate(validationContext))
|
||||||
{
|
{
|
||||||
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
|
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
|
||||||
|
|
@ -279,10 +288,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// don't display a duplicate error message if a binding error has already occurred for this field
|
// don't display a duplicate error message if a binding error has already occurred for this field
|
||||||
|
var targetInvocationException = ex as TargetInvocationException;
|
||||||
|
if (targetInvocationException != null &&
|
||||||
|
targetInvocationException.InnerException != null)
|
||||||
|
{
|
||||||
|
ex = targetInvocationException.InnerException;
|
||||||
|
}
|
||||||
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
||||||
if (bindingContext.ModelState.IsValidField(modelStateKey))
|
if (bindingContext.ModelState.IsValidField(modelStateKey))
|
||||||
{
|
{
|
||||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
|
bindingContext.ModelState.AddModelError(modelStateKey, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +318,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelMetadata propertyMetadata,
|
ModelMetadata propertyMetadata,
|
||||||
string modelStateKey)
|
string modelStateKey)
|
||||||
{
|
{
|
||||||
var validationContext = bindingContext.CreateValidationContext(propertyMetadata);
|
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
var addedError = false;
|
var addedError = false;
|
||||||
foreach (var validationResult in validator.Validate(validationContext))
|
foreach (var validationResult in validator.Validate(validationContext))
|
||||||
|
|
@ -313,5 +328,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
return addedError;
|
return addedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class PropertyValidationInfo
|
||||||
|
{
|
||||||
|
public PropertyValidationInfo()
|
||||||
|
{
|
||||||
|
RequiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
RequiredValidators = new Dictionary<string, IModelValidator>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
SkipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<string> RequiredProperties { get; private set; }
|
||||||
|
|
||||||
|
public Dictionary<string, IModelValidator> RequiredValidators { get; private set; }
|
||||||
|
|
||||||
|
public HashSet<string> SkipProperties { get; private set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
.Where(v => v != null);
|
.Where(v => v != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ModelValidationContext CreateValidationContext([NotNull] this ModelBindingContext context,
|
|
||||||
[NotNull] ModelMetadata metadata)
|
|
||||||
{
|
|
||||||
return new ModelValidationContext(metadata,
|
|
||||||
context.ModelState,
|
|
||||||
context.MetadataProvider,
|
|
||||||
context.ValidatorProviders);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<ModelValidationResult> Validate([NotNull] this ModelBindingContext bindingContext)
|
public static IEnumerable<ModelValidationResult> Validate([NotNull] this ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var validators = GetValidators(bindingContext, bindingContext.ModelMetadata);
|
var validators = GetValidators(bindingContext, bindingContext.ModelMetadata);
|
||||||
var compositeValidator = new CompositeModelValidator(validators);
|
var compositeValidator = new CompositeModelValidator(validators);
|
||||||
var modelValidationContext = CreateValidationContext(bindingContext, bindingContext.ModelMetadata);
|
var modelValidationContext = new ModelValidationContext(bindingContext, bindingContext.ModelMetadata);
|
||||||
return compositeValidator.Validate(modelValidationContext);
|
return compositeValidator.Validate(modelValidationContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
throw Error.Argument("propertyName", Resources.FormatCommon_PropertyNotFound(containerType, propertyName));
|
throw Error.Argument("propertyName", Resources.FormatCommon_PropertyNotFound(containerType, propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateMetadataFromPrototype(propertyInfo.Prototype, modelAccessor);
|
return CreatePropertyMetadata(modelAccessor, propertyInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Type modelType)
|
public ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Type modelType)
|
||||||
|
|
@ -64,10 +64,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Func<object, object> propertyGetter = propertyInfo.ValueAccessor;
|
Func<object, object> propertyGetter = propertyInfo.ValueAccessor;
|
||||||
modelAccessor = () => propertyGetter(container);
|
modelAccessor = () => propertyGetter(container);
|
||||||
}
|
}
|
||||||
yield return CreateMetadataFromPrototype(propertyInfo.Prototype, modelAccessor);
|
yield return CreatePropertyMetadata(modelAccessor, propertyInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TModelMetadata CreatePropertyMetadata(Func<object> modelAccessor, PropertyInformation propertyInfo)
|
||||||
|
{
|
||||||
|
var metadata = CreateMetadataFromPrototype(propertyInfo.Prototype, modelAccessor);
|
||||||
|
if (propertyInfo.IsReadOnly)
|
||||||
|
{
|
||||||
|
metadata.IsReadOnly = true;
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
private TypeInformation GetTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes = null)
|
private TypeInformation GetTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes = null)
|
||||||
{
|
{
|
||||||
// This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd to avoid the performance cost of creating instance delegates
|
// This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd to avoid the performance cost of creating instance delegates
|
||||||
|
|
@ -109,13 +119,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyInfo property)
|
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyInfo property)
|
||||||
{
|
{
|
||||||
var info = new PropertyInformation();
|
return new PropertyInformation
|
||||||
info.ValueAccessor = CreatePropertyValueAccessor(property);
|
{
|
||||||
info.Prototype = CreateMetadataPrototype(property.GetCustomAttributes(),
|
ValueAccessor = CreatePropertyValueAccessor(property),
|
||||||
containerType,
|
Prototype = CreateMetadataPrototype(property.GetCustomAttributes(),
|
||||||
property.PropertyType,
|
containerType,
|
||||||
property.Name);
|
property.PropertyType,
|
||||||
return info;
|
property.Name),
|
||||||
|
IsReadOnly = !property.CanWrite || property.SetMethod.IsPrivate
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Func<object, object> CreatePropertyValueAccessor(PropertyInfo property)
|
private static Func<object, object> CreatePropertyValueAccessor(PropertyInfo property)
|
||||||
|
|
@ -196,6 +208,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public Func<object, object> ValueAccessor { get; set; }
|
public Func<object, object> ValueAccessor { get; set; }
|
||||||
public TModelMetadata Prototype { get; set; }
|
public TModelMetadata Prototype { get; set; }
|
||||||
|
public bool IsReadOnly { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
var metadata = validationContext.ModelMetadata;
|
var metadata = validationContext.ModelMetadata;
|
||||||
var memberName = metadata.PropertyName ?? metadata.ModelType.Name;
|
var memberName = metadata.PropertyName ?? metadata.ModelType.Name;
|
||||||
var context = new ValidationContext(metadata.Model)
|
var instance = metadata.Model ?? validationContext.ContainerMetadata.Model;
|
||||||
|
var context = new ValidationContext(instance)
|
||||||
{
|
{
|
||||||
DisplayName = metadata.GetDisplayName(),
|
DisplayName = metadata.GetDisplayName(),
|
||||||
MemberName = memberName
|
MemberName = memberName
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,30 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class ModelValidationContext
|
public class ModelValidationContext
|
||||||
{
|
{
|
||||||
public ModelValidationContext([NotNull] ModelMetadata metadata,
|
public ModelValidationContext([NotNull] ModelBindingContext bindingContext,
|
||||||
[NotNull] ModelStateDictionary modelState,
|
[NotNull] ModelMetadata metadata)
|
||||||
[NotNull] IModelMetadataProvider metadataProvider,
|
: this(bindingContext.MetadataProvider,
|
||||||
[NotNull] IEnumerable<IModelValidatorProvider> validatorProviders)
|
bindingContext.ValidatorProviders,
|
||||||
|
bindingContext.ModelState,
|
||||||
|
metadata,
|
||||||
|
bindingContext.ModelMetadata)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelValidationContext([NotNull] IModelMetadataProvider metadataProvider,
|
||||||
|
[NotNull] IEnumerable<IModelValidatorProvider> validatorProviders,
|
||||||
|
[NotNull] ModelStateDictionary modelState,
|
||||||
|
[NotNull] ModelMetadata metadata,
|
||||||
|
ModelMetadata containerMetadata)
|
||||||
{
|
{
|
||||||
ModelMetadata = metadata;
|
ModelMetadata = metadata;
|
||||||
ModelState = modelState;
|
ModelState = modelState;
|
||||||
MetadataProvider = metadataProvider;
|
MetadataProvider = metadataProvider;
|
||||||
ValidatorProviders = validatorProviders;
|
ValidatorProviders = validatorProviders;
|
||||||
|
ContainerMetadata = containerMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelValidationContext([NotNull] ModelValidationContext parentContext,
|
public ModelValidationContext([NotNull] ModelValidationContext parentContext,
|
||||||
[NotNull] ModelMetadata metadata)
|
[NotNull] ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
ModelMetadata = metadata;
|
ModelMetadata = metadata;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,953 @@
|
||||||
|
#if NET45
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public class MutableObjectModelBinderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void BindModel_InitsInstance()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefix(It.IsAny<string>()))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var mockDtoBinder = new Mock<IModelBinder>();
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForObject(new Person()),
|
||||||
|
ModelName = "someName",
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
ModelBinder = mockDtoBinder.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
|
};
|
||||||
|
|
||||||
|
mockDtoBinder
|
||||||
|
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
||||||
|
.Returns((ModelBindingContext mbc) =>
|
||||||
|
{
|
||||||
|
// just return the DTO unchanged
|
||||||
|
return 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 = testableBinder.Object.BindModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(retValue);
|
||||||
|
Assert.IsType<Person>(bindingContext.Model);
|
||||||
|
Assert.True(bindingContext.ValidationNode.ValidateAllProperties);
|
||||||
|
testableBinder.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUpdateProperty_HasPublicSetter_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyMetadata = GetMetadataForCanUpdateProperty("ReadWriteString");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(canUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUpdateProperty_ReadOnlyArray_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyArray");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(canUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUpdateProperty_ReadOnlyReferenceTypeNotBlacklisted_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyObject");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(canUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUpdateProperty_ReadOnlyString_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyString");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(canUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUpdateProperty_ReadOnlyValueType_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyInt");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(canUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateModel_InstantiatesInstanceOfMetadataType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(Person))
|
||||||
|
};
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retModel = testableBinder.CreateModelPublic(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsType<Person>(retModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnsureModel_ModelIsNotNull_DoesNothing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForObject(new Person())
|
||||||
|
};
|
||||||
|
|
||||||
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var originalModel = bindingContext.Model;
|
||||||
|
testableBinder.Object.EnsureModelPublic(bindingContext);
|
||||||
|
var newModel = bindingContext.Model;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(originalModel, newModel);
|
||||||
|
testableBinder.Verify(o => o.CreateModelPublic(bindingContext), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnsureModel_ModelIsNull_CallsCreateModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(Person))
|
||||||
|
};
|
||||||
|
|
||||||
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
|
testableBinder.Setup(o => o.CreateModelPublic(bindingContext))
|
||||||
|
.Returns(new Person()).Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
object originalModel = bindingContext.Model;
|
||||||
|
testableBinder.Object.EnsureModelPublic(bindingContext);
|
||||||
|
object newModel = bindingContext.Model;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(originalModel);
|
||||||
|
Assert.IsType<Person>(newModel);
|
||||||
|
testableBinder.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForProperties_WithBindAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedPropertyNames = new[] { "FirstName", "LastName" };
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(bindingContext);
|
||||||
|
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForProperties_WithoutBindAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedPropertyNames = new[] { "DateOfBirth", "DateOfDeath", "ValueTypeRequired", "FirstName", "LastName", "PropertyWithDefaultValue" };
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(Person)),
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(bindingContext);
|
||||||
|
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedPropertyNames, returnedPropertyNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRequiredPropertiesCollection_MixedAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForObject(new ModelWithMixedBindingBehaviors()),
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validationInfo = MutableObjectModelBinder.GetPropertyValidationInfo(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { "Required" }, validationInfo.RequiredProperties);
|
||||||
|
Assert.Equal(new[] { "Never" }, validationInfo.SkipProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NullCheckFailedHandler_ModelStateAlreadyInvalid_DoesNothing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
modelState.AddModelError("foo.bar", "Some existing error.");
|
||||||
|
|
||||||
|
var modelMetadata = GetMetadataForType(typeof(Person));
|
||||||
|
var validationNode = new ModelValidationNode(modelMetadata, "foo");
|
||||||
|
var validationContext = new ModelValidationContext(new DataAnnotationsModelMetadataProvider(),
|
||||||
|
Enumerable.Empty<IModelValidatorProvider>(),
|
||||||
|
modelState,
|
||||||
|
modelMetadata,
|
||||||
|
null);
|
||||||
|
var e = new ModelValidatedEventArgs(validationContext, parentNode: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, incomingValue: null);
|
||||||
|
handler(validationNode, e);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(modelState.ContainsKey("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NullCheckFailedHandler_ModelStateValid_AddsErrorString()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
var modelMetadata = GetMetadataForType(typeof(Person));
|
||||||
|
var validationNode = new ModelValidationNode(modelMetadata, "foo");
|
||||||
|
var validationContext = new ModelValidationContext(new DataAnnotationsModelMetadataProvider(),
|
||||||
|
Enumerable.Empty<IModelValidatorProvider>(),
|
||||||
|
modelState,
|
||||||
|
modelMetadata,
|
||||||
|
null);
|
||||||
|
var e = new ModelValidatedEventArgs(validationContext, parentNode: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, incomingValue: null);
|
||||||
|
handler(validationNode, e);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelState.ContainsKey("foo"));
|
||||||
|
Assert.Equal("A value is required.", modelState["foo"].Errors[0].ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void ProcessDto_BindRequiredFieldMissing_RaisesModelError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ModelWithBindRequired
|
||||||
|
{
|
||||||
|
Name = "original value",
|
||||||
|
Age = -20
|
||||||
|
};
|
||||||
|
|
||||||
|
var containerMetadata = GetMetadataForObject(model);
|
||||||
|
var bindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = containerMetadata,
|
||||||
|
ModelName = "theModel",
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
|
};
|
||||||
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
|
||||||
|
var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
||||||
|
dto.Results[nameProperty] = new ComplexModelDtoResult("John Doe", new ModelValidationNode(nameProperty, ""));
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelStateDictionary = bindingContext.ModelState;
|
||||||
|
Assert.False(modelStateDictionary.IsValid);
|
||||||
|
Assert.Equal(1, modelStateDictionary.Count);
|
||||||
|
|
||||||
|
// Check Age error.
|
||||||
|
ModelState modelState;
|
||||||
|
Assert.True(modelStateDictionary.TryGetValue("theModel.Age", out modelState));
|
||||||
|
Assert.Equal(1, modelState.Errors.Count);
|
||||||
|
|
||||||
|
var modelError = modelState.Errors[0];
|
||||||
|
Assert.Null(modelError.Exception);
|
||||||
|
Assert.NotNull(modelError.ErrorMessage);
|
||||||
|
Assert.Equal("The 'Age' property is required.", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void ProcessDto_BindRequiredFieldNull_RaisesModelError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ModelWithBindRequired
|
||||||
|
{
|
||||||
|
Name = "original value",
|
||||||
|
Age = -20
|
||||||
|
};
|
||||||
|
|
||||||
|
var containerMetadata = GetMetadataForObject(model);
|
||||||
|
var bindingContext = new ModelBindingContext()
|
||||||
|
{
|
||||||
|
ModelMetadata = containerMetadata,
|
||||||
|
ModelName = "theModel",
|
||||||
|
ModelState = new ModelStateDictionary(),
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
|
};
|
||||||
|
var validationContext = new ModelValidationContext(new EmptyModelMetadataProvider(),
|
||||||
|
bindingContext.ValidatorProviders,
|
||||||
|
bindingContext.ModelState,
|
||||||
|
containerMetadata,
|
||||||
|
null);
|
||||||
|
|
||||||
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
var propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
||||||
|
dto.Results[propertyMetadata] =
|
||||||
|
new ComplexModelDtoResult("John Doe", new ModelValidationNode(propertyMetadata, "theModel.Name"));
|
||||||
|
|
||||||
|
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
|
||||||
|
// case because the binding exists.
|
||||||
|
propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Age");
|
||||||
|
dto.Results[propertyMetadata] =
|
||||||
|
new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
||||||
|
|
||||||
|
// Act; must also Validate because null-check error handler is late-bound
|
||||||
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
bindingContext.ValidationNode.Validate(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelStateDictionary = bindingContext.ModelState;
|
||||||
|
Assert.False(modelStateDictionary.IsValid);
|
||||||
|
Assert.Equal(1, modelStateDictionary.Count);
|
||||||
|
|
||||||
|
// Check Age error.
|
||||||
|
ModelState modelState;
|
||||||
|
Assert.True(modelStateDictionary.TryGetValue("theModel.Age", out modelState));
|
||||||
|
Assert.Equal(1, modelState.Errors.Count);
|
||||||
|
|
||||||
|
var modelError = modelState.Errors[0];
|
||||||
|
Assert.Null(modelError.Exception);
|
||||||
|
Assert.NotNull(modelError.ErrorMessage);
|
||||||
|
Assert.Equal("A value is required.", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void ProcessDto_RequiredFieldMissing_RaisesModelError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ModelWithRequired();
|
||||||
|
var containerMetadata = GetMetadataForObject(model);
|
||||||
|
var bindingContext = CreateContext(containerMetadata);
|
||||||
|
|
||||||
|
// Set no properties though Age (a non-Nullable struct) and City (a class) properties are required.
|
||||||
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelStateDictionary = bindingContext.ModelState;
|
||||||
|
Assert.False(modelStateDictionary.IsValid);
|
||||||
|
Assert.Equal(2, modelStateDictionary.Count);
|
||||||
|
|
||||||
|
// Check Age error.
|
||||||
|
ModelState modelState;
|
||||||
|
Assert.True(modelStateDictionary.TryGetValue("theModel.Age", out modelState));
|
||||||
|
Assert.Equal(1, modelState.Errors.Count);
|
||||||
|
|
||||||
|
var modelError = modelState.Errors[0];
|
||||||
|
Assert.Null(modelError.Exception);
|
||||||
|
Assert.NotNull(modelError.ErrorMessage);
|
||||||
|
Assert.Equal("The Age field is required.", modelError.ErrorMessage);
|
||||||
|
|
||||||
|
// Check City error.
|
||||||
|
Assert.True(modelStateDictionary.TryGetValue("theModel.City", out modelState));
|
||||||
|
Assert.Equal(1, modelState.Errors.Count);
|
||||||
|
|
||||||
|
modelError = modelState.Errors[0];
|
||||||
|
Assert.Null(modelError.Exception);
|
||||||
|
Assert.NotNull(modelError.ErrorMessage);
|
||||||
|
Assert.Equal("The City field is required.", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void ProcessDto_RequiredFieldNull_RaisesModelError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ModelWithRequired();
|
||||||
|
var containerMetadata = GetMetadataForObject(model);
|
||||||
|
var bindingContext = CreateContext(containerMetadata);
|
||||||
|
|
||||||
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Make Age valid and City invalid.
|
||||||
|
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "Age");
|
||||||
|
dto.Results[propertyMetadata] =
|
||||||
|
new ComplexModelDtoResult(23, new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
||||||
|
propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "City");
|
||||||
|
dto.Results[propertyMetadata] =
|
||||||
|
new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.City"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelStateDictionary = bindingContext.ModelState;
|
||||||
|
Assert.False(modelStateDictionary.IsValid);
|
||||||
|
Assert.Equal(1, modelStateDictionary.Count);
|
||||||
|
|
||||||
|
// Check City error.
|
||||||
|
ModelState modelState;
|
||||||
|
Assert.True(modelStateDictionary.TryGetValue("theModel.City", out modelState));
|
||||||
|
Assert.Equal(1, modelState.Errors.Count);
|
||||||
|
|
||||||
|
var modelError = modelState.Errors[0];
|
||||||
|
Assert.Null(modelError.Exception);
|
||||||
|
Assert.NotNull(modelError.ErrorMessage);
|
||||||
|
Assert.Equal("The City field is required.", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProcessDto_RequiredFieldMissing_RaisesModelErrorWithMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new Person();
|
||||||
|
var containerMetadata = GetMetadataForObject(model);
|
||||||
|
var bindingContext = CreateContext(containerMetadata);
|
||||||
|
|
||||||
|
// Set no properties though ValueTypeRequired (a non-Nullable struct) property is required.
|
||||||
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelStateDictionary = bindingContext.ModelState;
|
||||||
|
Assert.False(modelStateDictionary.IsValid);
|
||||||
|
Assert.Equal(1, modelStateDictionary.Count);
|
||||||
|
|
||||||
|
// Check ValueTypeRequired error.
|
||||||
|
ModelState modelState;
|
||||||
|
Assert.True(modelStateDictionary.TryGetValue("theModel.ValueTypeRequired", out modelState));
|
||||||
|
Assert.Equal(1, modelState.Errors.Count);
|
||||||
|
|
||||||
|
var modelError = modelState.Errors[0];
|
||||||
|
Assert.Null(modelError.Exception);
|
||||||
|
Assert.NotNull(modelError.ErrorMessage);
|
||||||
|
Assert.Equal("Sample message", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProcessDto_RequiredFieldNull_RaisesModelErrorWithMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new Person();
|
||||||
|
var containerMetadata = GetMetadataForObject(model);
|
||||||
|
|
||||||
|
var bindingContext = CreateContext(containerMetadata);
|
||||||
|
|
||||||
|
ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Make ValueTypeRequired invalid.
|
||||||
|
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "ValueTypeRequired");
|
||||||
|
dto.Results[propertyMetadata] =
|
||||||
|
new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.ValueTypeRequired"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
ModelStateDictionary modelStateDictionary = bindingContext.ModelState;
|
||||||
|
Assert.False(modelStateDictionary.IsValid);
|
||||||
|
Assert.Equal(1, modelStateDictionary.Count);
|
||||||
|
|
||||||
|
// Check ValueTypeRequired error.
|
||||||
|
ModelState modelState;
|
||||||
|
Assert.True(modelStateDictionary.TryGetValue("theModel.ValueTypeRequired", out modelState));
|
||||||
|
Assert.Equal(1, modelState.Errors.Count);
|
||||||
|
|
||||||
|
ModelError modelError = modelState.Errors[0];
|
||||||
|
Assert.Null(modelError.Exception);
|
||||||
|
Assert.NotNull(modelError.ErrorMessage);
|
||||||
|
Assert.Equal("Sample message", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProcessDto_Success()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dob = new DateTime(2001, 1, 1);
|
||||||
|
var model = new PersonWithBindExclusion
|
||||||
|
{
|
||||||
|
DateOfBirth = dob
|
||||||
|
};
|
||||||
|
var containerMetadata = GetMetadataForObject(model);
|
||||||
|
|
||||||
|
var bindingContext = CreateContext(containerMetadata);
|
||||||
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
|
||||||
|
var firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
|
||||||
|
dto.Results[firstNameProperty] = new ComplexModelDtoResult("John", new ModelValidationNode(firstNameProperty, ""));
|
||||||
|
var lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
|
||||||
|
dto.Results[lastNameProperty] = new ComplexModelDtoResult("Doe", new ModelValidationNode(lastNameProperty, ""));
|
||||||
|
var dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
|
||||||
|
dto.Results[dobProperty] = null;
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("John", model.FirstName);
|
||||||
|
Assert.Equal("Doe", model.LastName);
|
||||||
|
Assert.Equal(dob, model.DateOfBirth);
|
||||||
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_PropertyHasDefaultValue_SetsDefaultValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||||
|
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.First(o => o.PropertyName == "PropertyWithDefaultValue");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||||
|
var requiredValidator = bindingContext.ValidatorProviders
|
||||||
|
.SelectMany(v => v.GetValidators(propertyMetadata))
|
||||||
|
.Where(v => v.IsRequired)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||||
|
Assert.Equal(123.456m, person.PropertyWithDefaultValue);
|
||||||
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_PropertyIsReadOnly_DoesNothing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// If didn't throw, success!
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_PropertyIsSettable_CallsSetter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new Person();
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(model));
|
||||||
|
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
|
||||||
|
var requiredValidator = bindingContext.ValidatorProviders
|
||||||
|
.SelectMany(v => v.GetValidators(propertyMetadata))
|
||||||
|
.Where(v => v.IsRequired)
|
||||||
|
.FirstOrDefault();
|
||||||
|
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
validationNode.Validate(validationContext);
|
||||||
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
|
Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void SetProperty_PropertyIsSettable_SetterThrows_RecordsError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new Person
|
||||||
|
{
|
||||||
|
DateOfBirth = new DateTime(1900, 1, 1)
|
||||||
|
};
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(model));
|
||||||
|
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode);
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Date of death can't be before date of birth." + Environment.NewLine
|
||||||
|
+ "Parameter name: value",
|
||||||
|
bindingContext.ModelState["foo"].Errors[0].Exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorNotPresent_RegistersValidationCallback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||||
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
|
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
|
validationNode.Validate(validationContext, bindingContext.ValidationNode);
|
||||||
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorPresent_AddsModelError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||||
|
bindingContext.ModelName = " foo";
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||||
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
|
Assert.Equal("Sample message", bindingContext.ModelState["foo.ValueTypeRequired"].Errors[0].ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void SetProperty_SettingNullableTypeToNull_RequiredValidatorNotPresent_PropertySetterThrows_AddsRequiredMessageString()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(new ModelWhosePropertySetterThrows()));
|
||||||
|
bindingContext.ModelName = "foo";
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||||
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
|
Assert.Equal(1, bindingContext.ModelState["foo.NameNoAttribute"].Errors.Count);
|
||||||
|
Assert.Equal("This is a different exception." + Environment.NewLine
|
||||||
|
+ "Parameter name: value",
|
||||||
|
bindingContext.ModelState["foo.NameNoAttribute"].Errors[0].Exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_SettingNullableTypeToNull_RequiredValidatorPresent_PropertySetterThrows_AddsRequiredMessageString()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bindingContext = CreateContext(GetMetadataForObject(new ModelWhosePropertySetterThrows()));
|
||||||
|
bindingContext.ModelName = "foo";
|
||||||
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
|
||||||
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo.Name");
|
||||||
|
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||||
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
|
Assert.Equal(1, bindingContext.ModelState["foo.Name"].Errors.Count);
|
||||||
|
Assert.Equal("This message comes from the [Required] attribute.", bindingContext.ModelState["foo.Name"].Errors[0].ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelBindingContext CreateContext(ModelMetadata metadata)
|
||||||
|
{
|
||||||
|
return new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelState = new ModelStateDictionary(),
|
||||||
|
ModelMetadata = metadata,
|
||||||
|
ModelName = "theModel",
|
||||||
|
ValidatorProviders = new IModelValidatorProvider[]
|
||||||
|
{
|
||||||
|
new DataAnnotationsModelValidatorProvider(),
|
||||||
|
new DataMemberModelValidatorProvider()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IModelValidator GetRequiredValidator(ModelBindingContext bindingContext, ModelMetadata propertyMetadata)
|
||||||
|
{
|
||||||
|
return bindingContext.ValidatorProviders
|
||||||
|
.SelectMany(v => v.GetValidators(propertyMetadata))
|
||||||
|
.Where(v => v.IsRequired)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelMetadata GetMetadataForCanUpdateProperty(string propertyName)
|
||||||
|
{
|
||||||
|
DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
return metadataProvider.GetMetadataForProperty(null, typeof(MyModelTestingCanUpdateProperty), propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelMetadata GetMetadataForObject(object o)
|
||||||
|
{
|
||||||
|
DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
return metadataProvider.GetMetadataForType(() => o, o.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelMetadata GetMetadataForType(Type t)
|
||||||
|
{
|
||||||
|
DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
return metadataProvider.GetMetadataForType(null, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Person
|
||||||
|
{
|
||||||
|
private DateTime? _dateOfDeath;
|
||||||
|
|
||||||
|
public DateTime DateOfBirth { get; set; }
|
||||||
|
|
||||||
|
public DateTime? DateOfDeath
|
||||||
|
{
|
||||||
|
get { return _dateOfDeath; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < DateOfBirth)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("value", "Date of death can't be before date of birth.");
|
||||||
|
}
|
||||||
|
_dateOfDeath = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Sample message")]
|
||||||
|
public int ValueTypeRequired { get; set; }
|
||||||
|
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
public string LastName { get; set; }
|
||||||
|
public string NonUpdateableProperty { get; private set; }
|
||||||
|
|
||||||
|
[DefaultValue(typeof(decimal), "123.456")]
|
||||||
|
public decimal PropertyWithDefaultValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PersonWithBindExclusion
|
||||||
|
{
|
||||||
|
[BindNever]
|
||||||
|
public DateTime DateOfBirth { get; set; }
|
||||||
|
|
||||||
|
[BindNever]
|
||||||
|
public DateTime? DateOfDeath { get; set; }
|
||||||
|
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
public string LastName { get; set; }
|
||||||
|
public string NonUpdateableProperty { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelWithRequired
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public int Age { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string City { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelWithBindRequired
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[BindRequired]
|
||||||
|
public int Age { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[BindRequired]
|
||||||
|
private class ModelWithMixedBindingBehaviors
|
||||||
|
{
|
||||||
|
public string Required { get; set; }
|
||||||
|
|
||||||
|
[BindNever]
|
||||||
|
public string Never { get; set; }
|
||||||
|
|
||||||
|
[BindingBehavior(BindingBehavior.Optional)]
|
||||||
|
public string Optional { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class MyModelTestingCanUpdateProperty
|
||||||
|
{
|
||||||
|
public int ReadOnlyInt { get; private set; }
|
||||||
|
public string ReadOnlyString { get; private set; }
|
||||||
|
public string[] ReadOnlyArray { get; private set; }
|
||||||
|
public object ReadOnlyObject { get; private set; }
|
||||||
|
public string ReadWriteString { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ModelWhosePropertySetterThrows
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "This message comes from the [Required] attribute.")]
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
set { throw new ArgumentException("This is an exception.", "value"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NameNoAttribute
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
set { throw new ArgumentException("This is a different exception.", "value"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestableMutableObjectModelBinder : MutableObjectModelBinder
|
||||||
|
{
|
||||||
|
public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)
|
||||||
|
{
|
||||||
|
return base.CanUpdateProperty(propertyMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanUpdateProperty(ModelMetadata propertyMetadata)
|
||||||
|
{
|
||||||
|
return CanUpdatePropertyPublic(propertyMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual object CreateModelPublic(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
return base.CreateModel(bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object CreateModel(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
return CreateModelPublic(bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void EnsureModelPublic(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
base.EnsureModel(bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void EnsureModel(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
EnsureModelPublic(bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IEnumerable<ModelMetadata> GetMetadataForPropertiesPublic(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
return base.GetMetadataForProperties(bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
return GetMetadataForPropertiesPublic(bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetPropertyPublic(ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, IModelValidator requiredValidator)
|
||||||
|
{
|
||||||
|
base.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetProperty(ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, IModelValidator requiredValidator)
|
||||||
|
{
|
||||||
|
SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var provider = new DataAnnotationsModelValidatorProvider();
|
var provider = new DataAnnotationsModelValidatorProvider();
|
||||||
var model = new ObservableModel();
|
var model = new ObservableModel();
|
||||||
var metadata = _metadataProvider.GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty");
|
var metadata = _metadataProvider.GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty");
|
||||||
var context = new ModelValidationContext(metadata, null, null, null);
|
var context = new ModelValidationContext(null, null, null, metadata, null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var validators = provider.GetValidators(metadata).ToArray();
|
var validators = provider.GetValidators(metadata).ToArray();
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private static ModelValidationContext CreateValidationContext(ModelMetadata metadata)
|
private static ModelValidationContext CreateValidationContext(ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
return new ModelValidationContext(metadata, null, null, null);
|
return new ModelValidationContext(null, null, null, metadata, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DerivedRequiredAttribute : RequiredAttribute
|
class DerivedRequiredAttribute : RequiredAttribute
|
||||||
|
|
|
||||||
|
|
@ -275,10 +275,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
new DataMemberModelValidatorProvider()
|
new DataMemberModelValidatorProvider()
|
||||||
};
|
};
|
||||||
|
|
||||||
return new ModelValidationContext(metadata,
|
return new ModelValidationContext(new EmptyModelMetadataProvider(),
|
||||||
|
providers,
|
||||||
new ModelStateDictionary(),
|
new ModelStateDictionary(),
|
||||||
new EmptyModelMetadataProvider(),
|
metadata,
|
||||||
providers);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class LoggingValidatableObject : IValidatableObject
|
private sealed class LoggingValidatableObject : IValidatableObject
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue