// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Mvc.Test { public class DataAnnotationsModelValidatorProviderTest { // Validation attribute adapter registration private class MyValidationAttribute : ValidationAttribute { public override bool IsValid(object value) { throw new NotImplementedException(); } } private class MyValidationAttributeAdapter : DataAnnotationsModelValidator { public MyValidationAttributeAdapter(ModelMetadata metadata, ControllerContext context, MyValidationAttribute attribute) : base(metadata, context, attribute) { } } private class MyValidationAttributeAdapterBadCtor : ModelValidator { public MyValidationAttributeAdapterBadCtor(ModelMetadata metadata, ControllerContext context) : base(metadata, context) { } public override IEnumerable Validate(object container) { throw new NotImplementedException(); } } private class MyDefaultValidationAttributeAdapter : DataAnnotationsModelValidator { public MyDefaultValidationAttributeAdapter(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute) : base(metadata, context, attribute) { } } [MyValidation] private class MyValidatedClass { } [Fact] public void RegisterAdapter() { var oldFactories = DataAnnotationsModelValidatorProvider.AttributeFactories; try { // Arrange DataAnnotationsModelValidatorProvider.AttributeFactories = new Dictionary(); // Act DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), typeof(MyValidationAttributeAdapter)); // Assert var type = DataAnnotationsModelValidatorProvider.AttributeFactories.Keys.Single(); Assert.Equal(typeof(MyValidationAttribute), type); var factory = DataAnnotationsModelValidatorProvider.AttributeFactories.Values.Single(); var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object)); var context = new ControllerContext(); var attribute = new MyValidationAttribute(); var validator = factory(metadata, context, attribute); Assert.IsType(validator); } finally { DataAnnotationsModelValidatorProvider.AttributeFactories = oldFactories; } } [Fact] public void RegisterAdapterGuardClauses() { // Attribute type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterAdapter(null, typeof(MyValidationAttributeAdapter)), "attributeType"); // Adapter type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), null), "adapterType"); // Validation attribute must derive from ValidationAttribute Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(object), typeof(MyValidationAttributeAdapter)), "The type System.Object must derive from System.ComponentModel.DataAnnotations.ValidationAttribute\r\nParameter name: attributeType"); // Adapter must derive from ModelValidator Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), typeof(object)), "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType"); // Adapter must have the expected constructor Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), typeof(MyValidationAttributeAdapterBadCtor)), "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidationAttributeAdapterBadCtor must have a public constructor which accepts three parameters of types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext, and System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidationAttribute\r\nParameter name: adapterType"); } [Fact] public void RegisterAdapterFactory() { var oldFactories = DataAnnotationsModelValidatorProvider.AttributeFactories; try { // Arrange DataAnnotationsModelValidatorProvider.AttributeFactories = new Dictionary(); DataAnnotationsModelValidationFactory factory = delegate { return null; }; // Act DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(typeof(MyValidationAttribute), factory); // Assert var type = DataAnnotationsModelValidatorProvider.AttributeFactories.Keys.Single(); Assert.Equal(typeof(MyValidationAttribute), type); Assert.Same(factory, DataAnnotationsModelValidatorProvider.AttributeFactories.Values.Single()); } finally { DataAnnotationsModelValidatorProvider.AttributeFactories = oldFactories; } } [Fact] public void RegisterAdapterFactoryGuardClauses() { DataAnnotationsModelValidationFactory factory = (metadata, context, attribute) => null; // Attribute type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(null, factory), "attributeType"); // Factory cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(typeof(MyValidationAttribute), null), "factory"); // Validation attribute must derive from ValidationAttribute Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(typeof(object), factory), "The type System.Object must derive from System.ComponentModel.DataAnnotations.ValidationAttribute\r\nParameter name: attributeType"); } [Fact] public void RegisterDefaultAdapter() { var oldFactory = DataAnnotationsModelValidatorProvider.DefaultAttributeFactory; try { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatedClass)); var context = new ControllerContext(); DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(typeof(MyDefaultValidationAttributeAdapter)); // Act var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single(); // Assert Assert.IsType(result); } finally { DataAnnotationsModelValidatorProvider.DefaultAttributeFactory = oldFactory; } } [Fact] public void RegisterDefaultAdapterGuardClauses() { // Adapter type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(null), "adapterType"); // Adapter must derive from ModelValidator Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(typeof(object)), "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType"); // Adapter must have the expected constructor Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(typeof(MyValidationAttributeAdapterBadCtor)), "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidationAttributeAdapterBadCtor must have a public constructor which accepts three parameters of types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext, and System.ComponentModel.DataAnnotations.ValidationAttribute\r\nParameter name: adapterType"); } [Fact] public void RegisterDefaultAdapterFactory() { var oldFactory = DataAnnotationsModelValidatorProvider.DefaultAttributeFactory; try { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatedClass)); var context = new ControllerContext(); ModelValidator validator = new Mock(metadata, context).Object; DataAnnotationsModelValidationFactory factory = delegate { return validator; }; DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory(factory); // Act var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single(); // Assert Assert.Same(validator, result); } finally { DataAnnotationsModelValidatorProvider.DefaultAttributeFactory = oldFactory; } } [Fact] public void RegisterDefaultAdapterFactoryGuardClauses() { // Factory cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory(null), "factory"); } // IValidatableObject adapter registration private class MyValidatableAdapter : ModelValidator { public MyValidatableAdapter(ModelMetadata metadata, ControllerContext context) : base(metadata, context) { } public override IEnumerable Validate(object container) { throw new NotImplementedException(); } } private class MyValidatableAdapterBadCtor : ModelValidator { public MyValidatableAdapterBadCtor(ModelMetadata metadata, ControllerContext context, int unused) : base(metadata, context) { } public override IEnumerable Validate(object container) { throw new NotImplementedException(); } } private class MyValidatableClass : IValidatableObject { public IEnumerable Validate(ValidationContext validationContext) { throw new NotImplementedException(); } } [Fact] public void RegisterValidatableObjectAdapter() { var oldFactories = DataAnnotationsModelValidatorProvider.ValidatableFactories; try { // Arrange DataAnnotationsModelValidatorProvider.ValidatableFactories = new Dictionary(); IValidatableObject validatable = new Mock().Object; // Act DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(validatable.GetType(), typeof(MyValidatableAdapter)); // Assert var type = DataAnnotationsModelValidatorProvider.ValidatableFactories.Keys.Single(); Assert.Equal(validatable.GetType(), type); var factory = DataAnnotationsModelValidatorProvider.ValidatableFactories.Values.Single(); var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object)); var context = new ControllerContext(); var validator = factory(metadata, context); Assert.IsType(validator); } finally { DataAnnotationsModelValidatorProvider.ValidatableFactories = oldFactories; } } [Fact] public void RegisterValidatableObjectAdapterGuardClauses() { // Attribute type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(null, typeof(MyValidatableAdapter)), "modelType"); // Adapter type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), null), "adapterType"); // Validation attribute must derive from ValidationAttribute Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(object), typeof(MyValidatableAdapter)), "The type System.Object must derive from System.ComponentModel.DataAnnotations.IValidatableObject\r\nParameter name: modelType"); // Adapter must derive from ModelValidator Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), typeof(object)), "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType"); // Adapter must have the expected constructor Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), typeof(MyValidatableAdapterBadCtor)), "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidatableAdapterBadCtor must have a public constructor which accepts two parameters of types System.Web.Mvc.ModelMetadata and System.Web.Mvc.ControllerContext.\r\nParameter name: adapterType"); } [Fact] public void RegisterValidatableObjectAdapterFactory() { var oldFactories = DataAnnotationsModelValidatorProvider.ValidatableFactories; try { // Arrange DataAnnotationsModelValidatorProvider.ValidatableFactories = new Dictionary(); DataAnnotationsValidatableObjectAdapterFactory factory = delegate { return null; }; // Act DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(typeof(MyValidatableClass), factory); // Assert var type = DataAnnotationsModelValidatorProvider.ValidatableFactories.Keys.Single(); Assert.Equal(typeof(MyValidatableClass), type); Assert.Same(factory, DataAnnotationsModelValidatorProvider.ValidatableFactories.Values.Single()); } finally { DataAnnotationsModelValidatorProvider.ValidatableFactories = oldFactories; } } [Fact] public void RegisterValidatableObjectAdapterFactoryGuardClauses() { DataAnnotationsValidatableObjectAdapterFactory factory = (metadata, context) => null; // Attribute type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(null, factory), "modelType"); // Factory cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(typeof(MyValidatableClass), null), "factory"); // Validation attribute must derive from ValidationAttribute Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(typeof(object), factory), "The type System.Object must derive from System.ComponentModel.DataAnnotations.IValidatableObject\r\nParameter name: modelType"); } [Fact] public void RegisterDefaultValidatableObjectAdapter() { var oldFactory = DataAnnotationsModelValidatorProvider.DefaultValidatableFactory; try { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatableClass)); var context = new ControllerContext(); DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(typeof(MyValidatableAdapter)); // Act var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single(); // Assert Assert.IsType(result); } finally { DataAnnotationsModelValidatorProvider.DefaultValidatableFactory = oldFactory; } } [Fact] public void RegisterDefaultValidatableObjectAdapterGuardClauses() { // Adapter type cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(null), "adapterType"); // Adapter must derive from ModelValidator Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(typeof(object)), "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType"); // Adapter must have the expected constructor Assert.Throws( () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(typeof(MyValidatableAdapterBadCtor)), "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidatableAdapterBadCtor must have a public constructor which accepts two parameters of types System.Web.Mvc.ModelMetadata and System.Web.Mvc.ControllerContext.\r\nParameter name: adapterType"); } [Fact] public void RegisterDefaultValidatableObjectAdapterFactory() { var oldFactory = DataAnnotationsModelValidatorProvider.DefaultValidatableFactory; try { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatableClass)); var context = new ControllerContext(); ModelValidator validator = new Mock(metadata, context).Object; DataAnnotationsValidatableObjectAdapterFactory factory = delegate { return validator; }; DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapterFactory(factory); // Act var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single(); // Assert Assert.Same(validator, result); } finally { DataAnnotationsModelValidatorProvider.DefaultValidatableFactory = oldFactory; } } [Fact] public void RegisterDefaultValidatableObjectAdapterFactoryGuardClauses() { // Factory cannot be null Assert.ThrowsArgumentNull( () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapterFactory(null), "factory"); } // Pre-configured adapters [Fact] public void AdapterForRangeAttributeRegistered() { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object)); var context = new ControllerContext(); var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories; var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(RangeAttribute)).Value; // Act var adapter = adapterFactory(metadata, context, new RangeAttribute(1, 100)); // Assert Assert.IsType(adapter); } [Fact] public void AdapterForRegularExpressionAttributeRegistered() { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object)); var context = new ControllerContext(); var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories; var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(RegularExpressionAttribute)).Value; // Act var adapter = adapterFactory(metadata, context, new RegularExpressionAttribute("abc")); // Assert Assert.IsType(adapter); } [Fact] public void AdapterForRequiredAttributeRegistered() { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object)); var context = new ControllerContext(); var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories; var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(RequiredAttribute)).Value; // Act var adapter = adapterFactory(metadata, context, new RequiredAttribute()); // Assert Assert.IsType(adapter); } [Fact] public void AdapterForStringLengthAttributeRegistered() { // Arrange var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object)); var context = new ControllerContext(); var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories; var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(StringLengthAttribute)).Value; // Act var adapter = adapterFactory(metadata, context, new StringLengthAttribute(6)); // Assert Assert.IsType(adapter); } // Default adapter factory for unknown attribute type [Fact] public void UnknownValidationAttributeGetsDefaultAdapter() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var context = new ControllerContext(); var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(DummyClassWithDummyValidationAttribute)); // Act IEnumerable validators = provider.GetValidators(metadata, context); // Assert var validator = validators.Single(); Assert.IsType(validator); } private class DummyValidationAttribute : ValidationAttribute { } [DummyValidation] private class DummyClassWithDummyValidationAttribute { } // Default IValidatableObject adapter factory [Fact] public void IValidatableObjectGetsAValidator() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var mockValidatable = new Mock(); var context = new ControllerContext(); var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, mockValidatable.Object.GetType()); // Act IEnumerable validators = provider.GetValidators(metadata, context); // Assert Assert.Single(validators); } // Implicit [Required] attribute [Fact] public void ReferenceTypesDontGetImplicitRequiredAttribute() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var context = new ControllerContext(); var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(string)); // Act IEnumerable validators = provider.GetValidators(metadata, context); // Assert Assert.Empty(validators); } [Fact] public void NonNullableValueTypesGetImplicitRequiredAttribute() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var context = new ControllerContext(); var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(DummyRequiredAttributeHelperClass), "WithoutAttribute"); // Act IEnumerable validators = provider.GetValidators(metadata, context); // Assert ModelValidator validator = validators.Single(); ModelClientValidationRule rule = validator.GetClientValidationRules().Single(); Assert.IsType(rule); } [Fact] public void NonNullableValueTypesWithExplicitRequiredAttributeDoesntGetImplictRequiredAttribute() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var context = new ControllerContext(); var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(DummyRequiredAttributeHelperClass), "WithAttribute"); // Act IEnumerable validators = provider.GetValidators(metadata, context); // Assert ModelValidator validator = validators.Single(); ModelClientValidationRule rule = validator.GetClientValidationRules().Single(); Assert.IsType(rule); Assert.Equal("Custom Required Message", rule.ErrorMessage); } [Fact] public void NonNullableValueTypeDoesntGetImplicitRequiredAttributeWhenFlagIsOff() { DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; try { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var context = new ControllerContext(); var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(DummyRequiredAttributeHelperClass), "WithoutAttribute"); // Act IEnumerable validators = provider.GetValidators(metadata, context); // Assert Assert.Empty(validators); } finally { DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = true; } } private class DummyRequiredAttributeHelperClass { [Required(ErrorMessage = "Custom Required Message")] public int WithAttribute { get; set; } public int WithoutAttribute { get; set; } } // Integration with metadata system [Fact] public void DoesNotReadPropertyValue() { // Arrange ObservableModel model = new ObservableModel(); ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty"); ControllerContext controllerContext = new ControllerContext(); // Act ModelValidator[] validators = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, controllerContext).ToArray(); ModelValidationResult[] results = validators.SelectMany(o => o.Validate(model)).ToArray(); // Assert Assert.Empty(validators); Assert.False(model.PropertyWasRead()); } private class ObservableModel { private bool _propertyWasRead; public string TheProperty { get { _propertyWasRead = true; return "Hello"; } } public bool PropertyWasRead() { return _propertyWasRead; } } private class BaseModel { public virtual string MyProperty { get; set; } } private class DerivedModel : BaseModel { [StringLength(10)] public override string MyProperty { get { return base.MyProperty; } set { base.MyProperty = value; } } } [Fact] public void GetValidatorsReturnsClientValidatorForDerivedTypeAppliedAgainstBaseTypeViaFromLambdaExpression() { // Dev10 Bug #868619 // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var context = new ControllerContext(); var viewdata = new ViewDataDictionary(); var metadata = ModelMetadata.FromLambdaExpression(m => m.MyProperty, viewdata); // Bug is in FromLambdaExpression // Act IEnumerable validators = provider.GetValidators(metadata, context); // Assert ModelValidator validator = validators.Single(); ModelClientValidationRule clientRule = validator.GetClientValidationRules().Single(); Assert.IsType(clientRule); Assert.Equal(10, clientRule.ValidationParameters["max"]); } } }