Data validation is an important aspect of any line of business application. It is used to make sure the data stored in the backend is valid and also to guide users in their data input tasks.
Several mechanisms already exist in client technologies to communicate to the application user when a particular piece of data is invalid and needs to be fixed. In some cases, data validation is closely working with data binding which is the ability to automatically push data between the user interface (UI) and the backend. Typically data is then validated as it is initially read to be displayed in a form, and reciprocally as new user input is flushed into the backend.
Both Microsoft Windows Forms (WF) and Windows Presentation Foundation (WPF) client technologies support the IDataErrorInfo interface which allows backend objects to communicate their error information to the UI layer. So far this interface has not been supported in Silverlight. But this is changing with the release of Silverlight 4 Beta. Its binding engine now takes full advantage of sources that implement IDataErrorInfo, just like the WPF binding engine does. Therefore business objects that were created for WF or WPF can now be reused in Silverlight applications without losing the benefits of their IDataErrorInfo implementation.
However IDataErrorInfo was originally designed for pure client environments such as WF and WPF and shows a limitation in a client-server technology like Silverlight: It does not include a means for notifying consumers that an error status has changed.
This is fine for pure client applications where the error status is known as soon as new data is pushed into the business object since the validation rules are applied locally. But for Silverlight applications where user-provided data often needs to be validated on a server, there really needs to be a mechanism for the binding source to advertise new errors asynchronously. Enters the new INotifyDataErrorInfo interface which was designed to fill this gap, in the Silverlight 4 Beta release.
As in the .NET Framework 1.1 and subsequent releases, the IDataErrorInfo interface is defined in the System.ComponentModel namespace, in the System.dll assembly. The interface reference documentation can be found here: http://msdn.microsoft.com/en-us/library/system.componentmodel.idataerrorinfo.aspx. The Silverlight binding engine checks if the source of a binding is a property of an object that implements IDataErrorInfo when binding.ValidatesOnDataErrors is set to True. By default, Binding.ValidatesOnDataErrors is set to False. Because in Silverlight there is no concept of BindingGroup as in WPF, the binding engine will never access the IDataErrorInfo.Error property to report top-level validation errors. It only accesses the IDataErrorInfo.Item indexer for property-level errors.
Let’s get straight to the interface definition:
namespace System.ComponentModel {
using System;
using System.Collections;
public interface INotifyDataErrorInfo {
// Returns True if the object has at least one property-level or top-level error.
bool HasErrors { get; }
// Returns the current set of property-level errors for the provided property name, or
// the current top-level errors if the argument is null or empty.
IEnumerable GetErrors(string propertyName);
// Raised when the set of errors for a particular property has changed, or when the
// top-level errors have changed.
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
public sealed class DataErrorsChangedEventArgs : EventArgs {
// Constructor for the DataErrorsChangedEventArgs class. The provided argument can
// be null or empty for top-level object errors.
public DataErrorsChangedEventArgs(string propertyName);
// Returns the name of the property for which the errors have changed, or null/empty string
// when the notification is for the top-level errors.
public string PropertyName { get; }
}
}
Notice that this interface, like IDataErrorInfo, is UI-technology agnostic, hence its declaration in the System.ComponentModel namespace.
There are quite a few differences between the old IDataErrorInfo interface and the new INotifyDataErrorInfo. The presence of an ErrorsChanged event represents the key differentiator. That event is meant to be raised by the implementing object when the set of errors for a particular property or for the object itself has changed. This is the mechanism used by business objects to expose new errors asynchronously.
Beyond that the new interface addresses additional limitations of IDataErrorInfo:
What does it all mean for the user experience?
The user interface layer, if it’s so inclined, can take advantage of these improvements.
Three kinds of developers will be using this interface:
The number of those developers exponentially decreases as we go from category A to B to C. Therefore when designing this interface, a primary goal was to make the task for category A developers as easy as possible. This goal sometimes added an acceptable burden to category C developers. As a result the interface has a minimal set of properties, methods and events.
IEnumerable GetErrors(string propertyName)
When propertyName is a valid property name, the GetErrors method must return an enumerable set of objects that represent the current validation errors associated with this property. When propertyName is null or empty, the GetErrors method must return an enumerable set of objects that represent top-level or cross-property validation errors. In both cases, the returned value can be null or an empty IEnumerable set to signify that there is no more known error for the provided argument.
When the provided propertyName does not point to an existing public property, the business object may throw an exception or return null. A well-written consumer will not provide an erroneous parameter though.
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged
The business object needs to raise this event with the argument e.PropertyName whenever a call to GetErrors(e.PropertyName) would result in a different set of errors.
Again DataErrorsChangedEventArgs.PropertyName points to a public property of the business object. Alternatively, it is null or empty when the set of top-level/cross-property errors has changed.
The IEnumerable reference returned by GetErrors(e.PropertyName) may be the same before and after the event is raised, or it may be different. Similarly some error objects found in the set before the event may reappear in the set after the event. In other words, the business object is free to reuse the IEnumerable store and its content between successive ErrorsChanged events. Consumers of the interface need to be aware of that.
Well written business objects make sure that the HasErrors property returns the correct value when it’s being accessed within an ErrorsChanged event handler. Of course, the GetErrors method will typically be invoked within an ErrorsChanged event handler as well.
bool HasErrors { get; }
This property is meant to be a quick and easy way to check if there are known validation errors for the implementing business object, whether they’re property-level or top-level. If none of the possible GetErrors(propertyName) calls returns an error, the property should return false.
If the business object also implements INotifyPropertyChanged and HasErrors is exposed publicly, then PropertyChanged must be raised each time the HasErrors value changes.
INotifyDataErrorInfo implementers must not assume that the consumers will check if the IEnumerable set returned by GetErrors(…) also implements INotifyCollectionChanged. There is no problem with exposing a collection that implements INotifyCollectionChanged, such as an ObservableCollection
Regarding cross-property validation errors, such as “The Shipment Date cannot be prior to the Order Date.”, the business object has three choices for exposing them to the user:
Similar to the Binding.ValidatesOnDataErrors boolean property for IDataErrorInfo, the Binding class now also exposes a ValidatesOnNotifyDataErrorInfo property. This boolean property is set to True by default though. The True setting means that the binding engine will be at the lookout for sources that implement INotifyDataErrorInfo. When the source of a binding evaluates to a property of an object that implements INotifyDataErrorInfo, the binding engine will report property-level errors for that property. If the source of a binding evaluates to an object that implements INotifyDataErrorInfo, the binding engine will report top-level errors for this object. Those two situations are not mutually exclusive: the source of a binding can be a property of an INotifyDataErrorInfo object, and itself be an INotifyDataErrorInfo object. Here are some examples for illustration.
Let’s say an application deals with two business object types, SocialSecurityNumber and Person, both implementing INotifyDataErrorInfo. Also let’s assume the Person class has an SSN property of type SocialSecurityNumber. These are typical bindings that involve the INotifyDataErrorInfo interface:
<TextBox Text="{Binding Source={StaticResource samplePerson}, Path=FullName}"/>
In this example, the Source of the TextBox’s binding is set to a Person defined in resources. Since Binding.ValidatesOnNotifyDataErrorInfo is True by default, the TextBox will show a red border and tooltip when the Person.FullName property is declared invalid by the person.
<vc:ValidationErrorViewer DataContext="{Binding Source={StaticResource samplePerson}}">
Here ValidationErrorViewer represents a custom control capable of displaying its attached Validation.Errors collection. Because the DataContext property is bound to a Person business object, the control’s associated Validation.Errors collection will be populated with the top-level person errors.
<vc:ValidationErrorViewer DataContext="{Binding Source={StaticResource samplePerson}, Path=SSN}">
The Silverlight binding engine detects the fact that the DataContext property is bound to a business object of type SocialSecurityNumber which implements INotifyDataErrorInfo. It realizes that the owning type Person also implements that interface. Therefore the ValidationErrorViewer’s associated Validation.Errors collection will be populated with the property-level errors for the Person.SSN property and the top-level errors for the SocialSecurityNumber object.
When a binding is initially set up, the binding engine invokes the GetErrors(…) method to reflect any existing errors. It will then rely on the ErrorsChanged event to reflect any changes that may occur during the lifetime of the binding. How is that reporting happening?
The handling of the INotifyDataErrorInfo interface by the Silverlight binding engine is identical for the BindingMode.OneWay and BindingMode.TwoWay modes. However when Binding.Mode is set to BindingMode.OneTime, the binding engine only propagates the initial errors, if any, and does not listen to the ErrorsChanged notifications. The user sees a snapshot of the errors at the time the binding gets set up.
Note that the Silverlight binding engine expects the ErrorsChanged event to be raised on the main UI thread. An exception will be raised if the event is raised on a background thread.
One more note. The Silverlight binding engine never accesses the INotifyDataErrorInfo.HasErrors property. It has no need for it. That property is rather meant for application developers, or 3rd party component developers.
Exception-based validation comes with these limitations:
The IDataErrorInfo’s limitations were already listed above. In Silverlight, the main reason to adopt it would be to reuse existing business objects that implement this interface.
When given the choice though, using the new INotifyDataErrorInfo interface is the most sensible approach in a Silverlight 4+ application. None of the limitations above are encountered.
The Silverlight binding engine is capable of handling cases where the business object adopts more than one approach. The ValidatesOnExceptions, ValidatesOnDataErrors and ValidatesOnNotifyDataErrors Binding flags can all be set to true. Situations where the business object implements both IDataErrorInfo and INotifyDataErrorInfo are not likely to occur in practice though.
The exception-based and IDataErrorInfo-based validations influence each other, because together they can only result in one ValidationError in Validation.Errors. Exceptions take precedence. INotifyDataErrorInfo-based validation however does not influence the two others and vice-versa, at least in the SL4 Beta release. This design may be altered before Silverlight 4 ships, because you can argue that when the UI and backend become out of sync, the UI should no longer show the INotifyDataErrorInfo errors.
This Visual Studio .NET 2010 Beta 2 sample Solution illustrates the use of the new INotifyDataErrorInfo interface. Before playing with this application, you will need to setup your machine with:
The sample application includes two projects:
The Entity class represents a base class for business objects that implement INotifyDataErrorInfo. It stores the known validation errors in its _errors field. The constructor accepts two pieces of data: a flag that turns on entity-level validation when any property changes (only that mode has been tested), and a type that points to a static class responsible for applying all the validation rules for a particular entity type. That public static class, called an entity validation manager, needs to expose these three methods:
public static void ValidateEntity(Entity entity) public static void ValidateProperty(Entity entity, String propertyName, Object validatedValue) public static void CancelValidation(Entity entity, String propertyName)
Each application can implement its own way of running its mix of synchronous and asynchronous rules. All entity validation managers report their findings to entities via the IValidatedEntity interface, implemented by the base Entity class.
public interface IValidatedEntity
{
void AddError(String propertyName, DataErrorInfo dataErrorInfo);
void NotifyAsynchronousValidationStarted();
void NotifyAsynchronousValidationCompleted();
}
Thanks to that interface, an entity validation manager can push a new error into an entity and notify an entity that an asynchronous validation has started or stopped.
At any given time, an entity can be valid, invalid or being validated. This is a crucial piece of information for the application because it typically influences the flow of the user tasks. Thus the entity exposes a public ValidityStatus property of type EntityValidityStatus.
public enum EntityValidityStatus
{
Valid,
Invalid,
Validating
}
The Entity class also handles the validation of properties of type Entity thanks to its ValidateNestedEntity method. This makes it easier to create input forms for editable nested entities (for instance a form for editing Customer and Customer.Order).
Each Entity subclass is responsible for clearing existing known errors at the right time. It can call its protected ClearErrors method to do that job. Typically, an entity will do this when a property value is about to change.
Instead of using a plain string for its error reporting, the Entity class and its entity validation manager use a more sophisticated DataErrorInfo type. This richer type allows applications to handle both regular errors and warnings, thanks to the DataErrorInfo.Level property. An application can choose to represent warnings differently than errors. It can also decide that warnings do not prevent saving an entity like errors would.
The DataErrorInfo class also helps associate a single cross-field validation failure with multiple fields on the input form. The DataErrorInfo.ExtendedScope is used for that purpose. Finally the good old error message is stored in the DataErrorInfo.Message property. Notice that the DataErrorInfo class overrides the ToString() method. This is crucial since Silverlight’s default error tooltip invokes this method when the nature of the error object is not a simple string.
The EntityInputWindow class is designed to make it easy to create input forms based on a ChildWindow. The EntityInputWindow’s constructor is fed a top-level entity and a flag that indicates whether warnings must be treated like errors or not. This base class takes care of:
The ValidationErrorViewer is a custom ContentControl that can be used to visualize two types of errors (or warnings):
The control exposes a DataTemplate property called ErrorTemplate. It can be used to change the look of the error reporting, and also consume custom error objects such as DataErrorInfo objects.
The INotifyDataErrorInfoSample application builds upon those base classes. It uses two concrete Entity types: a SocialSecurityNumber and a Person type. The Person type has a SocialSecurityNumber property to showcase nested entities. The startup UI shows a read-only form for a person and allows bringing up 2 editing forms:
A few words about each building piece of the application.
The SampleEntity class derives from Entity and allows each custom entity to point to a data validation class via the Validator property of type SampleEntityValidator.
The concrete validator classes implement the entity-specific validation rules, for both property- and entity-level validation. In this illustrative implementation, each property-level or entity-level validation process includes a synchronous phase, thanks to the SampleEntityValidator.ValidateSynchronous(…) virtual method, and two asynchronous phases, thanks to the SampleEntityValidator.ValidateAsynchronous1(…) and SampleEntityValidator.ValidateAsynchronous2(…) virtual methods. This is a simplification of course – real professional applications are not likely to follow such a common pattern.
The SampleEntityValidationManager is the orchestra conductor that invokes those three virtual methods when it is requested to validate a property or entity. It is a concrete entity validation manager (a concept mentioned earlier) and therefore exposes the ValidateEntity, ValidateProperty and CancelValidation static methods. To simulate asynchronous validations, it simply launches two timers for each validation request. When those timers expire, somewhere between one and two seconds later, the ValidateAsynchronous1() and ValidateAsynchronous2() virtual methods get invoked. In reality, a web service call might be involved in validating the piece of data.
The SampleEntityValidationManager keeps track of the timers that were launched and for which purpose, thanks to a dictionary of DispatcherTimer (keys) and ValidationInfo (values) objects. The use of DispatcherTimer objects guarantees that the validation results will be handed off to the binding engine via the ErrorsChanged event on the UI thread. The application-specific ValidationInfo class allows the entity validation manager to figure out which entity or entity property needs to be validated when a timer expires. The dictionary also comes handy when the entity validation manager is requested to cancel a validation.
The SocialSecurityNumber and Person classes are two concrete implementations of SampleEntity. All the heavy lifting is done in the base SampleEntity and Entity classes – the concrete implementations are minimalist.
The SocialSecurityNumberValidator and PersonValidator classes derive from SampleEntityValidator and define concrete validation rules for their respective entity types. When validators need to report an error or warning, they instantiate a new DataErrorInfo object and hand it off to the offending entity via the IValidatedEntity.AddError(…) method.
MainPage.xaml is simply the startup page of the application which shows a default person in a read-only fashion. The “Edit SSN” button brings up the simple SocialSecurityNumberInputWindow child window, while the “Edit Person” button brings up the more elaborate PersonInputWindow child window. The potential changes made to the social security number or person are saved when the child window was closed with the OK button.
The SocialSecurityNumberInputWindow derives from EntityInputWindow. It does not do any UI customization and treats warnings as regular errors. The DataErrorInfo.ToString() method is invoked to show the red tooltips, and only the first error Validation.Errors[0] shows up.
Note that due to a bug in the current Silverlight SDK DatePicker control, the Issue Date field will not show any red validation failure cues.

Another EntityInputWindow concrete implementation. However this time, warnings are treated differently. They show up orange instead of red and they don’t disable the OK button. That means the person can be saved with existing warnings. The XAML for the TextBox, ComboBox, DatePicker validation tooltips is customized to fully take advantage of the DataErrorInfo richness. That customization also results in showing multiple errors/warnings per field instead of the default singleton. A custom DataTemplate is also specified for the two ValidationErrorViewer.ErrorTemplate properties in order to match the look of the validation tooltips.
Here are some basic scenarios you can run through to provoke validation failures. Each example assumes you start from scratch.
Comments (0)