// 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 System.Web.Http.Metadata; using System.Web.Http.Metadata.Providers; using System.Web.Http.Validation.Validators; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Http.Validation.Providers { public class DataAnnotationsModelValidatorProviderTest { private static DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider(); private static IEnumerable _noValidatorProviders = Enumerable.Empty(); // Validation attribute adapter registration private class MyValidationAttribute : ValidationAttribute { public override bool IsValid(object value) { throw new NotImplementedException(); } } private class MyValidationAttributeAdapter : DataAnnotationsModelValidator { public MyValidationAttributeAdapter(IEnumerable validatorProviders, ValidationAttribute attribute) : base(validatorProviders, attribute) { } } private class MyValidationAttributeAdapterBadCtor : ModelValidator { public MyValidationAttributeAdapterBadCtor(IEnumerable validatorProviders) : base(validatorProviders) { } public override IEnumerable Validate(ModelMetadata metadata, object container) { throw new NotImplementedException(); } } private class MyDefaultValidationAttributeAdapter : DataAnnotationsModelValidator { public MyDefaultValidationAttributeAdapter(IEnumerable validatorProviders, ValidationAttribute attribute) : base(validatorProviders, attribute) { } } [MyValidation] private class MyValidatedClass { } [Fact] public void RegisterAdapter() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); provider.AttributeFactories = new Dictionary(); // Act provider.RegisterAdapter(typeof(MyValidationAttribute), typeof(MyValidationAttributeAdapter)); // Assert var type = provider.AttributeFactories.Keys.Single(); Assert.Equal(typeof(MyValidationAttribute), type); var factory = provider.AttributeFactories.Values.Single(); var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(object)); var attribute = new MyValidationAttribute(); var validator = factory(_noValidatorProviders, attribute); Assert.IsType(validator); } [Fact] public void RegisterAdapterGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); // Attribute type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterAdapter(null, typeof(MyValidationAttributeAdapter)), "attributeType"); // Adapter type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterAdapter(typeof(MyValidationAttribute), null), "adapterType"); // Validation attribute must derive from ValidationAttribute Assert.ThrowsArgument( () => provider.RegisterAdapter(typeof(object), typeof(MyValidationAttributeAdapter)), "attributeType", "The type Object must derive from ValidationAttribute"); // Adapter must derive from ModelValidator Assert.ThrowsArgument( () => provider.RegisterAdapter(typeof(MyValidationAttribute), typeof(object)), "adapterType", "The type Object must derive from ModelValidator"); // Adapter must have the expected constructor Assert.ThrowsArgument( () => provider.RegisterAdapter(typeof(MyValidationAttribute), typeof(MyValidationAttributeAdapterBadCtor)), "adapterType", "The type MyValidationAttributeAdapterBadCtor must have a public constructor which accepts three parameters of types ModelMetadata, IEnumerable, and MyValidationAttribute"); } [Fact] public void RegisterAdapterFactory() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); provider.AttributeFactories = new Dictionary(); DataAnnotationsModelValidationFactory factory = delegate { return null; }; // Act provider.RegisterAdapterFactory(typeof(MyValidationAttribute), factory); // Assert var type = provider.AttributeFactories.Keys.Single(); Assert.Equal(typeof(MyValidationAttribute), type); Assert.Same(factory, provider.AttributeFactories.Values.Single()); } [Fact] public void RegisterAdapterFactoryGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); DataAnnotationsModelValidationFactory factory = (validatorProviders, attribute) => null; // Attribute type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterAdapterFactory(null, factory), "attributeType"); // Factory cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterAdapterFactory(typeof(MyValidationAttribute), null), "factory"); // Validation attribute must derive from ValidationAttribute Assert.ThrowsArgument( () => provider.RegisterAdapterFactory(typeof(object), factory), "attributeType", "The type Object must derive from ValidationAttribute"); } [Fact] public void RegisterDefaultAdapter() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(MyValidatedClass)); provider.RegisterDefaultAdapter(typeof(MyDefaultValidationAttributeAdapter)); // Act var result = provider.GetValidators(metadata, _noValidatorProviders).Single(); // Assert Assert.IsType(result); } [Fact] public void RegisterDefaultAdapterGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); // Adapter type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterDefaultAdapter(null), "adapterType"); // Adapter must derive from ModelValidator Assert.ThrowsArgument( () => provider.RegisterDefaultAdapter(typeof(object)), "adapterType", "The type Object must derive from ModelValidator"); // Adapter must have the expected constructor Assert.ThrowsArgument( () => provider.RegisterDefaultAdapter(typeof(MyValidationAttributeAdapterBadCtor)), "adapterType", "The type MyValidationAttributeAdapterBadCtor must have a public constructor which accepts three parameters of types ModelMetadata, IEnumerable, and ValidationAttribute"); } [Fact] public void RegisterDefaultAdapterFactory() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(MyValidatedClass)); ModelValidator validator = new Mock(_noValidatorProviders).Object; DataAnnotationsModelValidationFactory factory = delegate { return validator; }; provider.RegisterDefaultAdapterFactory(factory); // Act var result = provider.GetValidators(metadata, _noValidatorProviders).Single(); // Assert Assert.Same(validator, result); } [Fact] public void RegisterDefaultAdapterFactoryGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); // Factory cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterDefaultAdapterFactory(null), "factory"); } // IValidatableObject adapter registration private class MyValidatableAdapter : ModelValidator { public MyValidatableAdapter(IEnumerable validatorProviders) : base(validatorProviders) { } public override IEnumerable Validate(ModelMetadata metadata, object container) { throw new NotImplementedException(); } } private class MyValidatableAdapterBadCtor : ModelValidator { public MyValidatableAdapterBadCtor(IEnumerable validatorProviders, int unused) : base(validatorProviders) { } public override IEnumerable Validate(ModelMetadata metadata, object container) { throw new NotImplementedException(); } } private class MyValidatableClass : IValidatableObject { public IEnumerable Validate(ValidationContext validationContext) { throw new NotImplementedException(); } } [Fact] public void RegisterValidatableObjectAdapter() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); provider.ValidatableFactories = new Dictionary(); IValidatableObject validatable = new Mock().Object; // Act provider.RegisterValidatableObjectAdapter(validatable.GetType(), typeof(MyValidatableAdapter)); // Assert var type = provider.ValidatableFactories.Keys.Single(); Assert.Equal(validatable.GetType(), type); var factory = provider.ValidatableFactories.Values.Single(); var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(object)); var validator = factory(_noValidatorProviders); Assert.IsType(validator); } [Fact] public void RegisterValidatableObjectAdapterGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); // Attribute type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterValidatableObjectAdapter(null, typeof(MyValidatableAdapter)), "modelType"); // Adapter type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), null), "adapterType"); // Validation attribute must derive from ValidationAttribute Assert.ThrowsArgument( () => provider.RegisterValidatableObjectAdapter(typeof(object), typeof(MyValidatableAdapter)), "modelType", "The type Object must derive from IValidatableObject."); // Adapter must derive from ModelValidator Assert.ThrowsArgument( () => provider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), typeof(object)), "adapterType", "The type Object must derive from ModelValidator"); // Adapter must have the expected constructor Assert.ThrowsArgument( () => provider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), typeof(MyValidatableAdapterBadCtor)), "adapterType", "The type MyValidatableAdapterBadCtor must have a public constructor which accepts two parameters of types ModelMetadata and IEnumerable"); } [Fact] public void RegisterValidatableObjectAdapterFactory() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); provider.ValidatableFactories = new Dictionary(); DataAnnotationsValidatableObjectAdapterFactory factory = delegate { return null; }; // Act provider.RegisterValidatableObjectAdapterFactory(typeof(MyValidatableClass), factory); // Assert var type = provider.ValidatableFactories.Keys.Single(); Assert.Equal(typeof(MyValidatableClass), type); Assert.Same(factory, provider.ValidatableFactories.Values.Single()); } [Fact] public void RegisterValidatableObjectAdapterFactoryGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); DataAnnotationsValidatableObjectAdapterFactory factory = (context) => null; // Attribute type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterValidatableObjectAdapterFactory(null, factory), "modelType"); // Factory cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterValidatableObjectAdapterFactory(typeof(MyValidatableClass), null), "factory"); // Validation attribute must derive from ValidationAttribute Assert.ThrowsArgument( () => provider.RegisterValidatableObjectAdapterFactory(typeof(object), factory), "modelType", "The type Object must derive from IValidatableObject"); } [Fact] public void RegisterDefaultValidatableObjectAdapter() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(MyValidatableClass)); provider.RegisterDefaultValidatableObjectAdapter(typeof(MyValidatableAdapter)); // Act var result = provider.GetValidators(metadata, _noValidatorProviders).Single(); // Assert Assert.IsType(result); } [Fact] public void RegisterDefaultValidatableObjectAdapterGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); // Adapter type cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterDefaultValidatableObjectAdapter(null), "adapterType"); // Adapter must derive from ModelValidator Assert.ThrowsArgument( () => provider.RegisterDefaultValidatableObjectAdapter(typeof(object)), "adapterType", "The type Object must derive from ModelValidator"); // Adapter must have the expected constructor Assert.ThrowsArgument( () => provider.RegisterDefaultValidatableObjectAdapter(typeof(MyValidatableAdapterBadCtor)), "adapterType", "The type MyValidatableAdapterBadCtor must have a public constructor which accepts two parameters of types ModelMetadata and IEnumerable"); } [Fact] public void RegisterDefaultValidatableObjectAdapterFactory() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(MyValidatableClass)); ModelValidator validator = new Mock(_noValidatorProviders).Object; DataAnnotationsValidatableObjectAdapterFactory factory = delegate { return validator; }; provider.RegisterDefaultValidatableObjectAdapterFactory(factory); // Act var result = provider.GetValidators(metadata, _noValidatorProviders).Single(); // Assert Assert.Same(validator, result); } [Fact] public void RegisterDefaultValidatableObjectAdapterFactoryGuardClauses() { var provider = new DataAnnotationsModelValidatorProvider(); // Factory cannot be null Assert.ThrowsArgumentNull( () => provider.RegisterDefaultValidatableObjectAdapterFactory(null), "factory"); } // Default adapter factory for unknown attribute type [Fact] public void UnknownValidationAttributeGetsDefaultAdapter() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(DummyClassWithDummyValidationAttribute)); // Act IEnumerable validators = provider.GetValidators(metadata, _noValidatorProviders); // 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 metadata = _metadataProvider.GetMetadataForType(() => null, mockValidatable.Object.GetType()); // Act IEnumerable validators = provider.GetValidators(metadata, _noValidatorProviders); // Assert Assert.Single(validators); } // Integration with metadata system [Fact] public void DoesNotReadPropertyValue() { // Arrange var provider = new DataAnnotationsModelValidatorProvider(); var model = new ObservableModel(); ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty"); // Act ModelValidator[] validators = provider.GetValidators(metadata, _noValidatorProviders).ToArray(); ModelValidationResult[] results = validators.SelectMany(o => o.Validate(metadata, 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; } } } } }