Home > Validation > Validating Objects with ValidationRules

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.

Namespace Validation
    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:

Namespace Validation
    <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:

Protected Overridable Sub AttachRules(ByVal base As Object)

    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 ReadOnly RuleSetterProperty As DependencyProperty = DependencyProperty.RegisterAttached("RuleSetter", GetType(ValidationRuleSetter), GetType(Validation), New PropertyMetadata(AddressOf WPFGlue.Framework.StickyComponentManager.OnStickyComponentChanged))
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 v:Validation.RuleSetter="{StaticResource vrules}" >
    <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.

Advertisements
  1. January 2, 2011 at 13:44

    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.

    • January 7, 2011 at 16:21

      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

  2. Chikako
    December 20, 2011 at 15:25

    Hi,
    I’m a novice on WPF. I have been researching on WPF Validation where I can pass two bound values from XAML and do validation. It seems so easy to do but can’t find the solution. I would like to see the complete code of what you did here however, the download link was not working. Could you please fix the link? I think your way might be the best way to do it. Thanks!

    • February 25, 2012 at 14:10

      Hi,

      sorry to keep you hanging on for so long… I’m doing this in my spare time, and that was a rare commodity during the last year or so.

      Anyway, I have found a new place to host the downloads, so feel free to use them.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: