Wednesday, March 6, 2019

Data Annotation: Compare value against other member

This time again: no rocket sience :)

Data annotation gives some nice possiblity to automate data validation. But I couldn't find an out of the box validation attribute which lets check a member value against another member value.

This is typically required if you have f.ex. from date and to date: The from date shall be smaller (or equal)  to the to date. Or to lower-upper-bound ranges: start point shall be smaller (or equal) to end point.

So I created such a validation attribute from scratch. May be you can use it too - at least you can take it as template to create your own validation attribute classes if you need some jump start.

The validation attribute class itself:

 using System;  
 using System.Collections.Generic;  
 using System.ComponentModel;  
 using System.ComponentModel.DataAnnotations;  
 using System.Globalization;  
 using System.Reflection;  
 using System.Linq;  
   
 namespace AnyNamespace  
 {  
   /// <summary>  
   /// Provides functionality to validate member   
   /// value against other value through comparison  
   /// </summary>  
   [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property,   
     AllowMultiple = false)]  
   public sealed class MustCompareToAttribute : ValidationAttribute  
   {  
     /// <summary>  
     /// The comparison operators to fit validation  
     /// </summary>  
     public enum Operator  
     {  
       /// <summary>  
       /// The 'smaller' operator  
       /// </summary>  
       [Display(Description = "is not smaller than")]   
       // TODO replace with localized resource  
       SmallerThan,  
   
       /// <summary>  
       /// The 'smaller or equal' operator  
       /// </summary>  
       [Display(Description = "is not smaller than or equal to")]   
       // TODO replace with localized resource  
       SmallerOrEqualThan,  
   
       /// <summary>  
       /// The 'greater' operator  
       /// </summary>  
       [Display(Description = "is not greater than")]   
       // TODO replace with localized resource  
       GreaterThan,  
   
       /// <summary>  
       /// The 'greater or equal' operator  
       /// </summary>  
       [Display(Description = "is not greater than or equal to")]   
       // TODO replace with localized resource  
       GreaterOrEqualThan  
     }  
   
     /// <summary>  
     /// The counterpart for value comparison  
     /// </summary>  
     public object ComparisonReference { get; }  
   
     /// <summary>  
     /// The operator used for comparison  
     /// </summary>  
     public Operator ComparisonAgainstReference { get; }  
   
     /// <summary>  
     /// Gets a value that indicates whether the   
     /// attribute requires validation context.  
     /// <c>True</c> if the attribute requires validation context,   
     /// otherwise <c>false</c>.  
     /// </summary>  
     public override bool RequiresValidationContext => true;  
   
   
   
     /// <summary>  
     /// Constructor with fixed comparison value and error message  
     /// </summary>  
     /// <param name="comparisonReferenceMember">  
     /// A member name of the validation context's object instance subject   
     /// to retrieve it's value for comparison  
     /// </param>  
     /// <param name="comparisonAgainstReference">  
     /// The comparison operator which must fullify in order to   
     /// successfull validate member  
     /// </param>  
     /// <param name="errorMessage">  
     /// The error message to associate with a validation control.  
     /// The message will be formatted with following values:  
     /// {0} = Trigger member display name,  
     /// {1} = Trigger member value,  
     /// {2} = Comparison operator missmatch text fragment,  
     /// {3} = Comparsion member display name  
     /// {4} = Comparsion value,  
     /// </param>  
     public MustCompareToAttribute(string comparisonReferenceMember,   
       Operator comparisonAgainstReference, string errorMessage)  
         : base(errorMessage)  
     {  
       ComparisonAgainstReference = comparisonAgainstReference;  
       ComparisonReference = comparisonReferenceMember ??   
         throw new ArgumentNullException(nameof(comparisonReferenceMember));  
     }  
   
   
     /// <summary>  
     /// Constructor with fixed comparison value and error message  
     /// </summary>  
     /// <param name="comparisonReferenceMember">  
     /// A member name of the validation context's object   
     /// instance subject to retrieve it's value for comparison  
     /// </param>  
     /// <param name="comparisonAgainstReference">  
     /// The comparison operator which must fullify in order   
     /// to successfull validate member  
     /// </param>  
     /// <param name="errorMessageAccessor">  
     /// The function that enables access to validation resources.  
     /// The message will be formatted with following values:  
     /// {0} = Trigger member display name,  
     /// {1} = Trigger member value,  
     /// {2} = Comparison operator missmatch text fragment,  
     /// {3} = Comparsion member display name  
     /// {4} = Comparsion value,  
     /// </param>  
     public MustCompareToAttribute(string comparisonReferenceMember,   
       Operator comparisonAgainstReference, Func<string> errorMessageAccessor)   
         : base(errorMessageAccessor)  
     {  
       ComparisonAgainstReference = comparisonAgainstReference;  
       ComparisonReference = comparisonReferenceMember ??   
         throw new ArgumentNullException(nameof(comparisonReferenceMember));  
     }  
   
     /// <summary>  
     /// Constructor with fixed comparison value and error message  
     /// </summary>  
     /// <param name="comparisonReferenceType">  
     /// The type of the counterpart for value comparison  
     /// </param>  
     /// <param name="comparisonReferenceMemberName">  
     /// A member name of the validation context's object instance subject   
     /// to retrieve it's value for comparison  
     /// </param>  
     /// <param name="comparisonAgainstReference">  
     /// The comparison operator which must fullify  
     /// in order to successfull validate member  
     /// </param>  
     public MustCompareToAttribute(string comparisonReferenceMemberName,   
       Operator comparisonAgainstReference) : base((string)null)  
     {  
       ComparisonAgainstReference = comparisonAgainstReference;  
       ComparisonReference = comparisonReferenceMemberName;  
     }  
   
     string ComparisonOperatorText()  
     {  
       Type operatorType = typeof(Operator);  
       return operatorType.GetField(Enum.GetName(operatorType,   
         ComparisonAgainstReference))  
         .GetCustomAttribute<DisplayAttribute>().Description;  
     }  
   
     /// <summary>  
     /// Validates the specified value with respect   
     /// to the current validation attribute.  
     /// </summary>  
     /// <param name="value">  
     /// The value to validate.  
     /// </param>  
     /// <param name="validationContext">  
     /// The context information about the validation operation.  
     /// </param>  
     /// <returns>  
     /// The validation result  
     /// </returns>  
     protected override ValidationResult IsValid(object value,   
       ValidationContext validationContext)  
     {  
       ValidationResult ret = ValidationResult.Success;  
   
       if (value != null)  
       {  
         var comparisonValue = default(object);  
   
         var comparisonMemberDisplayName = default(string);  
         var comparisonMember = default(MemberInfo);  
   
         comparisonMember = validationContext.ObjectType  
           .GetMember((string)ComparisonReference)  
             .Where(x => x.MemberType == MemberTypes.Property  
               || x.MemberType == MemberTypes.Field)  
                 .FirstOrDefault();  
   
         if (comparisonMember == null)  
         {  
           throw new MissingMemberException(validationContext.ObjectType.Name,   
             (string)ComparisonReference);  
         }  
   
         if (comparisonMember is PropertyInfo comparisonPropertyInfo)  
         {  
           comparisonValue = comparisonPropertyInfo  
             .GetValue(validationContext.ObjectInstance);  
         }  
         else if (comparisonMember is FieldInfo comparisonFieldInfo)  
         {  
           comparisonValue = comparisonFieldInfo  
             .GetValue(validationContext.ObjectInstance);  
         }  
         comparisonMemberDisplayName = comparisonMember.Name;  
   
         var attribute = GetCustomAttribute(comparisonMember, typeof(DisplayAttribute));  
         if (attribute is DisplayAttribute displayAttribute)  
         {  
           comparisonMemberDisplayName = displayAttribute.Name;  
         }  
         else  
         {  
           attribute = GetCustomAttribute(comparisonMember, typeof(DisplayNameAttribute));  
           if (attribute is DisplayNameAttribute displayNameAttribute)  
           {  
             comparisonMemberDisplayName = displayNameAttribute.DisplayName;  
           }  
         }  
   
         if (comparisonValue != null)  
         {  
           int? comparison = null;  
   
   
           if (value is IComparable comparableValue)  
           {  
             comparison = comparableValue.CompareTo(comparisonValue);  
           }  
           else  
           {  
             throw new InvalidOperationException("Trigger member value must implement IComparable");   
             // TODO replace with localized resource  
           }  
   
           if (comparison <= 0   
             && ComparisonAgainstReference == Operator.GreaterThan  
               || comparison >= 0   
                 && ComparisonAgainstReference == Operator.SmallerThan  
                   || comparison > 0   
                     && ComparisonAgainstReference == Operator.SmallerOrEqualThan  
                       || comparison < 0   
                         && ComparisonAgainstReference == Operator.GreaterOrEqualThan)  
           {  
             var memberList = new List<string>() { { validationContext.MemberName } };  
   
             memberList.Add(comparisonMember.Name);  
   
             var errorMessageString = ErrorMessageString;  
   
             if (errorMessageString == null)  
             {  
               errorMessageString = "'{0}' ({1}) {2} '{3}' ({4})";   
               // TODO replace with localized resource  
             }  
   
             ret = new ValidationResult(string.Format(CultureInfo.CurrentCulture,  
               errorMessageString,  
                 validationContext.DisplayName,  
                   value.ToString(),  
                     ComparisonOperatorText(),  
                       comparisonMemberDisplayName,  
                         comparisonValue.ToString()),  
                           memberList);  
           }  
         }  
   
       }  
       return ret;  
     }  
   }  
 }  

