Nullable Min/Max Validation Rule

Nullable Min/Max Validation Rule

Old forum URL: forums.lhotka.net/forums/t/5134.aspx


bt1 posted on Tuesday, July 22, 2008

I would like to get some advise on the validation routine specifically if I am using generics correctly I had a problem calling AddRule<> with this.  I originally wanted to pass in the property type to the second type parameter of the RuleHandler (because I figured the rule handler would like to know what type it will be working on) but received an error stating that the parameters didn’t match.  It looks like it needs to inherit from RuleArgs.   So you will see some lines commented out that tried to use the second type parameter for that purpose.

 

public delegate bool RuleHandler<T, R>(T target, R e) where R : RuleArgs;

 

I would like my business objects to allow Nullable types within the object.  However, I would also like to enforce Min/Max values as well (when the property has a value) and null ability based on the database column.  So I set up my property like this:

 

private static PropertyInfo<DateTime?> EndDateProperty = RegisterProperty<DateTime?>(typeof(BusinessObject), new PropertyInfo<DateTime?>(" EndDate"));

        public DateTime? EndDate

        {

            get { return GetProperty<DateTime?>( EndDateProperty); }

            set { SetProperty<DateTime?>( EndDateProperty, value); }

        }

The example above shows an “End” Date that should remain null in the DB until the BusinessObject is complete (ie allow save but populate "End" on successive edits).  However, the “End” Date should have Min and Max values that are constrained by business logic not C#/.NET type min/max values (in this case I will be using SQL Server min max values).

 

public static DateTime DatabaseMinDate = new DateTime(1753, 1, 1, 0, 0, 0);

public static DateTime DatabaseMaxDate = new DateTime(9999, 12, 31, 23, 59, 59);

 

I would like to have the Null checks done via Validation.  That way I can code generate the validation section and add Validation Rules that prevent Nulls for columns that have NOT NULL specified.

This way I can create a new object with all the properties set to null until it is saved.  We will be using Infragistics controls that handle displaying null values so UI shouldn’t be an Issue.  So I created the following validation rule with the assumption that I would be validating built in types such as short, int, long, DateTime, etc ..

 

        /// <summary>

        /// Rule to enforce Min and Max values. MinValue and MaxValue are optional

        /// </summary>

        /// <typeparam name="TTarget">Type of object property is a member of</typeparam>

        /// <typeparam name="R">Type inheriting from DecoratedRuleArgs</typeparam>

        /// <param name="target">Object containing value to validate.</param>

        /// <param name="e">MinMaxValueRuleArgs variable specifying the

        /// name of the property to validate, along with the MinValue,MaxValue,

        /// Format,AllowNull values.</param>

        /// <returns>False if the rule is broken, True if not broken</returns>

//public static bool MinMaxValueNullable<TTarget, TPropertyType>(TTarget target, DecoratedRuleArgs e)

public static bool MinMaxValueNullable<TTarget, R>(TTarget target, R e) where R : Csla.Validation.DecoratedRuleArgs

        {

            //Initialize return value, we will return out of the function if this validation fails(false)

            bool returnValue = true;

 

            //Get value from property

    //TPropertyType value = (TPropertyType)Utilities.CallByName(target, e.PropertyName, CallType.Get);

            var value = Utilities.CallByName(target, e.PropertyName, CallType.Get);

 

            //If value does not have a value (==null) then it can't meet a min / max validation and it fails validation

            if (value == null)

            {

                bool AllowNull = (bool)e["AllowNull"];

                if (!AllowNull)

                {

                    //TODO Pull Description from Resource file 0 Name of Property

                    e.Description = string.Format("Value of property {0} can not be null", e.PropertyName);

                    return false;

                }

                else

                {

                    return true;

                }

 

            }

 

            //Check if TPropertyType supports Comparison

            if (!(value is IComparable))

            {

                throw new ApplicationException(string.Format("Type {0} is not IComparable", value.GetType().Name));

            }

 

            //Get the Min/Max values from DecoratedRuleArgs

    //TPropertyType min = (TPropertyType)e["MinValue"];

            var min = e["MinValue"];

    //TPropertyType max = (TPropertyType)e["MaxValue"];

            var max = e["MaxValue"];

 

 

            //Get display fomat to use for description (if provide)

            string format = (string)e["Format"];

            if (!string.IsNullOrEmpty(format))

            {

                format = string.Format("{{0:{0}}}", format); //ex {0:D}

            }

 

 

            //If Provided min has a value then perform the comparison

            if (min != null)

            {

                //Less than zero | This object is less than the other parameter.

                IComparable valueComparable = (IComparable)value;

                if (valueComparable.CompareTo(min) < 0)

                {

                    string valueString;

                    string minString;

 

                    if (!string.IsNullOrEmpty(format))

                    {

                        valueString = string.Format(format, value);

                        minString = string.Format(format, min);

                    }

                    else

                    {

                        valueString = string.Format("{0}", value);

                        minString = string.Format("{0}", min);

                    }

                    //TODO Pull Description from Resource file 0 Property Name, 1 Value, 2 Min value

                    e.Description += string.Format("Value of {0}({1}) is less than {2}. ", e.PropertyName, valueString, minString);

                    returnValue = false;

                }

            }

 

            if (max != null)

            {

                //Greater than zero |  This object is greater than other

                IComparable valueComparable = (IComparable)value;

                if (valueComparable.CompareTo(max) > 0)

                {

                    string valueString;

                    string maxString;

 

                    if (!string.IsNullOrEmpty(format))

                    {

                        valueString = string.Format(format, value);

                        maxString = string.Format(format, max);

                    }

                    else

                    {

                        valueString = string.Format("{0}", value);

                        maxString = string.Format("{0}", max);

                    }

                    //TODO Pull Description from Resource file 0 Property Name, 1 Value, 2 Max value

                    e.Description += string.Format("Value of {0}({1}) is greater than {2}. ", e.PropertyName, valueString, maxString);

                    returnValue = false;

                }

            }

            return returnValue;

        }

 

 

I added the following to the protected override void AddBusinessRules() function.

 

ValidationRules.AddRule<BusinessObject, MinMaxValueRuleArgs<DateTime?>>(CommonValidationRules.MinMaxValueNullable<BusinessObject, DecoratedRuleArgs>, new MinMaxValueRuleArgs<DateTime?>(EndDateProperty, DatabaseMinDate, DatabaseMaxDate, DateFormat,true));

 

 

This is the RuleArgs that goes with the Rule:

 

    public class MinMaxValueRuleArgs<T> : DecoratedRuleArgs

    {

        public T MinValue

        {

            get { return (T)this["MinValue"]; }

        }

 

        public T MaxValue

        {

            get { return (T)this["MaxValue"]; }

        }

 

        public bool AllowNull

        {

            get { return (bool)this["AllowNull"]; }

        }

 

        public MinMaxValueRuleArgs(string propertyName, T minValue, bool AllowNull)

            : base(propertyName)

        {

            this["MinValue"] = minValue;

            this["Format"] = string.Empty;

            this["ValueType"] = typeof(T).FullName;

            this["AllowNull"] = AllowNull;

        }

 

 

        public MinMaxValueRuleArgs(Csla.Core.IPropertyInfo propertyInfo, T minValue, T maxValue, bool AllowNull)

            : base(propertyInfo)

        {

            this["MinValue"] = minValue;

            this["MaxValue"] = maxValue;

            this["Format"] = string.Empty;

            this["ValueType"] = typeof(T).FullName;

            this["AllowNull"] = AllowNull;

        }

 

        public MinMaxValueRuleArgs(

          string propertyName, string friendlyName, T minValue, T maxValue, bool AllowNull)

            : base(propertyName, friendlyName)

        {

            this["MinValue"] = minValue;

            this["MaxValue"] = maxValue;

            this["Format"] = string.Empty;

            this["ValueType"] = typeof(T).FullName;

            this["AllowNull"] = AllowNull;

        }

 

        public MinMaxValueRuleArgs(string propertyName, T minValue, T maxValue, string format, bool AllowNull)

            : base(propertyName)

        {

            this["MinValue"] = minValue;

            this["MaxValue"] = maxValue;

            this["Format"] = format;

            this["ValueType"] = typeof(T).FullName;

            this["AllowNull"] = AllowNull;

        }

 

        public MinMaxValueRuleArgs(Csla.Core.IPropertyInfo propertyInfo, T minValue, T maxValue, string format, bool AllowNull)

            : base(propertyInfo)

        {

            this["MinValue"] = minValue;

            this["MaxValue"] = maxValue;

            this["Format"] = format;

            this["ValueType"] = typeof(T).FullName;

            this["AllowNull"] = AllowNull;

        }

 

        public MinMaxValueRuleArgs(

          string propertyName, string friendlyName, T minValue, T maxValue, string format, bool AllowNull)

            : base(propertyName, friendlyName)

        {

            this["MinValue"] = minValue;

            this["MaxValue"] = maxValue;

            this["Format"] = format;

            this["ValueType"] = typeof(T).FullName;

            this["AllowNull"] = AllowNull;

        }

    }

 

 

I also altered the SafeDataReader to SafeDataReaderNullable that basically did the same as before except returned null when null.  I know some of you will just say don’t use the SafeDataRead but I did get errors when calling GetXXX on the SqlDataReader when the column was null.  I found an article on the net that explains it more completely (http://techembassy.blogspot.com/2006/08/nullable-types-and-datareader.html) .

 

 

       public int GetInt32(string name)

        {

            return GetInt32(_dataReader.GetOrdinal(name));

        }

 

        public int? GetInt32Nullable(string name)

        {

            return GetInt32Nullable(_dataReader.GetOrdinal(name));

        }

 

        public virtual int GetInt32(int i)

        {

            if (_dataReader.IsDBNull(i))

                return 0;

            else

                return _dataReader.GetInt32(i);

        }

 

        public virtual int? GetInt32Nullable(int i)

        {

            if (_dataReader.IsDBNull(i))

                return null;

            else

                return _dataReader.GetInt32(i);

        }

 

 

rsbaker0 replied on Tuesday, July 22, 2008

This is basically what we do (except that we allow null values):

The rule args are hijacked from CSLA... :)

 

 

        public static bool NullableIntegerMaxValue<T>(object target, RuleArgs e) where T : struct
        {
            e.Severity = RuleSeverity.Error;
            int max = ((CommonRules.IntegerMaxValueRuleArgs)e).MaxValue;
            Nullable<T> value = (Nullable<T>)Utilities.CallByName(target, e.PropertyName, CallType.Get);

            if (value.HasValue && Convert.ToInt32(value.Value) > max)
            {
                e.Description = String.Format("{0} must be less than or equal to {1}",
                  RuleArgs.GetPropertyName(e), max.ToString());
                return false;
            }
            return true;
        }

Sample use:

 

ValidationRules.AddRule(CustomRules.NullableIntegerMaxValue<int>, new Csla.Validation.CommonRules.IntegerMaxValueRuleArgs("HostPortNumber", 65535));

 

bt1 replied on Tuesday, July 22, 2008

Thanks that helped me think through the generics by looking at it with the AddRule() (non generic) vs AddRule<>() (generic) method. I changed the validation rule to include the property type as the second type parameter.

public static bool MinMaxValueNullable<TTarget, TPropertyType>(TTarget target, DecoratedRuleArgs e)

Which made the call look like this. (that is one long line of code)

ValidationRules.AddRule<BusinessObject, MinMaxValueRuleArgs<DateTime?>>(CommonValidationRules.MinMaxValueNullable<BusinessObject, DateTime?>, new MinMaxValueRuleArgs<DateTime?>(EndDateProperty, DatabaseMinDate, DatabaseMaxDate, DateFormat, true));

I also removed the var and did a type cast based on the Generic type:

TPropertyType min = (TPropertyType)e["MinValue"];

TPropertyType max = (TPropertyType)e["MaxValue"];

//var min = e["MinValue"];

//var max = e["MaxValue"];

Copyright (c) Marimer LLC