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

First, you have to remember to pass your IValidatorAdapter implementation to ValidatingModelBase. You can do this either by setting the validator property, or by calling an appropriate constructor:

public class MyViewModel : ValidatingModelBase
{
   public MyViewModel(IValidatorAdapter validator) : base(validator)
   {
   }
}

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

Implementing IValidatorAdapter

I'm going to take you through an example of implementing validation, using the very useful FluentValidation library.

FluentValidation works by you creating a new class, which implements IValidator<T> (you'll usually do this by extending AbstractValidator<T>, and which can validate a particular sort of model T. You then create a new instance of this, and use it to run your validations. So for example if you have a UserViewModel, you'll define a UserViewModelValidator which extends AbstractValidator<UserViewModel, and therefore implements IValidator<UserViewModel>, like this:

public class UserViewModel : Screen
{
   private string _name;
   public string Name
   {
      get { return this._name; }
      set { SetAndNotify(ref this._name, value); }
   }
}

public class UserViewModelValidator : AbstractValidator<UserViewModel>
{
   public UserViewModelValidator()
   {
      RuleFor(x => x.Name).NotEmpty();
   }
}

If we were using the UserViewModelValidator directly, we'd do something like:

   public UserViewModel(UserViewModelValidator validator)
   {
      this.validator = validator;
   }
   // ...
   this.validator.Validate(this);

However, we're not using UserViewModelValidator directly, we're wrapping it up and passing it to ValidatingModelBase, so it can use it.

Therefore we need to write an adapter for our UserViewModelValidator. In fact, we'll write an adapter for an IValidator<T> (remember that UserViewModelValidator implements IValidator<UserViewModelValidator>), so that it can be re-used for any ViewModel type.

This might look something like this:

public class FluentValidationAdapter<T> : IValidatorAdapter<T>
{
    private readonly IValidator<T> validator;
    private T subject;

    public FluentValidationAdapter(IValidator<T> validator)
    {
        this.validator = validator;
    }

    public void Initialize(object subject)
    {
        this.subject = (T)subject;
    }

    public async Task<string[]> ValidatePropertyAsync(string propertyName)
    {
        return (await this.validator.ValidateAsync(this.subject, propertyName)).Errors.Select(x => x.ErrorMessage).ToArray();
    }

    public async Task<Dictionary<string, string[]>> ValidateAllPropertiesAsync()
    {
        return (await this.validator.ValidateAsync(this.subject)).Errors
            .GroupBy(x => x.PropertyName)
            .ToDictionary(x => x.Key, x => x.Select(failure => failure.ErrorMessage).ToArray());
    }
}

Notice how this class accepts an IValidator<T> as a constructor argument, but the UserViewModel instance as a parameter to the Initialize method? We'll come to this in a minute.

Then, we'll write our UserViewModel constructor to accept an instance of IValidatorAdapter<UserViewModel>, and pass it down to the ValidatingModelBase constructor, like this:

public class UserViewModel
{
   // ...
   public UserViewModel(IValidatorAdapter<UserViewModel> validator) : base(validator)
   {
   }

   // ...
}

Now it's time to configure out IoC container.

When we request an IValidatorAdapter<UserViewModel> above, we want the IoC container to construct us a new FluentValidatorAdapter<UserViewModel>. Since FluentValidatorAdapter's constructor requires an IValidator, we'll also want the IoC container to instantiate a new UserViewModelValidator` here.

This is achievable in a few simple lines, in your bootstrapper:

public class Bootstrapper : Bootstrapper<ShellViewModel>
{
   public override void ConfigureIoC(IStyletIoCBuilder builder)
   {
      builder.Bind(typeof(IValidatorAdapter<>)).To(typeof(FluentValidatorAdapter<>));
      builder.Bind(typeof(IValidator<>)).ToAllImplementations();
   }
}

The first of these lines tells StyletIoC that whenever we request an IValidatorAdpater<T>, for any T, it should create a new FluentValidatorAdapter<T>.

The second line tells StyletIoC to search through the current assembly, and discover all implementations of IValidator<T>. It will find UserViewModelValidator, and realise that it implements IValidator<UserViewModel>. When we ask StyletIoC for a new IValidaotr<UserViewModel>, therefore, it will instantiate us a new UserViewModelValidator.

For more information, see StyletIoC Configuration.

Clone this wiki locally