// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; #if DNXCORE50 using System.Reflection; #endif using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Testing; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.Internal { public class DefaultObjectValidatorTests { private IModelMetadataProvider MetadataProvider { get; } = TestModelMetadataProvider.CreateDefaultProvider(); [Fact] public void Validate_SimpleValueType_Valid_WithPrefix() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)15; modelState.SetModelValue("parameter", "15", "15"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert AssertKeysEqual(modelState, "parameter"); var entry = modelState["parameter"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] public void Validate_SimpleReferenceType_Valid_WithPrefix() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)"test"; modelState.SetModelValue("parameter", "test", "test"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "parameter"); var entry = modelState["parameter"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] public void Validate_SimpleType_MaxErrorsReached() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)"test"; modelState.MaxAllowedErrors = 1; modelState.AddModelError("other.Model", "error"); modelState.SetModelValue("parameter", "test", "test"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, string.Empty, "parameter"); var entry = modelState["parameter"]; Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] public void Validate_SimpleType_SuppressValidation() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)"test"; modelState.SetModelValue("parameter", "test", "test"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter", SuppressValidation = true }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "parameter"); var entry = modelState["parameter"]; Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] public void Validate_ComplexValueType_Valid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new ValueType() { Reference = "ref", Value = 256 }; modelState.SetModelValue("parameter.Reference", "ref", "ref"); modelState.SetModelValue("parameter.Value", "256", "256"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "parameter.Reference", "parameter.Value"); var entry = modelState["parameter.Reference"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["parameter.Value"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] public void Validate_ComplexReferenceType_Valid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new ReferenceType() { Reference = "ref", Value = 256 }; modelState.SetModelValue("parameter.Reference", "ref", "ref"); modelState.SetModelValue("parameter.Value", "256", "256"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "parameter.Reference", "parameter.Value"); var entry = modelState["parameter.Reference"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["parameter.Value"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] public void Validate_ComplexReferenceType_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new Person(); validationState.Add(model, new ValidationStateEntry() { Key = string.Empty }); // Act validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "Name", "Profession"); var entry = modelState["Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Name"), error.ErrorMessage); entry = modelState["Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); } [Fact] public void Validate_ComplexType_SuppressValidation() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = new Person2() { Name = "Billy", Address = new Address { Street = "GreaterThan5Characters" } }; modelState.SetModelValue("person.Name", "Billy", "Billy"); modelState.SetModelValue("person.Address.Street", "GreaterThan5Characters", "GreaterThan5Characters"); validationState.Add(model, new ValidationStateEntry() { Key = "person" }); validationState.Add(model.Address, new ValidationStateEntry() { Key = "person.Address", SuppressValidation = true }); // Act validator.Validate(actionContext, validatorProvider, validationState, "person", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "person.Name", "person.Address.Street"); var entry = modelState["person.Name"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["person.Address.Street"]; Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] [ReplaceCulture] public void Validate_ComplexReferenceType_Invalid_MultipleErrorsOnProperty() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new Address() { Street = "Microsoft Way" }; modelState.SetModelValue("parameter.Street", "Microsoft Way", "Microsoft Way"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "parameter.Street"); var entry = modelState["parameter.Street"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); Assert.Equal(2, entry.Errors.Count); var errorMessages = entry.Errors.Select(e => e.ErrorMessage); Assert.Contains(ValidationAttributeUtil.GetStringLengthErrorMessage(null, 5, "Street"), errorMessages); Assert.Contains(ValidationAttributeUtil.GetRegExErrorMessage("hehehe", "Street"), errorMessages); } [Fact] [ReplaceCulture] public void Validate_ComplexReferenceType_Invalid_MultipleErrorsOnProperty_EmptyPrefix() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new Address() { Street = "Microsoft Way" }; modelState.SetModelValue("Street", "Microsoft Way", "Microsoft Way"); validationState.Add(model, new ValidationStateEntry() { Key = string.Empty }); // Act validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "Street"); var entry = modelState["Street"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); Assert.Equal(2, entry.Errors.Count); var errorMessages = entry.Errors.Select(e => e.ErrorMessage); Assert.Contains(ValidationAttributeUtil.GetStringLengthErrorMessage(null, 5, "Street"), errorMessages); Assert.Contains(ValidationAttributeUtil.GetRegExErrorMessage("hehehe", "Street"), errorMessages); } [Fact] [ReplaceCulture] public void Validate_NestedComplexReferenceType_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new Person() { Name = "Rick", Friend = new Person() }; modelState.SetModelValue("Name", "Rick", "Rick"); validationState.Add(model, new ValidationStateEntry() { Key = string.Empty }); // Act validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "Name", "Profession", "Friend.Name", "Friend.Profession"); var entry = modelState["Name"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); entry = modelState["Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); entry = modelState["Friend.Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Name"), error.ErrorMessage); entry = modelState["Friend.Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); } // IValidatableObject is significant because the validators are on the object // itself, not just the properties. [Fact] [ReplaceCulture] public void Validate_ComplexType_IValidatableObject_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new ValidatableModel(); modelState.SetModelValue("parameter", "model", "model"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "parameter", "parameter.Property1", "parameter.Property2", "parameter.Property3"); var entry = modelState["parameter"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.Equal("Error1", error.ErrorMessage); entry = modelState["parameter.Property1"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal("Error2", error.ErrorMessage); entry = modelState["parameter.Property2"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal("Error3", error.ErrorMessage); entry = modelState["parameter.Property3"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal("Error3", error.ErrorMessage); } [Fact] public void Validate_ComplexType_IValidatableObject_CanUseRequestServices() { // Arrange var service = new Mock(); service.Setup(x => x.DoSomething()).Verifiable(); var provider = new ServiceCollection().AddSingleton(service.Object).BuildServiceProvider(); var httpContext = new Mock(); httpContext.SetupGet(x => x.RequestServices).Returns(provider); var actionContext = new ActionContext { HttpContext = httpContext.Object }; var validatorProvider = CreateValidatorProvider(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = new Mock(); model .Setup(x => x.Validate(It.IsAny())) .Callback((ValidationContext context) => { var receivedService = context.GetService(); Assert.Equal(service.Object, receivedService); receivedService.DoSomething(); }) .Returns(new List()); // Act validator.Validate( actionContext, validatorProvider, validationState, null, model.Object); // Assert service.Verify(); } [Fact] [ReplaceCulture] public void Validate_ComplexType_FieldsAreIgnored_Valid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new VariableTest() { test = 5 }; modelState.SetModelValue("parameter", "5", "5"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.True(modelState.IsValid); Assert.Equal(1, modelState.Count); var entry = modelState["parameter"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] [ReplaceCulture] public void Validate_ComplexType_CyclesNotFollowed_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var person = new Person() { Name = "Billy" }; person.Friend = person; var model = (object)person; modelState.SetModelValue("parameter.Name", "Billy", "Billy"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "parameter.Name", "parameter.Profession"); var entry = modelState["parameter.Name"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["parameter.Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.Equal(error.ErrorMessage, ValidationAttributeUtil.GetRequiredErrorMessage("Profession")); } [Fact] public void Validate_ComplexType_ShortCircuit_WhenMaxErrorCountIsSet() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(typeof(string)); var model = new User() { Password = "password-val", ConfirmPassword = "not-password-val" }; modelState.MaxAllowedErrors = 2; modelState.AddModelError("key1", "error1"); modelState.SetModelValue("user.Password", "password-val", "password-val"); modelState.SetModelValue("user.ConfirmPassword", "not-password-val", "not-password-val"); validationState.Add(model, new ValidationStateEntry() { Key = "user", }); // Act validator.Validate(actionContext, validatorProvider, validationState, "user", model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, string.Empty, "key1", "user.ConfirmPassword", "user.Password"); var entry = modelState[string.Empty]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.IsType(error.Exception); } [Fact] [ReplaceCulture] public void Validate_CollectionType_ArrayOfSimpleType_Valid_DefaultKeyPattern() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new int[] { 5, 17 }; modelState.SetModelValue("parameter[0]", "5", "17"); modelState.SetModelValue("parameter[1]", "17", "5"); validationState.Add(model, new ValidationStateEntry() { Key = "parameter" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "parameter", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "parameter[0]", "parameter[1]"); var entry = modelState["parameter[0]"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["parameter[0]"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] [ReplaceCulture] public void Validate_CollectionType_ArrayOfComplexType_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new Person[] { new Person(), new Person() }; validationState.Add(model, new ValidationStateEntry() { Key = string.Empty }); // Act validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "[0].Name", "[0].Profession", "[1].Name", "[1].Profession"); var entry = modelState["[0].Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Name"), error.ErrorMessage); entry = modelState["[0].Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); entry = modelState["[1].Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Name"), error.ErrorMessage); entry = modelState["[1].Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); } [Fact] [ReplaceCulture] public void Validate_CollectionType_ListOfComplexType_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new List { new Person(), new Person() }; validationState.Add(model, new ValidationStateEntry() { Key = string.Empty }); // Act validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual(modelState, "[0].Name", "[0].Profession", "[1].Name", "[1].Profession"); var entry = modelState["[0].Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Name"), error.ErrorMessage); entry = modelState["[0].Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); entry = modelState["[1].Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Name"), error.ErrorMessage); entry = modelState["[1].Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); } public static TheoryData ValidCollectionData { get { return new TheoryData() { { new int[] { 1, 2, 3 }, typeof(int[]) }, { new string[] { "Foo", "Bar", "Baz" }, typeof(string[]) }, { new List { "Foo", "Bar", "Baz" }, typeof(IList)}, { new HashSet { "Foo", "Bar", "Baz" }, typeof(string[]) }, { new List { DateTime.Parse("1/1/14"), DateTime.Parse("2/1/14"), DateTime.Parse("3/1/14"), }, typeof(ICollection) }, { new HashSet { new Uri("http://example.com/1"), new Uri("http://example.com/2"), new Uri("http://example.com/3"), }, typeof(HashSet) }, }; } } [Theory] [MemberData(nameof(ValidCollectionData))] public void Validate_IndexedCollectionTypes_Valid(object model, Type type) { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); modelState.Add("items[0]", new ModelStateEntry()); modelState.Add("items[1]", new ModelStateEntry()); modelState.Add("items[2]", new ModelStateEntry()); validationState.Add(model, new ValidationStateEntry() { Key = "items", // Force the validator to treat it as the specified type. Metadata = MetadataProvider.GetMetadataForType(type), }); // Act validator.Validate(actionContext, validatorProvider, validationState, "items", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "items[0]", "items[1]", "items[2]"); var entry = modelState["items[0]"]; Assert.Equal(entry.ValidationState, ModelValidationState.Valid); Assert.Empty(entry.Errors); entry = modelState["items[1]"]; Assert.Equal(entry.ValidationState, ModelValidationState.Valid); Assert.Empty(entry.Errors); entry = modelState["items[2]"]; Assert.Equal(entry.ValidationState, ModelValidationState.Valid); Assert.Empty(entry.Errors); } [Fact] public void Validate_CollectionType_DictionaryOfSimpleType_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = new Dictionary() { { "FooKey", "FooValue" }, { "BarKey", "BarValue" } }; modelState.Add("items[0].Key", new ModelStateEntry()); modelState.Add("items[0].Value", new ModelStateEntry()); modelState.Add("items[1].Key", new ModelStateEntry()); modelState.Add("items[1].Value", new ModelStateEntry()); validationState.Add(model, new ValidationStateEntry() { Key = "items" }); // Act validator.Validate(actionContext, validatorProvider, validationState, "items", model); // Assert Assert.True(modelState.IsValid); AssertKeysEqual(modelState, "items[0].Key", "items[0].Value", "items[1].Key", "items[1].Value"); var entry = modelState["items[0].Key"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["items[0].Value"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["items[1].Key"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["items[1].Value"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] [ReplaceCulture] public void Validate_CollectionType_DictionaryOfComplexType_Invalid() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = (object)new Dictionary { { "Joe", new Person() }, { "Mark", new Person() } }; modelState.SetModelValue("[0].Key", "Joe", "Joe"); modelState.SetModelValue("[1].Key", "Mark", "Mark"); validationState.Add(model, new ValidationStateEntry() { Key = string.Empty }); // Act validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); // Assert Assert.False(modelState.IsValid); AssertKeysEqual( modelState, "[0].Key", "[0].Value.Name", "[0].Value.Profession", "[1].Key", "[1].Value.Name", "[1].Value.Profession"); var entry = modelState["[0].Key"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["[1].Key"]; Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Empty(entry.Errors); entry = modelState["[0].Value.Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); Assert.Equal(error.ErrorMessage, ValidationAttributeUtil.GetRequiredErrorMessage("Name")); entry = modelState["[0].Value.Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(error.ErrorMessage, ValidationAttributeUtil.GetRequiredErrorMessage("Profession")); entry = modelState["[1].Value.Name"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(error.ErrorMessage, ValidationAttributeUtil.GetRequiredErrorMessage("Name")); entry = modelState["[1].Value.Profession"]; Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); Assert.Equal(error.ErrorMessage, ValidationAttributeUtil.GetRequiredErrorMessage("Profession")); } [Fact] [ReplaceCulture] public void Validate_DoesntCatchExceptions_FromPropertyAccessors() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = new ThrowingProperty(); // Act & Assert Assert.Throws( typeof(InvalidTimeZoneException), () => { validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); }); } // We use the reference equality comparer for breaking cycles [Fact] public void Validate_DoesNotUseOverridden_GetHashCodeOrEquals() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(); var model = new TypeThatOverridesEquals[] { new TypeThatOverridesEquals { Funny = "hehe" }, new TypeThatOverridesEquals { Funny = "hehe" } }; // Act & Assert (does not throw) validator.Validate(actionContext, validatorProvider, validationState, string.Empty, model); } [Fact] public void Validate_ForExcludedComplexType_PropertiesMarkedAsSkipped() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(typeof(User)); var model = new User() { Password = "password-val", ConfirmPassword = "not-password-val" }; // Note that user.ConfirmPassword has no entry in modelstate - we should not // create one just to mark it as skipped. modelState.SetModelValue("user.Password", "password-val", "password-val"); validationState.Add(model, new ValidationStateEntry() { Key = "user", }); // Act validator.Validate(actionContext, validatorProvider, validationState, "user", model); // Assert Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); AssertKeysEqual(modelState, "user.Password"); var entry = modelState["user.Password"]; Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Empty(entry.Errors); } [Fact] public void Validate_ForExcludedCollectionType_PropertiesMarkedAsSkipped() { // Arrange var validatorProvider = CreateValidatorProvider(); var actionContext = new ActionContext(); var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); var validator = CreateValidator(typeof(List)); var model = new List() { "15", }; modelState.SetModelValue("userIds[0]", "15", "15"); validationState.Add(model, new ValidationStateEntry() { Key = "userIds", }); // Act validator.Validate(actionContext, validatorProvider, validationState, "userIds", model); // Assert Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); AssertKeysEqual(modelState, "userIds[0]"); var entry = modelState["userIds[0]"]; Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Empty(entry.Errors); } private static IModelValidatorProvider CreateValidatorProvider() { return TestModelValidatorProvider.CreateDefaultProvider(); } private static DefaultObjectValidator CreateValidator(Type excludedType) { var excludeFilters = new List(); if (excludedType != null) { excludeFilters.Add(new ValidationExcludeFilter(excludedType)); } var provider = TestModelMetadataProvider.CreateDefaultProvider(excludeFilters.ToArray()); return new DefaultObjectValidator(provider); } private static DefaultObjectValidator CreateValidator(params IMetadataDetailsProvider[] providers) { var provider = TestModelMetadataProvider.CreateDefaultProvider(providers); return new DefaultObjectValidator(provider); } private static void AssertKeysEqual(ModelStateDictionary modelState, params string[] keys) { Assert.Equal(keys.OrderBy(k => k).ToArray(), modelState.Keys.OrderBy(k => k).ToArray()); } private class ThrowingProperty { public string WatchOut { get { throw new InvalidTimeZoneException(); } } } private class Person { [Required, StringLength(10)] public string Name { get; set; } [Required] public string Profession { get; set; } public Person Friend { get; set; } } private class Person2 { public string Name { get; set; } public Address Address { get; set; } } private class Address { [StringLength(5)] [RegularExpression("hehehe")] public string Street { get; set; } } private struct ValueType { public int Value { get; set; } public string Reference { get; set; } } private class ReferenceType { public int Value { get; set; } public string Reference { get; set; } } private class ValidatableModel : IValidatableObject { public IEnumerable Validate(ValidationContext validationContext) { yield return new ValidationResult("Error1", new string[] { }); yield return new ValidationResult("Error2", new[] { "Property1" }); yield return new ValidationResult("Error3", new[] { "Property2", "Property3" }); } } private class TypeThatOverridesEquals { [StringLength(2)] public string Funny { get; set; } public override bool Equals(object obj) { throw new InvalidOperationException(); } public override int GetHashCode() { throw new InvalidOperationException(); } } private class VariableTest { [Range(15, 25)] public int test; } private class User : IValidatableObject { public string Password { get; set; } [Compare("Password")] public string ConfirmPassword { get; set; } public IEnumerable Validate(ValidationContext validationContext) { if (Password == "password") { yield return new ValidationResult("Password does not meet complexity requirements."); } } } public interface IExampleService { void DoSomething(); } } }