public void Validation_Method_Invalid_Bad_Params_Throws()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);

            object[] parameters = { "LongerThan5Chars", 2.0 }; // legal params

            ExceptionHelper.ExpectValidationException(delegate()
            {
                ValidationUtilities.ValidateMethodCall("MethodWithParameters", context, parameters);
            }, "The field FirstParameterDisplayName must be a string with a maximum length of 5.", typeof(StringLengthAttribute), "LongerThan5Chars");
        }
Пример #2
0
        public void ComplexType_CustomValidator_MultipleMemberNames()
        {
            // ensure TDPs are registered
            DomainServiceDescription.GetDescription(typeof(ComplexTypes_TestService));

            ComplexType_Parent entity = new ComplexType_Parent
            {
                ID          = 1,
                ContactInfo =
                    new ContactInfo
                {
                    Name        = "Mathew",
                    HomeAddress = new Address {
                        AddressLine1 = "47 South Wynn Rd.", City = "Oregon", State = "OH"
                    },
                    PrimaryPhone = new Phone {
                        AreaCode = "419", Number = "693-6096"
                    }
                },
            };

            // configure multi member validation errors
            DynamicTestValidator.ForcedValidationResults.Clear();
            ValidationResult contactResult = new ValidationResult("ContactInfo", new string[] { "Name", "PrimaryPhone" });
            ValidationResult phoneResult   = new ValidationResult("Phone", new string[] { "AreaCode", "Number" });

            DynamicTestValidator.ForcedValidationResults[entity.ContactInfo] = contactResult;
            DynamicTestValidator.ForcedValidationResults[typeof(Phone)]      = phoneResult;

            ValidationContext       validationContext = ValidationUtilities.CreateValidationContext(entity, null);
            List <ValidationResult> results           = new List <ValidationResult>();
            bool isValid = ValidationUtilities.TryValidateObject(entity, validationContext, results);

            Assert.IsFalse(isValid);

            // Verify that the member names have been transformed into full paths
            ValidationResult result = results.Single(q => q.ErrorMessage == "ContactInfo-ContactInfo");

            string[] memberNames = result.MemberNames.ToArray();
            Assert.AreEqual(2, memberNames.Length);
            Assert.IsTrue(memberNames.Contains("ContactInfo.Name"));
            Assert.IsTrue(memberNames.Contains("ContactInfo.PrimaryPhone"));

            // here we expect member names to be transformed into full paths
            result      = results.Single(q => q.ErrorMessage == "Phone-TypeLevel");
            memberNames = result.MemberNames.ToArray();
            Assert.AreEqual(2, memberNames.Length);
            Assert.IsTrue(memberNames.Contains("ContactInfo.PrimaryPhone.AreaCode"));
            Assert.IsTrue(memberNames.Contains("ContactInfo.PrimaryPhone.Number"));
        }
        public void Validation_Method_Invalid_Good_Params_Throws()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);

            object[] parameters = { "hello", 2.0 }; // legal params

            vtc._failMethod = true;                 // tells validation method below to fail the IsValid

            ExceptionHelper.ExpectValidationException(delegate()
            {
                ValidationUtilities.ValidateMethodCall("MethodWithParameters", context, parameters);
            }, "-MethodDisplayName", typeof(CustomValidationAttribute), vtc);
        }
        public void Validation_Method_Valid_No_Parameters()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);

            object[] parameters = null;
            bool     isValid    = ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithNoParameters", context, parameters, null);

            Assert.IsTrue(isValid);

            List <ValidationResult> output = new List <ValidationResult>();

            isValid = ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithNoParameters", context, parameters, output);
            Assert.IsTrue(isValid);
            Assert.AreEqual(0, output.Count);
        }
        public void Validation_Method_No_Attributes()
        {
            ValTestNoAttributesClass instance = new ValTestNoAttributesClass();
            ValidationContext        context  = ValidationUtilities.CreateValidationContext(instance, null);

            object[] parameters = { "hello", 2.0 };
            bool     isValid    = ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithoutAttributes", context, parameters, null);

            Assert.IsTrue(isValid);

            List <ValidationResult> output = new List <ValidationResult>();

            isValid = ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithoutAttributes", context, parameters, output);
            Assert.IsTrue(isValid);
            Assert.AreEqual(0, output.Count);
        }
        public void Validation_Method_Invalid_No_Params()
        {
            ValTestClass vtc = new ValTestClass();

            vtc._failMethod = true; // forces failure
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);

            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, null, null);
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters' accepting zero parameters could not be found.");

            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, null, new List <ValidationResult>());
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters' accepting zero parameters could not be found.");
        }
        public void Validation_Method_Invalid_Null_Params()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);

            object[] parameters = null; // actually requires 2 params

            // IsValid entry point
            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, parameters, null);
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters' accepting zero parameters could not be found.");

            // Validate entry point
            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.ValidateMethodCall("MethodWithParameters", context, parameters);
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters' accepting zero parameters could not be found.");
        }
        public void Validation_Fail_Method_Null_ValueType()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);

            object[] parameters = { "xxx", null }; // 2nd param should be double

            // IsValid entry point
            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, parameters, null);
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters('System.String', null)' could not be found. Parameter count: 2.");

            // Validate entry point
            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.ValidateMethodCall("MethodWithParameters", context, parameters);
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters('System.String', null)' could not be found. Parameter count: 2.");
        }
        public void Validation_Fail_Method_Too_Many_Params()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);

            object[] parameters = { "xxx", 2.0, 5 }; // actually requires 2 params

            // IsValid entry point
            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, parameters, null);
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters('System.String', 'System.Double', 'System.Int32')' could not be found. Parameter count: 3.");

            // Validate entry point
            ExceptionHelper.ExpectException <MissingMethodException>(delegate()
            {
                ValidationUtilities.ValidateMethodCall("MethodWithParameters", context, parameters);
            }, "Method 'OpenRiaServices.DomainServices.Hosting.Test.ValidationUtilitiesTests+ValTestClass.MethodWithParameters('System.String', 'System.Double', 'System.Int32')' could not be found. Parameter count: 3.");
        }
        public void Validation_Method_Invalid_Null_Required_Nullable()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);
            string            message = "The doubleParam field is required.";

            object[] parameters = { null }; // param is nullable but marked [Required]
            ExceptionHelper.ExpectValidationException(delegate
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithRequiredNullableParameter", context, parameters, null);
            }, message, typeof(RequiredAttribute), parameters[0]);

            List <ValidationResult> output = new List <ValidationResult>();
            bool isValid = ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithRequiredNullableParameter", context, parameters, output);

            Assert.IsFalse(isValid);
            Assert.AreEqual(1, output.Count);
            UnitTestHelper.AssertListContains(output, message);
        }
        public void Validation_Method_Invalid_Bad_Params()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);
            string            message = "The field FirstParameterDisplayName must be a string with a maximum length of 5.";

            object[] parameters = { "LongerThan5Chars", 2.0 }; // legal params
            ExceptionHelper.ExpectValidationException(delegate()
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, parameters, null);
            }, message, typeof(StringLengthAttribute), parameters[0]);

            List <ValidationResult> output = new List <ValidationResult>();
            bool isValid = ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, parameters, output);

            Assert.IsFalse(isValid);
            Assert.AreEqual(1, output.Count);
            UnitTestHelper.AssertListContains(output, message);
        }
        public void Validation_Method_Invalid_Good_Params()
        {
            ValTestClass      vtc     = new ValTestClass();
            ValidationContext context = ValidationUtilities.CreateValidationContext(vtc, null);
            string            message = "-MethodDisplayName";

            vtc._failMethod = true;                 // tells validation method below to fail the IsValid

            object[] parameters = { "hello", 2.0 }; // legal params
            ExceptionHelper.ExpectValidationException(delegate
            {
                ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, parameters, null);
            }, message, typeof(CustomValidationAttribute), vtc);

            List <ValidationResult> output = new List <ValidationResult>();
            bool isValid = ValidationUtilities.TryValidateCustomUpdateMethodCall("MethodWithParameters", context, parameters, output);

            Assert.IsFalse(isValid);
            Assert.AreEqual(1, output.Count);
            UnitTestHelper.AssertListContains(output, message);    // hyphen means it came from default formatter
        }
        internal static bool ValidateMethodCall(string methodName,
                                                ValidationContext validationContext, object[] parameters,
                                                List <ValidationResult> validationResults, bool performTypeValidation)
        {
            if (string.IsNullOrEmpty(methodName))
            {
                throw new ArgumentNullException("methodName");
            }

            if (validationContext == null)
            {
                throw new ArgumentNullException("validationContext");
            }

            if (validationContext.ObjectInstance == null)
            {
                throw new ArgumentException(DataResource.ValidationUtilities_ContextInstance_CannotBeNull, "validationContext");
            }

            MethodInfo        method        = GetMethod(validationContext.ObjectInstance, methodName, parameters);
            ValidationContext methodContext = CreateValidationContext(validationContext.ObjectInstance, validationContext);

            methodContext.MemberName = method.Name;

            DisplayAttribute display = GetDisplayAttribute(method);

            if (display != null)
            {
                methodContext.DisplayName = display.GetName();
            }

            string memberPath = string.Empty;

            if (performTypeValidation)
            {
                memberPath = method.Name + ".";
            }

            IEnumerable <ValidationAttribute> validationAttributes = method.GetCustomAttributes(typeof(ValidationAttribute), true).Cast <ValidationAttribute>();
            bool success = ValidationUtilities.ValidateValue(validationContext.ObjectInstance, methodContext, validationResults, validationAttributes, string.Empty);

            ParameterInfo[] parameterInfos = method.GetParameters();

            for (int paramIndex = 0; paramIndex < parameterInfos.Length; paramIndex++)
            {
                ParameterInfo methodParameter = parameterInfos[paramIndex];
                object        value           = (parameters.Length > paramIndex ? parameters[paramIndex] : null);

                ValidationContext parameterContext = ValidationUtilities.CreateValidationContext(validationContext.ObjectInstance, validationContext);
                parameterContext.MemberName = methodParameter.Name;

                string paramName = methodParameter.Name;

                display = GetDisplayAttribute(methodParameter);

                if (display != null)
                {
                    paramName = display.GetName();
                }

                parameterContext.DisplayName = paramName;

                string parameterPath = memberPath;
                if (performTypeValidation)
                {
                    parameterPath += methodParameter.Name;
                }

                IEnumerable <ValidationAttribute> parameterAttributes = methodParameter.GetCustomAttributes(typeof(ValidationAttribute), false).Cast <ValidationAttribute>();
                bool parameterSuccess = ValidationUtilities.ValidateValue(value, parameterContext, validationResults,
                                                                          parameterAttributes, ValidationUtilities.NormalizeMemberPath(parameterPath, methodParameter.ParameterType));

                if (parameterSuccess && performTypeValidation && value != null)
                {
                    Type parameterType = methodParameter.ParameterType;

                    // If the user expects an exception we perform shallow validation only.
                    if (validationResults == null)
                    {
                        if (TypeUtility.IsComplexType(parameterType))
                        {
                            ValidationContext context = ValidationUtilities.CreateValidationContext(value, parameterContext);
                            context.DisplayName = paramName;
                            Validator.ValidateObject(value, context, /*validateAllProperties*/ true);
                        }
                    }
                    // Else we are able to report fully recursive validation errors to the user, perform deep validation.
                    else
                    {
                        if (TypeUtility.IsComplexType(parameterType))
                        {
                            parameterSuccess = ValidationUtilities.ValidateObjectRecursive(value, parameterPath, validationContext, validationResults);
                        }
                        else if (TypeUtility.IsComplexTypeCollection(parameterType))
                        {
                            parameterSuccess = ValidationUtilities.ValidateComplexCollection(value as IEnumerable, parameterPath, validationContext, validationResults);
                        }
                    }
                }

                success &= parameterSuccess;
            }

            return(success);
        }
        internal static bool TryValidateMethodCall(DomainOperationEntry operationEntry, ValidationContext validationContext, object[] parameters, List <ValidationResult> validationResults)
        {
            bool breakOnFirstError = validationResults == null;

            ValidationContext methodContext = CreateValidationContext(validationContext.ObjectInstance, validationContext);

            methodContext.MemberName = operationEntry.Name;

            DisplayAttribute display = (DisplayAttribute)operationEntry.Attributes[typeof(DisplayAttribute)];

            if (display != null)
            {
                methodContext.DisplayName = display.GetName();
            }

            string methodPath = string.Empty;

            if (operationEntry.Operation == DomainOperation.Custom)
            {
                methodPath = operationEntry.Name + ".";
            }

            IEnumerable <ValidationAttribute> validationAttributes = operationEntry.Attributes.OfType <ValidationAttribute>();
            bool success = Validator.TryValidateValue(validationContext.ObjectInstance, methodContext, validationResults, validationAttributes);

            if (!breakOnFirstError || success)
            {
                for (int paramIndex = 0; paramIndex < operationEntry.Parameters.Count; paramIndex++)
                {
                    DomainOperationParameter methodParameter = operationEntry.Parameters[paramIndex];
                    object value = (parameters.Length > paramIndex ? parameters[paramIndex] : null);

                    ValidationContext parameterContext = ValidationUtilities.CreateValidationContext(validationContext.ObjectInstance, validationContext);
                    parameterContext.MemberName = methodParameter.Name;

                    string paramName = methodParameter.Name;

                    AttributeCollection parameterAttributes = methodParameter.Attributes;
                    display = (DisplayAttribute)parameterAttributes[typeof(DisplayAttribute)];

                    if (display != null)
                    {
                        paramName = display.GetName();
                    }

                    parameterContext.DisplayName = paramName;

                    string parameterPath = string.Empty;
                    if (!string.IsNullOrEmpty(methodPath) && paramIndex > 0)
                    {
                        parameterPath = methodPath + methodParameter.Name;
                    }

                    IEnumerable <ValidationAttribute> parameterValidationAttributes = parameterAttributes.OfType <ValidationAttribute>();
                    bool parameterSuccess = ValidationUtilities.ValidateValue(value, parameterContext, validationResults, parameterValidationAttributes,
                                                                              ValidationUtilities.NormalizeMemberPath(parameterPath, methodParameter.ParameterType));

                    // Custom methods run deep validation as well as parameter validation.
                    // If parameter validation has already failed, stop further validation.
                    if (parameterSuccess && operationEntry.Operation == DomainOperation.Custom && value != null)
                    {
                        Type parameterType = methodParameter.ParameterType;

                        if (TypeUtility.IsComplexType(parameterType))
                        {
                            parameterSuccess = ValidationUtilities.ValidateObjectRecursive(value, parameterPath, parameterContext, validationResults);
                        }
                        else if (TypeUtility.IsComplexTypeCollection(parameterType))
                        {
                            parameterSuccess = ValidationUtilities.ValidateComplexCollection(value as IEnumerable, parameterPath, parameterContext, validationResults);
                        }
                    }

                    success &= parameterSuccess;

                    if (breakOnFirstError && !success)
                    {
                        break;
                    }
                }
            }

            return(success);
        }
        /// <summary>
        /// This method recursively validates an object, first validating all properties, then
        /// validating the type. This method implements the classic Try pattern. However it serves
        /// as code sharing for the validation pattern where an exception is thrown on first error.
        /// </summary>
        /// <param name="instance">The object to validate.</param>
        /// <param name="memberPath">The dotted path of the member.</param>
        /// <param name="validationContext">The validation context.</param>
        /// <param name="validationResults">The collection in which the validation results will be
        /// stored. The collection can be <c>null</c>.</param>
        /// <returns><c>True</c> if the object was successfully validated with no errors.</returns>
        /// <exception cref="ValidationException">When <paramref name="validationResults"/> is
        /// <c>null</c> and the object has a validation error.</exception>
        private static bool ValidateObjectRecursive(object instance, string memberPath,
                                                    ValidationContext validationContext, List <ValidationResult> validationResults)
        {
            MetaType metaType = MetaType.GetMetaType(instance.GetType());

            if (!metaType.RequiresValidation)
            {
                return(true);
            }

            // First validate all properties
            bool hasValidationErrors = false;

            foreach (MetaMember metaMember in metaType.Members.Where(m => m.RequiresValidation || m.IsComplex))
            {
                ValidationContext propertyValidationContext = ValidationUtilities.CreateValidationContext(instance, validationContext);
                propertyValidationContext.MemberName = metaMember.Member.Name;

                // Form the current member path, appending the current
                // member name if it is complex.
                string currMemberPath = memberPath;
                if (metaMember.IsComplex)
                {
                    if (currMemberPath.Length > 0)
                    {
                        currMemberPath += ".";
                    }
                    currMemberPath += metaMember.Member.Name;
                }

                object value = metaMember.GetValue(instance);

                // first validate the property itself
                if (metaMember.RequiresValidation)
                {
                    hasValidationErrors |= !ValidationUtilities.ValidateProperty(value, propertyValidationContext, validationResults, currMemberPath);
                }

                // for complex members, in addition to property level validation we need to
                // do deep validation recursively
                if (value != null && metaMember.IsComplex)
                {
                    if (!metaMember.IsCollection)
                    {
                        hasValidationErrors |= !ValidateObjectRecursive(value, currMemberPath, validationContext, validationResults);
                    }
                    else
                    {
                        hasValidationErrors |= !ValidateComplexCollection((IEnumerable)value, currMemberPath, validationContext, validationResults);
                    }
                }
            }

            // Only proceed to Type level validation if there are no property validation errors
            if (hasValidationErrors)
            {
                return(false);
            }

            // Next perform Type level validation without validating properties, since we've already validated all properties.
            // Note that we can't use Validator.ValidateObject specifying 'validateAllProperties' since even when specifying false,
            // that API will validate RequiredAttribute.
            ValidationContext context = ValidationUtilities.CreateValidationContext(instance, validationContext);

            if (metaType.ValidationAttributes.Any())
            {
                hasValidationErrors |= !ValidationUtilities.ValidateValue(instance, context, validationResults, metaType.ValidationAttributes, memberPath);
            }

#if !SILVERLIGHT
            // Only proceed to IValidatableObject validation if there are no errors
            if (hasValidationErrors)
            {
                return(false);
            }

            // Test for IValidatableObject implementation and run the validation if applicable
            // Note : this interface doesn't exist in Silverlight
            IValidatableObject validatable = instance as IValidatableObject;
            if (validatable != null)
            {
                IEnumerable <ValidationResult> results = validatable.Validate(context);

                if (!string.IsNullOrEmpty(memberPath))
                {
                    results = ValidationUtilities.ApplyMemberPath(results, memberPath);
                }

                foreach (ValidationResult result in results.Where(r => r != ValidationResult.Success))
                {
                    validationResults.Add(result);
                    hasValidationErrors = true;
                }
            }
#endif

            return(!hasValidationErrors);
        }
