Friday, July 16, 2010

WPF Validation - Using Validation Rules

Who would not agree if we say that WPF binding is greatest across all technologies. But equally important is the validation of data entered by the user. Developers always get confused how they should be validating the data. In the series of discussion, including this one and following discussions, we will be discussing about different validations options in WPF. We will also discuss the specific situations in which any particular technique makes more sense.

1. Validation Rules:
This is the most simplest of all validation options available in WPF. Generally these rules are defined for individual fields.

Let us create a Window, named Window1.
<Window x:Class="wpf_validation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf_validation"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Height="25" Margin="6,14,26,0" Name="textBox1" VerticalAlignment="Top" >
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:SpecialCharactersRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
</Grid>
</Window>

The code behind of this Window is as follows:
public partial class Window1 : Window
{
Window1ViewModel _vm;
public Window1()
{
InitializeComponent();
_vm = new Window1ViewModel();
this.DataContext = _vm;
}
}

Here we have created an instance of View Model (Window1ViewModel) and assigned it as data source of the window. We have data bounded the single text box on the window with the Name property of the data context. We also have specified SpecialCharacterRule as the validation rule for this binding. The View Model is as follows:
public class Window1ViewModel : DependencyObject
{
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(Window1ViewModel));
}

The validaton rule (SpecialCharacterRule) is presented as below:
public class SpecialCharactersRule : System.Windows.Controls.ValidationRule
{
public override System.Windows.Controls.ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
System.Windows.Controls.ValidationResult validationResult;
string val = (string)value;

//if field is null / empty or contains some special characters, fail validation
if (!string.IsNullOrEmpty(val) &&
System.Text.RegularExpressions.Regex.IsMatch(val, "[/!@#?/}[}{]"))

validationResult = new ValidationResult(false, "The value contains invalid characters");
else
validationResult = new ValidationResult(true, null);

return validationResult;
}
}

This is the most simple case of validation when MVVM is used. We have separated our code in three different parts. One part is the Window itself (Window1) which not only binds its properties to the View Model (Window1ViewModel) but also defines validation rules to be used for its different fields, like SpecialCharacterRule for the text box. If the validation rule evaluates to false, the validation is considered failed. The fields is shown with the default validation adorner template.



You can notice that as soon as we enter "/" in the text box, the validation fails and the default adorner is applied to the text box. This is basically because we have specified UpdateSourceTrigger="PropertyChanged", which not only keeps copying the entered text to the data context object but also keeps on applying validation logic with each change. Basically If we need to change the validation logic for this text box, we just need to modify the validation rule.

Note:
Defining validation rules provide reusability if same validation rules are applied for more than one fields within the same form or application or even across different applications. It also provides separation of concern because a view has no knowledge how bound data would be validated. It results in light weight views as you might have noticed the code behind of the view (we just provided the context in the constructor. It provides ease of maintenance (both corrective and adaptive) because when you need to change the business rules for changing business requirements then you just need to update these validation rules.

No comments: