Data Validation in Silverlight DataGrid

One of the features added to datagrid in Silverlight 3 Beta is Validation. This can be done

- Once we modify the content of a particular cell (cell validation)

- Once we modify the content of a particular datagrid row (would like to commit changes to row) (Row Validation)

In this blog, I will try to give an intro both cell validation and row validation by using examples.

 

Cell Validation

Cell Validation can be done in two ways

- Raising ValidationException in the property setter

- Using ValidationAttributes and calling ValidateProperty in property setter

Cell Validation by throwing ValidationException in the property setter

In order to understand this concept, let’s consider that our datagrid is populated with a collection of Employee (with the following properties) objects on which we want to do Cell Validation

        public String firstName;

        public String lastName;

        public int age;

        public String phone;

        public String city;

        public String state;

The validations that we want to do on these properties are

- Firstname, lastname, phone, city, state should not be null or empty

- Age has to be in the range 18 to 50

We define validation rules for these properties (in the setter properties) in the following way. The ValidationException (belonging to System.ComponentModel.DataAnnotations ) thrown is displayed as Cell Validation Error.

        public string LastName

        {

            get { return lastName; }

            set

            {

                if (value != lastName)

                {

                    if (value == null || value == string.Empty)

                    {

                        throw new ValidationException("Last Name cannot be empty");

                    }

                    lastName = value;

                    NotifyPropertyChanged("LastName");

                }

            }

        }

cellvalidation1

        public int Age

        {

            get { return age; }

            set

            {

                if (value != age)

                {

                    if (value < 18 || value > 50 )

                    {

           throw new ValidationException("Age should be between 18 and 50");

                    }

                    age = value;

                    NotifyPropertyChanged("Age");

                }

            }

        }

 cellvalidation2

Using Validation Attributes and calling ValidateProperty in property setter

The properties on which we want to do validation are decorated by validation attributes and we call the validationobject.validateproperty method in the setter of the property. This tells the validator to verify on the decorated validation attribute when validateProperty is called.

In the following code snippet, the Required, Range and RegularExpression validation attributes are used. The image below them shows the validation error when a validation rule is not met

        [Required]

        public string FirstName

        {

            get { return firstName; }

            set

            {

               if (value != firstName)

                {

                    Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "FirstName" });

                    firstName = value;

                    NotifyPropertyChanged("FirstName");

                }

            }

        }

cellvalidation3

 

        [Required]

        [Range(18,50)]

        public int Age

        {

            get { return age; }

            set

            {

                if (value != age)

                {

                    Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Age" });

                    age = value;

                    NotifyPropertyChanged("Age");

                }

            }

        }

cellvalidation4

 

        [Required]

        [RegularExpression("\\d{3}[-]\\d{3}[-]\\d{4}")] // (e.g - "999-999-1234")

        public string Phone

        {

            get { return phone; }

            set

            {

                if (value != phone)

                {

                    Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Phone" });

                    phone = value;

                    NotifyPropertyChanged("Phone");

                }

            }

        }

cellvalidation5 

Row Validation

In addition to doing validation when we edit a particular cell, we can perform validation when we commit a particular row or entity in the datagrid. In order to do this,we decorate our BusinessObject class with the CustomValidation attribute that specifies the Validation class and the method that are used for validation. The row validation errors are shown at the bottom of the data grid in a error listbox. Clicking on an error in the listbox focuses on the cell that that has validation error. Resolving the validation error dynamically removes the error from the listbox.

In addition to specifying the CustomValidation attribute on the business object, we can also use the validation attributes on the Properties to show invalid entries as Row Validation errors.

        [Required]

        public string FirstName

        {

            get { return firstName; }

            set

            {

                if (value != firstName)

                {

                    firstName = value;

                    NotifyPropertyChanged("FirstName");

                }

            }

        }

I will use the Employee class with the following properties as an example. Note that I also show the decorated validation attributes on the properties of Employee just for demo purpose (In actuality the property definition looks similar to the above).

        [Required]

        public string FirstName

        [Required]

        public string LastName

        [Required]

        public DateTime? BirthDate

        [Required]

        [Range(18,50)]

        public int? Age

        [Required]

        public String City

        [Required]

        public String State

        [Required]

        [RegularExpression("\\d{3}[-]\\d{3}[-]\\d{4}")] // (e.g - "999-999-1234")

        public string Phone

 

Please note that we are not explicity calling the Validator.ValidateObject method or throwing ValidationExceptions in the setter for invalid values (Doing so will show cell validation errors).

The business object Employee class is decorated with the following customvalidation attributes

    [CustomValidation(typeof(EmployeeValidator), "IsValidBirthDay")]

    [CustomValidation(typeof(EmployeeValidator), "IsValidBirthDayAge")]

    public class Employee : INotifyPropertyChanged

The EmployeeValidator Class is static and we define validation methods inside it. The class looks like below for this particular example.

    public static class EmployeeValidator

    {

     public static bool IsValidBirthDay(object employeeObject, ValidationContext context, out ValidationResult validationResult)

        {

            validationResult = null;

            Employee employee = employeeObject as Employee;

            DateTime date = employee.BirthDate.Value;

            int dateComparison = date.CompareTo(DateTime.Today);

            if (dateComparison > 1)

            {

                List<string> properties = new List<string>() { "BirthDate" };

                validationResult = new ValidationResult("Birthday cannot be further than today!", properties);

            }

            return !(dateComparison > 1);

        }

        public static bool IsValidBirthDayAge(object employeeObject, ValidationContext context, out ValidationResult validationResult)

        {

            validationResult = null;

            Employee employee = employeeObject as Employee;

            DateTime date = employee.BirthDate.Value;

            int age = DateTime.Today.Year - date.Year;

            if (age != employee.Age)

            {

                List<string> properties = new List<string>() { "Age", "BirthDate" };

                validationResult = new ValidationResult("Age does not match with Birthday! Your age should be " + age, properties);

            }

            return (age == employee.Age);

        }

    }

If you observe the IsValidBirthDay validation method above, this checks if the propertry “Birthdate” is not further from today. In the following code snippet

List<string> properties = new List<string>() { "BirthDate" };

                validationResult = new ValidationResult("Birthday cannot be further than today!", properties);

 

rowvalidation1

we tell the validator that in this validationmethod, we are checking the property BirthDate only. We can check more than one property and associate them with a validation error. This is done for example in the IsValidBirthDayAge where the (Age,BirthDate) properties are checked for which the code which does this looks like below. When we click on the validation error in the listbox, we toggle between the properties (Age, BirthDate).

List<string> properties = new List<string>() { "Age", "BirthDate" };

validationResult = new ValidationResult("Age does not match with Birthday! Your age should be " + age, properties);

rowvalidation2

As you can see, developers can use these validation features that datagrid supports to develop compeling apps.