Parcourir la source

[DataAnnotations] Implemented the Validator and CustomValidationAttribute 4.0 classes

Marek Habersack il y a 14 ans
Parent
commit
60c269eae9

+ 83 - 7
mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations/CustomValidationAttribute.cs

@@ -2,9 +2,9 @@
 // CustomValidationAttribute.cs
 //
 // Authors:
-//	Marek Habersack <[email protected]>
+//	Marek Habersack <[email protected]>
 //
-// Copyright (C) 2010 Novell Inc. (http://novell.com)
+// Copyright (C) 2010-2011 Novell Inc. (http://novell.com)
 //
 
 //
@@ -30,35 +30,111 @@
 #if NET_4_0
 using System;
 using System.Collections.Generic;
+using System.Reflection;
 
 namespace System.ComponentModel.DataAnnotations
 {
 	[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true)]
 	public sealed class CustomValidationAttribute : ValidationAttribute
 	{
+		Tuple <string, Type> typeId;
+		MethodInfo validationMethod;
+		bool validationMethodChecked;
+		bool validationMethodSignatureValid;
+		int validationMethodParamCount;
+		
 		public string Method { get; private set; }
+
 		public override object TypeId {
-			get {
-				throw new NotImplementedException ();
-			}
+			get { return typeId; }
 		}
+		
 		public Type ValidatorType { get; private set; }
 		
 		public CustomValidationAttribute (Type validatorType, string method)
 		{
 			this.ValidatorType = validatorType;
 			this.Method = method;
+			this.typeId = new Tuple <string, Type> (method, validatorType);
 		}
 
 		public override string FormatErrorMessage (string name)
 		{
-			throw new NotImplementedException ();
+			ThrowIfAttributeNotWellFormed ();
+			return String.Format ("{0} is not valid.", name);
 		}
 
 		// LAMESPEC: MSDN doesn't document it at all, but corcompare shows it in the type
 		protected override ValidationResult IsValid (object value, ValidationContext validationContext)
 		{
-			throw new NotImplementedException ();
+			ThrowIfAttributeNotWellFormed ();
+			object[] p;
+				
+			if (validationMethodParamCount == 2)
+				p = new object [] {value, validationContext};
+			else
+				p = new object [] {value};
+			try {
+				return validationMethod.Invoke (null, p) as ValidationResult;
+			} catch (TargetInvocationException ex) {
+				if (ex.InnerException != null)
+					throw ex.InnerException;
+				throw;
+			}
+		}
+
+		void ThrowIfAttributeNotWellFormed ()
+		{
+			Type type = ValidatorType;
+			if (type == null)
+				throw new InvalidOperationException ("The CustomValidationAttribute.ValidatorType was not specified.");
+
+			if (type.IsNotPublic)
+				throw new InvalidOperationException (String.Format ("The custom validation type '{0}' must be public.", type.Name));
+
+			string method = Method;
+			if (String.IsNullOrEmpty (method))
+				throw new InvalidOperationException ("The CustomValidationAttribute.Method was not specified.");
+
+			if (validationMethod == null) {
+				if (!validationMethodChecked) {
+					validationMethod = type.GetMethod (method, BindingFlags.Public | BindingFlags.Static);
+					validationMethodChecked = true;
+				}
+				
+				if (validationMethod == null)
+					throw new InvalidOperationException (
+						String.Format ("The CustomValidationAttribute method '{0}' does not exist in type '{1}' or is not public and static.",
+							       method, type.Name));
+
+				if (!typeof (ValidationResult).IsAssignableFrom (validationMethod.ReturnType))
+					throw new InvalidOperationException (String.Format ("The CustomValidationAttribute method '{0}' in type '{1}' must return System.ComponentModel.DataAnnotations.ValidationResult.  Use System.ComponentModel.DataAnnotations.ValidationResult.Success to represent success.", method, type.Name));
+
+				validationMethodSignatureValid = true;
+				ParameterInfo[] parameters = validationMethod.GetParameters ();
+				if (parameters == null)
+					validationMethodSignatureValid = false;
+				else {
+					validationMethodParamCount = parameters.Length;
+					switch (validationMethodParamCount) {
+						case 1:
+							break;
+
+						case 2:
+							if (parameters [1].ParameterType != typeof (ValidationContext))
+								validationMethodSignatureValid = false;
+							break;
+
+						default:
+							validationMethodSignatureValid = false;
+							break;
+					}
+				}
+			}
+			
+			if (!validationMethodSignatureValid)
+				throw new InvalidOperationException (String.Format ("The CustomValidationAttribute method '{0}' in type '{1}' must match the expected signature: public static ValidationResult MethodTwo(object value, ValidationContext context).  The value can be strongly typed.  The ValidationContext parameter is optional.", method, type.Name));
+			
 		}
 	}
 }

+ 42 - 9
mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations/ValidationAttribute.cs

@@ -37,7 +37,10 @@ namespace System.ComponentModel.DataAnnotations
 	public abstract class ValidationAttribute : Attribute
 	{
 		const string DEFAULT_ERROR_MESSAGE = "The field {0} is invalid.";
-#if !NET_4_0
+#if NET_4_0
+		object nestedCallLock = new object ();
+		bool nestedCall;
+#else
 		string errorMessageResourceName;
 		string errorMessageString;
 		Type errorMessageResourceType;
@@ -127,20 +130,50 @@ namespace System.ComponentModel.DataAnnotations
 			get { return GetStringFromResourceAccessor (); }
 		}
 #if NET_4_0
