Wednesday, April 13, 2011

UI Validation with Silverlight 4

I am currently designing components of the application architecture for a LOB web based MVVM Silverlight 4 application that will contain many forms so obviously I will need a solid, simple way to do UI validation. I want to leverage out-of-the-box Silverlight validation. And the validation will need to run business rule logic in another tier.

There are a few ways to go about this with Silverlight 4:

  • Throw exceptions in your property setters
  • Implement IDataErrorInfo
  • Implement INotifyDataErrorInfo

With Silverlight 4 came the new interface INotifyDataErrorInfo. So why another interface?

Well, INotifyDataErrorInfo newly supports multiple validation errors per property.

Ok, let's see it in action.

We create a base view model class which implements INotifyDataErrorInfo and we might as well implement INotifyPropertyChanged too.

public class ViewModelBase : INotifyDataErrorInfo, INotifyPropertyChanged

The idea is that view models register methods that validate a particular property and when a property is set, ViewModelBase will know to fire validation methods registered for that property. It then collects up validation errors and presents them to the UI.

As an example I've created a simple PersonViewModel that has an age and a first name.

So first we derive our class from ViewModelBase.

public class PersonViewModel : ViewModelBase

Then in the constructor or somewhere during initialisation we register our validation methods against our properties.

In this example I've created one validation method for Age and two methods for First Name.

protected void ValidateAge()

{

if (Age < 18 || Age > 55)

throw new Exception("Age must be between 18 and 55");

}

protected void ValidateFirstNameLength()

{

if(FirstName.Length < 3)

throw new Exception("First name must be at least 3 characters long.");

}

protected void ValidateFirstNameCharacters()

{

foreach (char c in FirstName.ToCharArray())

if (Char.IsDigit(c))

throw new Exception("First name cannot contain digits.");

}

To register our validation methods we call ViewModelBase.RegisterForValidation() passing in the property and the validation method.

public PersonViewModel()

{

RegisterForValidation("Age", ValidateAge);

RegisterForValidation("FirstName", ValidateFirstNameLength);

RegisterForValidation("FirstName", ValidateFirstNameCharacters);

}

protected void RegisterForValidation(string propertyName, Action validationMethod)

{

if (!_registeredValidationMethods.Keys.Contains<string>(propertyName))

_registeredValidationMethods.Add(propertyName, new List<Action>() { validationMethod });

else

if (!_registeredValidationMethods[propertyName].Contains(validationMethod))

_registeredValidationMethods[propertyName].Add(validationMethod);

}

In the setters for our properties we call the SetValue() method on ViewModelBase.

private int _age = 0;

public int Age

{

get

{

return _age;

}

set

{

SetValue<int>("Age", value, ref _age);

}

}

SetValue() performs validation, sets the value of the underlying field and notifies the UI the property has changed.

public void SetValue(string propertyName, T newValue, ref T underlyingField)

{

//set value

underlyingField = newValue;

//validate

ValidateProperty(propertyName);

//notify

RaisePropertyChangedEvent(propertyName);

}

So let's see it in action. Setting the Age to 4 and removing focus from the age text box causes our validation to fire and fail:

Correcting the age satisfies the validation logic and the call out disappears:

Changing the name to less than 3 chars works as expected:

And so does including digits in the name:

What about a scenario where the first name fails both validation rules?

In this example, the viewmodel knows about both failed rules but it only displays one callout.

Silverlight doesnt know how to handle multiple errors per property at the UI with this implementation.

we need to add some kind of validation summary control that displays all errors.

With one simple line of code we can add a validation summary control in XAML and it all works out of the box.

xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"

<sdk:ValidationSummary>sdk:ValidationSummary>

And here it is in action: