Validation in Silverlight
- by Timmy Kokke
Getting started with the basics Validation in Silverlight can get very complex pretty easy. The DataGrid control is the only control that does data validation automatically, but often you want to validate your own entry form. Values a user may enter in this form can be restricted by the customer and have to fit an exact fit to a list of requirements or you just want to prevent problems when saving the data to the database. Showing a message to the user when a value is entered is pretty straight forward as I’ll show you in the following example. This (default) Silverlight textbox is data-bound to a simple data class. It has to be bound in “Two-way” mode to be sure the source value is updated when the target value changes. The INotifyPropertyChanged interface must be implemented by the data class to get the notification system to work. When the property changes a simple check is performed and when it doesn’t match some criteria an ValidationException is thrown. The ValidatesOnExceptions binding attribute is set to True to tell the textbox it should handle the thrown ValidationException. Let’s have a look at some code now. The xaml should contain something like below. The most important part is inside the binding. In this case the Text property is bound to the “Name” property in TwoWay mode. It is also told to validate on exceptions. This property is false by default. <StackPanel Orientation="Horizontal">
<TextBox Width="150"
x:Name="Name"
Text="{Binding Path=Name,
Mode=TwoWay,
ValidatesOnExceptions=True}"/>
<TextBlock Text="Name"/>
</StackPanel>
The data class in this first example is a very simplified person class with only one property: string Name. The INotifyPropertyChanged interface is implemented and the PropertyChanged event is fired when the Name property changes. When the property changes a check is performed to see if the new string is null or empty. If this is the case a ValidationException is thrown explaining that the entered value is invalid.
public class PersonData:INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
if(string.IsNullOrEmpty(value))
throw new ValidationException("Name is required");
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged=delegate { };
}
The last thing that has to be done is letting binding an instance of the PersonData class to the DataContext of the control. This is done in the code behind file.
public partial class Demo1 : UserControl
{
public Demo1()
{
InitializeComponent();
this.DataContext = new PersonData() {Name = "Johnny Walker"};
}
}
Error Summary
In many cases you would have more than one entry control. A summary of errors would be nice in such case. With a few changes to the xaml an error summary, like below, can be added.
First, add a namespace to the xaml so the control can be used. Add the following line to the header of the .xaml file.
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
Next, add the control to the layout. To get the result as in the image showed earlier, add the control right above the StackPanel from the first example. It’s got a small margin to separate it from the textbox a little.
<Controls:ValidationSummary Margin="8"/>
The ValidationSummary control has to be notified that an ValidationException occurred. This can be done with a small change to the xaml too. Add the NotifyOnValidationError to the binding expression. By default this value is set to false, so nothing would be notified. Set the property to true to get it to work.
<TextBox Width="150"
x:Name="Name"
Text="{Binding Name,
Mode=TwoWay,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}"/>
Data annotation
Validating data in the setter is one option, but not my personal favorite. It’s the easiest way if you have a single required value you want to check, but often you want to validate more. Besides, I don’t consider it best practice to write logic in setters. The way used by frameworks like WCF Ria Services is the use of attributes on the properties. Instead of throwing exceptions you have to call the static method ValidateProperty on the Validator class. This call stays always the same for a particular property, not even when you change the attributes on the property.
To mark a property “Required” you can use the RequiredAttribute. This is what the Name property is going to look like:
[Required]
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null){ MemberName = "Name" });
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
The ValidateProperty method takes the new value for the property and an instance of ValidationContext. The properties passed to the constructor of the ValidationContextclass are very straight forward. This part is the same every time. The only thing that changes is the MemberName property of the ValidationContext. Property has to hold the name of the property you want to validate. It’s the same value you provide the PropertyChangedEventArgs with.
The System.ComponentModel.DataAnnotation contains eight different validation attributes including a base class to create your own.
They are:
RequiredAttribute
Specifies that a value must be provided.
RangeAttribute
The provide value must fall in the specified range.
RegularExpressionAttribute
Validates is the value matches the regular expression.
StringLengthAttribute
Checks if the number of characters in a string falls between a minimum and maximum amount.
CustomValidationAttribute
Use a custom method to validate the value.
DataTypeAttribute
Specify a data type using an enum or a custom data type.
EnumDataTypeAttribute
Makes sure the value is found in a enum.
ValidationAttribute
A base class for custom validation attributes
All of these will ensure that an validation exception is thrown, except the DataTypeAttribute. This attribute is used to provide some additional information about the property. You can use this information in your own code.
[Required]
[Range(0,125,ErrorMessage = "Value is not a valid age")]
public int Age
{
It’s no problem to stack different validation attributes together. For example, when an Age is required and must fall in the range from 0 to 125:
[Required, StringLength(255,MinimumLength = 3)]
public string Name
{
Or in one row like this, for a required Name with at least 3 characters and a maximum of 255:
Delayed validation
Having properties marked as required can be very useful. The only downside to the technique described earlier is that you have to change the value in order to get it validated. What if you start out with empty an empty entry form? All fields are empty and thus won’t be validated. With this small trick you can validate at the moment the user click the submit button.
<TextBox Width="150"
x:Name="NameField"
Text="{Binding Name,
Mode=TwoWay,
ValidatesOnExceptions=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=Explicit}"/>
By default, when a TwoWay bound control looses focus the value is updated. When you added validation like I’ve shown you earlier, the value is validated. To overcome this, you have to tell the binding update explicitly by setting the UpdateSourceTrigger binding property to Explicit:
private void SubmitButtonClick(object sender, RoutedEventArgs e)
{
NameField.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
This way, the binding is in two direction but the source is only updated, thus validated, when you tell it to. In the code behind you have to call the UpdateSource method on the binding expression, which you can get from the TextBox.
Conclusion
Data validation is something you’ll probably want on almost every entry form. I always thought it was hard to do, but it wasn’t. If you can throw an exception you can do validation.
If you want to know anything more in depth about something I talked about in this article let me know. I might write an entire post to that.