Sunday, July 18, 2010

WPF Validation - Using Validation Application Block

As most of us like to take advantage of Enterprise library for many of required cross cutting concerns, we wish to use them with every technology we use to develop our softwares. One of the most used application block in Enterprise library is Validation Application Block. The great feature of this application block is that we can specify validation logic in different places including code, attributes and configuration files. We love it!!!

The issue was that this application block was not providing any built-in support for Windows Presentation Foundation. As we know for validating data in a WPF form, we generally use one of the specializations of ValidationRule class. We needed support for hooking up the validation logic specified in Validation Application Block with these Validation rules. It might be a news for you that the updated Validation Block released with Enterprise library 5.0 supports this. Zindabad!!!

Enterprise library 5.0 can be downloaded from this location:
http://www.microsoft.com/downloads/details.aspx?FamilyId=bcb166f7-dd16-448b-a152-9845760d9b4c&displaylang=en


After downloading install it. You will have to reference the assemblies as specified below:



In this post, we will specify our validation logic in the following places individually and combinations of them and see how we can use them in our WPF XAML.
  1. Attributes
  2. Configuration
  3. Code

Validation specification in Attributes
Let us create our View Model. We name it as StudentViewModel. Please notice that we have included two namespaces System.Windows (for DependencyProperty) and Microsoft.Practices.EnterpriseLibrary.Validation.Validators (for Validation attributes).

namespace ValidationBlockExampleWPF
{
using System.Windows;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

class StudentViewModel : DependencyObject
{
[StringLengthValidator(5, RangeBoundaryType.Inclusive, 20, RangeBoundaryType.Inclusive,
MessageTemplate = "[{0}]Name must be between {3} and {5} characters.")]
public string StudentName
{
get { return (string) GetValue(StudentNameProperty); }
set { SetValue(StudentNameProperty, value); }
}

public static DependencyProperty StudentNameProperty =
DependencyProperty.Register("StudentName", typeof(string), typeof(StudentViewModel));
}
}


In this simple View model, we have created a property StudentName. We have registered it with WPF property system. We have specified validation for this property. These only validation is about limitations for width (5 to 20 characters). Here Inclusive means that boundary values (5 and 20) are included in Valid limits for length. In the case that this property exceeds this limit, error message can be obtained as provided in the MessageTemplate in validation attribute.

Now let us create a simple WPF window using this View model as its data context.

<Window x:Class="ValidationBlockExampleaWPF.StudentWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValidationBlockExampleaWPF"
xmlns:vab="clr-namespace:Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WPF;assembly=Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WPF"
Title="StudentWindow" Height="300" Width="540"
DataContext="{DynamicResource ViewModel}" >
<Window.Resources>
<local:StudentViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid>
<Label HorizontalAlignment="Left" Margin="12,20,0,0" Name="label1"
Width="94" Height="21" VerticalAlignment="Top">Student Name</Label>
<TextBox Height="24" Margin="112,20,12,0" Name="textBox1" VerticalAlignment="Top" >
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<vab:ValidatorRule SourceType="{x:Type local:StudentViewModel}" SourcePropertyName="StudentName" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</Window>

Here we have added two namespaces local (for the current project) and vab (for using WPF integration features of Validation Application Block). We have bound the only text box in the window with StudentName property of the data context. The most important thing in the above code is following:
<vab:ValidatorRule SourceType="{x:Type local:StudentViewModel}" SourcePropertyName="StudentName" />

It is asking to use the validation logic as specified in the attributes of StudentName property of StudentViewModel class. Now this is to support the cases when we are binding with the view model but want to use validation as specified in any property of any other class (including model classes). In our case, since we have directly specified the validation in view model, we can update the text box definition as follows:
<TextBox Height="24" Margin="112,20,12,0" Name="textBox1" VerticalAlignment="Top" 
vab:Validate.BindingForProperty="Text">
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>

Here it is asking the runtime to use the validation logic from the property in the data context bound with Text property of the control. Basically, WPF integration in validation block, adds a validation rule whenever it sees this.

We are trying to adhere to MVVM to the best. The code behind of the above window is as follows:

namespace ValidationBlockExampleaWPF
{
///
/// Interaction logic for StudentWindow.xaml
///

public partial class StudentWindow : Window
{
public StudentWindow()
{
InitializeComponent();
}
}
}

Now we run the project. As we start typing the name, we can see the validation being done.



But as the number of characters reach the valid limit, the error adorner is no more displayed with the text box.



Validation specification in Configuration:
As we discussed above, we can also specify validation details in configuration file. Let us define configuration for our view model validation. We can define validation configuration in Enterprise library configuration utility. When you install Enterprise library it is available in start menu:



It must be remembered that in order to define validation configuration in the configuration utility, the type must be public. Let us make our StudentViewModel public and build the project. If we don't want to keep it public, we can later update it. Now it should be available as follows:



Now we add a regular expression validator for our StudentName property. Here we are specifying that StudentName can not start with any digit between 0 and 9. We have defined this validation in BasicInfoRuleSet rule set.