+		NotImplementedException NestedNIEX ()
+		{
+			return new NotImplementedException ("IsValid(object value) has not been implemented by this class.  The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context).");
+		}
+		
+		//
+		// This is the weirdest (to be gentle) idea ever... The IsValid (object) overload
+		// throws the NIEX when it is called from the default IsValid (object,
+		// ValidationContext) overload, but not when directly. And the reverse situation is
+		// true as well. That means, the calls detect the "nested" calls and that we need to
+		// protect the nestedCall flag... ugh
+		//
 		public virtual bool IsValid (object value)
 		{
-			throw new NotImplementedException ("IsValid(object value) has not been implemented by this class.  The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context).");
+			lock (nestedCallLock) {
+				if (nestedCall)
+					throw NestedNIEX ();
+				try {
+					nestedCall = true;
+					return IsValid (value, null) == ValidationResult.Success;
+				} finally {
+					nestedCall = false;
+				}
+			}
 		}
 
 		protected virtual ValidationResult IsValid (object value, ValidationContext validationContext)
 		{
-			// .NET emulation
-			if (validationContext == null)
-				throw new NullReferenceException (".NET emulation.");
-			
-			if (!IsValid (value)) {
-				string memberName = validationContext.MemberName;
-				return new ValidationResult (FormatErrorMessage (validationContext.DisplayName), memberName != null ? new string[] { memberName } : new string[] {});
+			lock (nestedCallLock) {
+				if (nestedCall)
+					throw NestedNIEX ();
+				
+				try {
+					nestedCall = true;
+					if (!IsValid (value)) {
+						// .NET emulation
+						if (validationContext == null)
+							throw new NullReferenceException (".NET emulation.");
+						string memberName = validationContext.MemberName;
+						return new ValidationResult (FormatErrorMessage (validationContext.DisplayName), memberName != null ? new string[] { memberName } : new string[] {});
+					}
+				} finally {
+					nestedCall = false;
+				}
 			}
 
 			return ValidationResult.Success;

+ 81 - 0
mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations/ValidationAttributeCollectionExtensions.cs

@@ -0,0 +1,81 @@
+//
+// Authors:
+//      Marek Habersack <[email protected]>
+//
+// Copyright (C) 2011 Novell Inc. http://novell.com
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+
+namespace System.ComponentModel.DataAnnotations
+{
+	static class ValidationAttributeCollectionExtensions
+	{
+		public static void Validate <TAttribute> (this AttributeCollection attributes, object value, ValidationContext validationContext,
+							  ICollection <ValidationResult> validationResults, ref bool valid)
+			where TAttribute: ValidationAttribute
+		{
+			if (attributes == null || attributes == AttributeCollection.Empty || attributes.Count <= 0)
+				return;
+
+			ValidationResult result;
+			foreach (TAttribute attr in attributes.OfType <TAttribute> ()) {
+				result = attr.GetValidationResult (value, validationContext);
+				if (result != ValidationResult.Success) {
+					valid = false;
+					if (validationResults != null)
+						validationResults.Add (result);
+				}
+			}
+		}
+
+		public static void ValidateExcept <TAttribute> (this AttributeCollection attributes, object value, ValidationContext validationContext,
+								ICollection <ValidationResult> validationResults, ref bool valid)
+			where TAttribute: ValidationAttribute
+		{
+			if (attributes == null || attributes == AttributeCollection.Empty || attributes.Count <= 0)
+				return;
+
+			ValidationResult result;
+			ValidationAttribute vattr;
+			foreach (Attribute attr in attributes) {
+				if (attr is TAttribute)
+					continue;
+				vattr = attr as ValidationAttribute;
+				if (vattr == null)
+					continue;				
+
+				result = vattr.GetValidationResult (value, validationContext);
+				if (result != ValidationResult.Success) {
+					valid = false;
+					if (validationResults != null)
+						validationResults.Add (result);
+				}
+			}
+		}
+	}
+}

+ 5 - 1
mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations/ValidationException.cs

@@ -4,7 +4,7 @@
 // Author:
 //	Atsushi Enomoto <[email protected]>
 //
-// Copyright (C) 2008 Novell Inc. http://novell.com
+// Copyright (C) 2008-2011 Novell Inc. http://novell.com
 //
 
 //
@@ -63,8 +63,12 @@ namespace System.ComponentModel.DataAnnotations
 		}
 #if NET_4_0
 		public ValidationException (ValidationResult validationResult, ValidationAttribute validatingAttribute, object value)
+			: this (validationResult != null ? validationResult.ErrorMessage : null, validatingAttribute, value)
 		{
+			this.ValidationResult = validationResult;
 		}
+
+		public ValidationResult ValidationResult { get; private set; }
 #endif
 		public ValidationAttribute ValidationAttribute { get; private set; }
 		public object Value { get; private set; }

+ 211 - 0
mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations/Validator.cs

@@ -0,0 +1,211 @@
+//
+// Authors:
+//      Marek Habersack <[email protected]>
+//
+// Copyright (C) 2011 Novell Inc. http://novell.com
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+
+namespace System.ComponentModel.DataAnnotations
+{
+	// TODO: we could probably use some kind of type cache here
+	public static class Validator
+	{
+		public static bool TryValidateObject (object instance, ValidationContext validationContext, ICollection <ValidationResult> validationResults)
+		{
+			return TryValidateObject (instance, validationContext, validationResults, false);
+		}
+
+		public static bool TryValidateObject (object instance, ValidationContext validationContext, ICollection <ValidationResult> validationResults, bool validateAllProperties)
+		{
+			if (instance == null)
+				throw new ArgumentNullException ("instance");
+
+			if (validationContext == null)
+				throw new ArgumentNullException ("validationContext");
+
+			if (!Object.ReferenceEquals (instance, validationContext.ObjectInstance))
+				throw new ArgumentException ("The instance provided must match the ObjectInstance on the ValidationContext supplied.", "instance");
+
+			ValidationResult result;
+			bool valid = true;
+			Type instanceType = instance.GetType ();
+			TypeDescriptor.GetAttributes (instanceType).Validate <ValidationAttribute> (instance, validationContext, validationResults, ref valid);
+			
+			PropertyDescriptorCollection properties = TypeDescriptor.GetProperties (instance);
+			if (properties != PropertyDescriptorCollection.Empty && properties.Count > 0) {
+				foreach (PropertyDescriptor pdesc in properties) {
+					object value = pdesc.GetValue (instance);
+					ValidateProperty (pdesc, value, validationContext, validationResults, validateAllProperties, ref valid);
+				}
+			}
+			
+			return valid;
+		}
+
+		static void ValidateProperty (PropertyDescriptor pdesc, object value, ValidationContext validationContext, ICollection <ValidationResult> validationResults,
+					      bool validateAll, ref bool valid)
+		{
+			AttributeCollection attributes = pdesc.Attributes;
+			attributes.Validate <RequiredAttribute> (value, validationContext, validationResults, ref valid);
+			if (validateAll)
+				attributes.ValidateExcept <RequiredAttribute> (value, validationContext, validationResults, ref valid);
+		}
+		
+		static PropertyDescriptor GetProperty (Type type, string propertyName, object value)
+		{
+			if (String.IsNullOrEmpty (propertyName))
+				throw new ArgumentNullException ("propertyName");
+
+			PropertyDescriptorCollection properties = TypeDescriptor.GetProperties (type);
+			PropertyDescriptor pdesc = null;
+			if (properties != PropertyDescriptorCollection.Empty && properties.Count > 0)
+				pdesc = properties.Find (propertyName, false);
+
+			if (pdesc == null)
+				throw new ArgumentException (String.Format ("The type '{0}' does not contain a public property named '{1}'.", type.Name, propertyName), "propertyName");
+
+			Type valueType = value == null ? null : value.GetType ();
+			Type propertyType = pdesc.PropertyType;
+			bool invalidType = false;
+
+			Console.WriteLine ("valueType == {0}; propertyType == {1} (reference? {2})", valueType == null ? "<null>" : valueType.FullName,
+					   propertyType, !propertyType.IsValueType || (Nullable.GetUnderlyingType (propertyType) != null));
+			if (valueType == null)
+				invalidType = !(!propertyType.IsValueType || (Nullable.GetUnderlyingType (propertyType) != null));
+			else if (propertyType != valueType)
+				invalidType = true;
+
+			if (invalidType)
+				throw new ArgumentException (String.Format ("The value of property '{0}' must be of type '{1}'.", propertyName, type.FullName), "propertyName");
+			
+			return pdesc;
+		}
+		
+		public static bool TryValidateProperty (object value, ValidationContext validationContext, ICollection <ValidationResult> validationResults)
+		{
+			// LAMESPEC: value can be null, validationContext must not
+			if (validationContext == null)
+				throw new ArgumentNullException ("validationContext");
+
+			PropertyDescriptor pdesc = GetProperty (validationContext.ObjectType, validationContext.MemberName, value);
+			if (value == null)
+				return true;
+
+			bool valid = true;
+			ValidateProperty (pdesc, value, validationContext, validationResults, true, ref valid);
+
+			return valid;
+		}
+
+		public static bool TryValidateValue (object value, ValidationContext validationContext, ICollection<ValidationResult> validationResults,
+						     IEnumerable <ValidationAttribute> validationAttributes)
+		{
+			if (validationContext == null)
+				throw new ArgumentNullException ("validationContext");
+
+			ValidationResult result;
+			
+			// It appears .NET makes this call before checking whether
+			// validationAttributes is null...
+			ValidationAttribute vattr = validationAttributes.FirstOrDefault <ValidationAttribute> (attr => attr is RequiredAttribute);
+			if (vattr != null) {
+				result = vattr.GetValidationResult (value, validationContext);
+				if (result != ValidationResult.Success) {
+					if (validationResults != null)
+						validationResults.Add (result);
+					return false;
+				}
+			}
+
+			if (validationAttributes == null)
+				return true;
+
+			bool valid = true;
+			foreach (ValidationAttribute attr in validationAttributes) {
+				if (attr == null || (attr is RequiredAttribute))
+					continue;
+				
+				result = attr.GetValidationResult (value, validationContext);
+				if (result != ValidationResult.Success) {
+					valid = false;
+					if (validationResults != null)
+						validationResults.Add (result);
+				}
+			}
+			
+			return valid;
+		}
+
+		public static void ValidateObject (object instance, ValidationContext validationContext)
+		{
+			ValidateObject (instance, validationContext, false);
+		}
+
+		public static void ValidateObject (object instance, ValidationContext validationContext, bool validateAllProperties)
+		{
+			if (instance == null)
+				throw new ArgumentNullException ("instance");
+			if (validationContext == null)
+				throw new ArgumentNullException ("validationContext");
+
+			var validationResults = new List <ValidationResult> ();
+			if (TryValidateObject (instance, validationContext, validationResults, validateAllProperties))
+				return;
+
+			ValidationResult result = validationResults.Count > 0 ? validationResults [0] : null;
+			throw new ValidationException (result, null, instance);
+		}
+
+		public static void ValidateProperty (object value, ValidationContext validationContext)
+		{
+			if (validationContext == null)
+				throw new ArgumentNullException ("validationContext");
+
+			var validationResults = new List <ValidationResult> ();
+			if (TryValidateProperty (value, validationContext, validationResults))
+				return;
+
+			ValidationResult result = validationResults.Count > 0 ? validationResults [0] : null;
+			throw new ValidationException (result, null, value);
+		}
+
+		public static void ValidateValue (object value, ValidationContext validationContext, IEnumerable <ValidationAttribute> validationAttributes)
+		{
+			if (validationContext == null)
+				throw new ArgumentNullException ("validationContext");
+
+			var validationResults = new List <ValidationResult> ();
+			if (TryValidateValue (value, validationContext, validationResults, validationAttributes))
+				return;
+
+			ValidationResult result = validationResults.Count > 0 ? validationResults [0] : null;
+			throw new ValidationException (result, null, value);
+		}
+	}
+}

+ 2 - 0
mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations_test.dll.sources

@@ -1,6 +1,7 @@
 ../../System.Web.DynamicData/Test/Common/AssertExtensions.cs
 System.ComponentModel.DataAnnotations/AssociatedMetadataTypeTypeDescriptionProviderTests.cs
 System.ComponentModel.DataAnnotations/AssociationAttributeTest.cs
+System.ComponentModel.DataAnnotations/CustomValidationAttributeTest.cs
 System.ComponentModel.DataAnnotations/DisplayAttributeTest.cs
 System.ComponentModel.DataAnnotations/EnumDataTypeAttributeTest.cs
 System.ComponentModel.DataAnnotations/RangeAttributeTest.cs
@@ -9,4 +10,5 @@ System.ComponentModel.DataAnnotations/StringLengthAttributeTest.cs
 System.ComponentModel.DataAnnotations/ValidationAttributeTest.cs
 System.ComponentModel.DataAnnotations/ValidationContextTest.cs
 System.ComponentModel.DataAnnotations/ValidationResultTest.cs
+System.ComponentModel.DataAnnotations/ValidatorTest.cs
 System.ComponentModel.DataAnnotations/RegularExpressionAttributeTest.cs

+ 332 - 0
mcs/class/System.ComponentModel.DataAnnotations/Test/System.ComponentModel.DataAnnotations/CustomValidationAttributeTest.cs

@@ -0,0 +1,332 @@
+//
+// Authors:
+//      Marek Habersack <[email protected]>
+//
+// Copyright (C) 2011 Novell, Inc. (http://novell.com/)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.Design;
+using System.Text;
+
+using NUnit.Framework;
+using MonoTests.Common;
+
+namespace MonoTests.System.ComponentModel.DataAnnotations
+{
+#if NET_4_0
+	[TestFixture]
+	public class CustomValidationAttributeTest
+	{
+		[Test]
+		public void Constructor ()
+		{
+			var attr = new CustomValidationAttribute (null, "MyMethod");
+			Assert.IsNull (attr.ValidatorType, "#A1-1");
+			Assert.AreEqual ("MyMethod", attr.Method, "#A1-2");
+
+			attr = new CustomValidationAttribute (typeof (string), null);
+			Assert.AreEqual (typeof (string), attr.ValidatorType, "#A2-1");
+			Assert.IsNull (attr.Method, "#A2-2");
+
+			attr = new CustomValidationAttribute (null, null);
+			Assert.IsNull (attr.ValidatorType, "#A3-1");
+			Assert.IsNull (attr.Method, "#A3-2");
+
+			attr = new CustomValidationAttribute (typeof (string), "NoSuchMethod");
+			Assert.AreEqual (typeof (string), attr.ValidatorType, "#A5-1");
+			Assert.AreEqual ("NoSuchMethod", attr.Method, "#A5-2");
+		}
+
+		[Test]
+		public void TypeId ()
+		{
+			var attr = new CustomValidationAttribute (null, "MyMethod");
+			Assert.IsNotNull (attr.TypeId, "#A1-1");
+			Assert.AreEqual (typeof (Tuple<string, Type>), attr.TypeId.GetType (), "#A1-2");
+
+			var typeid = attr.TypeId as Tuple <string, Type>;
+			Assert.IsNotNull (typeid.Item1, "#A2-1");
+			Assert.AreEqual ("MyMethod", typeid.Item1, "#A2-2");
+			Assert.IsNull (typeid.Item2, "#A2-3");
+
+			attr = new CustomValidationAttribute (typeof (CustomValidationAttributeTest), "MyMethod");
+			typeid = attr.TypeId as Tuple<string, Type>;
+			Assert.IsNotNull (typeid.Item1, "#A3-1");
+			Assert.AreEqual ("MyMethod", typeid.Item1, "#A3-2");
+			Assert.IsNotNull (typeid.Item2, "#A3-3");
+			Assert.AreEqual (typeof (CustomValidationAttributeTest), typeid.Item2, "#A3-4");
+
+			var typeid2 = attr.TypeId as Tuple<string, Type>;
+			Assert.IsTrue (Object.ReferenceEquals (typeid, typeid2), "#A4");
+		}
+
+		[Test]
+		public void FormatErrorMessage ()
+		{
+			var attr = new CustomValidationAttribute (null, null);
+			string msg = null;
+
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute.ValidatorType was not specified.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 88
+
+				msg = attr.FormatErrorMessage (null);
+			}, "#A1");
+
+			attr = new CustomValidationAttribute (typeof (string), null);
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute.Method was not specified.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 102
+
+				msg = attr.FormatErrorMessage (null);
+			}, "#A2");
+
+			attr = new CustomValidationAttribute (typeof (string), String.Empty);
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute.Method was not specified.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 117
+
+				msg = attr.FormatErrorMessage (null);
+			}, "#A3");
+
+			attr = new CustomValidationAttribute (typeof (string), "NoSuchMethod");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute method 'NoSuchMethod' does not exist in type 'String' or is not public and static.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 126
+
+				msg = attr.FormatErrorMessage (null);
+			}, "#A4");
+
+			attr = new CustomValidationAttribute (typeof (PrivateValidatorMethodContainer), "MethodOne");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The custom validation type 'PrivateValidatorMethodContainer' must be public.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 138
+
+				msg = attr.FormatErrorMessage (null);
+			}, "#A5");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodOne");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute method 'MethodOne' in type 'PublicValidatorMethodContainer' 
+				//        must return System.ComponentModel.DataAnnotations.ValidationResult.  Use System.ComponentModel.DataAnnotations.ValidationResult.Success 
+				//        to represent success.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 150
+				msg = attr.FormatErrorMessage (null);
+			}, "#A6");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodTwo");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute method 'MethodTwo' in type 'PublicValidatorMethodContainer' must match the expected signature: public static ValidationResult MethodTwo(object value, ValidationContext context).  The value can be strongly typed.  The ValidationContext parameter is optional.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 163
+				msg = attr.FormatErrorMessage (null);
+			}, "#A7");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodThree");
+			msg = attr.FormatErrorMessage (null);
+			Assert.IsNotNull (msg, "#A8-1");
+			Assert.IsTrue (msg.Length > 0, "#A8-2");
+			Assert.AreEqual (" is not valid.", msg, "#A8-3");
+			
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodFour");
+			msg = attr.FormatErrorMessage ("test");
+			Assert.IsNotNull (msg, "#A9-1");
+			Assert.IsTrue (msg.Length > 0, "#A9-2");
+			Assert.AreEqual ("test is not valid.", msg, "#A9-3");
+			
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodFive");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute method 'MethodFive' in type 'PublicValidatorMethodContainer' must match the expected signature: public static ValidationResult MethodFive(object value, ValidationContext context).  The value can be strongly typed.  The ValidationContext parameter is optional.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 180
+				msg = attr.FormatErrorMessage (null);
+			}, "#A10");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodSix");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage:
+				// System.InvalidOperationException : The CustomValidationAttribute method 'MethodSix' in type 'PublicValidatorMethodContainer' must match the expected signature: public static ValidationResult MethodSix(object value, ValidationContext context).  The value can be strongly typed.  The ValidationContext parameter is optional.
+				//
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.ThrowIfAttributeNotWellFormed()
+				// at System.ComponentModel.DataAnnotations.CustomValidationAttribute.FormatErrorMessage(String name)
+				// at MonoTests.System.ComponentModel.DataAnnotations.CustomValidationAttributeTest.FormatErrorMessage() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\CustomValidationAttributeTest.cs:line 191
+				msg = attr.FormatErrorMessage (null);
+			}, "#A11");
+		}
+
+		[Test]
+		public void IsValid ()
+		{
+			var attr = new CustomValidationAttribute (null, null);
+
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A1");
+
+			attr = new CustomValidationAttribute (typeof (string), null);
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A2");
+
+			attr = new CustomValidationAttribute (typeof (string), String.Empty);
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A3");
+
+			attr = new CustomValidationAttribute (typeof (string), "NoSuchMethod");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A4");
+
+			attr = new CustomValidationAttribute (typeof (PrivateValidatorMethodContainer), "MethodOne");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A5");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodOne");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A6");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodTwo");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A7");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodThree");
+			bool valid = attr.IsValid ("test");
+			Assert.IsTrue (valid, "#A8-1");
+			valid = attr.IsValid (null);
+			Assert.IsFalse (valid, "#A8-2");
+			valid = attr.IsValid ("failTest");
+			Assert.IsFalse (valid, "#A8-3");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodFour");
+			valid = attr.IsValid ("test");
+			Assert.IsTrue (valid, "#A9-1");
+			valid = attr.IsValid (null);
+			Assert.IsFalse (valid, "#A9-2");
+			valid = attr.IsValid ("failTest");
+			Assert.IsFalse (valid, "#A9-3");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodFive");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A10");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodSix");
+			AssertExtensions.Throws<InvalidOperationException> (() => {
+				attr.IsValid ("test");
+			}, "#A11");
+
+			attr = new CustomValidationAttribute (typeof (PublicValidatorMethodContainer), "MethodSeven");
+			AssertExtensions.Throws<ApplicationException> (() => {
+				attr.IsValid ("test");
+			}, "#A12");
+		}
+
+		class PrivateValidatorMethodContainer
+		{
+			public static void MethodOne ()
+			{ }
+		}
+
+		public class PublicValidatorMethodContainer
+		{
+			public static void MethodOne ()
+			{ }
+
+			public static ValidationResult MethodTwo ()
+			{
+				return ValidationResult.Success;
+			}
+
+			public static ValidationResult MethodThree (object o)
+			{
+				if (o == null)
+					return new ValidationResult ("Object cannot be null");
+				string s = o as string;
+				if (s == null || s != "failTest")
+					return ValidationResult.Success;
+				return new ValidationResult ("Test failed as requested");
+			}
+
+			public static ValidationResult MethodFour (object o, ValidationContext ctx)
+			{
+				if (o == null)
+					return new ValidationResult ("Object cannot be null");
+				string s = o as string;
+				if (s == null || s != "failTest")
+					return ValidationResult.Success;
+				return new ValidationResult ("Test failed as requested");
+			}
+
+			public static ValidationResult MethodFive (object o, string s)
+			{
+				return ValidationResult.Success;
+			}
+
+			public static ValidationResult MethodSix (object o, ValidationContext ctx, string s)
+			{
+				return ValidationResult.Success;
+			}
+
+			public static ValidationResult MethodSeven (object o, ValidationContext ctx)
+			{
+				throw new ApplicationException ("SNAFU");
+			}
+		}
+	}
+#endif
+}

