Introduction
Validizor
is a WPF control that can be used to validate objects and their properties. When validation fails, a Validizor
displays an error icon with the error message(s) in a tooltip.
This article explains the advantages of using Validizor
and how to use it. To see Validizor
in action, download the demo executable and give it a try!
Background
WPF offers validation in its Binding.ValidationRules
, but these associate validation rules with UI controls. This is undesirable because sometimes an application allows the same data to be entered in more than one screen. Validation rules would need to be duplicated for each screen. It seems more logical to keep your validation rules with your data objects.
Microsoft's Enterprise Library Validation Application Block (VAB) allows validation rules to be associated directly with your data objects, but Microsoft does not currently offer a WPF control to take advantage of it.
Validizor
leverages Enterprise Library's VAB to offer data object validation packaged in a WPF control. VAB comes with a nice assortment of Validators allowing you to build validation rules involving string lengths, enums, numerical ranges, regular expressions, object types, nulls, and more.
Benefits
- Unlike WPF's
Binding.ValidationRules
which define validation rules in your UI's XAML, Validizor
uses Enterprise Library's VAB. This means your validation rules are directly associated with your object classes and their properties. Plus you can use the Validators that VAB comes with. - WPF's
Binding
validation will not set a value into your source object when its Binding.ValidationRules
fail. Validizor
WILL set invalid values into your objects. This allows you to easily retain erroneous data during user editing, yet gives you a mechanism to avoid using/committing it before they are corrected. - WPF's
Binding
does not perform validation when a window/control first loads. The Binding ValidationExample at MSDN demonstrates this. Initial values aren't validated. A Validizor
validates its data when it is loaded and whenever the source value changes. - All the examples I've seen that use WPF's
Binding
validation target a TextBox
. When there is a validation error, the TextBox
's ToolTip is set to the first validation error. None of the examples ever mention the fact that now you've overwritten any ToolTip that may have been on the TextBox
in the first place. Whoops! Validizor
uses its own ToolTip which displays all the invalid validation rules for the source object, not just the first.
Using the Code
Simple Example
Here is a quick demonstration of using a Validizor
. You really should download the demo application for a more complete example.
In a simple scenario, you might have a Person
object with a FirstName
property. A validation rule could be defined as an attribute on FirstName
like so:
[StringLengthValidator(1, 15, MessageTemplate =
"First Name must be between 1 and 15 characters", Ruleset = "Default")]
public string FirstName
{
...
}
In your XAML, you might have a TextBox
bound to this property, along with a Validizor
:
<StackPanel Orientation="Horizontal" >
<TextBox Text="{Binding Path=FirstName,
UpdateSourceTrigger=PropertyChanged}" />
<val:Validizor Source="{Binding Path=FirstName}" Ruleset="Default" />
</StackPanel>
The result would be a UI that shows the invalid values while you enter them:
Validizor Properties
An instance of Validizor
can be configured through its properties:
Source
: This is a dependency property of type object and should be bound to the data being validated. By default, Validizor
will assume Source
is a property of some object. It will use the validation rules that target the property, defined for the "owner" object . Validizor
will use the Source
binding's Path to find the Type of the "owner" object, allowing it to use the validation rules that target this object's property. In some cases this behavior is not desired and/or the Validizor
cannot obtain the "owner" object based on the binding to Source
. In these cases, the Validizor
has other properties that can be used (as explained below). In any case, Source
must be bound to a source object that will be validated. SourceOverride
: This property can be used to override the Validizor
's behavior of automatically using the Source
binding to find the "owner" Type. In some circumstances the "owner" object's Type cannot be obtained by using Source
's binding Path. SourceOverride
can be used to directly specify OwnerType
and PropertyName
. When these are defined, Validizor
will use OwnerType
as the type of the "owner" object, and PropertyName
as the name of the property being validated. It will still use the Source
value as the value to validate. The demo application demonstrates this. ObjectValidation
: This property is a boolean and defaults to False
. When set to true
, Validizor
will no longer look for the validation rules within the "owner" object. Instead it will treat the Source
value as an object that can be validated by itself. The demo application demonstrates this also. Ruleset
: This property is a string that indicates the ruleset to validate with. Enterprise Library VAB validation rules can be labeled with a ruleset. This allows you to put multiple rules on individual properties and objects that will only get evaluated when a specific ruleset is being targeted. RuleSource
: This property is a string that indicates the Enterprise Library VAB ValidationSpecificationSource
. It defaults to "Both." "Attributes" indicates that only the validation rules defined within the source code should be used. "Configuration" indicates that only the validation rules defined in App.config should be used. "Both" indicates that validation rules from both attributes and App.config should be combined and used.
Helper Methods
Validizor
also offers several static
helper methods for validating lists of objects and for formatting validation results. These can be used for validating the data objects in your code-behind, for example, when a user clicks a "Submit" button to save the data he/she has edited.
Points of Interest
Enterprise Library Validation Application Block (VAB)
In order to use Validizor
, you'll need to install Microsoft Enterprise Library 3.1. Once it is installed, you will be able to add References to Microsoft.Practices.EnterpriseLibrary.Validation
in your projects. This will allow you to take advantage of the existing Validators when defining your validation rules. It also allows you to edit your App.config (if you choose to put rules there) using the Enterprise Library Configuration Editor.
VAB validation rules can be defined in two places. The Enterprise Library comes with a configuration editor to easily define validation rules in your App.config. Alternatively, as I did in the demo application, validation rules can be defined directly in your classes as attributes. Either way, this is a feature of the VAB, and Validizor
simply takes advantage of it. The VAB has a good variety of Validators from which to base your rules, and is in active development at the time this article was written.
Enterprise Library Validation integration with WPF
Martin Bennedik has written a library to integrate Enterprise Library Validation with WPF. Validizor
leverages Martin's work, but uses its own versions of his code, as a few modifications were necessary.
Invalid Type Issue
As mentioned before, one of the benefits of Validizor
over Binding.ValidationRules
is that it allows invalid values to be set in your data objects. But what it can't do is set values that cannot be converted into the type of the source object. For example, if a DateTime
object is bound to the Text of a TextBox
, the user would be capable of typing in gibberish. Text such as "Blah blah blah" cannot be converted into a DateTime
and so WPF binding will not be able to set the DateTime
property in your data object. One way to make this a moot point is to use controls that only generate appropriate objects/values. In the demo application, a DatePicker
control is used to guarantee that a valid DateTime
is always entered by the user. But you will see that the demo application also demonstrates this issue in its "Hair Count" TextBox
.
Knowing When Properties Within an Object Changed
If your class implements INotifyPropertyChanged
, and you instrument your property setters, binding will know when a property value has been changed. But if your property is an object itself, then binding won't know when a property WITHIN that object has changed. I wish there was a better mechanism for this built directly into the .NET framework. For now, one solution I like is Josh Smith's BusinessObjectHolder generic class. I have used it in the demo application.
History
- October 2007: Initial creation