This article is compatible with the latest version of Silverlight.
Show more books...
This article is Part 1 of the series “The validation story in Silverlight”.
If one feature is important when it comes to building line-of-business applications, it certainly is validation of business rules. At all levels throughout the application, we need to validate that the data that is entered by the users to verify that it meets the requirements. Some basic validation can be done by the database itself, but most (and in many cases, all) data that it trying to make its way to the database should be validated up front in order not to end up with corrupted data within the data store.
If we think of validation in the ASP.NET world, we can identify 2 places where validation is needed. First, we try validating user input using JavaScript on the client-side (perhaps using one of the built-in validation controls). This ensures quick feedback to the user, preventing him from having to wait for a post-back to the server in which we can do server validation of the data. However, we can’t just rely on the fact that JavaScript is enabled (and therefore risk that users can go around our validation rules) so server-side validation is needed as well. The latter of course requires us to post back to the server, after which we can send back the errors to the user, if any, for example using a validation summary.
When doing validation in Silverlight, things are a bit different. Because everything runs on the client and we want to give the user the richest experience on that client, we need to make sure that validation that we build in, adds to that rich experience as well. Immediate feedback where possible is one of the things that comes to mind: we don’t want the user to have to wait for validation to happen on the server. Instead, where possible, we want to display validation messages as soon as the user leaves the field or clicks on a button.
The validation story in Silverlight up until version 4 was somewhat lacking in features. While Silverlight 3 had some options available, they were not really enough for real-world applications. That changed with Silverlight 4, mainly because of the introduction of 2 new interfaces, namely the IDataErrorInfo and the INotifyDataErrorInfo. The first one may sound familiar as it already exists for WinForms validation. The latter will be helpful in situations where we need to perform calls to the server and return asynchronous validation results.
This is a two-part article. The code for this first part article exists of some samples that we are using to explain the features and can be downloaded here.
Overview of the validation options in Silverlight 4
As mentioned in the previous paragraph, Silverlight 3 had limited options to validate user input whereas Silverlight 4 added to this quite a lot. To start, here’s a brief overview of the options we have at our disposal today:
Type
|
Property
|
Throw exceptions in your setters
|
Enable ValidatesOnExceptions
|
IDataErrorInfo
|
Enable ValidatesOnDataErrors
|
INotifyDataErrorInfo
|
Enable ValidatesOnNotifyDataErrors
|
If you want the errors to be passed on in the hierarchy
|
Enable NotifyOnValidationError
|
We’ll start this overview by looking at the “throwing exceptions” option (that is, the Silverlight 3 option).
Silverlight 3 validation options
Validation in Silverlight 3 is limited to working with 2 properties on a data binding expression: ValidatesOnExceptions and NotifyOnValidationErrors.
When we apply ValidatesOnExceptions, when we try to update a value using data binding (that is, a TwoWay binding, since we are pushing data from the target control to the source property), Silverlight will check if any exceptions are being thrown, for example trying to enter a string into a numeric field. If an exception happens, Silverlight’s data binding engine will create ValidationError and add this error to the Validation.Errors collection of the target control (that is the UI element we are applying the binding on). If we want to receive notifications that these exceptions occurred, we need to set NotifyOnValidationErrors to true as well.
Let’s look at the obligatory “Binding to a Person object” person to show this type of validation. The Person class itself has just two string and one integer properties.
public class SimplePerson
{
private string _firstName;
private string _lastName;
private int _age;
public string FirstName
{
get
{
return _firstName;
}
set
{
if (value.Length == 0)
{
throw new Exception("First name length can not be 0");
}
_firstName = value;
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
if (value.Length == 0)
{
throw new Exception("Last name length can not be 0");
}
_lastName = value;
}
}
public int Age
{
get
{
return _age;
}
set
{
_age = value;
}
}
}
Note that the FirstName and LastName throw an error if the value entered by the user is an empty string. The parameter for the Exception is the error message we want to show to the user. The Age property, since it’s of type int, will throw an error if we try to enter a string.
The data binding expressions are now defined with the above mentioned properties, as can be seen in the code below.
<TextBox Name="FirstNameTextBox" Text="{Binding FirstName, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
/>
<TextBox Name="LastNameTextBox" Text="{Binding LastName, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
/>
<TextBox Name="AgeTextBox" VerticalAlignment="Top" Text="{Binding Age, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
/>
This results in Silverlight automatically showing the default behavior for an exception being raised:
This default look is implemented within the control template on most controls within Silverlight. We’ll look later how we can override this and apply our own type of look and feel for this error reporting.
Adding attributes
If we have to write Exceptions ourselves for all validation, this can become quite a cumbersome task. To solve this somewhat, it’s possible to include attributes on the properties. These validation attributes include most common types of validation, including Required fields and StringLength. When applying these, the validation engine will work in the exact same way, as can be seen below. First, the updated Person class.
[Required]
[StringLength(50)]
public string FirstName
{
get
{
return _firstName;
}
set
{
if (_firstName != value)
{
_firstName = value;
}
}
}
[Required]
[StringLength(50)]
public string LastName
{
get
{
return _lastName;
}
set
{
if (_lastName != value)
{
_lastName = value;
}
}
}
[Range(1, 120)]
[Required]
public int Age
{
get
{
return _age;
}
set
{
if (_age != value)
{
_age = value;
}
}
}
If we try our exact same sample again now, we’ll notice that the binding won’t display an error, even though we have applied the attributes. Instead, we need to trigger the validation to happen. We can do this using the ValidationContext.ValidateProperty, passing in the name of the property we want to validate. By applying this in the setters of the properties, we can use it to trigger all attributes to be validated.
[StringLength(50)]
public string FirstName
{
get
{
return _firstName;
}
set
{
if (_firstName != value)
{
Validator.ValidateProperty(value, new ValidationContext(this, null, null)
{ MemberName = "FirstName" });
_firstName = value;
}
}
}
[Required]
[StringLength(50)]
public string LastName
{
get
{
return _lastName;
}
set
{
if (_lastName != value)
{
Validator.ValidateProperty(value, new ValidationContext(this, null, null)
{ MemberName = "LastName" });
_lastName = value;
}
}
}
[Range(1, 120)]
[Required]
public int Age
{
get
{
return _age;
}
set
{
if (_age != value)
{
Validator.ValidateProperty(value, new ValidationContext(this, null, null)
{ MemberName = "Age" });
_age = value;
}
}
}
Custom rules
One attribute deserves some special attention: the CustomAttribute. Using this attribute, we can define custom methods to perform more advanced business rule validation. It accepts two parameters by default: the type that contains the validation methods and the validation method you want to use on the property. Let’s look at an example on the Age property (agreed, the rule can be done using the Range attribute as well, but I hope you get the point). Note that the method returns a ValidationResult. If everything runs fine, we return ValidationResult.Success; otherwise, we create a new ValidationResult, passing in the custom message we want to display. Note also that it’s easy to localize these error messages here to return translated strings for the exceptions.
public class CustomAgeValidator
{
public static ValidationResult ValidateAge(int age)
{
if (age < 120 && age > 0)
{
return ValidationResult.Success;
}
else if(age < 0)
{
return new ValidationResult("You can't have a negative age!");
}
else
{
return new ValidationResult("You are too old!!");
}
}
}
The CustomAttribute can now be used as such:
[Required]
[CustomValidation(typeof(CustomAgeValidator), "ValidateAge")]
public int Age
{
get
{
return _age;
}
set
{
if (_age != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "Age" });
_age = value;
}
}
}
And here we see the custom validator in action, including the custom error message:
Summarizing all errors with the ValidationSummary
Instead of displaying errors only on the fields, a separate control can help us displaying all errors on a central location: the ValidationSummary. This control, which lives in the System.Windows.Controls namespace, will display object and property errors in a list (each item in this list is of type ValidationSummaryItem and can be template).When clicking on an item, the ValidationSummary control will try to apply focus on the control that caused the error, giving the user a better experience when fixing errors in a form. For the control to pick up on the errors, the NotifyOnValidationErrors should be set to true. This causes the binding to raise a notification event for the failing validation.
The code does not need a lot of changes. In fact, we can just add the ValidationSummary and it will work as expected.
<TextBox Name="FirstNameTextBox" Text="{Binding FirstName, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
/>
<TextBox Name="LastNameTextBox" Text="{Binding LastName, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
/>
<TextBox Name="AgeTextBox" VerticalAlignment="Top" Text="{Binding Age, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
/>
<sdk:ValidationSummary Grid.Row="3" Grid.ColumnSpan="2"></sdk:ValidationSummary>
Summary
With that, we have seen the basic validation capabilities of Silverlight. As mentioned, these are where Silverlight 3 leaves of and honestly, we need more. All what is explained here of course works in the latest version as well. On top of that, Silverlight 4 added some nice features. In the second part, we’ll look at some more advanced concepts, such as the IDataErrorInfo.