Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Wpf ErrorProvider - Integrating IDataErrorInfo, WPF, and the Validation Application Block (VAB)

0.00/5 (No votes)
23 Sep 2008 2  
A WPF validation control integrated with VAB and IDataErrorInfo.

Screenshot

Introduction

ErrorProvider is a WPF control that attempts to integrate the best features of WPF, the IDataErrorInfo interface, and Microsoft's Validation Application Block.

Background

Let me clear this straightaway. This control is more of an inspirational one, than an innovational control. As soon as I started with WPF, I needed Validation, and WPF provides me no control for this. After Googling out, I found links to Validation in Windows Presentation Foundation, Validizor - A Validation Control for WPF, and Delegates and Business Objects.

There was not much in these articles that I could disagree with. Specifically, my high level design goals for the Validation control were derived from this Paul Stovell article. I am stating them again below (borrowed as is from that article):

  • A business object should be allowed to be invalid, so long as you don't try to save it.
  • Rules should not be implemented in property setters.
  • A rule should not be checked unless it absolutely has to be.
  • No exceptions should be used, unless the object is trying to save without being validated.
  • Any rules that have been broken should be retrievable from the business object.

Due to the .NET support for IDataErrorInfo, I was almost certain to use it as the source for providing errors in business objects. However, the declarative approach for validation used in ValidationRule was always alluring. That was when I decided to use the Validation Application Block as a way to declare validation rules, and use the IDataErrorInfo interface to validate those rules.

Here, my approach is different from Paul Stovell's article, as he uses custom Rule classes for validation. For one thing, they required me to implement procedural code for validation. For another, that approach was not standardized, and so I decided to use VAB instead.

Lastly, this control is more or less a hybrid of Paul Stovell's and Buzz Weetman's controls, adding many features and flexibility to both of them. Considerable efforts have been made to keep the control as easy to use as the WinForms ErrorProvider (and I believe, with success). You need to keep only a single instance of ErrorProvider on your form, like in Windows Forms.

Using the code

First of all, let me explain the Business Object part. The Business Object needs to implement IDataErrorInfo to provide error information and INotifyPropertyChanged to generate Property Change Notifications. The library project has a BusinessObjectBase abstract class that implements both of these, and even provides the complete functionality required for these two interfaces.

For IDataErrorInfo, it uses ValidationRules on object properties for validation. The rules can be defined as attributes or in the configuration, as VAB allows both. For your custom objects, you just need to derive from this class, and provide the declarative rules in the attributes or the configuration. You don't need to write a single line of code to implement this interface as the base class handles everything. This can be verified from the Customer object in the test application.

As for INotifyPropertyChanged, you would just need to call the Base class's protected OnPropertyChanged method in your property setters. A Sample Property looks like follows:

<StringLengthValidator(1, 25, 
  Messagetemplate:="Address must be between 1 and 20 characters in length")> _
<ContainsCharactersValidator("a", 
  Messagetemplate:="Address Should contain character a")> _
Public Property address() As String
    Get
        Return (Me._address)
    End Get
    Set(ByVal value As String)
        Me._address = value

        Me.onPropertyChanged(New PropertyChangedEventArgs("address"))
    End Set
End Property

You can even use the <HasSelfValidation> attribute on your object to perform advanced validation, and ErrorProvider would recognize it.

ErrorProvider control

You basically need to put a single instance of the control on your window, and add all other controls as its children. The high level design of the ErrorProvider is adapted from Paul Stovell's ErrorProvider, and I advise you take a look at this article for better understanding of how this works.

The following text has been adapted from Paul's article as it fits in here perfectly.

The ErrorProvider that I have built (found in the attached sample code) inherits from the WPF Decorator class, which means you can "put stuff inside it". To use it, you just need to do something like this:

<validators:ErrorProvider>
    <StackPanel>
        <TextBox Text="{Binding Path=Name}" />
        <TextBox Text="{Binding Path=Age}" />
    </StackPanel>
</validators:ErrorProvider> 

My ErrorProvider works by cycling through all of the controls that are inside of it, and looking for any data bindings on their properties. When a value of one of the bound properties changes, it checks if its DataContext implements IDataErrorInfo, and if so, it gets any error messages, and displays them using the built-in static Validation class. That means you can use the styles and control templates that I showed above, whilst keeping all of your validation logic in another class.

Cycling through the control hierarchy in WPF is done using the LogicalTreeHelper class. The code itself is a little long to post here, but is included in the sample code download at the top of the page.

Using the WPF ErrorProvider brings a number of benefits:

  • Your XAML is much more compact, as you don't need to add a list of ValidationRules to each binding.
  • All of your validation can be done by your business objects, rather than at the UI. This lets you have great encapsulation on your objects while still having an informative UI.
  • You can call the Validate() method on the ErrorProvider to force validation, and check if the controls are valid, rather than having to inspect each one.
  • You can use the GetFirstInvalidElement() method to get the first control with an error on your form, to set focus to it easily. This is great if, for example, your "Save" button has been clicked but you want to show them that they still have errors.

Points of interest

ErrorProvider properties

ErrorProvider provides the following properties that can be used to set its default behaviour:

  • DefaultErrorTemplate - The default error template to apply to controls that violate validations.
  • DefaultRuleSet - RuleSet to use to perform validations.
  • DefaultRuleSource - Default rule source (attributes, configuration, or both) to perform validations.

Attached properties

In addition, the ErrorProvider provides the following attached properties that can be used to override validation behaviours on a per-control basis:

  • CausesValidation - Switching it to False would not validate the control. The default value is True.
  • ErrorTemplate - Assign a custom ErrorTemplate to a control.
  • RuleSet - Assign a custom RuleSet to a control.
  • RuleSource - Assign a custom RuleSource to a control.
  • BindingOverride - This is an interesting property. It allows you to perform validation on some other property of the DataContext than the property the control is bound to. Although the idea was derived from Buzz Weetman's work, it has been greatly enhanced. You can set OverrideRelativeSource to Self (the substitute property is evaluated as a child of the Binding), or PreviousData (the substitute property is evaluated as a child of the DataContext of the control). An example is available in the test application.
  • IsObjectValidation - Set it to True if you want to validate the entire object, instead of its property. You can again use BindingOverride to customize this further. An example is available in the test application.

Conclusion

There is no one size fits all approach here. However, I do believe that my ErrorProvider can fit maximum sizes ;-)

It uses a standardized approach, and integrates the best features of WPF and IDataErrorInfo (many WPF and WinForms controls provide support for IDataErrorInfo, thus allowing your business object to be used as is with these controls), with the enterprise scalability of VAB.

Acknowledgements

As has been mentioned throughout the article, I studied Paul's and Buzz's work, found it lacking for my scenario, combined and improved the two of them, and suddenly decided to release it on CodeProject, as I owed it to it. I fully acknowledge the features and article snippets I have sourced from their work.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here