+ 69 - 17
mcs/class/System.ComponentModel.DataAnnotations/Test/System.ComponentModel.DataAnnotations/ValidationAttributeTest.cs

@@ -31,6 +31,7 @@ using System.ComponentModel.DataAnnotations;
 using System.Text;
 
 using NUnit.Framework;
+using MonoTests.Common;
 
 namespace MonoTests.System.ComponentModel.DataAnnotations
 {
@@ -446,19 +447,19 @@ namespace MonoTests.System.ComponentModel.DataAnnotations
 		{
 			var attr = new ValidateFooAttribute ();
 
-			try {
+			AssertExtensions.Throws <NotImplementedException> (() => {
+				// It calls IsValid (object, validationContext) which throws the NIEX, but when that overload is called directly, there's
+				// no exception.
+				//
+				// at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value, ValidationContext validationContext)
+				// at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidationAttributeTest.IsValid_Object() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 450
 				attr.IsValid (null);
-				Assert.Fail ("#A1-1");
-			} catch (NotImplementedException) {
-				// success
-			}
-
-			try {
+			}, "#A1-1");
+			
+			AssertExtensions.Throws <NotImplementedException> (() => {
 				attr.IsValid ("stuff");
-				Assert.Fail ("#A1-2");
-			} catch (NotImplementedException) {
-				// success
-			}
+			}, "#A1-2");
 		}
 
 		[Test]
@@ -466,13 +467,9 @@ namespace MonoTests.System.ComponentModel.DataAnnotations
 		{
 			var attr = new ValidateBarAttribute ();
 
-			try {
-				// ...
+			AssertExtensions.Throws <NullReferenceException> (() => {
 				attr.CallIsValid (null, null);
-				Assert.Fail ("#A1");
-			} catch (NullReferenceException) {
-				// success
-			}
+			}, "#A1");
 
 			var vc = new ValidationContext ("stuff", null, null);
 			var vr = attr.CallIsValid (null, vc);
@@ -500,6 +497,41 @@ namespace MonoTests.System.ComponentModel.DataAnnotations
 			Assert.AreEqual ("SomeMember", list [0], "#A3-6");
 		}
 
+		[Test]
+		public void IsValid_Object_ValidationContext_CrossCallsWithNIEX ()
+		{
+			var attr = new ValidateSomethingAttribute ();
+
+			AssertExtensions.Throws<NotImplementedException> (() => {
+				// Thrown from the IsValid (object, ValidationContext) overload!
+				//
+				// MonoTests.System.ComponentModel.DataAnnotations.ValidationAttributeTest.IsValid_Object_ValidationContext_02:
+				// System.NotImplementedException : IsValid(object value) has not been implemented by this class.  The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context).
+				//
+				// at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value, ValidationContext validationContext)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidateSomethingAttribute.IsValid(Object value) in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 639
+				// at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value, ValidationContext validationContext)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidateSomethingAttribute.IsValid(Object value) in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 639
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidationAttributeTest.IsValid_Object_ValidationContext_02() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 514
+				attr.IsValid ("stuff");
+			}, "#A1");
+
+			AssertExtensions.Throws<NotImplementedException> (() => {
+				// And this one is thrown from the IsValid (object) overload!
+				//
+				// MonoTests.System.ComponentModel.DataAnnotations.ValidationAttributeTest.IsValid_Object_ValidationContext_CrossCallsWithNIEX:
+				// System.NotImplementedException : IsValid(object value) has not been implemented by this class.  The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context).
+				//
+				// at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidateSomethingAttribute.IsValid(Object value, ValidationContext validationContext) in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 660
+				// at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidateSomethingAttribute.IsValid(Object value, ValidationContext validationContext) in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 660
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidateSomethingAttribute.CallIsValid(Object value, ValidationContext validationContext) in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 667
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidationAttributeTest.IsValid_Object_ValidationContext_CrossCallsWithNIEX() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidationAttributeTest.cs:line 530
+
+				attr.CallIsValid ("stuff", null);
+			}, "#A2");
+		}
 		[Test]
 		public void Validate_Object_ValidationContext ()
 		{
@@ -618,7 +650,27 @@ namespace MonoTests.System.ComponentModel.DataAnnotations
 			return base.FormatErrorMessage (name);
 		}
 	}
+#if NET_4_0
+	class ValidateSomethingAttribute : ValidationAttribute
+	{
+		public override bool IsValid (object value)
+		{
+			return base.IsValid (value, null) == ValidationResult.Success;
+		}
 
+		protected override ValidationResult IsValid (object value, ValidationContext validationContext)
+		{
+			if (base.IsValid (value))
+				return ValidationResult.Success;
+			return new ValidationResult ("failed to validate in base class");
+		}
+
+		public ValidationResult CallIsValid (object value, ValidationContext validationContext)
+		{
+			return IsValid (value, validationContext);
+		}
+	}
+#endif
 	class FooErrorMessageProvider
 	{
 		public static string ErrorProperty1

+ 1120 - 0
mcs/class/System.ComponentModel.DataAnnotations/Test/System.ComponentModel.DataAnnotations/ValidatorTest.cs

@@ -0,0 +1,1120 @@
+//
+// Authors:
+//      Marek Habersack <[email protected]>
+//
+// Copyright (C) 2011 Novell, Inc. (http://novell.com/)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.Design;
+using System.Text;
+
+using NUnit.Framework;
+using MonoTests.Common;
+
+namespace MonoTests.System.ComponentModel.DataAnnotations
+{
+#if NET_4_0
+	[TestFixture]
+	public class ValidatorTest
+	{
+		[Test]
+		public void TryValidateObject_Object_ValidationContext_ICollection_01 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null);
+			var results = new List<ValidationResult> ();
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateObject (null, ctx, results);
+			}, "#A1-1");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateObject (dummy, null, results);
+			}, "#A1-2");
+
+			bool valid = Validator.TryValidateObject (dummy, ctx, null);
+			Assert.IsTrue (valid, "#A2-1");
+			Assert.IsTrue (results.Count == 0, "#A2-2");
+		}
+
+		[Test]
+		public void TryValidateObject_Object_ValidationContext_ICollection_02 ()
+		{
+			var dummy = new Dummy ();
+			var ctx = new ValidationContext (dummy, null, null);
+			var results = new List<ValidationResult> ();
+
+			bool valid = Validator.TryValidateObject (dummy, ctx, results);
+			Assert.IsTrue (valid, "#A1-1");
+			Assert.AreEqual (0, results.Count, "#A1-2");
+
+			dummy = new Dummy {
+				NameField = null
+			};
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// The instance provided must match the ObjectInstance on the ValidationContext supplied.
+				valid = Validator.TryValidateObject (dummy, ctx, results);
+			}, "#A2");
+
+			// Fields are ignored
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results);
+			Assert.IsTrue (valid, "#A3-1");
+			Assert.AreEqual (0, results.Count, "#A3-2");
+
+			// Required properties existence is validated
+			dummy = new Dummy {
+				RequiredDummyField = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results);
+			Assert.IsTrue (valid, "#A4-1");
+			Assert.AreEqual (0, results.Count, "#A4-2");
+
+			dummy = new Dummy {
+				RequiredDummyProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results);
+			Assert.IsFalse (valid, "#A5-1");
+			Assert.AreEqual (1, results.Count, "#A5-2");
+
+			results.Clear ();
+
+			// validation attributes other than Required are ignored
+			dummy = new Dummy {
+				NameProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results);
+			Assert.IsTrue (valid, "#A6-1");
+			Assert.AreEqual (0, results.Count, "#A6-2");
+
+			dummy = new Dummy {
+				MinMaxProperty = 0
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results);
+			Assert.IsTrue (valid, "#A7-1");
+			Assert.AreEqual (0, results.Count, "#A7-2");
+
+			dummy = new Dummy {
+				FailValidation = true
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results);
+			Assert.IsFalse (valid, "#A8-1");
+			Assert.AreEqual (1, results.Count, "#A8-2");
+		}
+
+		[Test]
+		public void TryValidateObject_Object_ValidationContext_ICollection_Bool_01 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null);
+			var results = new List<ValidationResult> ();
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateObject (null, ctx, results, false);
+			}, "#A1-1");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateObject (dummy, null, results, false);
+			}, "#A1-2");
+
+			bool valid = Validator.TryValidateObject (dummy, ctx, null, false);
+			Assert.IsTrue (valid, "#A2-1");
+			Assert.IsTrue (results.Count == 0, "#A2-2");
+
+			valid = Validator.TryValidateObject (dummy, ctx, null, true);
+			Assert.IsTrue (valid, "#A3-1");
+			Assert.IsTrue (results.Count == 0, "#A3-2");
+		}
+
+		[Test]
+		public void TryValidateObject_Object_ValidationContext_ICollection_Bool_02 ()
+		{
+			var dummy = new Dummy ();
+			var ctx = new ValidationContext (dummy, null, null);
+			var results = new List<ValidationResult> ();
+
+			bool valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			Assert.IsTrue (valid, "#A1-1");
+			Assert.AreEqual (0, results.Count, "#A1-2");
+
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsTrue (valid, "#A1-3");
+			Assert.AreEqual (0, results.Count, "#A1-4");
+
+			dummy = new Dummy {
+				NameField = null
+			};
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// The instance provided must match the ObjectInstance on the ValidationContext supplied.
+				valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			}, "#A2-1");
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// The instance provided must match the ObjectInstance on the ValidationContext supplied.
+				valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			}, "#A2-2");
+
+			// Fields are ignored
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			Assert.IsTrue (valid, "#A3-1");
+			Assert.AreEqual (0, results.Count, "#A3-2");
+
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsTrue (valid, "#A3-3");
+			Assert.AreEqual (0, results.Count, "#A3-4");
+
+			dummy = new Dummy {
+				RequiredDummyField = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			Assert.IsTrue (valid, "#A4-1");
+			Assert.AreEqual (0, results.Count, "#A4-2");
+
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsTrue (valid, "#A4-3");
+			Assert.AreEqual (0, results.Count, "#A4-4");
+
+			// Required properties existence is validated
+			dummy = new Dummy {
+				RequiredDummyProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			Assert.IsFalse (valid, "#A5-1");
+			Assert.AreEqual (1, results.Count, "#A5-2");
+			results.Clear ();
+			
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsFalse (valid, "#A5-3");
+			Assert.AreEqual (1, results.Count, "#A5-4");
+			results.Clear ();
+
+			dummy = new Dummy {
+				NameProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			Assert.IsTrue (valid, "#A6-1");
+			Assert.AreEqual (0, results.Count, "#A6-2");
+
+			// NameProperty is null, that causes the StringLength validator to skip its tests
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsTrue (valid, "#A6-3");
+			Assert.AreEqual (0, results.Count, "#A6-4");
+
+			dummy.NameProperty = "0";
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsFalse (valid, "#A6-5");
+			Assert.AreEqual (1, results.Count, "#A6-6");
+			results.Clear ();
+
+			dummy.NameProperty = "name too long (invalid value)";
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsFalse (valid, "#A6-7");
+			Assert.AreEqual (1, results.Count, "#A6-8");
+			results.Clear ();
+
+			dummy = new Dummy {
+				MinMaxProperty = 0
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			Assert.IsTrue (valid, "#A7-1");
+			Assert.AreEqual (0, results.Count, "#A7-2");
+
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsFalse (valid, "#A7-3");
+			Assert.AreEqual (1, results.Count, "#A7-4");
+			results.Clear ();
+
+			dummy = new Dummy {
+				FailValidation = true
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			valid = Validator.TryValidateObject (dummy, ctx, results, false);
+			Assert.IsFalse (valid, "#A8-1");
+			Assert.AreEqual (1, results.Count, "#A8-2");
+			results.Clear ();
+
+			valid = Validator.TryValidateObject (dummy, ctx, results, true);
+			Assert.IsFalse (valid, "#A8-3");
+			Assert.AreEqual (1, results.Count, "#A8-4");
+			results.Clear ();
+
+			var dummy2 = new DummyWithException ();
+			ctx = new ValidationContext (dummy2, null, null);
+			AssertExtensions.Throws<ApplicationException> (() => {
+				Validator.TryValidateObject (dummy2, ctx, results, true);
+			}, "#A9");
+		}
+
+		[Test]
+		public void TryValidateProperty ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null) {
+				MemberName = "NameProperty"
+			};
+			var results = new List<ValidationResult> ();
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty:
+				// System.ArgumentException : The type 'DummyNoAttributes' does not contain a public property named 'NameProperty'.
+				// Parameter name: propertyName
+				//
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.TypeStoreItem.GetPropertyStoreItem(String propertyName)
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.GetPropertyType(ValidationContext validationContext)
+				// at System.ComponentModel.DataAnnotations.Validator.TryValidateProperty(Object value, ValidationContext validationContext, ICollection`1 validationResults)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidatorTest.cs:line 283
+
+				Validator.TryValidateProperty ("dummy", ctx, results);
+			}, "#A1-1");
+			Assert.AreEqual (0, results.Count, "#A1-2");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateProperty ("dummy", null, results);
+			}, "#A1-2");
+
+			var dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "NameProperty"
+			};
+			
+			bool valid = Validator.TryValidateProperty (null, ctx, results);
+			Assert.IsTrue (valid, "#A1-3");
+			Assert.AreEqual (0, results.Count, "#A1-4");
+
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "MinMaxProperty"
+			};
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				Validator.TryValidateProperty (null, ctx, results);
+			}, "#A1-5");
+
+			ctx = new ValidationContext (dummy2, null, null);
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty:
+				// System.ArgumentNullException : Value cannot be null.
+				// Parameter name: propertyName
+				//
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.TypeStoreItem.TryGetPropertyStoreItem(String propertyName, PropertyStoreItem& item)
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.TypeStoreItem.GetPropertyStoreItem(String propertyName)
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.GetPropertyType(ValidationContext validationContext)
+				// at System.ComponentModel.DataAnnotations.Validator.TryValidateProperty(Object value, ValidationContext validationContext, ICollection`1 validationResults)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidatorTest.cs:line 289
+
+				Validator.TryValidateProperty ("dummy", ctx, results);
+			}, "#A2-1");
+			Assert.AreEqual (0, results.Count, "#A2-2");
+
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = String.Empty
+			};
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty:
+				// System.ArgumentNullException : Value cannot be null.
+				// Parameter name: propertyName
+				//
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.TypeStoreItem.TryGetPropertyStoreItem(String propertyName, PropertyStoreItem& item)
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.TypeStoreItem.GetPropertyStoreItem(String propertyName)
+				// at System.ComponentModel.DataAnnotations.ValidationAttributeStore.GetPropertyType(ValidationContext validationContext)
+				// at System.ComponentModel.DataAnnotations.Validator.TryValidateProperty(Object value, ValidationContext validationContext, ICollection`1 validationResults)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidatorTest.cs:line 289
+
+				Validator.TryValidateProperty ("dummy", ctx, results);
+			}, "#A2-2");
+			Assert.AreEqual (0, results.Count, "#A2-2");
+
+			dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "NameProperty"
+			};
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty:
+				// System.ArgumentException : The value for property 'NameProperty' must be of type 'System.String'.
+				// Parameter name: value
+				//
+				// at System.ComponentModel.DataAnnotations.Validator.EnsureValidPropertyType(String propertyName, Type propertyType, Object value)
+				// at System.ComponentModel.DataAnnotations.Validator.TryValidateProperty(Object value, ValidationContext validationContext, ICollection`1 validationResults)
+				// at MonoTests.System.ComponentModel.DataAnnotations.ValidatorTest.TryValidateProperty() in C:\Users\grendel\Documents\Visual Studio 2010\Projects\System.Web.Test\System.Web.Test\System.ComponentModel.DataAnnotations\ValidatorTest.cs:line 315
+
+				Validator.TryValidateProperty (1234, ctx, results);
+			}, "#A3-1");
+			Assert.AreEqual (0, results.Count, "#A3-2");
+
+			dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "NameProperty"
+			};
+			
+			valid = Validator.TryValidateProperty (String.Empty, ctx, results);
+			Assert.IsFalse (valid, "#A4-1");
+			Assert.AreEqual (1, results.Count, "#A4-2");
+			results.Clear ();
+
+			valid = Validator.TryValidateProperty ("this value is way too long", ctx, results);
+			Assert.IsFalse (valid, "#A4-3");
+			Assert.AreEqual (1, results.Count, "#A4-4");
+			results.Clear ();
+
+			valid = Validator.TryValidateProperty ("good value", ctx, results);
+			Assert.IsTrue (valid, "#A4-5");
+			Assert.AreEqual (0, results.Count, "#A4-6");
+
+			dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "CustomValidatedProperty"
+			};
+
+			valid = Validator.TryValidateProperty (String.Empty, ctx, results);
+			Assert.IsFalse (valid, "#A5-1");
+			Assert.AreEqual (1, results.Count, "#A5-2");
+			results.Clear ();
+
+			valid = Validator.TryValidateProperty ("fail", ctx, results);
+			Assert.IsFalse (valid, "#A5-3");
+			Assert.AreEqual (1, results.Count, "#A5-4");
+			results.Clear ();
+
+			valid = Validator.TryValidateProperty ("f", ctx, results);
+			Assert.IsFalse (valid, "#A5-5");
+			Assert.AreEqual (2, results.Count, "#A5-6");
+			results.Clear ();
+
+			valid = Validator.TryValidateProperty ("good value", ctx, results);
+			Assert.IsTrue (valid, "#A5-7");
+			Assert.AreEqual (0, results.Count, "#A5-8");
+		}
+
+		[Test]
+		public void TryValidateValue_01 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null) {
+				MemberName = "NameProperty"
+			};
+			var results = new List<ValidationResult> ();
+			var attributes = new List <ValidationAttribute> ();
+			
+			bool valid = Validator.TryValidateValue (null, ctx, results, attributes);
+			Assert.IsTrue (valid, "#A1-1");
+			Assert.AreEqual (0, results.Count, "#A1-2");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateValue ("dummy", null, results, attributes);
+			}, "#A2");
+
+			valid = Validator.TryValidateValue ("dummy", ctx, null, attributes);
+			Assert.IsTrue (valid, "#A3-1");
+			Assert.AreEqual (0, results.Count, "#A3-2");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateValue ("dummy", ctx, results, null);
+			}, "#A4");
+		}
+
+		[Test]
+		public void TryValidateValue_02 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null);
+			var results = new List<ValidationResult> ();
+			var log = new List<string> ();
+			var attributes = new List<ValidationAttribute> () {
+				new StringLengthAttributePoker (10, log) {
+					MinimumLength = 2
+				},
+				new RequiredAttributePoker (log)
+			};
+
+			bool valid = Validator.TryValidateValue (null, ctx, results, attributes);
+			Assert.IsFalse (valid, "#A1-1");
+			Assert.AreEqual (1, results.Count, "#A1-2");
+			Assert.AreEqual (1, log.Count, "#A1-3");
+			Assert.IsTrue (log [0].StartsWith ("RequiredAttributePoker.IsValid (object)"), "#A1-4");
+			results.Clear ();
+			log.Clear ();
+
+			AssertExtensions.Throws<InvalidCastException> (() => {
+				// Thrown by StringValidatorAttribute
+				Validator.TryValidateValue (1234, ctx, results, attributes);
+			}, "#A2-1");
+			Assert.AreEqual (0, results.Count, "#A2-2");
+			Assert.AreEqual (2, log.Count, "#A2-3");
+			Assert.IsTrue (log[0].StartsWith ("RequiredAttributePoker.IsValid (object)"), "#A2-4");
+			Assert.IsTrue (log[1].StartsWith ("StringLengthAttributePoker.IsValid (object)"), "#A2-5");
+			results.Clear ();
+			log.Clear ();
+
+			attributes.Add (new CustomValidationAttribute (typeof (ValidatorTest), "ValueValidationMethod"));
+			attributes.Add (new CustomValidationAttribute (typeof (ValidatorTest), "ValueValidationMethod"));
+			valid = Validator.TryValidateValue ("test", ctx, results, attributes);
+			Assert.IsFalse (valid, "#A3-1");
+			Assert.AreEqual (2, results.Count, "#A3-2");
+			Assert.AreEqual (2, log.Count, "#A3-3");
+			Assert.IsTrue (log[0].StartsWith ("RequiredAttributePoker.IsValid (object)"), "#A3-4");
+			Assert.IsTrue (log[1].StartsWith ("StringLengthAttributePoker.IsValid (object)"), "#A3-5");
+			Assert.AreEqual ("ValueValidationMethod", results[0].ErrorMessage, "#A3-6");
+			Assert.AreEqual ("ValueValidationMethod", results[1].ErrorMessage, "#A3-7");
+			results.Clear ();
+			log.Clear ();
+			attributes.RemoveAt (2);
+			attributes.RemoveAt (2);
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateValue ("dummy", null, results, attributes);
+			}, "#B1");
+
+			valid = Validator.TryValidateValue ("dummy", ctx, null, attributes);
+			Assert.IsTrue (valid, "#B2-1");
+			Assert.AreEqual (0, results.Count, "#B2-2");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.TryValidateValue ("dummy", ctx, results, null);
+			}, "#B3");
+		}
+
+		[Test]
+		public void ValidateObject_Object_ValidationContext_01 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null);
+			
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateObject (null, ctx);
+			}, "#A1-1");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateObject (dummy, null);
+			}, "#A1-2");
+
+			try {
+				Validator.ValidateObject (dummy, ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A2 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+		}
+
+		[Test]
+		public void ValidateObject_Object_ValidationContext_02 ()
+		{
+			var dummy = new Dummy ();
+			var ctx = new ValidationContext (dummy, null, null);
+
+			try {
+				Validator.ValidateObject (dummy, ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A1 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			dummy = new Dummy {
+				NameField = null
+			};
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// The instance provided must match the ObjectInstance on the ValidationContext supplied.
+				Validator.ValidateObject (dummy, ctx);
+			}, "#A2");
+
+			// Fields are ignored
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx);
+			}  catch (Exception ex) {
+				Assert.Fail ("#A3 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+			
+			dummy = new Dummy {
+				RequiredDummyField = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A4 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			dummy = new Dummy {
+				RequiredDummyProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx);
+			}, "#A5");
+
+			// validation attributes other than Required are ignored
+			dummy = new Dummy {
+				NameProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A6 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+			
+			dummy = new Dummy {
+				MinMaxProperty = 0
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A7 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			dummy = new Dummy {
+				FailValidation = true
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx);
+			}, "#A8");
+
+			var dummy2 = new DummyMultipleCustomValidators ();
+			ctx = new ValidationContext (dummy2, null, null);
+			try {
+				Validator.ValidateObject (dummy2, ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A9 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+		}
+
+		[Test]
+		public void ValidateObject_Object_ValidationContext_Bool_01 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null);
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateObject (null, ctx, false);
+			}, "#A1-1");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateObject (dummy, null, false);
+			}, "#A1-2");
+
+			try {
+				Validator.ValidateObject (dummy, ctx, false);
+			} catch (Exception ex) {
+				Assert.Fail ("#A2 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			try {
+				Validator.ValidateObject (dummy, ctx, true);
+			} catch (Exception ex) {
+				Assert.Fail ("#A3 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+		}
+
+		[Test]
+		public void ValidateObject_Object_ValidationContext_Bool_02 ()
+		{
+			var dummy = new Dummy ();
+			var ctx = new ValidationContext (dummy, null, null);
+
+			try {
+				Validator.ValidateObject (dummy, ctx, false);
+			} catch (Exception ex) {
+				Assert.Fail ("#A1 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			try {
+				Validator.ValidateObject (dummy, ctx, true);
+			} catch (Exception ex) {
+				Assert.Fail ("#A2 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			dummy = new Dummy {
+				NameField = null
+			};
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// The instance provided must match the ObjectInstance on the ValidationContext supplied.
+				Validator.ValidateObject (dummy, ctx, false);
+			}, "#A3-1");
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				// The instance provided must match the ObjectInstance on the ValidationContext supplied.
+				Validator.ValidateObject (dummy, ctx, true);
+			}, "#A3-2");
+
+			// Fields are ignored
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx, false);
+			} catch (Exception ex) {
+				Assert.Fail ("#A4-1 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			try {
+				Validator.ValidateObject (dummy, ctx, true);
+			} catch (Exception ex) {
+				Assert.Fail ("#A4-2 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			dummy = new Dummy {
+				RequiredDummyField = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx, false);
+			} catch (Exception ex) {
+				Assert.Fail ("#A5-1 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			try {
+				Validator.ValidateObject (dummy, ctx, true);
+			} catch (Exception ex) {
+				Assert.Fail ("#A5-2 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			// Required properties existence is validated
+			dummy = new Dummy {
+				RequiredDummyProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx, false);
+			}, "#A6-1");
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx, true);
+			}, "#A6-2");
+
+			dummy = new Dummy {
+				NameProperty = null
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx, false);
+			} catch (Exception ex) {
+				Assert.Fail ("#A7 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			// NameProperty is null, that causes the StringLength validator to skip its tests
+			try {
+				Validator.ValidateObject (dummy, ctx, true);
+			} catch (Exception ex) {
+				Assert.Fail ("#A8 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			dummy.NameProperty = "0";
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx, true);
+			}, "#A9");
+
+			dummy.NameProperty = "name too long (invalid value)";
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx, true);
+			}, "#A10");
+
+			dummy = new Dummy {
+				MinMaxProperty = 0
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			try {
+				Validator.ValidateObject (dummy, ctx, false);
+			} catch (Exception ex) {
+				Assert.Fail ("#A11 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx, true);
+			}, "#A12");
+
+			dummy = new Dummy {
+				FailValidation = true
+			};
+			ctx = new ValidationContext (dummy, null, null);
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx, false);
+			}, "#A13-1");
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateObject (dummy, ctx, true);
+			}, "#A13-2");
+
+			var dummy2 = new DummyWithException ();
+			ctx = new ValidationContext (dummy2, null, null);
+			AssertExtensions.Throws<ApplicationException> (() => {
+				Validator.ValidateObject (dummy2, ctx, true);
+			}, "#A14");
+
+			var dummy3 = new DummyMultipleCustomValidators ();
+			ctx = new ValidationContext (dummy3, null, null);
+			try {
+				Validator.ValidateObject (dummy3, ctx, false);
+			} catch (Exception ex) {
+				Assert.Fail ("#A9 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			try {
+				Validator.ValidateObject (dummy3, ctx, true);
+			} catch (ValidationException ex) {
+				Assert.AreEqual ("FirstPropertyValidationMethod", ex.Message, "#A10");
+			} catch (Exception ex) {
+				Assert.Fail ("#A10 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+		}
+
+		[Test]
+		public void ValidateProperty ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null) {
+				MemberName = "NameProperty"
+			};
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				Validator.ValidateProperty ("dummy", ctx);
+			}, "#A1-1");
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateProperty ("dummy", null);
+			}, "#A1-2");
+
+			var dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "NameProperty"
+			};
+
+			try {
+				Validator.ValidateProperty (null, ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A2 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "MinMaxProperty"
+			};
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				Validator.ValidateProperty (null, ctx);
+			}, "#A3");
+
+			ctx = new ValidationContext (dummy2, null, null);
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateProperty ("dummy", ctx);
+			}, "#A4");
+
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = String.Empty
+			};
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateProperty ("dummy", ctx);
+			}, "#A5");
+
+			dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "NameProperty"
+			};
+
+			AssertExtensions.Throws<ArgumentException> (() => {
+				Validator.ValidateProperty (1234, ctx);
+			}, "#A6");
+
+			dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "NameProperty"
+			};
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateProperty (String.Empty, ctx);
+			}, "#A7");
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateProperty ("this value is way too long", ctx);
+			}, "#A8");
+
+			try {
+				Validator.ValidateProperty ("good value", ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A9 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			dummy2 = new Dummy ();
+			ctx = new ValidationContext (dummy2, null, null) {
+				MemberName = "CustomValidatedProperty"
+			};
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateProperty (String.Empty, ctx);
+			}, "#A10");
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateProperty ("fail", ctx);
+			}, "#A11");
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateProperty ("f", ctx);
+			}, "#A12");
+
+			try {
+				Validator.ValidateProperty ("good value", ctx);
+			} catch (Exception ex) {
+				Assert.Fail ("#A13 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+		}
+
+		[Test]
+		public void ValidateValue_01 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null) {
+				MemberName = "NameProperty"
+			};
+			var attributes = new List<ValidationAttribute> ();
+
+			try {
+				Validator.ValidateValue (null, ctx, attributes);
+			} catch (Exception ex) {
+				Assert.Fail ("#A1 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateValue ("dummy", null, attributes);
+			}, "#A2");
+
+			try {
+				Validator.ValidateValue ("dummy", ctx, attributes);
+			} catch (Exception ex) {
+				Assert.Fail ("#A3 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateValue ("dummy", ctx, null);
+			}, "#A4");
+		}
+
+		[Test]
+		public void ValidateValue_02 ()
+		{
+			var dummy = new DummyNoAttributes ();
+			var ctx = new ValidationContext (dummy, null, null);
+			var log = new List<string> ();
+			var attributes = new List<ValidationAttribute> () {
+				new StringLengthAttributePoker (10, log) {
+					MinimumLength = 2
+				},
+				new RequiredAttributePoker (log)
+			};
+
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateValue (null, ctx, attributes);
+			}, "#A1-1");
+			Assert.AreEqual (1, log.Count, "#A1-2");
+			Assert.IsTrue (log[0].StartsWith ("RequiredAttributePoker.IsValid (object)"), "#A1-3");
+			log.Clear ();
+
+			AssertExtensions.Throws<InvalidCastException> (() => {
+				// Thrown by StringValidatorAttribute
+				Validator.ValidateValue (1234, ctx, attributes);
+			}, "#A2-1");;
+			Assert.AreEqual (2, log.Count, "#A2-2");
+			Assert.IsTrue (log[0].StartsWith ("RequiredAttributePoker.IsValid (object)"), "#A2-3");
+			Assert.IsTrue (log[1].StartsWith ("StringLengthAttributePoker.IsValid (object)"), "#A2-4");
+			log.Clear ();
+
+			attributes.Add (new CustomValidationAttribute (typeof (ValidatorTest), "ValueValidationMethod"));
+			attributes.Add (new CustomValidationAttribute (typeof (ValidatorTest), "ValueValidationMethod"));
+			AssertExtensions.Throws<ValidationException> (() => {
+				Validator.ValidateValue ("test", ctx, attributes);
+			}, "#A3-1");
+			Assert.AreEqual (2, log.Count, "#A3-2");
+			Assert.IsTrue (log[0].StartsWith ("RequiredAttributePoker.IsValid (object)"), "#A3-3");
+			Assert.IsTrue (log[1].StartsWith ("StringLengthAttributePoker.IsValid (object)"), "#A3-4");
+			log.Clear ();
+			attributes.RemoveAt (2);
+			attributes.RemoveAt (2);
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateValue ("dummy", null, attributes);
+			}, "#B1");
+
+			try {
+				Validator.ValidateValue ("dummy", ctx, attributes);
+			} catch (Exception ex) {
+				Assert.Fail ("#B2 (exception {0} thrown: {1})", ex.GetType (), ex.Message);
+			}
+
+			AssertExtensions.Throws<ArgumentNullException> (() => {
+				Validator.ValidateValue ("dummy", ctx, null);
+			}, "#B3");
+		}
+
+		public static ValidationResult DummyValidationMethod (object o)
+		{
+			var dummy = o as Dummy;
+			if (dummy == null)
+				return new ValidationResult ("Invalid DummyValidationMethod input - broken test?");
+
+			if (dummy.FailValidation)
+				return new ValidationResult ("Dummy validation failed.");
+			return ValidationResult.Success;
+		}
+
+		public static ValidationResult CustomValidatedPropertyValidationMethod (object o)
+		{
+			var dummy = o as string;
+			if (dummy != null && (dummy == "f" || dummy == "fail"))
+				return new ValidationResult ("Dummy.CustomValidatedProperty validation failed.");
+			return ValidationResult.Success;
+		}
+
+		public static ValidationResult ValidationMethodException (object o)
+		{
+			throw new ApplicationException ("SNAFU");
+		}
+
+		public static ValidationResult ValueValidationMethod (object o, ValidationContext validationContext)
+		{
+			return new ValidationResult ("ValueValidationMethod");
+		}
+
+		public static ValidationResult FirstPropertyValidationMethod (object o, ValidationContext validationContext)
+		{
+			return new ValidationResult ("FirstPropertyValidationMethod");
+		}
+
+		public static ValidationResult SecondPropertyValidationMethod (object o, ValidationContext validationContext)
+		{
+			return new ValidationResult ("SecondPropertyValidationMethod");
+		}
+
+		public class RequiredAttributePoker : RequiredAttribute
+		{
+			List <string> log;
+
+			public RequiredAttributePoker (List<string> log)
+			{
+				if (log == null)
+					throw new ArgumentNullException ("log");
+				this.log = log;
+			}
+
+			public override bool IsValid (object value)
+			{
+				log.Add ("RequiredAttributePoker.IsValid (object)");
+				return base.IsValid (value);
+			}
+		}
+
+		public class StringLengthAttributePoker : StringLengthAttribute
+		{
+			List <string> log;
+
+			public StringLengthAttributePoker (int maximumLength, List<string> log)
+				: base (maximumLength)
+			{
+				if (log == null)
+					throw new ArgumentNullException ("log");
+				this.log = log;
+			}
+
+			public override bool IsValid (object value)
+			{
+				log.Add ("StringLengthAttributePoker.IsValid (object)");
+				return base.IsValid (value);
+			}
+		}
+
+		class DummyNoAttributes
+		{ }
+
+		[CustomValidation (typeof (ValidatorTest), "DummyValidationMethod")]
+		class Dummy
+		{
+			[StringLength (10, MinimumLength=2)]
+			public string NameField;
+
+			[Required]
+			public DummyNoAttributes RequiredDummyField;
+
+			[StringLength (10, MinimumLength = 2)]
+			public string NameProperty { get; set; }
+
+			[Required]
+			public DummyNoAttributes RequiredDummyProperty { get; set; }
+			
+			[global::System.ComponentModel.DataAnnotations.RangeAttribute ((int)1, (int)10)]
+			public int MinMaxProperty { get; set; }
+
+			[StringLength (10, MinimumLength = 2)]
+			[CustomValidation (typeof (ValidatorTest), "CustomValidatedPropertyValidationMethod")]
+			public string CustomValidatedProperty { get; set; }
+
+			[CustomValidation (typeof (ValidatorTest), "CustomValidatedPropertyValidationMethod")]
+			[StringLength (10, MinimumLength = 2)]
+			public string AnotherCustomValidatedProperty { get; set; }
+
+			public bool FailValidation { get; set; }
+
+			public Dummy ()
+			{
+				NameField = "name";
+				NameProperty = "name";
+				RequiredDummyField = new DummyNoAttributes ();
+				RequiredDummyProperty = new DummyNoAttributes ();
+				MinMaxProperty = 5;
+				AnotherCustomValidatedProperty = "I'm valid";
+			}
+		}
+
+		class DummyWithException
+		{
+			[CustomValidation (typeof (ValidatorTest), "ValidationMethodException")]
+			public string AnotherCustomValidatedProperty { get; set; }
+		}
+
+		class DummyForValueValidation
+		{
+			public string DummyNoAttributes;
+
+			public DummyForValueValidation ()
+			{
+				this.DummyNoAttributes = "I am valid";
+			}
+		}
+
+		class DummyMultipleCustomValidators
+		{
+			[CustomValidation (typeof (ValidatorTest), "FirstPropertyValidationMethod")]
+			public string FirstProperty { get; set; }
+
+			[CustomValidation (typeof (ValidatorTest), "SecondPropertyValidationMethod")]
+			public string SecondProperty { get; set; }
+		}
+	}
+#endif
+}

+ 2 - 0
mcs/class/System.ComponentModel.DataAnnotations/net_4_0_System.ComponentModel.DataAnnotations.dll.sources

@@ -1,4 +1,6 @@
 #include System.ComponentModel.DataAnnotations.dll.sources
 
+System.ComponentModel.DataAnnotations/ValidationAttributeCollectionExtensions.cs
 System.ComponentModel.DataAnnotations/ValidationContext.cs
 System.ComponentModel.DataAnnotations/ValidationResult.cs
+System.ComponentModel.DataAnnotations/Validator.cs