Andrej Tozon's blog

In the Attic

NAVIGATION - SEARCH

Silverlight LOB: Validation (#2 – Annotations and shared classes)

In my previous post, I wrote about “the first line of defense” against inputting invalid data in Silverlight applications (or any kind of application, for that matter) – preventing the user from entering invalid data through some an input form. Input form fields are commonly directly bound to the underlying object’s properties – either directly or through a of value converter – in both cases the entered value gets assigned to the bound object’s property; and this is exactly the place where we would want to put our second line of defense.

Silverlight 3 supports data validation. The majority of controls now feature new visual states for presenting themselves whenever they are associated with invalid data. Putting a control into the invalid state is as easy as throwing an exception in its bound property’s setter code:

public DateTime BirthDate
{
    get { return birthDate; }
    set
    {
        if (birthDate == value)
        {
            return;
        }

        if (birthDate > DateTime.Today)
        {
            throw new ArgumentException("The birth date is invalid.");
        }

        birthDate = value;
        OnPropertyChanged("BirthDate");
    }
}

When the property is set to an invalid date, the exception gets thrown. This exception is then caught by the data binding engine, which in turn triggers the BindingValidationError event, prompting the control to go to invalid state:

 image

This kind of notification can be enabled by setting binding’s ValidatesOnExceptions and NotifyOnValidationError properties to true.

System.ComponentModel.DataAnnotation

There is another way to perform validation over entity’s properties:  by using validators from the System.ComponentModel.DataAnnotation assembly. What you do is put an appropriate attribute above the property definition, describing the kind of validation to performed over property value.

[Required(ErrorMessage = "The date of birth is required.")]
public DateTime BirthDate
{
    get { return birthDate; }
    set
    {
        if (birthDate == value)
        {
            return;
        }

        birthDate = value;
        OnPropertyChanged("BirthDate");
    }
}

You can even write your own validation attributes by deriving from the ValidationAttribute class and overriding the validation method. Here’s an example to match my first method of validation (throwing an exception in property setter):

public class BirthDateValidationAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
ValidationContext validationContext) { if (value == null) { return ValidationResult.Success; } DateTime date = (DateTime)value; if (date > DateTime.Today) { return new ValidationResult("The birth date is invalid."); } return ValidationResult.Success; } }

Let’s put this to work. Say we’re using shared entity classes in our project; a kind of a real world scenario. When projects are set up as described in my previous blog entry, we immediately bump into a problem – decorating the Person’s BirthDate property with the new BirthDateValidationAttribute results with the compilation error: it’s just that full .NET framework’s ValidationAttribute implementation differs form how Silverlight’s version is implemented and therefore above BirthDateValidationAttribute can’t be shared between both entity projects:

Client side: protected abstract ValidationResult IsValid(object value, ValidationContext validationContext)
Server side: public abstract bool IsValid(object value)

To work around this, we need to create a dummy validator attribute class in the server side entity project, having the same name as its client counterpart, except always returning true as the validation result:

public class BirthDateValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        return true;
    }
}

Validation

Instead of throwing an exception from the property trigger as in the previous example, we can now simply call the validation method, which would validate against every ValidationAttribute defined for the property.

Modified Property definition (Validate() method is called instead of throwing an exception):

[DataMember]
[BirthDateValidation]
public DateTime BirthDate
{
    get { return birthDate; }
    set
    {
        if (birthDate == value)
        {
            return;
        }

        Validate("BirthDate", value);
        birthDate = value;
        OnPropertyChanged("BirthDate");
    }
}

Because we only want to validate properties on the client, we need to implement the Validate() method just on the client side. But since the Person class is shared, we really have to include this method on both sides, except we would implement it differently.

In the end, Validate() method on the client is defined as:

private void Validate(string propertyName, object value)
{
    Validator.ValidateProperty(value, new ValidationContext(this, null, null)
    {
        MemberName = propertyName,
    });
}

… while the server side method can be left empty:

private void Validate(string propertyName, object value)
{
}

Single base class for entities?

To avoid having duplicate validation methods for all entities, it’s best to put it in a single base page, where you would put all the common entity code (like INotifyPropertyChanged etc.). In this case, partial methods are going to prove themselves very useful:

Main base class part:

partial void ValidateProperty(string propertyName, object value);
protected void Validate(string propertyName, object value)
{
    ValidateProperty(propertyName, value);
}

Client side base class part:

public partial class EntityBase
{
    partial void ValidateProperty(string propertyName, object value)
    {
        Validator.ValidateProperty(value, new ValidationContext(this, null, null)
        {
            MemberName = propertyName,
        });
    }
}

We can just leave the server side base class part out and forget about it. The best thing with with partial methods is that you don’t have to provide any implementation if you don’t want to – in which case the method simply won’t be called.

Source code for this project is available: