Reintroducing BindingBehaviorAttribute attribute

* Porting MutableObjectModelBinder tests
* Fix issues in MutableObjectModelBinder introduced by changing from
  TypeDescriptors to reflection
This commit is contained in:
Pranav K 2014-03-17 15:04:14 -07:00
parent 9cd99a42a7
commit f8dd52dfe3
14 changed files with 1130 additions and 76 deletions

View File

@ -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)
{
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -0,0 +1,10 @@

namespace Microsoft.AspNet.Mvc.ModelBinding
{
public enum BindingBehavior
{
Optional = 0,
Never,
Required
}
}

View File

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

View File

@ -57,10 +57,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
}
var validationContext = new ModelValidationContext(bindingContext.ModelMetadata,
bindingContext.ModelState,
bindingContext.MetadataProvider,
bindingContext.ValidatorProviders);
var validationContext = new ModelValidationContext(bindingContext.MetadataProvider,
bindingContext.ValidatorProviders,
bindingContext.ModelState,
bindingContext.ModelMetadata,
containerMetadata: null);
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
bindingContext.Model = newBindingContext.Model;

View File

@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// TODO: Revive ModelBinderConfig
// 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)
{
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
@ -136,17 +136,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
{
// keep a set of the required properties so that we can cross-reference bound properties later
HashSet<string> requiredProperties;
Dictionary<string, IModelValidator> requiredValidators;
HashSet<string> skipProperties;
GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
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;
var validationInfo = GetPropertyValidationInfo(bindingContext);
return bindingContext.ModelMetadata.Properties
.Where(propertyMetadata =>
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
CanUpdateProperty(propertyMetadata));
}
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
@ -155,43 +150,55 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return (attr != null) ? attr.Value : null;
}
internal static void GetRequiredPropertiesCollection(ModelBindingContext bindingContext,
out HashSet<string> requiredProperties,
out Dictionary<string, IModelValidator> requiredValidators,
out HashSet<string> skipProperties)
internal static PropertyValidationInfo GetPropertyValidationInfo(ModelBindingContext bindingContext)
{
requiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
requiredValidators = new Dictionary<string, IModelValidator>(StringComparer.OrdinalIgnoreCase);
skipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// TODO: HttpBindingBehaviorAttribute
var validationInfo = new PropertyValidationInfo();
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)
.FirstOrDefault(v => v.IsRequired);
// TODO: Revive HttpBindingBehaviorAttribute
if (requiredValidator != null)
{
requiredValidators[propertyName] = requiredValidator;
requiredProperties.Add(propertyName);
validationInfo.RequiredValidators[propertyName] = requiredValidator;
}
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)
{
HashSet<string> requiredProperties;
Dictionary<string, IModelValidator> requiredValidators;
HashSet<string> skipProperties;
GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
var validationInfo = GetPropertyValidationInfo(bindingContext);
// 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 modelStateKey = ModelBindingHelper.CreatePropertyModelName(
@ -205,12 +212,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Execute validator (if any) to get custom error message.
IModelValidator validator;
if (requiredValidators.TryGetValue(missingRequiredProperty, out validator))
if (validationInfo.RequiredValidators.TryGetValue(missingRequiredProperty, out validator))
{
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.
if (!addedError)
{
@ -227,7 +234,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var dtoResult = entry.Value;
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);
}
}
@ -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.")]
protected virtual void SetProperty(ModelBindingContext bindingContext,
ModelMetadata propertyMetadata,
ComplexModelDtoResult dtoResult)
ComplexModelDtoResult dtoResult,
IModelValidator requiredValidator)
{
var property = bindingContext.ModelType
.GetProperty(propertyMetadata.PropertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
@ -258,10 +268,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
var requiredValidator = bindingContext.GetValidators(propertyMetadata).FirstOrDefault(v => v.IsRequired);
if (requiredValidator != null)
{
var validationContext = bindingContext.CreateValidationContext(propertyMetadata);
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
foreach (var validationResult in requiredValidator.Validate(validationContext))
{
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
@ -279,10 +288,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
catch (Exception ex)
{
// 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;
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,
string modelStateKey)
{
var validationContext = bindingContext.CreateValidationContext(propertyMetadata);
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
var addedError = false;
foreach (var validationResult in validator.Validate(validationContext))
@ -313,5 +328,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
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; }
}
}
}

View File

@ -12,20 +12,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
.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)
{
var validators = GetValidators(bindingContext, bindingContext.ModelMetadata);
var compositeValidator = new CompositeModelValidator(validators);
var modelValidationContext = CreateValidationContext(bindingContext, bindingContext.ModelMetadata);
var modelValidationContext = new ModelValidationContext(bindingContext, bindingContext.ModelMetadata);
return compositeValidator.Validate(modelValidationContext);
}
}

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
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)
@ -64,10 +64,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Func<object, object> propertyGetter = propertyInfo.ValueAccessor;
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)
{
// 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)
{
var info = new PropertyInformation();
info.ValueAccessor = CreatePropertyValueAccessor(property);
info.Prototype = CreateMetadataPrototype(property.GetCustomAttributes(),
containerType,
property.PropertyType,
property.Name);
return info;
return new PropertyInformation
{
ValueAccessor = CreatePropertyValueAccessor(property),
Prototype = CreateMetadataPrototype(property.GetCustomAttributes(),
containerType,
property.PropertyType,
property.Name),
IsReadOnly = !property.CanWrite || property.SetMethod.IsPrivate
};
}
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 TModelMetadata Prototype { get; set; }
public bool IsReadOnly { get; set; }
}
}
}

View File

@ -28,7 +28,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
var metadata = validationContext.ModelMetadata;
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(),
MemberName = memberName

View File

@ -4,18 +4,30 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ModelValidationContext
{
public ModelValidationContext([NotNull] ModelMetadata metadata,
[NotNull] ModelStateDictionary modelState,
[NotNull] IModelMetadataProvider metadataProvider,
[NotNull] IEnumerable<IModelValidatorProvider> validatorProviders)
public ModelValidationContext([NotNull] ModelBindingContext bindingContext,
[NotNull] ModelMetadata metadata)
: this(bindingContext.MetadataProvider,
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;
ModelState = modelState;
MetadataProvider = metadataProvider;
ValidatorProviders = validatorProviders;
ContainerMetadata = containerMetadata;
}
public ModelValidationContext([NotNull] ModelValidationContext parentContext,
public ModelValidationContext([NotNull] ModelValidationContext parentContext,
[NotNull] ModelMetadata metadata)
{
ModelMetadata = metadata;

View File

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

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var provider = new DataAnnotationsModelValidatorProvider();
var model = new ObservableModel();
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
var validators = provider.GetValidators(metadata).ToArray();

View File

@ -218,7 +218,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private static ModelValidationContext CreateValidationContext(ModelMetadata metadata)
{
return new ModelValidationContext(metadata, null, null, null);
return new ModelValidationContext(null, null, null, metadata, null);
}
class DerivedRequiredAttribute : RequiredAttribute

View File

@ -275,10 +275,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
new DataMemberModelValidatorProvider()
};
return new ModelValidationContext(metadata,
return new ModelValidationContext(new EmptyModelMetadataProvider(),
providers,
new ModelStateDictionary(),
new EmptyModelMetadataProvider(),
providers);
metadata,
null);
}
private sealed class LoggingValidatableObject : IValidatableObject