ValidatingModelBase - canton7/Stylet GitHub Wiki
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 big 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 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.
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 IModelValidator
. The intention is that you write your own implementation of IModelValidator
, 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<IEnumerable<string>> ValidatePropertyAsync(string propertyName);
Task<Dictionary<string, IEnumerable<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 IModelValidator
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, IModelValidator<T>
, which merely extends IModelValidator
, and adds nothing extra. This is, again, useful when IoC contains come into the picture - more on that later.
First, you have to remember to pass your IModelValidator
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(IModelValidator 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 ValidateProperty("PropertyName")
, or ValidateProperty(() => this.PropertyName)
, if you want. There are also asynchronous versions of these if your validations are asynchronous - more on this later. If you want to validate a single property whenever it is set, you can do something like this:
private string _name
public string Name
{
get { return this._name; }
set
{
SetAndNotify(ref this._name, value);
ValidateProperty();
}
}
Additionally, you can run validations on all properties by calling Validate()
.
If you wish to run some custom code whenever the validation state changes (any property's validation errors change), override OnValidationStateChanged()
.
In the next couple of sections, 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 (without the help of ValidatingModelBase), we'd do something like:
public UserViewModel(UserViewModelValidator validator)
{
this.Validator = validator;
}
// ...
this.Validator.Validate(this);
However, the point of using ValidatingModelBase is that it will automate running and reporting the validations. As discussed earlier, we'll need to wrap up our UserViewModelValidator
in a way that ValidatingModelBase know how to interact with.
The easiest way to doing this is to write an adapter which can take any implementation of IValidator<T>
(i.e. any custom validator which you've written), and expose it in a way which ValidatingModelBase understands. In case you've got lost, I'll run over the intended class hierarchies again:
- ValidatingModelBase.Validator is an IModelValidator
- UserViewModelValidator is an IValidator
- We will write an adapter, FluentValidationAdapter, which is an IModelValidator
- FluentValidationAdapter will accept an IValidator, and wrap it up so that it can be accessed through IModelValidator
- Therefore, FluentValidationAdapter will take a UserViewModelValidator, and expose it as an IModelValidator;
Make sense so far? This may sound like a lot of work, but we can get our IoC container to do most of the heavy lifting, as we'll see soon.
Now, what will this look like in practice? First, remember when I said that IModelValidator<T>
is defined as an interface which just implements IModelValidator
? I'm not going to show you why just yet, but keep in mind that they're basically synonyms.
// Define the adapter
public class FluentValidationAdapter<T> : IModelValidator<T>
{
public FluentValidationAdapter(IValidator<T> validator)
{
// Store the validator
}
// Implement all IModelValidator methods, using the stored validator
}
// This implements IValidator<UserViewModel>
public class UserViewModelValidator : AbtractValidator<UserViewModel>
{
public UserViewModelValidator()
{
// Set up validation rules
}
}
public class UserViewModel
{
public UserViewModel(IModelValidator<UserViewModel> validator) : base(validator)
{
// ...
}
}
There! If we were going to instantiate a new UserViewModel
by hand, we'd do this:
var validator = new UserViewModelValidator();
var validatorAdapter = new FluentValidationAdapter<UserViewModel>(validator);
var viewModel = new UserViewModel(validatorAdapter);
However, we can configure the IoC container to do this for us. This assumes you're using StyletIoC, although other containers can be configured similarly.
In your ConfigureIoC
override in your bootstrapper, first tell StyletIoC to return a FluentValidationAdapter<T>
whenever you ask for an IModelValidator<T>
, by doing this:
builder.Bind(typeof(IModelValidator<>)).To(typeof(FluentValidationAdapter<>));
So, whenever StyletIoC creates a new UserViewModel
, it will realise that it needs an IModelValidator<UserViewModel>
. It knows that it's been told how to create an IModelValidator<T>
- by instantiating a new FluentValidationAdapter<T>
. So it will try and create a new FluentValidationAdapter<UserViewModel>
, see that that requires a new IValidator<UserViewModel>
, and fall over because it can't find one.
Therefore, we need to tell StyletIoC how to create a new IValidator<UserViewModel>
. We could do this the long way, by doing this:
// The long way
builder.Bind<IValidator<UserViewModel>>().To<UserViewModelValidator>();
However, if you have many validators, you'll need many lines of configuration. It's better to tell StyletIoC to discover all IValidator<T>
implementations, and bind them itself, by this doing:
// The short way
builder.Bind(typeof(IValidator<>)).ToAllImplementations();
There we go! When StyletIoC tries to create a new FluentValidationAdapter<UserViewModel>
, it will see that it needs an IValidator<UserViewModel>
, and will instantiate a new UserViewModelValidator
.
Now you can see why we use IModelValidator<T>
instead of IModelValidator
here. Had UserViewModel
required an IModelValidator
, StyletIoC wouldn't have been able to work out that it should create a FluentValidationAdapter<UserViewModel>
, rather than, say, a FluentValidationAdapter<LogInViewModel>
. By adding type information to IModelValidator
, we give the IoC container enough information to work with.
I've written the following IModelValidator implementations, which you're welcome to use:
If you write one, and you're happy to share it, please let me know and I'll add it.
Writing an IModelValidator
implementation is conceptually straightforward, but has a few gotchas. As before, this section will assume we're implementing an adapter for the FluentValidation library, although you can apply the knowledge gained here to write an adapter for almost any library.
For now, let's off assume that all of our validations are synchronous. For the methods which return Tasks, we'll just return a completed task. Easy.
First off, we'll implement IModelValidator<T>
for reasons discussed in the previous section. It will also need to accept a IValidator<T>
, as a constructor argument, like so:
public class FluentValidationAdapter : IModelValidator<T>
{
private readonly IValidator<T> validator;
public FluentValidationAdapter(IValidator<T> validator)
{
this.validator = validator;
}
}
Remember that ValidatingModelBase
wants an IModelValidator
which is specialised to validating a particular ViewModel instance, as it adds more flexibility. This means that ValidationModelBase
can call ValidateAllPropertiesAsync()
, and the correct ViewModel instance will be validated. However, here we have a chicken-and-egg situation - in order to specialise the adapter, the ViewModel must exist. However, the ViewModel can't be instantiated until after the adapter has been validated, as the ViewModel requires the adapter as a constructor argument.
The solution is the Initialize(object subject)
method. This is called by ValidatingModelBase
when it's passed a new adapter, and it will pass itself as the argument. The adapter will then store this instance, and use it when it's running validations. Like this:
public class FluentValidationAdapter : IModelValidator<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;
}
}
Now, implementing ValidatePropertyAsync
. This should validate a single property, and return a list of validation errors, or null/emptyarray if there are none. Using FluentValidation to perform synchronous validation, this might look like this:
public Task<IEnumerable<string>> ValidatePropertyAsync(string propertyName)
{
var errors = this.validator.Validate(this.subject, propertyName).Errors.Select(x => x.ErrorMessage);
return Task.FromResult(errors);
}
Similarly, the ValidateAllPropertiesAsync
method validates all properties, and returns a Dictionary of { propertyName => array of validation errors }
. If a property doesn't have any validation errors, you can either omit it from the Dictionary entirely, or have its value set to null/emptyarray.
public Task<Dictionary<string, IEnumerable<string>>> ValidateAllPropertiesAsync()
{
var errors = this.validator.Validate(this.subject).Errors.GroupBy(x => x.PropertyName).ToDictionary(x => x.Key, x => x.Select(failure => failure.ErrorMessage));
return Task.FromResult(errors);
}
Put that all together, and you have your adapter!
Implementing asynchronous validation (for libraries which support it is a bit trickier).
First, remember that ValidatingModelBase
has both a set of synchronous methods (Validate
, ValidateProperty
) and asynchronous methods (ValidateAsync
, ValidatePropertyAsync
). Under the hood, the synchronous versions call the asynchronous versions, but block the thread until the asynchronous operation has completed (using Task.Wait()
).
Now, if you're used tasks much, this should be setting off alarm bells. You see, when you await DoSomethingAsync(); DoSomethingElse();
, you're saying "capture the current thread [*]. when the DoSomethingAsync()
asynchronous operation completes, I want you post a message to that captured thread, telling it to run DoSomethingElse()
". However, if that thread is waiting for the asynchronous operation to complete, it will never receive that message, the operation will never complete, and you have deadlock.
[*] not entirely true - it captures the current SynchronizationContext
. On the UI thread, this amounts to the same, though.
Put another way, this means that the following code, run from the UI thread, will deadlock:
public async Task DoSomethingAsync()
{
await Task.Delay(100);
}
// ...
DoSomethingAsync().Wait();
DoSomethingElse();
When the Task.Delay(100)
task completes, it will post a message back to the UI thread saying "right, run DoSomethingElse()
". However, the UI thread is stuck on that Wait()
, will never process the message, and you're deadlocked.
Why is this relevant? Well. If you write an IModelValidator<T>
method which looks like this:
public async Task<IEnumerable<string>> ValidatePropertyAsync(string propertyName)
{
var result = await this.Validator.ValidateAsync(this.subject, propertyName);
return result.Errors.Select(x => x.ErrorMessage);
}
Then call ValidateProperty
, you will deadlock.
The trick is to tell await
not to capture the current thread, using ConfigureAwait(false)
, i.e.
public async Task<IEnumerable<string>> ValidatePropertyAsync(string propertyName)
{
var result = await this.Validator.ValidateAsync(this.subject, propertyName).ConfigureAwait(false);
return result.Errors.Select(x => x.ErrorMessage);
}
Now, the return result.Errors...
line will be run on another thread (rather than posted to the UI thread), and no deadlock occurs.