Validating our ViewModel

One of the key features that need to be implemented in a Line of Business application. Although Silverlight supports validation very well it’s not that easy compared to enabling Data Binding your ViewModel.

When we want our ViewModel to be fully Data Bindable we implement the INotifyPropertyChanged interface. It’s really an easy interface with an Event that needs to be called when the value of a property has changed. A lot of people don’t implement this interface themselves but use a framework like MVVM Light. I’m using that as well.

To make the validation work in a similar way we will have to implement INotifyDataErrorInfo. This is similar but I haven’t found a framework implementing it for me, so I wrote my own implementation of a ViewModelBase that both supports INotifyPropertyChanged and INotifyDataErrorInfo.

INotifyDataErrorInfo interface

Declaring the validation rules

Now that we have a ViewModel which supports validation we need to use it in our ViewModels. Let’s say that we have a LoginViewModel, which requires a Username and Password to be filled in.

So we need to declare our properties Username and Password as Required. Besides that we also want the message text to be changed to something different than the default, so we set the ErrorMessage property.

public class Login : ViewModelBase
{
    private string _password;
    private bool _rememberMe;
    private string _userName;

    [Required(AllowEmptyStrings = false, ErrorMessage = "Username is required")]
    public string UserName
    {
        get { return _userName; }
        set
        {
            if (_userName != value)
            {
                ValidateProperty("UserName", value);
                _userName = value;
                base.RaisePropertyChanged("UserName");
            }
        }
    }

    [Required(AllowEmptyStrings = false, ErrorMessage = "Password is required")]
    public string Password
    {
        get { return _password; }
        set
        {
            if (_password != value)
            {
                ValidateProperty("Password", value);
                _password = value;
                base.RaisePropertyChanged("Password");
            }
        }
    }

    public bool RememberMe
    {
        get { return _rememberMe; }
        set
        {
            if (_rememberMe != value)
            {
                _rememberMe = value;
                base.RaisePropertyChanged("RememberMe");
            }
        }
    }
}

Alright this was the usage of one of the most rudimentary validation types. A small overview of all the different supported validation types.

- RequiredAttribute used to check if a property has a value and will return invalid in case of null, empty string, or only white space characters.

- DataTypeAttribute like it’s name the data type validator validates a value against certain ‘data types’: DateTime, Date, Time, Duration, PhoneNumber, Currency, Text, Html, MultilineText, EmailAddress, Password, Url, ImageUrl.

- RangeAttribute is used to validate int, decimal and double values which need to fall between certain min and max values.

- RegularExpressionAttribute can be used for more advanced scenario’s where you can write the full regular expression which should be used to validate against.

- StringLengthAttribute can be used to validate the length of a string in characters, you can specify both the maximum length and minimum length, but one of them is also possible. Be aware that a null value will be seen as valid!

- CustomValidationAttribute used in the most complex validation scenario’s. The CustomValidationAttribute needs to be used in conjunction with a static method which returns a ValidationResult. This static method can be used to implement your very complex validation rules.

And if the above validation types don’t fit, you can always derive from the ValidationAttribute and write your own implementation. It’s important to know that in case of a null-value the properties won’t be validated. This is something that’s not always helpful, specially in case of the CustomValidation where you want to implement a conditional required field validation. The Required validation does get fired in case of null-value.

Tools to enforce our ValidationAttributes

We have the Validator class that has a few static methods that can validate an object that’s decorated with ValidationAttributes. There is a method for validating a single property, which is handy in case you want immediate feedback on the change of the value of the property. And there is of course a method for validating the full object.

Time to implement the INotifyDataErrorInfo

To give everyone a clear understanding the implementation of INotifyDataErrorInfo will be a step by step implementation. If you’re only interested in the solution, you can of course skip this, and go to the end of the article.

ErrorsChanged event:

To start with the easy stuff, we need a way to notify that there are some errors. So besides the ErrorsChanged event we need a method to fire ErrorsChanged.

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

protected void NotifyErrorsChanged(string propertyName)
{
    if (ErrorsChanged != null)
        ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}

GetErrors method and HasErrors property:

The GetErrors method needs to return errors for a specific property, so what we need is someway to store the errors. To store the errors I added a dictionary where the key would be the property name and the value would be a list of errror messages. GetErrors is in that case easily implemented by just returning data based on the key.

Further more the HasErrors is a matter of looking whether or not the dictionary has any data.

private readonly IDictionary<string, IList<string>> _errors = new Dictionary<string, IList<string>>();

public IEnumerable GetErrors(string propertyName)
{
    if (_errors.ContainsKey(propertyName))
    {
        IList<string> propertyErrors = _errors[propertyName];
        foreach (string propertyError in propertyErrors)
        {
            yield return propertyError;
        }
    }
    yield break;
}

public bool HasErrors
{
    get { return _errors.Count > 0; }
}

But how do we get some errors in the Dictionary?

As you have seen the implementation of INotifyDataErrorInfo is really straightforward, but we don’t do any validation yet. One of the scenario’s that’s very neat I would say is the ability to immediately give feedback to our users about validation problems. So when the value of a property is set (through two way binding for example) we want to validate immediately.

That’s not particularly difficult if we compare it to the normal MVVM property signature we only need to Validate a property. To keep it similar to the MVVM Light RaisePropertyChanged, we have to include the property name and value as well.

[Required(AllowEmptyStrings = false, ErrorMessage = "Username is required")]
public string UserName
{
    get { return _userName; }
    set
    {
        if (_userName != value)
        {
            ValidateProperty("UserName", value);
            _userName = value;
            base.RaisePropertyChanged("UserName");
        }
    }
}

Now we of course want to have an implementation for ValidateProperty. This implementation needs to validate only a specific property value against the rules for that property. We can make use of the Validator class that implements the actual checking against the validation rules. Together with two helper methods to add and remove validation messages from Errors collection we have the following code. We make use of the Validator to validate a property, and after we have the results we notify that Errors have been changed.

protected void ValidateProperty(string propertyName, object value)
{
    ViewModelBase objectToValidate = this;
    var results = new List<ValidationResult>();
    bool isValid = Validator.TryValidateProperty(
        value,
        new ValidationContext(objectToValidate, null, null)
            {
                MemberName = propertyName
            },
        results);

    if (isValid)
        RemoveErrorsForProperty(propertyName);
    else
        AddErrorsForProperty(propertyName, results);

    NotifyErrorsChanged(propertyName);
}

private void AddErrorsForProperty(string propertyName, IEnumerable<ValidationResult> validationResults)
{
    RemoveErrorsForProperty(propertyName);
    _errors.Add(propertyName, validationResults.Select(vr => vr.ErrorMessage).ToList());
}

private void RemoveErrorsForProperty(string propertyName)
{
    if (_errors.ContainsKey(propertyName))
        _errors.Remove(propertyName);
}

This works very well in the conjunction with the View, but somehow we also need to check if the full ViewModel is valid. This is especially important in when we hit for example the buttons that need to save our data to a “repository”. Besides the Validator.TryValidateProperty there also is a Validator.TryValidateObject. So let’s write a similar operation as the ValidateProperty, but now something we are able to use on Commands, so it needs to return a boolean where or not the ViewModel is valid. Again most of the magic is in the Validator.TryValidateObject, afterwards we split the full list of ValidationResults per property, to give feedback to our View.

public bool ValidateObject()
{
    ViewModelBase objectToValidate = this;
    _errors.Clear();
    var results = new List<ValidationResult>();
    bool isValid = Validator.TryValidateObject(objectToValidate,
                                                new ValidationContext(objectToValidate, null, null), results,
                                                true);
    if (!isValid)
    {
        IEnumerable<string> propertiesWithMessage = results.SelectMany(vr => vr.MemberNames);
        foreach (string property in propertiesWithMessage)
        {
            IEnumerable<ValidationResult> messages = results.Where(vr => vr.MemberNames.Contains(property));
            AddErrorsForProperty(property, messages);
            NotifyErrorsChanged(property);
        }
    }
    return isValid;
}

Problem to solve

Next is a problem we will need to solve. When the value of a property is null none of the validation attributes will be enforced, except the RequiredAttribute. This is really a problem for our CustomValidationAttribute when we implement a rule that should do something special in case of a null value.

After a better investigation I found out that all validation attributes are enforced when we validate a single property with the Validator class. Which isn’t happening when we use the full object validation implemented by the Validator class.

So why not solve it ourselves, by executing the Validate property method on all properties? Good idea, so I implemented it in our ViewModelBase. It’s pretty straightforward the first get all the properties, and then filter these to get the properties which have ValidationAttributes applied. End will be the call to validate the property for that specific value in line 12.

public bool ValidateObject()
{
    ViewModelBase objectToValidate = this;
    _errors.Clear();
    Type objectType = objectToValidate.GetType();
    PropertyInfo[] properties = objectType.GetProperties();
    foreach (PropertyInfo property in properties)
    {
        if (property.GetCustomAttributes(typeof (ValidationAttribute), true).Any())
        {
            object value = property.GetValue(objectToValidate, null);
            ValidateProperty(property.Name, value);
        }
    }

    return !HasErrors;
}

Full source

As always the full source of my ViewModelBase, which enhances the functionality of the MVVM Light ViewModelBase.

public abstract class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase, INotifyDataErrorInfo
{
    private readonly IDictionary<string, IList<string>> _errors = new Dictionary<string, IList<string>>();

    protected ViewModelBase()
    {
    }

    protected ViewModelBase(IMessenger messenger)
        : base(messenger)
    {
    }

    #region INotifyDataErrorInfo Members

    public IEnumerable GetErrors(string propertyName)
    {
        if (_errors.ContainsKey(propertyName))
        {
            IList<string> propertyErrors = _errors[propertyName];
            foreach (string propertyError in propertyErrors)
            {
                yield return propertyError;
            }
        }
        yield break;
    }

    public bool HasErrors
    {
        get { return _errors.Count > 0; }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    #endregion

    protected void NotifyErrorsChanged(string propertyName)
    {
        if (ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }

    protected void ValidateProperty(string propertyName, object value)
    {
        ViewModelBase objectToValidate = this;
        var results = new List<ValidationResult>();
        bool isValid = Validator.TryValidateProperty(
            value,
            new ValidationContext(objectToValidate, null, null)
                {
                    MemberName = propertyName
                },
            results);

        if (isValid)
            RemoveErrorsForProperty(propertyName);
        else
            AddErrorsForProperty(propertyName, results);

        NotifyErrorsChanged(propertyName);
    }

    private void AddErrorsForProperty(string propertyName, IEnumerable<ValidationResult> validationResults)
    {
        RemoveErrorsForProperty(propertyName);
        _errors.Add(propertyName, validationResults.Select(vr => vr.ErrorMessage).ToList());
    }

    private void RemoveErrorsForProperty(string propertyName)
    {
        if (_errors.ContainsKey(propertyName))
            _errors.Remove(propertyName);
    }

    public bool ValidateObject()
    {
        ViewModelBase objectToValidate = this;
        _errors.Clear();
        Type objectType = objectToValidate.GetType();
        PropertyInfo[] properties = objectType.GetProperties();
        foreach (PropertyInfo property in properties)
        {
            if (property.GetCustomAttributes(typeof (ValidationAttribute), true).Any())
            {
                object value = property.GetValue(objectToValidate, null);
                ValidateProperty(property.Name, value);
            }
        }

        return !HasErrors;
    }
}