As I discussed in my previous post the only Validation tool available in WPF that is worth to be used is the support for the interface IDataErrorInfo. On the other hand the more effective way to specify constraints is Data Annotations. Data Annotations are a declarative way to specify validation rules by decorating properties and classes with attributes, so the code defining the validation rules and the business logic are kept completely separate.
In this post I will explain how to use IDataErrorInfo as a bridge between Data Annotations and WPF. More specifically I will show how to provide an implementation of IDataErrorInfo that uses Data Annotations and Validation Exceptions generated on the server side by some web service as source for the validation rules.The code can be found on Codeplex here.
My implementation of IDataErrorInfo is able to control the input against the Data Annotations defined on the Vie Model on the client side and also against constraints defined on the server-side. The server side constraints may come either from Data Annotations defined on classes that are not available on the client side or directly from the data sources (for instance database fields).
The only way to communicate validation errors through web service without intermixing them with the business objects is by embedding them into web exceptions(Fault Contract), since in the web services protocols arena there is no standard that is specific for validation errors. Therefore a global architecture for handling validation errors need to deal with validation exceptions returned by, possibly asynchronous, calls to web services.
A Short Review of Validation attributes.
The pre-defined validation attributes available in the System.ComponentModel.DataAnnotations namespace are:
- RangeAttribute: It can be applied to any property or field that implements IComparable. It requires the value be within a constant interval
- RequiredAttribute: It can be applied to any property, field or parameter. It requires that the value be not null or white spaces in case of a string
- StringLegthAttribute: It can be applied only to strings. It specifies minimum and maximum number of characters allowed in the string.
- RegularExpressionAttribute: It can be applied only to strings. I requires that the string conforms with a regular expression.
You can define custom validation rules in two ways:
- By using the CustomValidationAttribute. You can apply it to class, properties, fields or parameters of any type. Then you pass it a Type and the name of a static method of that Type. The method is called passing it the value to be validated. The method must return a ValidationResult. if the value returned is ValidationResult.Success the validation is considered successful, otherwise the error information contained in the ValidationResult are used by the layer in charge for displaying the validation errors.
- By inheriting from ValidationAttribute to define a new validation attribute. Your custom attribute needs essentially to override the IsValid method.
Attributes applied on a whole class definition are used to perform a global validation involving all values inserted for that class, while attributes applied to individual members validate only that member.
Sometime your business class is generated by some design tool such as the one of the Entity data model and you can not apply attributes to the members that have been defined by that design tool. In such cases you can define a partial class where you can apply class level attributes. In order to specify validation attributes also for the single properties you define a meta class associated to the initial partial class through the MetadataTypeAttribute, and you define properties with the same name of the properties of the initial class that you want to apply validation annotations too, and then apply the validation attributes to the newly defined properties of the meta class. See the official Microsoft documentation here for a simple example.
A Short Review of the IDataErrorInfo interface and of its support in WPF.
IDataErrorInfo is defined as:
public interface IDataErrorInfo { string this[string columnName] { get; } string Error { get; } }
The first member is required to return the error associated with the columnName property passed as parameter, while Error is required to return an object level validation error. null or string.Empty Return values are interpreted as success. The first member is called as soon as the WPF binder pass a value from the UI to a property, Errors returned are inserted into the Validation.Errors attached property of the control that is an array containing all errors associated with the control. When the Validation.Errors property is not empty the Validation.HasError is set to true.
The above properties can be used to change the look of the controls in error. Below a typical way to show errors in a TextBox(similar techniques apply also to other controls):
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
When the error is removed it is automatically deleted from the Validation.Errors collection.
In order to enable the support to IDataErrorInfo you have to set the property ValidatesOnDataErrors of the binding to true. If you set also the NotifyOnValidationError to true, the RoutedEvent BindingValidationError is triggered when the error state of the binding changes. Such RoutedEvent can be intercepted by an higher level control (exploiting RoutedEvent bubbling) to update a Validation Summary.
The IDataErrorInfo.Error is not called automatically but you can bind it to a control that shows object level validation errors.If you use it this way, don’t forget to fire the PropertyChanged event when the object level error state changes.
The Validation Toolkit for WPF & Silverlight
The Validation Toolkit for WPF and Silverlight you can download freely from Codeplex here do the job of furnishing a IDataErrorInfo implementation that uses annotations attributes to validate data. Data are validated against validation attributes by using the static methods Validator.TryValidateProperty and Validator.TryValidateObject of the Validator class.
In order to supply an implementation of IDataErrorInfo that works with all classes the only solution is to wrap the original object into another object that implements the interface. I have chosen to implement the wrapper with a dynamic object that inherit from DynamicObject, this way, thanks to the .net 4.0 support for dynamic objects you can interact with the wrapper as it were the original object: you can set and get the properties of the original object just by setting and getting properties of the wrapper with the same name. The wrapper implements also the INotifyPropertyChanged interface so you don’t need to implement it on your class.
Using the Validation Toolkit for WPF & Silverlight is easy:
- First, you have to enable ValidatesOnDataErros for the bindings you want to apply validation to.
- Then, you need to wrap your View Model into the dynamic object BindWrapper with the instruction: new BindWrapper(ViewModel, true, true); Setting the second parameter to true causes all son objects of the View Model be recursively wrapped, too. Recursive wrapping will continue also through the boundaries ofIEnumerables if the third parameter is set to true .
- If there is no top level View Model class but your top level structure is either a simple enumerable or a dictionary you can wrap recursively through them by calling respectively:
static ObservableCollection<BindWrapper> WrapEnumerable(IEnumerable source, bool wrapDeep = false, bool wrapEnumerables=false)
or
public static ObservableDictionary<string, BindWrapper> WrapDictionary<TValue>(IDictionary<string, TValue> source, bool wrapDeep = false, bool wrapEnumerables = false)
The result is respectively either an observable collection or an observable dictionary(the observable dictionary type is implemented in the toolkit). The meaning of the parameters is the same as the ones of the BindWrapper constructor.
- Use the wrapper in place of your original object. You can get or set a property of your original View Model by getting or setting a property of the wrapper with the same name: It is a dynamic object it will accept it, and it will retrieve or update the original property of your View Model while triggering the adequate events to update the interface and to perform validation.
- Bind the wrapper to your user interface. You can write the names of the properties as if they would come from your original View Model.
- Validation of the simple properties is done automatically. When you want to trigger object level validation on the whole View Model or on a part of it, you call the ValidateWholeObject method of the relative wrapper. If Some Validation Exceptions are already available you can pass them to ValidateWholeObject as a parameter.
- Each time Validation Exceptions comes from a web service you can call AddRemoteErrors(IEnumerable<ValidationException> remoteErrors) to update the interface.
- If for some reason you need to reset the interface from object level validation errors you can call ResetGlobalEvaluation(), but normally you don't need to do it.
The download on Codeplex contains also a simple WPF example where you can see how all this works in practice.
In the next posts we will see the how INotifyDataErrorInfo interface works in Silverlight, and how the Validation Toolkit for WPF & Silverlight takes advantage of it to implement a wrapper similar to the one of WPF.
Stay Tuned!
Francesco
For more information or consulences feel free to contact me
Tags: Data Validation, Validation, WPF, MVVC, Object level validation, Data Annotations, Validation Toolkit for WPF & Silverlight, IDataErrorInfo, Validation Exception