Now save the configuration as App.config. The configuration file is created in xml. If you open the configuration file, you can find that the configuration is defined as follows:
<configuration>
<configSections>
<section name="validation" type="Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.ValidationSettings, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
</configSections>
<validation>
<type name="ValidationBlockExampleaWPF.StudentViewModel" assemblyName="ValidationBlockExampleaWPF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<ruleset name="BasicInfoRuleSet">
<properties>
<property name="StudentName">
<validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RegexValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
pattern="^[^0-9]" name="Regular Expression Validator" />
</property>
</properties>
</ruleset>
</type>
</validation>
</configuration>

We update the StudentName property in StudentViewModel and specify the previously defined validation (length between 5 and 20) as follows:

[StringLengthValidator(5, RangeBoundaryType.Inclusive, 20, RangeBoundaryType.Inclusive,
MessageTemplate = "[{0}]Name must be between {3} and {5} characters.", Ruleset = "BasicInfoRuleSet")]
public string StudentName
{
get { return (string) GetValue(StudentNameProperty); }
set { SetValue(StudentNameProperty, value); }
}

If you have not noticed then let me explain. Some amazing thing has just happened. If you remember from above, we defined our regular expression validator to be part of BasicInfoRuleSet. Now we have added one more validation in the same rule set but now it is in the attributes in code. So the same ruleset can span in multiple places. It is the responsibility of Validation application block to get all the validations defined for the property and decide if the value is valid. Amazing!!!

In order to use the validations from all these different sources, we have to update the text box in StudentWindow as follows:

<TextBox Height="24" Margin="112,20,12,0" Name="textBox1" VerticalAlignment="Top"
vab:Validate.UsingRulesetName="BasicInfoRuleSet" vab:Validate.BindingForProperty="Text" vab:Validate.UsingSource="All">
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>

Here we have specified that the validation for data entered should be using BasicInfoRuleSet. It should be using the validation defined in all sources possible.

Note:
It must be remembered that in the whole discussion above, the validation would be executed for the converted value and the value would be committed to the bounded property. We can control which value to use (converted / committed etc) using ValidationStep attribute in ValidationRule. So if you want to control which value to use then use ValidationRule tag instead of defining validation in the TextBox itself.



Note:
We can not use Self Validation feature of Validation Application Block with ValidationRule in XAML.

3 comments:

Brian said...

I tried your approach describing validation specification in attributes.

I get an error message in StringLengthValidator.cs on line 184 within the GetMessage routine with a FormatException indicating that "Index (zero based) must be greater than or equal to zero and less than the size of the argument list.".

CurrentInfo.Culture = en_US
MessageTemplate = "xxx"
objectToValidate = "input string"
key = ""
ttg = null
LowerBound = 5
LowerBoundType = ...Inclusive
UpperBound = 20
UpperBoundType = ...Inclusive

Have you encountered this before? I am trying to determine why this happening.

Brian said...

Here is the exact error...

System.FormatException was unhandled
Message="Index (zero based) must be greater than or equal to zero and less than the size of the argument list."
Source="mscorlib"
StackTrace:
at System.Text.StringBuilder.AppendFormat(IFormatProvider provider, String format, Object[] args)
at System.String.Format(IFormatProvider provider, String format, Object[] args)
at Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator.GetMessage(Object objectToValidate, String key) in e:\Builds\EntLib\Latest\Source\Blocks\Validation\Src\Validation\Validators\StringLengthValidator.cs:line 184
at Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator.DoValidate(String objectToValidate, Object currentTarget, String key, ValidationResults validationResults) in e:\Builds\EntLib\Latest\Source\Blocks\Validation\Src\Validation\Validators\StringLengthValidator.cs:line 167
at Microsoft.Practices.EnterpriseLibrary.Validation.Validator`1.DoValidate(Object objectToValidate, Object currentTarget, String key, ValidationResults validationResults) in e:\Builds\EntLib\Latest\Source\Blocks\Validation\Src\Validation\Validator.Generic.cs:line 114
at Microsoft.Practices.EnterpriseLibrary.Validation.Validators.ValueAccessValidator.DoValidate(Object objectToValidate, Object currentTarget, String key, ValidationResults validationResults) in e:\Builds\EntLib\Latest\Source\Blocks\Validation\Src\Validation\Validators\ValueAccessValidator.cs:line 70
at Microsoft.Practices.EnterpriseLibrary.Validation.Validator.Validate(Object target) in e:\Builds\EntLib\Latest\Source\Blocks\Validation\Src\Validation\Validator.cs:line 49
at Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WPF.ValidatorRule.Validate(Object value, CultureInfo cultureInfo) in e:\Builds\EntLib\Latest\Source\Blocks\Validation\Src\Validation.Integration.WPF\ValidatorRule.cs:line 91
...

Muhammad Shujaat Siddiqi said...

Brian,
Did you reference System.ComponentModel.DataAnnotation?