Validating Objects with ValidationRules
In examples about writing WPF applications with the MVVM pattern, normally one doesn’t find any reference to ValidationRules. The normal way to do things there is to wrap the business object into the ViewModel and to implement IDataErrorInfo on the ViewModel. However, there are several points about this approach which I don’t like:
- I don’t like to wrap classes into other classes. The kind of code this produces is exactly the kind of useless boilerplate code I want to get rid of by using a flexible data binding system like WPF’s. If it can’t be helped, this kind of code should be generated, but that is not the topic of this blog.
- Validation on the ViewModel or Business level removes validation logic and error messages from the domain of the user interface definition to the domain of business logic. Now I like my business logic to be language independent, and it is absolutely valid for a user interface to implement different (more restrictive) checks on a property value than the business object it accesses. So, even if validation logic is implemented on the business level, I want to be able to choose how to use it and what error messages to display on the UI level, i.e. in XAML.
That said, ValidationRules have another significant advantage: they can check values before they are passed to the properties of the bound object. This means that I don’t have to implement rollback capabilities on the business object, but can make sure on the UI level that only valid values are set on the business object. This will be covered in a later post.
With all these advantages, there is a serious drawback to ValidationRules: defining them in XAML is rather unwieldy. Done the traditional way, they tend to bloat the XAML code, and sometimes it is difficult to get at all the reference data they need for their checks.
This is where WPFGlue comes into the picture: here, I want to introduce some ideas which allow to define and apply ValidationRules in a more concise manner, and to do some basic checks even without specifying ValidationRules explicitly.
The BindingGroup Class
The BindingGroup class has been introduced in WPF 3.5 SP1. A BindingGroup can be set on every FrameworkElement. If set, it can define ValidationRules which are able to access multiple properties of the DataContext in order to validate them against each other. In order to afford this possibility, the BindingGroup class provides methods which can access all Bindings in the subtree under the element which defines the BindingGroup, as long as these Bindings have the same DataContext as that element.
The idea here is to use this ability of the BindingGroup class in order to set up ValidationRules for all bindings which share a given DataContext.
So, in order to validate objects of a given class, first of all one would use a container like a StackPanel, Grid or GroupBox to combine a group of controls which share an instance of that class as their DataContext. Then, one would create a BindingGroup object and set it to the BindingGroup property of that container. Further, one would define a set of ValidationRules for a given class. Some of them would be at the property level, others would access multiple properties, as would be normal for ValidationRules on a BindingGroup object. Then, one would apply this set of ValidationRules to the container, instead of the separate controls which edit the object’s properties. One could reuse the set of ValidationRules wherever an instance of the DataContext class was used.
Using the BindingGroup’s ability for finding Bindings which have the same DataContext, one would add the ValidationRules to the appropriate Bindings inside the container.
How to Modify Bindings Which are Defined in XAML
The documentation says that Bindings can not be modified after they have been used. However, there is an event that comes right between the time when the Bindings have all been created, and when they are used for the first time: Initialized. If the DataContext is not null and a BindingGroup is present on a FrameworkElement, this BindingGroup can access and modify those Bindings during the FrameworkElement’s Initialized event.
So, one would need a couple of things:
- A way to define sets of ValidationRules.
- A Sticky Component which handles distributing the ValidationRules to the Bindings.
- A Sticky Property which attaches an instance of a set of ValidationRules to a container.
Defining Sets of ValidationRules
First of all, I said that I wanted to be able to define error messages in XAML. Using the techniques from the WPFGlue Localization namespace, I can handle multiple languages quite conveniently in XAML, and there might be other considerations like the intended audience of an application which might induce me to change the text of error messages. So, my first addition to a WPFGlue ValidationRule base class would be a Message property, which allows me to set the error message in XAML. In addition to that, I just added the functionality to format the property value into the error message.
Public MustInherit Class ValidationRule
Inherits System.Windows.Controls.ValidationRule
Private _Message As String = String.Empty
Public Property Message() As String
Get
Return _Message
End Get
Set(ByVal value As String)
_Message = value
End Set
End Property
Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
Dim result As ValidationResult = ValidationResult.ValidResult
If Not ValidateCore(value, cultureInfo) Then
Dim message As String = Me.FormatMessage(value)
result = New ValidationResult(False, message)
End If
Return result
End Function
Protected MustOverride Function ValidateCore(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As Boolean
Protected Overridable Function FormatMessage(ByVal value As Object) As String
Return String.Format(Me.Message, value)
End Function
End Class
End Namespace
The set of ValidationRules would be defined as a resource in XAML. In order to be able to do that, I create several classes which allow to define collections of ValidationRules grouped by property name:
<System.Windows.Markup.ContentProperty("Setters")>
Public Class ValidationRuleSetter
Inherits DependencyObject
Implements IStickyComponent
Private Shared _DummyDataContext As Object = New Object
Protected Overridable ReadOnly Property DummyDataContext As Object
Get
Return _DummyDataContext
End Get
End Property
Private _Setters As New PropertyValidationRuleSetterCollection
Public ReadOnly Property Setters As PropertyValidationRuleSetterCollection
Get
Return _Setters
End Get
End Property
The collection Setters contains collections of ValidationRules, grouped under different property names.
The ValidationRuleSetter class is a Sticky Component: in addition to defining the set of ValidationRules, it also has the ability to set the ValidationRules on the Bindings which belong to the container’s BindingGroup. The following method will be called during the container’s Initialized event:
Dim f As FrameworkElement = TryCast(base, FrameworkElement)
If f IsNot Nothing Then
Dim bg As BindingGroup = f.BindingGroup
If bg IsNot Nothing Then
For Each be As BindingExpression In bg.BindingExpressions.OfType(Of BindingExpression)()
If Setters.Contains(be.ParentBinding.Path.Path) Then
For Each v As System.Windows.Controls.ValidationRule In Setters.Item(be.ParentBinding.Path.Path).ValidationRules
be.ParentBinding.ValidationRules.Add(v)
Next
End If
If Setters.Contains(String.Empty) Then
For Each v As System.Windows.Controls.ValidationRule In Setters.Item(String.Empty).ValidationRules
bg.ValidationRules.Add(v)
Next
End If
Next
End If
End If
End Sub
For each Binding that is accessible to the BindingGroup, it looks up the name of the bound property and attaches all ValidationRules which are grouped under that name, if any. By convention, ValidationRules which have an empty String as property name are attached to the BindingGroup itself.
How to Use ValidationRuleSetter on a Container
In order to be able to attach the Sticky Component to a container, one defines a custom attached property like this:
Public Shared Function GetRuleSetter(ByVal d As DependencyObject) As ValidationRuleSetter
Return d.GetValue(RuleSetterProperty)
End Function
Public Shared Sub SetRuleSetter(ByVal d As DependencyObject, ByVal value As ValidationRuleSetter)
d.SetValue(RuleSetterProperty, value)
End Sub
This is actually the standard way of defining a sticky property in the WPFGlue framework.
One would then define the container in XAML like this:
<StackPanel.BindingGroup>
<BindingGroup/>
</StackPanel.BindingGroup>
<TextBox Text="{Binding StringProperty, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
<TextBox Text="{Binding IntegerProperty, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
<TextBox Text="{Binding DoubleProperty, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
</StackPanel>
Side Effects of Using BindingGroups
The presence of a BindingGroup on a container has various effects:
- Any failed ValidationRule on any Binding inside the BindingGroup will make Validation.HasError return true on the container.
- The BindingGroup’s own ValidationRules will cause the Validation.ErrorTemplate to be displayed on the container. ValidationRules on the Bindings will show the ErrorTemplate on the respective control and also the container’s ErrorTemplate.
- The UpdateSourceTrigger on all Bindings inside the BindingGroup will be set to Explicit by default, which means that one would have to call BindingGroup.CommitEdit in order to have the Bindings update their sources. However, if the UpdateSourceTrigger on a Binding is set to another value in XAML, this value will be honoured.
Conclusion
Using this technique, it is possible to separate the definition of validation rules both from the model and from the view. This overcomes the main disadvantage of ValidationRules and opens up the way for further applications.
In posts to come, I intend to cover how to extend this technique in order to do type checks on property values before they are committed to the bound properties, and how to connect this with general data entry handling.
On the Downloads page, you can find the full code of the examples, plus some additions which will be covered in a later post.

I think it’s wrong to define validation in the UI for the following reasons:
1) You can’t test it via unit tests
2) What if a designer forgets it? This way, bugs will be created
I do agree that wrapping model classes into ViewModel classes is wrong. That’s why we (developers of Catel), came up with the idea to be able to define a model (via a ModelAttribute) and view model to model mappings (via a ViewModelToModelAttribute). This way, you can have validation inside your model, and the validation is directly passed to the view model (and therefore, you don’t have to implement validation of a model again inside a view model).
In my opinion, that’s a safer approach (testable) instead of letting a UI designer create the validations.
Hi Geert,
I view ValidationRules as part of the ViewModel, and I think you can unit test them. What you can’t simulate so easily is how WPF activates them, but that is documented fairly well, and you’d have to invest some research about it, as you have to do it about data binding and commanding, anyway. You might even win in that you wouldn’t need a complete mock setup in order to test a ValidationRule, while you’d probably need one if you wanted to test a ViewModel while the Model is not yet complete.
About using attributes: where exactly do you define them? I wouldn’t want to have them in the Model, actually… basically, the Model should be completely unaware of how it is used, and make no assumptions about whether or how its client checks input values.
So, the way for models to report invalid states or input values would be through exceptions, and the way for ViewModels would be through ValidationRules. The set of ValidationRules for a given use case would have to make sure that the model is only called with valid arguments.
Greetings,
Hans