Skip to content

ValidatingModelBase

Antony Male edited this page May 2, 2014 · 12 revisions

Introduction

Imagine the scene... The user is filling out a form you've painstakingly written, and they enter their name where they should have entered their email address. You need to detect this, and display the problem in a clear way.

Input validation is a bit area, and there are many ways to go about it. The easiest and most appealing is to throw an exception in the setter for your property, like this:

private string _name;
public string Name
{
   get { return this._name; }
   set
   {
      if (someConditionIsFalse)
         throw new ValidationException("Message");
      this._name = value;
   }

When the binding sets this property, it notices if an exception is thrown, and updates the validation state of the control appropriately.

This, however, ends up being a thoroughly bad idea. It means that your property can only be validated when it's set (you can't go through and validate the whole form when the user clicks 'Submit', for example), and it leads to big fat property setters with lots of duplicated logic. Horrible.

C# also defines two interfaces, both of which WPF knows about: IDataErrorInfo and INotifyDataErrorInfo. Both of these provide a means for the ViewModel to tell the View, through events and PropertyChanged notifications, that one or more properties have one or more validation errors. Of these, INotifyDataErrorInfo is newer, easier to use, and allows for asynchronous validation.

However, driving INotifyDataErrorInfo is still a bit unintuitive: it allows you to broadcast the fact that one or more properties have errors, but provides no easy means for you to run your validations, and requires that you keep a record of what errors are associated with which properties.

ValidatingModelBase aims to solve this, and provide an intuitive and easy way of running and reporting your validations.

ValidatingModelBase

ValidatingModelBase derives from PropertyChangedBase, and is inherited by Screen. It builds on PropertyChangeBase's ability to notice when properties have changed to run and report your validations.

IValidatorAdapter

There are many ways to run validations, and many good libraries out there to help you. It isn't Stylet's intention to provide another validation library, so instead Stylet allows you to provide your own validation library to be used by ValidatingModelBase.

This manifests itself in ValidatingModelBase's validator property, which is an IValidatorAdapter. The intention is that you write your own implementation of IValidatorAdapter, which wraps your preferred validation library (I'll cover some examples of how to do this later), so that it can be used by ValidatingModelBase.

This interface has two important methods:

Task<string[]> ValidatePropertyAsync(string propertyName);
Task<Dictionary<string, string[]>> ValidateAllPropertiesAsync();

The first one is called by ValidatingModelBase when it wants to validate a single property by name, and returns an array of validation errors. The second one is called by ValidatingModelBase when you ask it to do a full validation, and returns a dictionary of property name => array of validation errors.

The fact that these methods are asynchronous allows you to take advantage of INotifyDataErrorInfo's asynchronous validation capabilities, and run your validations on some external service if you wish. However, it's expected that most implementations of this interface will just return a completed Task.

There's also a third method:

void Initialize(object subject);

This is called by ValidatingModelBase when it is setting up its validation for the first time, and it passes in an instance of itself. This allows the implementation of IValidationAdapter to specialise itself for validating that particular instance of ValidatingModelBase. This has more relevance when we tie things into StyletIoC, see later.

There's also a generic version of this interface, IValidatorAdapter<T>, which merely extends IValidatorAdapter, and adds nothing extra. This is, again, useful when IoC contains come into the picture - more on that later.

Running Validations

By default, ValidatingModelBase will run the validations for a property whenever that property changes (provided you call SetAndNotify, use NotifyOfPropertyChange, or use PropertyChanged.Fody to raise a PropertyChanged notification using the mechanisms defined in PropertyChangedBase). It will then report any changes in the validation state of that property using the mechanisms defined in the INotifyDataErrorInfo interface. It will also change the value of the HasErrors property.

If you want to disable this auto-validation behaviour, set the autoValidate property to false.

You can manually run validations for a single property by calling ValidatePropertyAsync("PropertyName"), or ValidatePropertyAsync(() => this.PropertyName), if you want.

Additionally, you can run validations on all properties by calling ValidateAsync().

If you wish to run some custom code whenever the validation state changes (any property's validation errors change), override OnValidationStateChanged().

Clone this wiki locally