The usage:

The unit tests (in case you want to adjust the code and make sure all is still working well) :

 using System;  
 using System.Collections.Generic;  
 using System.ComponentModel.DataAnnotations;  
 using Microsoft.VisualStudio.TestTools.UnitTesting;  
   
 namespace AnyNamespace  
 {  
   
   [TestClass]  
   public class MustCompareToAttributeTests  
   {  
     #region Mock models  
   
     class MockModelSmallerThan<T1, T2>  
     {  
       [MustCompareTo(nameof(Value2), MustCompareToAttribute.Operator.SmallerThan)]  
       public T1 Value1 { get; set; }  
   
       public T2 Value2 { get; set; }  
     }  
   
     class MockModelSmallerOrEqualThan<T1, T2>  
     {  
       [MustCompareTo(nameof(Value2), MustCompareToAttribute.Operator.SmallerOrEqualThan)]  
       public T1 Value1 { get; set; }  
   
       public T2 Value2 { get; set; }  
     }  
   
     class MockModelGreaterThan<T1, T2>  
     {  
       [MustCompareTo(nameof(Value2), MustCompareToAttribute.Operator.GreaterThan)]  
       public T1 Value1 { get; set; }  
   
       public T2 Value2 { get; set; }  
     }  
   
     class MockModelGreaterOrEqualThan<T1, T2>  
     {  
       [MustCompareTo(nameof(Value2), MustCompareToAttribute.Operator.GreaterOrEqualThan)]  
       public T1 Value1 { get; set; }  
   
       public T2 Value2 { get; set; }  
     }  
   
     class MockModelWrongMemberDeclaration<T1, T2>  
     {  
       [MustCompareTo("Typo", MustCompareToAttribute.Operator.GreaterOrEqualThan)]  
       public T1 Value1 { get; set; }  
   
       public T2 Value2 { get; set; }  
     }  
   
     #endregion  
   
     #region Wrong usage  
   
     [TestMethod]  
     public void NotCompatible()  
     {  
       var model = new MockModelSmallerThan<int, string>()  
       {  
          Value1 = 1,  
          Value2 = "a"  
       };  
       // cannot compare integer with string, ArgumentException expected  
       Assert.ThrowsException<ArgumentException>(() => Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void WrongMemberDeclaration()  
     {  
       var model = new MockModelWrongMemberDeclaration<int, string>()  
       {  
         Value1 = 1,  
         Value2 = "a"  
       };  
       // cannot retrieve value from member since member declaration is wrong, MissingMemberException expected  
       Assert.ThrowsException<MissingMemberException>(() => Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
   
     [TestMethod]  
     public void TriggerMemberNotComparable()  
     {  
       var model = new MockModelSmallerThan<MockModelSmallerThan<int,int>, MockModelSmallerThan<int, int>>()  
       {  
         Value1 = new MockModelSmallerThan<int, int>(),  
         Value2 = new MockModelSmallerThan<int, int>()  
       };  
       // type MockModelSmallerThan<T1, T2> does not implement IComparable  
       Assert.ThrowsException<InvalidOperationException>(() => Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
   
     #endregion  
   
     #region Smaller than  
   
     [TestMethod]  
     public void SmallerThanNullableInt()  
     {  
       var model = new MockModelSmallerThan<int?, int?>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 1;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = 2;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 3;  
       model.Value2 = 3;  
       // Value1 is not smaller than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 5;  
       model.Value2 = 4;  
       // Value1 is not smaller than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 6;  
       model.Value2 = 7;  
       // Value1 is smaller than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void SmallerThanMixedInt()  
     {  
       var model = new MockModelSmallerThan<int, int?>();  
   
       model.Value1 = 100;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 101;  
       model.Value2 = 101;  
       // Value1 is not smaller than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 103;  
       model.Value2 = 102;  
       // Value1 is not smaller than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 104;  
       model.Value2 = 105;  
       // Value1 is smaller than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void SmallerThanStr()  
     {  
       var model = new MockModelSmallerThan<string, string>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "a";  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = "a";  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "b";  
       model.Value2 = "b";  
       // Value1 is not smaller than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "d";  
       model.Value2 = "c";  
       // Value1 is not smaller than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "e";  
       model.Value2 = "f";  
       // Value1 is smaller than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     #endregion  
   
     #region Smaller or equal than  
   
     [TestMethod]  
     public void SmallerOrEqualThanNullableInt()  
     {  
       var model = new MockModelSmallerOrEqualThan<int?, int?>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 8;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = 9;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 10;  
       model.Value2 = 10;  
       // Value1 is not smaller or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 12;  
       model.Value2 = 11;  
       // Value1 is not smaller or equal than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 13;  
       model.Value2 = 14;  
       // Value1 is smaller or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void SmallerOrEqualThanMixedInt()  
     {  
       var model = new MockModelSmallerOrEqualThan<int?, int>();  
   
       model.Value1 = null;  
       model.Value2 = 106;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 107;  
       model.Value2 = 108;  
       // Value1 is not smaller or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 110;  
       model.Value2 = 109;  
       // Value1 is not smaller or equal than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 111;  
       model.Value2 = 112;  
       // Value1 is smaller or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void SmallerOrEqualThanStr()  
     {  
       var model = new MockModelSmallerOrEqualThan<string, string>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "g";  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = "h";  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "i";  
       model.Value2 = "i";  
       // Value1 is not smaller or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "l";  
       model.Value2 = "k";  
       // Value1 is not smaller or equal than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "m";  
       model.Value2 = "n";  
       // Value1 is smaller or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     #endregion  
   
     #region Smaller or equal than  
   
     [TestMethod]  
     public void GreaterThanNullableInt()  
     {  
       var model = new MockModelGreaterThan<int?, int?>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 15;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = 16;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 17;  
       model.Value2 = 17;  
       // Value1 is not greater than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 19;  
       model.Value2 = 18;  
       // Value1 is greater than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 20;  
       model.Value2 = 21;  
       // Value1 is not greater than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void GreaterThanMixedInt()  
     {  
       var model = new MockModelGreaterThan<int, int?>();  
   
       model.Value1 = 113;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 114;  
       model.Value2 = 114;  
       // Value1 is not greater than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 116;  
       model.Value2 = 115;  
       // Value1 is greater than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 117;  
       model.Value2 = 118;  
       // Value1 is not greater than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void GreaterThanStr()  
     {  
       var model = new MockModelGreaterThan<string, string>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "o";  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = "p";  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "q";  
       model.Value2 = "q";  
       // Value1 is not greater than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "s";  
       model.Value2 = "r";  
       // Value1 is greater than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "t";  
       model.Value2 = "u";  
       // Value1 is not greater than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
   
     #endregion  
   
     #region Greater or equal than  
   
     [TestMethod]  
     public void GreaterOrEqualThanNullableInt()  
     {  
       var model = new MockModelGreaterOrEqualThan<int?, int?>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 22;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = 23;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 24;  
       model.Value2 = 24;  
       // Value1 is greater or equal than Value2, so NOT OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 26;  
       model.Value2 = 25;  
       // Value1 is greater or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 27;  
       model.Value2 = 28;  
       // Value1 is not greater or equal than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void GreaterOrEqualThanMixedInt()  
     {  
       var model = new MockModelGreaterOrEqualThan<int, int?>();  
   
       model.Value1 = 119;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 120;  
       model.Value2 = 120;  
       // Value1 is greater or equal than Value2, so NOT OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 122;  
       model.Value2 = 121;  
       // Value1 is greater or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = 123;  
       model.Value2 = 124;  
       // Value1 is not greater or equal than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     [TestMethod]  
     public void GreaterOrEqualThanStr()  
     {  
       var model = new MockModelGreaterOrEqualThan<string, string>();  
   
       model.Value1 = null;  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "v";  
       model.Value2 = null;  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = null;  
       model.Value2 = "w";  
       // null values are not compared, so the validation is OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "x";  
       model.Value2 = "x";  
       // Value1 is greater or equal than Value2, so NOT OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "z";  
       model.Value2 = "y";  
       // Value1 is greater or equal than Value2, so OK  
       Assert.IsTrue(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
   
       model.Value1 = "A";  
       model.Value2 = "B";  
       // Value1 is not greater or equal than Value2, so NOT OK  
       Assert.IsFalse(Validator.TryValidateObject(model, new ValidationContext(model), new List<ValidationResult>(), true));  
     }  
   
     #endregion  
   }  
 }  
   
   

Sorry for the missing c# formatting style - this time I wasn't able to copy the code nicely. Last time used Microsoft Word and it worked quite well, but this time I didn't figured out, what is wrong. May be I should restart my PC, since it's running for a while ...