Powered by

US - English
NEW! Silverlight 5 is available Learn More

Implementing Data Validation in Silverlight with INotifyDataErrorInfo

By Microsoft Silverlight Team|November 24, 2009|Level 300 : Intermediate

Introduction

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.

A few words about Silverlight's new support for IDataErrorInfo

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.

Now to the new kid in town, INotifyDataErrorInfo

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:

  • A business object can expose more than one error per property.
  • A business object can expose more than one top-level error.
  • A convenient HasErrors property indicates whether the object is currently valid or not.
  • The error representation is no longer limited to strings. The objects returned by GetErrors can be of any type. In fact GetErrors can return a heterogeneous set of objects. It is recommended though that those objects override the ToString() method and return a user-friendly text. This method is indeed invoked by default by the Silverlight binding engine to represent the error in the UI. Other consumers of the interface may do the same. You can get away with not overriding that method if you customize the way the error objects are consumed by the user interface. More on that subject further down.

What does it all mean for the user experience?

The user interface layer, if it’s so inclined, can take advantage of these improvements.

  • It can display cues for several validation errors next to a single input field.
  • It can display cues for several top-level errors simultaneously.
  • It can display a general validity cue for the object based on the HasErrors property.
  • It can consume application-specific error objects to convey richer information than just a message string. For example, the custom error object can convey whether the validation failure is critical or a mere warning.

Three kinds of developers will be using this interface:

  1. Developers who build business objects that implement the interface
  2. Application developers who consume the interface (in this case, the HasErrors property in particular)
  3. Framework or 3rd party component developers who consume the 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.

Deep dive into each INotifyDataErrorInfo member

The GetErrors method

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.

The ErrorsChanged event

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.

The HasErrors property

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.

Consumers can ignore INotifyCollectionChanged

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, but the Silverlight binding engine for one will ignore that notification mechanism. The consumer relies on the ErrorsChanged event being raised whenever a set of errors has changed.

Dealing with cross-property errors

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:

  1. Treat the error as a top-level error. Raise ErrorsChanged with e.PropertyName null or empty to report the error. The UI is likely to show the error in a centralized location for top-level errors.
  2. Associate the error to a single property, for example ShipmentDate. Raise ErrorsChanged with e.PropertyName set to “ShipmentDate”. The UI is likely going to show a cue next to the Shipment Date input control.
  3. Associate the error to all inconsistent properties. Raise ErrorsChanged with e.PropertyName set to “OrderDate” and again with e.PropertyName set to “ShipmentDate”. The UI is likely going to show a cue next to both offending input controls. The user can correct any of the two fields.

Exploring how the Silverlight binding engine consumes the new interface

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:

Binding to a property

<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.

Binding to an object

<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.

Binding to both a property and object

<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?

  • Typically the target of the binding is a FrameworkElement, or it has a FrameworkElement mentor when it is a DependencyObject. The Validation.Errors attached property associated to that FrameworkElement gets updated automatically by the binding engine according to the INotifyDataErrorInfo implementation.
  • For each error object exposed by INotifyDataErrorInfo.GetErrors(…), a ValidationError object gets added to the Validation.Errors collection, in the same order. The error object provided by the business object is pushed ‘as is’ into the ValidationError.ErrorContent property. Unlike for exception-based validation errors, the ValidationError.Exception property remains null.
  • If that FrameworkElement happens to be a Control with a "ValidationStates" state group, it will automatically go into the "InvalidUnfocused" or "InvalidFocused" state when its attached Validation.Errors becomes none-empty and return to the "Valid" state when all the errors are fixed. See http://msdn.microsoft.com/en-us/library/system.windows.visualstatemanager(VS.95).aspx for more information about the VisualStateManager.
  • Each ValidationError.ErrorContent value from a Validation.Errors collection can be consumed by the UI and shown to the user. For example the built-in red tooltip that appeared in the Silverlight 3 release shows the first ValidationError.ErrorContent value (or ValidationError.ErrorContent.ToString() if needed), and ignores the subsequent ones.
  • If the Binding.NotifyOnValidationError flag is set to True as well, the binding engine makes sure that the FrameworkElement mentioned above raises its BindingValidationError event for each error that gets added or removed from the associated Validation.Errors collection.

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.

When to use exception, IDataErrorInfo, or INotifyDataErrorInfo-based data validation in Silverlight

Exception-based validation comes with these limitations:

  • The error reporting has to be synchronous.
  • The scenario “Binding to an object” above can’t be achieved since the reference to the object is not likely to change.
  • The reporting is limited to one error per property.
  • Purist’s complaint: an input error is not an exception.

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.

Analysis of the sample application

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:

  • ValidationControls: A set of reusable base classes
  • INotifyDataErrorInfoSample: A concrete use of those base classes to showcase a couple of input forms with synchronous and asynchronous data validations

Digging into the ValidationControls’s reusable classes

Entity class

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.

DataErrorInfo class

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.

EntityInputWindow class

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:

  • Enabling / disabling the OK button according to the validity status of its top-level entity and potential nested entities. Both frontend and backend errors are taken into account.

  • Momentarily disabling input controls while the final validation occurs.
  • Showing a visual cue when an asynchronous validation is in progress.
  • Triggering the validation of the top-level entity when a nested entity property changed.

ValidationErrorViewer control

The ValidationErrorViewer is a custom ContentControl that can be used to visualize two types of errors (or warnings):

  • Entity-level errors.
  • Property-level errors of a nested entity.

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.

Taking those classes for a spin

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 SocialSecurityNumber editing form that uses the default Silverlight error reporting look.
  • a Person editing form that uses a customized look for error and warning reporting.

A few words about each building piece of the application.

SampleEntity.cs and SampleEntityValidator.cs

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.

SampleEntityValidationManager.cs and ValidationInfo.cs

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.

SocialSecurityNumber.cs and Person.cs

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.

SocialSecurityNumberValidator.cs and PersonValidator.cs

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 & .cs

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.

SocialSecurityNumberInputWindow.xaml & .cs

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.

PersonInputWindow.xaml & .cs

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.

Let’s make some mistakes

Here are some basic scenarios you can run through to provoke validation failures. Each example assumes you start from scratch.

Scenarios for the SSN form:

  • Type in 222 instead of 111 and tab off ? cross-field errors

  • Type in 700 instead of 111, 1/18/1969 instead of 1/18/1949 and tab off ? cross-field errors, plus entity-level error

  • Type in abc instead of 111 and tab off ? frontend property-level error

  • Type in 0 instead of 11 and tab off ? backend property-level error

Scenarios for the Person form:

  • Empty the Full Name field and tab off ? backend property-level error
  • Type abc instead of 111 and tab off ? frontend property-level error
  • Type 12/8/1950 instead of 12/8/1948 and tab off ? Person entity-level error
  • Type 222 instead of 111 and tab off ? cross-field warnings
  • Type 1000 instead of 111 and tab off ? multiple property-level errors
  • Type 222 instead of 111, select Delaware instead of NewYork ? Person raises a property warning for Person.SocialSecurityNumber
  • Type 700 instead of 111, 1/18/1969 instead of 1/18/1949, select Other instead of NewYork ? SSN entity-level warning, plus Person property-level warning
Microsoft Silverlight Team

By Microsoft Silverlight Team, Silverlight is a powerful development platform for creating engaging, interactive user experiences for Web, desktop, and mobile applications when online or offline.

Comments (0)