Introduction
Validating user inputs and providing good feedback is very important to any product level software applications. This article shows how user inputs can be validated dynamically – meaning that some parts of the validation rules can change at run time – by using FluentValidation
and INotifyDataErrorInfo
in a WPF/MVVM application. The demo application written for the article solves the Euler Project’s problem 5 with a little twist – allowing the user to change the two numbers in the subject against which the program has to validate and provide feedback, hence demonstrating the capabilities.
Background
WPF has been supporting validations in the data binding pipeline since its inception – e.g., ValidationRule
s, ErrorTemplate
, and so on. There are, however, some issues with them. First of all, the binding engine throws a type-cast exception (and swallows it by default) when you bind the TextBox.Text
property to a numeric property, such as int
and double
, of the source object and the user types in some alpha characters or leaves the text box blank. As Josh Smith pointed out years ago, not all users can understand the default error messages that can be displayed in a ToolTip
as a result of the type-cast failure as shown below:
The first ToolTip
is displayed when you set the Binding.ValidatesOnExceptions
property to true and the second when false – i.e., doing nothing in the Binding
statement.
You could override the message by adding a custom ValidationRule
to the Binding
with the desired message such that it would stop the validation process before the Binding
engine tries to type-cast. Still, there can be a lot more ValidationRule
s you would want to add after a successful type-casting. For example, you might want to constrain the value within a certain range, or to validate the value whose valid range changes dynamically depending on some other property values. As the validation rules become more complex, they would not only scatter over multiple classes, but also be harder to debug.
That was when I found an open-source library called FluentValidation written by Jeremy Skinners that allows us to put all input validations on a bound object in a single class where every validation rule can be stated declaratively in a fluent syntax. So, I decided to start this project to see if the library could satisfy the following requirements:
- All validations, including type-cast exceptions, should be handled in a single place. This rules out the use of custom
ValidationRule
s. It also means that every validation must occur within the setter function of the string property, rather than int or double.
- The object that owns the string properties – be it a view model or model – should implement
INotifyDataErrorInfo
, rather than the older IDataErrorInfo
. The Binding engine does support IDataErrorInfo
. But to use it, we have to set Binding.ValidatesOnDataErrors
property to true. Also, its Error
property is never used. On the other hand, the Binding
engine supports INotifyDataErrorInfo
by default, without adding anything to the Binding
. All we have to do is to implement the interface in the bound object.
- Every error message that is relevant to a single UI element should be displayed in a
ToolTip
. The offensive UI element should be decorated by a red border as done by ErrorTemplate
by default.
- All error messages should be consolidated and displayed somewhere so that the user can immediately see the problems without hovering the mouse over the offensive UI elements, as is done by the Silverlight’s ValidationSummary, which is very neat but not implemented in WPF for some reason.
- Validation must take place whenever the user changes the texts. This means that the
Binding.UpdateSourceTrigger
property is set to PropertyChanged.
- Validation rules that spans over multiple properties should also be supported. For example, property A must be greater than property B + C.
- Runtime changes on validation rules should be observed. For example, property A’s allowable maximum value can change from 10 to 20 depending on some other situations at runtime. This rules out the use of
DataAnnotation
s where validation rules are attached as attributes of the property and so are fixed and cannot be changed at runtime.
Results were satisfactory and produced the following single class:
public class Problem5Validator : FluentValidator<Problem5>
{
private const string FromProperty = "'N'";
private const string ToProperty = "'M'";
private const string CannotBeLeftBlank = " cannot be left blank.";
private const string MustBeValidWholeNumber = " must be a valid whole number.";
private const string MustBeLessThan = " must be less than ";
private const string MustBeGreaterThan = " must be greater than ";
public Problem5Validator()
{
this.CascadeMode = CascadeMode.StopOnFirstFailure;
this.RuleFor(x => x.From)
.NotEmpty()
.WithMessage(FromProperty + CannotBeLeftBlank)
.Must(a => a.IsInteger())
.WithMessage(FromProperty + MustBeValidWholeNumber)
.Must(
(x, a) =>
{
var from = int.Parse(a);
return x.MinFrom <= from && from <= x.MaxFrom;
})
.WithMessage("{0} <= " + FromProperty + " <= {1}", x => x.MinFrom, x => x.MaxFrom)
.Must(
(x, a) =>
{
int to;
if (int.TryParse(x.To, out to) && x.MinTo <= to && to <= x.MaxTo)
{
return int.Parse(a) < to;
}
return true;
})
.WithMessage(FromProperty + MustBeLessThan + ToProperty + ".");
this.RuleFor(x => x.To)
.NotEmpty()
.WithMessage(ToProperty + CannotBeLeftBlank)
.Must(a => a.IsInteger())
.WithMessage(ToProperty + MustBeValidWholeNumber)
.Must(
(x, a) =>
{
var to = int.Parse(a);
return x.MinTo <= to && to <= x.MaxTo;
})
.WithMessage("{0} <= " + ToProperty + " <= {1}", x => x.MinTo, x => x.MaxTo)
.Must(
(x, a) =>
{
int from;
if (int.TryParse(x.From, out from) && x.MinFrom <= from && from <= x.MaxFrom)
{
return int.Parse(a) > from;
}
return true;
})
.WithMessage(ToProperty + MustBeGreaterThan + FromProperty + ".");
}
}
As you can see, all validations from the string-to-int type conversions to range validation to relation validation with the other property are handled within the same class. Clicking the "Change validation range" button will change the valid ranges and immediately revalidate the properties.
However, the intentional use of the string properties prevented me from taking advantage of the library’s built-in validators such as "GreaterThan()
" and "LessThan()
", much to my chagrin. I had to use "Must()
" with a lambda where the custom rules with explicit conversions from string to integer are implemented. Still, the library’s customizability and configuarability allowed me to do so without losing much of the "fluentness". You wouldn’t have this hassle if the properties are numeric, or there is no need to dynamically change some of the parameters of the validation rules.
Using the Code
The sample application consists of three projects – main UI, infrastructure, and business. MVVM is used throughout. No particular MVVM framework is used. The BindableBase
class and DelegateCommand
class in the infrastructure project can easily be replaced by the equivalent classes in your favorite MVVM framework such as Prism and MVVM Light.
Now, let’s start looking at the ValidatableBindableBase
class code that allows classes that inherit from it to validate properties whenever the setter is called with a new value:
public abstract class ValidatableBindableBase : BindableBase
{
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public abstract void ValidateAllProperties();
protected virtual bool SetPropertyAndValidateAllProperties<T>(
ref T storage,
T value,
[CallerMemberName] string propertyName = null)
{
var result = this.SetProperty(ref storage, value, propertyName);
if (result)
{
this.ValidateAllProperties();
}
return result;
}
protected virtual void OnErrorsChanged(DataErrorsChangedEventArgs e)
{
var handler = this.ErrorsChanged;
if (handler != null)
{
handler(this, e);
}
}
}
SetPropertyAndValidateAllProperties()
function calls ValidateAllProperties()
, that must be implemented in derived classes, when a different value is set to the property. It also houses ErrorsChanged
event invocator that is needed to support INotifyDataErrorInfo
. The INotifyPropertyChanged
is implemented in the BindableBase
class as is usual.
Problem5 – the model class
The Problem5
is a model class that inherits from ValidatableBindableBase
:
public class Problem5 : ValidatableBindableBase, INotifyDataErrorInfo
{
private readonly IValidator<Problem5> validator;
private string from;
private string to;
private string result;
public Problem5(IValidator<Problem5> validator)
{
this.validator = validator;
this.validator.ErrorsChanged += (s, e) => this.OnErrorsChanged(e);
this.ClearResult();
this.MaxFrom = 10;
this.MinFrom = 1;
this.MaxTo = 100;
this.MinTo = 2;
this.From = "1";
this.To = "20";
}
public int MaxFrom { get; set; }
public int MinFrom { get; set; }
public int MaxTo { get; set; }
public int MinTo { get; set; }
public string From
{
get
{
return this.from;
}
set
{
if (this.SetPropertyAndValidateAllProperties(ref this.from, value))
{
this.ClearResult();
}
}
}
public string To
{
get
{
return this.to;
}
set
{
if (this.SetPropertyAndValidateAllProperties(ref this.to, value))
{
this.ClearResult();
}
}
}
public string Result
{
get { return this.result; }
set { this.SetProperty(ref this.result, value); }
}
public bool HasErrors
{
get { return this.validator.HasErrors; }
}
public void Solve()
{
this.Result = Solver.Solve(int.Parse(this.From), int.Parse(this.To)).ToString("D");
}
public IEnumerable GetErrors(string propertyName)
{
return this.validator.GetErrors(propertyName);
}
public IList<string> GetAllErrors()
{
return this.validator.GetAllErrors();
}
public override void ValidateAllProperties()
{
this.validator.Validate(this);
}
private void ClearResult()
{
this.Result = string.Empty;
}
}
It has three string properties (From
, To
, and Result
) that can be directly data-bound to UI elements. It, of course, can solve the Euler project problem #5, which is the business purpose of this application. But the responsibility of validating the input properties and implementing the INotifyDataErrorInfo
interface is delegated to the validator object that is declared as a dependency in the constructor with the type of IValidator<Problem5>
, which has the following signature:
public interface IValidator<in T> : INotifyDataErrorInfo
{
IDictionary<string, string> Validate(T instance);
IList<string> GetAllErrors();
}
This tells us that the model class depends on (or uses) something that implements not only INotifyDataErrorInfo
but also two functions, one of which actually validates the entire model. The validator is supposed to raise the ErrorsChanged
event as a result of validation, if errors are actually changed. The Problem5
class just relays it so that the UI can show appropriate error messages.
Additionally, the class has four validation related parameters – MaxFrom
, MinFrom
, MaxTo
, and MinTo
that specify the valid ranges of the From
and To
parameters, respectively. Alternatively, you can implement those four validation related parameters inside the Problem5Validator
class, rather than in the model class. In that case, you will need to change lambdas in WithMessage()
such as “x => x.MinTo
” to “x => this.MinTo
”.
FluentValidator<T> class
The infrastructure project’s FluentValidator<T>
class inherits from the FluentValidation’s AbstractValidator<T>
and at the same time implements IValidator<T>
:
public class FluentValidator<T> : AbstractValidator<T>, IValidator<T>
{
private readonly Dictionary<string, string> errors = new Dictionary<string, string>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get { return this.errors.Count > 0; }
}
IDictionary<string, string> IValidator<T>.Validate(T instance)
{
var currentErrors = new Dictionary<string, string>(this.errors);
this.ValidateAndUpdateErrors(instance);
this.RaiseErrorsChangedIfReallyChanged(currentErrors, this.errors);
this.RaiseErrorsChangedIfReallyChanged(this.errors, currentErrors);
return this.errors;
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return this.GetAllErrors();
}
ThrowIfInvalidPropertyName(propertyName);
return this.ExtractErrorMessageOf(propertyName);
}
public IList<string> GetAllErrors()
{
return this.errors.Select(error => error.Value).ToList();
}
protected virtual void OnErrorsChanged(DataErrorsChangedEventArgs e)
{
var handler = this.ErrorsChanged;
if (handler != null)
{
handler(this, e);
}
}
private static void ThrowIfInvalidPropertyName(string propertyName)
{
var propertyInfo = typeof(T).GetRuntimeProperty(propertyName);
if (propertyInfo == null)
{
var msg = string.Format("No such property name '{0}' in {1}", propertyName, typeof(T));
throw new ArgumentException(msg, propertyName);
}
}
private void ValidateAndUpdateErrors(T instance)
{
this.errors.Clear();
var result = this.Validate(instance);
if (result.IsValid)
{
return;
}
foreach (var err in result.Errors)
{
this.errors.Add(err.PropertyName, err.ErrorMessage);
}
}
private void RaiseErrorsChangedIfReallyChanged(
IEnumerable<KeyValuePair<string, string>> errors1,
IReadOnlyDictionary<string, string> errors2)
{
foreach (var err in errors1)
{
var propertyName = err.Key;
var message = err.Value;
if (!errors2.ContainsKey(propertyName) || !errors2[propertyName].Equals(message))
{
this.RaiseErrorsChanged(propertyName);
}
}
}
private void RaiseErrorsChanged(string propertyName)
{
this.OnErrorsChanged(new DataErrorsChangedEventArgs(propertyName));
}
private IEnumerable ExtractErrorMessageOf(string propertyName)
{
var result = new List<string>();
if (this.errors.ContainsKey(propertyName))
{
result.Add(this.errors[propertyName]);
}
return result;
}
}
This class basically converts the AbstractValidator<T>
’s validation results so that the INotifyDataErrorInfo
can understand. The AbstractValidator<T>.Validate(T)
returns an object of ValidationResult
, which is a class defined in the FluentValidation libraray, has just two members (IsValid
Booelan and Errors
collection), and has nothing to do with a WPF’s class that bears the same name. The ValidateAndUpdateErrors()
shows how to harvest the validation results:
private void ValidateAndUpdateErrors(T instance)
{
this.errors.Clear();
var result = this.Validate(instance);
if (result.IsValid)
{
return;
}
foreach (var err in result.Errors)
{
this.errors.Add(err.PropertyName, err.ErrorMessage);
}
}
If the result of validation is not valid, we put the errors in the Dictionary<string, string>
errors dictionary whose key is the property name and the value is the actual error message in string.
On the other hand, INotifyDataErrorInfo
requires three members (HasErrors
Boolean, GetErrors(propertyName)
function that has to return an IEnumerable
, and ErrorsChanged
event). Implementing HasErrors is easy – just returning if the errors dictionary has any content. Implementing GetErrors(propertyName)
needs some thought because it has two modes – one with the propertyName
set to the actual property name, and the other with the parameter set to string.Empty
in which case we have to return all errors relevant to the bound object. The implementation distinguishes them clearly:
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return this.GetAllErrors();
}
ThrowIfInvalidPropertyName(propertyName);
return this.ExtractErrorMessageOf(propertyName);
}
In reality, the Binding
engine always calls GetErrors()
with the actual property name unless you use a BindingGroup
with a custom ValidationRule
. As I mentioned earlier, I decided not to use any ValidationRule
s. So, it is irrelevant. But having a method that returns all errors can be useful in other places.
The RaiseErrorsChangedIfReallyChanged()
implements the ErrorsChanged
event where the contents of the errors dictionary is compared before and after the Validate()
call.
I used the explicit interface implementation for the Validate(T instance)
function because the base class already has a function with the same name with a different return value.
Problem5Validator class
The Problem5Validator
inherits from FluentValidator<Problem5>
and states all of the validation rules that are relevant to the specific client object declaratively as can be seen in any FluentValidation examples and tutorials found on the Internet. The code was shown in the Background section of this article.
There are some things in the code that are worth mentioning.
CascadeMode.StopOnFirstFailure
By default, all cascaded validators that are attached to a particular property carries out validations. It would have been completely useless, if the library did not have the capability to change the mode. The validators that validate the numeric range and relation cannot execute if the string property cannot be recognized as a number. "Must(a => a.IsInteger())
" validator stops validation right there, if the property is not an integer.
- Accessing property value
The "Must()
" validator allows the user to access the property value in lambda.
- Accessing instance object
Both "Must()
" validator and "WithMessage()
" message formatter allow the user to access the instance object. This is how I access the valid ranges of the properties at runtime, contributing to dynamic validation that cannot be done with DataAnnotation
s.
MainWindowViewModel class
Finally, the MainWindowViewModel
class instantiates the Problem5 model object with the appropriate validator. When a DI container is available, as is the case with Prism and MVVM Light frameworks, you can just specify it in its constructor as a dependency. The view model exposes the object, two commands, and AllErrors
collection to the corresponding View class – i.e., MainWindow
. Whenever the ErrorsChanged
is raised, it replaces the AllErrors
collection and also controls the "Solve" button’s IsEnabled
property by raising the CanExecuteChanged
of the DelegateCommand
.
Note that AllErrors
collection does not need to be an ObservableCollection<string>
because it is simply replaced with a new one, rather than items are added/removed to/from the same collection object, every time errors are changed.
Conclusion
Avoiding the Binding
engine’s type-cast exceptions is, in my opinion, crucial in order to be able to handle all validations in a single location and to provide the user consistent error messages. To do so, all numeric properties of the models need to be exposed as strings, following Josh Smith’s recommendation. When doing so, WPF’s ValidationRule
objects have no place to serve. Instead, the FluentValidation library can nicely handle all validations, which are carried out in the bound properties’ setter functions. Once validated, the Binding
engine smoothly works with INotifyDataErrorInfo
that the bound model/view model objects implement so that we can easily show validation results in ToolTip
and in ItemsControl
.
I thank Jeremy Skinners for sharing such an excellent library with us.
Reference
History
1/4/2016: Initial post