public void CombineWith()
        {
            // Arrange
            List<string> log = new List<string>();

            ModelValidationNode[] allChildNodes = new[]
            {
                new ModelValidationNode(GetModelMetadata(), "key1"),
                new ModelValidationNode(GetModelMetadata(), "key2"),
                new ModelValidationNode(GetModelMetadata(), "key3"),
            };

            ModelValidationNode parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1");
            parentNode1.ChildNodes.Add(allChildNodes[0]);
            parentNode1.Validating += (sender, e) => log.Add("Validating parent1.");
            parentNode1.Validated += (sender, e) => log.Add("Validated parent1.");

            ModelValidationNode parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2");
            parentNode2.ChildNodes.Add(allChildNodes[1]);
            parentNode2.ChildNodes.Add(allChildNodes[2]);
            parentNode2.Validating += (sender, e) => log.Add("Validating parent2.");
            parentNode2.Validated += (sender, e) => log.Add("Validated parent2.");

            // Act
            parentNode1.CombineWith(parentNode2);
            parentNode1.Validate(ContextUtil.CreateActionContext());

            // Assert
            Assert.Equal(new[] { "Validating parent1.", "Validating parent2.", "Validated parent1.", "Validated parent2." }, log.ToArray());
            Assert.Equal(allChildNodes, parentNode1.ChildNodes.ToArray());
        }
        public ComplexModelDtoResult(object model, ModelValidationNode validationNode)
        {
            if (validationNode == null)
            {
                throw Error.ArgumentNull("validationNode");
            }

            Model = model;
            ValidationNode = validationNode;
        }
        public ModelValidatingEventArgs(HttpActionContext actionContext, ModelValidationNode parentNode)
        {
            if (actionContext == null)
            {
                throw Error.ArgumentNull("actionContext");
            }

            ActionContext = actionContext;
            ParentNode = parentNode;
        }
 private void AddChildNodes(XElement parent, ModelValidationNode child)
 {
     XElement childElement = new XElement("ModelValidationNode");
     childElement.Add(new XAttribute("ModelStateKey", child.ModelStateKey));
     childElement.Add(new XAttribute("Value", child.ModelMetadata.Model));
     foreach (ModelValidationNode childNode in child.ChildNodes)
     {
         AddChildNodes(childElement, childNode);
     }
     parent.Add(childElement);
 }
 public void CombineWith(ModelValidationNode otherNode)
 {
     if (otherNode != null && !otherNode.SuppressValidation)
     {
         Validated += otherNode.Validated;
         Validating += otherNode.Validating;
         foreach (ModelValidationNode childNode in otherNode.ChildNodes)
         {
             ChildNodes.Add(childNode);
         }
     }
 }
 public void CombineWith(ModelValidationNode otherNode)
 {
     if (otherNode != null && !otherNode.SuppressValidation)
     {
         Validated += otherNode.Validated;
         Validating += otherNode.Validating;
         List<ModelValidationNode> otherChildNodes = otherNode._childNodes;
         for (int i = 0; i < otherChildNodes.Count; i++)
         {
             ModelValidationNode childNode = otherChildNodes[i];
             _childNodes.Add(childNode);
         }
     }
 }
        private static ModelValidationNode CreateModelValidationNode(object o, ModelMetadataProvider metadataProvider, ModelStateDictionary modelStateDictionary, string modelStateKey)
        {
            ModelMetadata metadata = metadataProvider.GetMetadataForType(() =>
            {
                return o;
            }, o.GetType());
            ModelValidationNode validationNode = new ModelValidationNode(metadata, modelStateKey);

            // for this root node, recursively add all child nodes
            HashSet<object> visited = new HashSet<object>();
            CreateModelValidationNodeRecursive(o, validationNode, metadataProvider, metadata, modelStateDictionary, modelStateKey, visited);

            return validationNode;
        }
        public void PropertiesAreSet()
        {
            // Arrange
            ModelMetadata metadata = GetModelMetadata();
            string modelStateKey = "someKey";

            // Act
            ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey);

            // Assert
            Assert.Equal(metadata, node.ModelMetadata);
            Assert.Equal(modelStateKey, node.ModelStateKey);
            Assert.NotNull(node.ChildNodes);
            Assert.Empty(node.ChildNodes);
        }
        public void ConstructorSetsCollectionInstance()
        {
            // Arrange
            ModelMetadata metadata = GetModelMetadata();
            string modelStateKey = "someKey";
            ModelValidationNode[] childNodes = new[]
            {
                new ModelValidationNode(metadata, "someKey0"),
                new ModelValidationNode(metadata, "someKey1")
            };

            // Act
            ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey, childNodes);

            // Assert
            Assert.Equal(childNodes, node.ChildNodes.ToArray());
        }
        private object TryConvertContainerToMetadataType(ModelValidationNode parentNode)
        {
            if (parentNode != null)
            {
                object containerInstance = parentNode.ModelMetadata.Model;
                if (containerInstance != null)
                {
                    Type expectedContainerType = ModelMetadata.ContainerType;
                    if (expectedContainerType != null)
                    {
                        if (expectedContainerType.IsInstanceOfType(containerInstance))
                        {
                            return containerInstance;
                        }
                    }
                }
            }

            return null;
        }
        private static void CreateModelValidationNodeRecursive(object o, ModelValidationNode parentNode, ModelMetadataProvider metadataProvider, ModelMetadata metadata, ModelStateDictionary modelStateDictionary, string modelStateKey, HashSet<object> visited)
        {
            if (visited.Contains(o))
            {
                return;
            }
            visited.Add(o);

            foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(o))
            {
                // append the current property name to the model state path
                string propertyKey = modelStateKey;
                if (propertyKey.Length > 0)
                {
                    propertyKey += ".";
                }
                propertyKey += property.Name;

                // create the node for this property and add to the parent node
                object propertyValue = property.GetValue(o);
                metadata = metadataProvider.GetMetadataForProperty(() =>
                {
                    return propertyValue;
                }, o.GetType(), property.Name);
                ModelValidationNode childNode = new ModelValidationNode(metadata, propertyKey);
                parentNode.ChildNodes.Add(childNode);

                // add the property node to model state
                ModelState modelState = new ModelState();
                modelState.Value = new ValueProviderResult(propertyValue, null, CultureInfo.CurrentCulture);
                modelStateDictionary.Add(propertyKey, modelState);

                if (propertyValue != null)
                {
                    CreateModelValidationNodeRecursive(propertyValue, childNode, metadataProvider, metadata, modelStateDictionary, propertyKey, visited);
                }
            }
        }
        public void Validate(HttpActionContext actionContext, ModelValidationNode parentNode)
        {
            if (actionContext == null)
            {
                throw Error.ArgumentNull("actionContext");
            }

            if (SuppressValidation)
            {
                // no-op
                return;
            }

            // pre-validation steps
            ModelValidatingEventArgs validatingEventArgs = new ModelValidatingEventArgs(actionContext, parentNode);
            OnValidating(validatingEventArgs);
            if (validatingEventArgs.Cancel)
            {
                return;
            }

            ValidateChildren(actionContext);
            ValidateThis(actionContext, parentNode);

            // post-validation steps
            ModelValidatedEventArgs validatedEventArgs = new ModelValidatedEventArgs(actionContext, parentNode);
            OnValidated(validatedEventArgs);
        }
        public void Validate_ValidateAllProperties_AddsValidationErrors()
        {
            // Arrange
            ValidateAllPropertiesModel model = new ValidateAllPropertiesModel
            {
                RequiredString = null /* error */,
                RangedInt = 0 /* error */,
                ValidString = "dog"
            };

            ModelMetadata modelMetadata = GetModelMetadata(model);
            HttpActionContext context = ContextUtil.CreateActionContext();
            ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey")
            {
                ValidateAllProperties = true
            };
            context.ModelState.AddModelError("theKey.RequiredString.Dummy", "existing Error Text");

            // Act
            node.Validate(context);

            // Assert
            Assert.Null(context.ModelState["theKey.RequiredString"]);
            Assert.Equal("existing Error Text", context.ModelState["theKey.RequiredString.Dummy"].Errors[0].ErrorMessage);
            Assert.Equal("The field RangedInt must be between 10 and 30.", context.ModelState["theKey.RangedInt"].Errors[0].ErrorMessage);
            Assert.Null(context.ModelState["theKey.ValidString"]);
            Assert.Null(context.ModelState["theKey"]);
        }
        public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorPresent_AddsModelError()
        {
            // Arrange
            HttpActionContext context = ContextUtil.CreateActionContext();
            ModelBindingContext bindingContext = new ModelBindingContext
            {
                ModelMetadata = GetMetadataForObject(new Person()),
                ModelName = "foo"
            };

            ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
            ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired");
            ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
            ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();

            TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();

            // Act
            testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);

            // Assert
            Assert.False(bindingContext.ModelState.IsValid);
            Assert.Equal("Sample message", bindingContext.ModelState["foo.ValueTypeRequired"].Errors[0].ErrorMessage);
        }
        public void SetProperty_PropertyIsSettable_SetterThrows_RecordsError()
        {
            // Arrange
            Person model = new Person
            {
                DateOfBirth = new DateTime(1900, 1, 1)
            };
            ModelBindingContext bindingContext = new ModelBindingContext
            {
                ModelMetadata = GetMetadataForObject(model)
            };

            ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
            ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
            ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode);

            TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();

            // Act
            testableBinder.SetPropertyPublic(null, 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);
        }
        public void SetProperty_PropertyIsReadOnly_DoesNothing()
        {
            // Arrange
            ModelBindingContext bindingContext = new ModelBindingContext
            {
                ModelMetadata = GetMetadataForType(typeof(Person))
            };

            ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
            ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
            ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);

            TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();

            // Act
            testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult, requiredValidator: null);

            // Assert
            // If didn't throw, success!
        }
        public void NullCheckFailedHandler_ModelStateValid_CallbackReturnsNull_DoesNothing()
        {
            // Arrange
            HttpActionContext context = ContextUtil.CreateActionContext();
            ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
            ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
            ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);

            // Act
            ModelBinderErrorMessageProvider originalProvider = ModelBinderConfig.ValueRequiredErrorMessageProvider;
            try
            {
                ModelBinderConfig.ValueRequiredErrorMessageProvider = (ec, mm, value) => null;
                EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
                handler(validationNode, e);
            }
            finally
            {
                ModelBinderConfig.ValueRequiredErrorMessageProvider = originalProvider;
            }

            // Assert
            Assert.True(context.ModelState.IsValid);
        }
        public void NullCheckFailedHandler_ModelStateAlreadyInvalid_DoesNothing()
        {
            // Arrange
            HttpActionContext context = ContextUtil.CreateActionContext();
            context.ModelState.AddModelError("foo.bar", "Some existing error.");

            ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
            ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
            ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);

            // Act
            EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
            handler(validationNode, e);

            // Assert
            Assert.False(context.ModelState.ContainsKey("foo"));
        }
        private void ValidateThis(HttpActionContext actionContext, ModelValidationNode parentNode)
        {
            ModelStateDictionary modelState = actionContext.ModelState;
            if (!modelState.IsValidField(ModelStateKey))
            {
                return; // short-circuit
            }

            // If 'this' is null and there is no parent, we cannot validate, and
            // the DataAnnotationsModelValidator will throw.   So we intercept here
            // to provide a catch-all value-required validation error
            if (parentNode == null && ModelMetadata.Model == null)
            {
                string trueModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey, ModelMetadata.GetDisplayName());
                modelState.AddModelError(trueModelStateKey, SRResources.Validation_ValueNotFound);
                return;
            }

            _validators = actionContext.GetValidators(ModelMetadata);

            object container = TryConvertContainerToMetadataType(parentNode);
            foreach (ModelValidator validator in _validators)
            {
                foreach (ModelValidationResult validationResult in validator.Validate(ModelMetadata, container))
                {
                    string trueModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey, validationResult.MemberName);
                    modelState.AddModelError(trueModelStateKey, validationResult.Message);
                }
            }
        }
        public void NullCheckFailedHandler_ModelStateValid_AddsErrorString()
        {
            // Arrange
            HttpActionContext context = ContextUtil.CreateActionContext();
            ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
            ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
            ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);

            // Act
            EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
            handler(validationNode, e);

            // Assert
            Assert.True(context.ModelState.ContainsKey("foo"));
            Assert.Equal("A value is required.", context.ModelState["foo"].Errors[0].ErrorMessage);
        }
        public void Validate_Ordering()
        {
            // Proper order of invocation:
            // 1. OnValidating()
            // 2. Child validators
            // 3. This validator
            // 4. OnValidated()

            // Arrange
            List<string> log = new List<string>();
            LoggingValidatableObject model = new LoggingValidatableObject(log);
            ModelMetadata modelMetadata = GetModelMetadata(model);
            ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "ValidStringProperty");
            ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
            node.Validating += (sender, e) => log.Add("In OnValidating()");
            node.Validated += (sender, e) => log.Add("In OnValidated()");
            node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.ValidStringProperty"));

            // Act
            node.Validate(ContextUtil.CreateActionContext());

            // Assert
            Assert.Equal(new[] { "In OnValidating()", "In LoggingValidatonAttribute.IsValid()", "In IValidatableObject.Validate()", "In OnValidated()" }, log.ToArray());
        }
        public void SetProperty_PropertyHasDefaultValue_SetsDefaultValue()
        {
            // Arrange
            HttpActionContext context = ContextUtil.CreateActionContext();

            ModelBindingContext bindingContext = new ModelBindingContext
            {
                ModelMetadata = GetMetadataForObject(new Person())
            };

            ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "PropertyWithDefaultValue");
            ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
            ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
            ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();

            TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();

            // Act
            testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);

            // Assert
            var person = Assert.IsType<Person>(bindingContext.Model);
            Assert.Equal(123.456m, person.PropertyWithDefaultValue);
            Assert.True(context.ModelState.IsValid);
        }
        public void Validate_SkipsRemainingValidationIfModelStateIsInvalid()
        {
            // Because a property validator fails, the model validator shouldn't run

            // Arrange
            List<string> log = new List<string>();
            LoggingValidatableObject model = new LoggingValidatableObject(log);
            ModelMetadata modelMetadata = GetModelMetadata(model);
            ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "InvalidStringProperty");
            ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
            node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.InvalidStringProperty"));
            node.Validating += (sender, e) => log.Add("In OnValidating()");
            node.Validated += (sender, e) => log.Add("In OnValidated()");
            HttpActionContext context = ContextUtil.CreateActionContext();

            // Act
            node.Validate(context);

            // Assert
            Assert.Equal(new[] { "In OnValidating()", "In IValidatableObject.Validate()", "In OnValidated()" }, log.ToArray());
            Assert.Equal("Sample error message", context.ModelState["theKey.InvalidStringProperty"].Errors[0].ErrorMessage);
        }
        public void SetProperty_PropertyIsSettable_CallsSetter()
        {
            // Arrange
            Person model = new Person();
            HttpActionContext context = ContextUtil.CreateActionContext();
            ModelBindingContext bindingContext = new ModelBindingContext
            {
                ModelMetadata = GetMetadataForObject(model)
            };

            ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
            ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
            ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
            ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();

            TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();

            // Act
            testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);

            // Assert
            validationNode.Validate(context);
            Assert.True(context.ModelState.IsValid);
            Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth);
        }
        public void Validate_SkipsValidationIfHandlerCancels()
        {
            // Arrange
            List<string> log = new List<string>();
            LoggingValidatableObject model = new LoggingValidatableObject(log);
            ModelMetadata modelMetadata = GetModelMetadata(model);
            ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
            node.Validating += (sender, e) =>
            {
                log.Add("In OnValidating()");
                e.Cancel = true;
            };
            node.Validated += (sender, e) => log.Add("In OnValidated()");

            // Act
            node.Validate(ContextUtil.CreateActionContext());

            // Assert
            Assert.Equal(new[] { "In OnValidating()" }, log.ToArray());
        }
        public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorNotPresent_RegistersValidationCallback()
        {
            // Arrange
            HttpActionContext context = ContextUtil.CreateActionContext();
            ModelBindingContext bindingContext = new ModelBindingContext
            {
                ModelMetadata = GetMetadataForObject(new Person()),
            };

            ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
            ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
            ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
            ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();

            TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();

            // Act
            testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);

            // Assert
            Assert.True(context.ModelState.IsValid);
            validationNode.Validate(context, bindingContext.ValidationNode);
            Assert.False(context.ModelState.IsValid);
        }
        public void Validate_SkipsValidationIfSuppressed()
        {
            // Arrange
            List<string> log = new List<string>();
            LoggingValidatableObject model = new LoggingValidatableObject(log);
            ModelMetadata modelMetadata = GetModelMetadata(model);
            ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey")
            {
                SuppressValidation = true
            };

            node.Validating += (sender, e) => log.Add("In OnValidating()");
            node.Validated += (sender, e) => log.Add("In OnValidated()");

            // Act
            node.Validate(ContextUtil.CreateActionContext());

            // Assert
            Assert.Empty(log);
        }
        public void SetProperty_SettingNullableTypeToNull_RequiredValidatorPresent_PropertySetterThrows_AddsRequiredMessageString()
        {
            // Arrange
            HttpActionContext context = ContextUtil.CreateActionContext();
            ModelBindingContext bindingContext = new ModelBindingContext
            {
                ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()),
                ModelName = "foo"
            };

            ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
            ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.Name");
            ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
            ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();

            TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();

            // Act
            testableBinder.SetPropertyPublic(context, 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);
        }
        public void Validate_ThrowsIfControllerContextIsNull()
        {
            // Arrange
            ModelValidationNode node = new ModelValidationNode(GetModelMetadata(), "someKey");

            // Act & assert
            Assert.ThrowsArgumentNull(
                () => node.Validate(null),
                "actionContext");
        }