Пример #16
0
        public void TestEntityValidation_ComplexTypes()
        {
            // ensure TDPs are registered
            DomainServiceDescription.GetDescription(typeof(ComplexTypes_TestService));

            ComplexType_Parent entity = new ComplexType_Parent
            {
                ID          = 1,
                ContactInfo =
                    new ContactInfo
                {
                    Name        = "Mathew",
                    HomeAddress = new Address {
                        AddressLine1 = "47 South Wynn Rd.", City = "Oregon", State = "OH"
                    },
                    PrimaryPhone = new Phone {
                        AreaCode = "419", Number = "693-6096"
                    }
                },
            };

            ValidationContext       validationContext = ValidationUtilities.CreateValidationContext(entity, null);
            List <ValidationResult> results           = new List <ValidationResult>();
            bool isValid = ValidationUtilities.TryValidateObject(entity, validationContext, results);

            Assert.IsTrue(isValid && results.Count == 0);

            // set an invalid property and revalidate
            entity.ContactInfo.PrimaryPhone.AreaCode = "Invalid";
            results = new List <ValidationResult>();
            isValid = ValidationUtilities.TryValidateObject(entity, validationContext, results);
            Assert.IsTrue(!isValid && results.Count == 1);
            ValidationResult result = results.Single();

            Assert.AreEqual("The field AreaCode must be a string with a maximum length of 3.", result.ErrorMessage);
            Assert.AreEqual("ContactInfo.PrimaryPhone.AreaCode", result.MemberNames.Single());

            // create TWO validation errors
            entity.ContactInfo.PrimaryPhone.AreaCode = "Invalid";
            entity.ContactInfo.HomeAddress.State     = "Invalid";
            results = new List <ValidationResult>();
            isValid = ValidationUtilities.TryValidateObject(entity, validationContext, results);
            Assert.IsTrue(!isValid && results.Count == 2);
            Assert.AreEqual("The field State must be a string with a maximum length of 2.", results[0].ErrorMessage);
            Assert.AreEqual("ContactInfo.HomeAddress.State", results[0].MemberNames.Single());
            Assert.AreEqual("The field AreaCode must be a string with a maximum length of 3.", results[1].ErrorMessage);
            Assert.AreEqual("ContactInfo.PrimaryPhone.AreaCode", results[1].MemberNames.Single());

            // verify custom validation was called at CT type and property levels
            entity.ContactInfo.PrimaryPhone.AreaCode = "419";
            entity.ContactInfo.HomeAddress.State     = "OH";
            DynamicTestValidator.Monitor(true);
            results = new List <ValidationResult>();
            isValid = ValidationUtilities.TryValidateObject(entity, validationContext, results);
            Assert.AreEqual(3, DynamicTestValidator.ValidationCalls.Count);
            Assert.IsNotNull(DynamicTestValidator.ValidationCalls.Single(v => v.MemberName == null && v.ObjectType == typeof(ContactInfo)));
            Assert.IsNotNull(DynamicTestValidator.ValidationCalls.Single(v => v.MemberName == "ContactInfo" && v.ObjectType == typeof(ComplexType_Parent)));
            Assert.IsNotNull(DynamicTestValidator.ValidationCalls.Single(v => v.MemberName == null && v.ObjectType == typeof(Phone)));
            DynamicTestValidator.Monitor(false);

            // verify deep CT collection validation
            List <ComplexType_Recursive> children = new List <ComplexType_Recursive> {
                new ComplexType_Recursive {
                    P1 = "1", P4 = -1
                },                                                // invalid element
                new ComplexType_Recursive {
                    P1 = "2", P3 =
                        new List <ComplexType_Recursive> {
                        new ComplexType_Recursive {
                            P1 = "3", P4 = -5
                        }                                                // invalid element in nested collection
                    }
                }
            };
            ComplexType_Scenarios_Parent parent = new ComplexType_Scenarios_Parent
            {
                ID = 1,
                ComplexType_Recursive = new ComplexType_Recursive {
                    P1 = "1", P3 = children
                }
            };

            validationContext = ValidationUtilities.CreateValidationContext(parent, null);
            results           = new List <ValidationResult>();
            isValid           = ValidationUtilities.TryValidateObject(parent, validationContext, results);
            Assert.AreEqual(2, results.Count);
            result = results.Single(p => p.MemberNames.Single() == "ComplexType_Recursive.P3().P4");
            Assert.AreEqual("The field P4 must be between 0 and 5.", result.ErrorMessage);
            result = results.Single(p => p.MemberNames.Single() == "ComplexType_Recursive.P3().P3().P4");
            Assert.AreEqual("The field P4 must be between 0 and 5.", result.ErrorMessage);
        }