Archive

Archive for April, 2010

Implementing validation in your ViewModels

April 7, 2010 1 comment

It’s easy to implement validation in WPF. If your ViewModel implements IDataErrorInfo interface from the System.ComponentModel namespace, then WPF will take care of all the heavy-lifting for you… you’ll probably find your VM looking something like this:

#region IDataErrorInfo Members

        public string Error { get; private set; }

        public string this[string columnName]
        {
            get
            {
                Error = null;

                switch (columnName)
                {
                    case "Name":
                        if (string.IsNullOrEmpty(Name))
                            Error = "Name cannot be blank";
                        break;
                    case "Age":
                        if (Age < 0)
                            Error = "Age cannot be less than zero";
                        break;
                }

                return Error;
            }
        }

        #endregion

Now, to get this validation to appear in the View, we just need to add an attribute to our property binding, like this:

<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />

Now if the field fails validation, WPF will (by default) draw a red box around the invalid field, so it will look like this:

Invalid text box

So this is all lovely, but if you have dozens and dozens of ViewModels that each contain dozens of properties that need validating, then wouldn’t it be much nicer to simplify the process? Well, the good news is that we can…

Firstly, we will create an abstract base class for our ViewModels, and make this class implement IDataErrorInfo. Now we will use custom attributes to perform the actual property validation, so we can decorate the VM properties in a single line…

We’ll probably have a collection of common validation rules, for which we will create a custom attribute, so we’ll create a ValidationAttribute base class, like this:

        [AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
        protected abstract class ValidPropertyAttribute : Attribute
        {
            protected string _errorMessage;

            public ValidPropertyAttribute(string errorMessage)
            {
                _errorMessage = errorMessage;
            }

            public abstract string ValidateProperty<T>(T value);
        }

Just a couple of things to note here:
- We have constrained our attributes to only apply to Properties on the ViewModel class – Visual Studio will alert us at design-time if we attempt to decorate a method or a field.
- We have allowed for multiple attributes to be applied to a single property.
- The base constructor takes a string parameter to hold the ‘error message’ – this is what will be passed back to the user if validation fails
- We have included an abstract generic method ValidateProperty – this is where the calling code will pass in the property value to be validated. This will need to be implemented on all concrete Attributes that inherit this base class.

A most common validation would be that a string property cannot be blank… our custom attribute for this validation will look like this:

        protected class PropertyCannotBeEmptyStringAttribute : ValidPropertyAttribute
        {
            public PropertyCannotBeEmptyStringAttribute(string errorMessage)
                : base(errorMessage) { }

            public override string ValidateProperty<T>(T value)
            {
                string stringValue = value as string;
                if (string.IsNullOrEmpty(stringValue))
                    return _errorMessage;
                return null;
            }
        }

So the ValidateProperty method simply passes in the parameter, this gets cast as a string, and if the string is null or empty, we return the _errorMessage stored in the base class. Nice, eh?

Finally, to wire all this up, we need to join the dots between the IDataErrorInfo interface and our custom attributes. To do this, we need to put the following code in the ViewModelBase:

        public string Error { get; private set; }

        public string this[string columnName]
        {
            get { return Validate(columnName); }
        }

        protected string Validate(string propertyName)
        {
            var property = GetType().GetProperty(propertyName);
            if (property == null)
                throw new ArgumentException("propertyName must indicate a valid property on the object");

            Error = null;
            foreach (ValidPropertyAttribute attribute in property.GetCustomAttributes(typeof(ValidPropertyAttribute), true))
            {
                var err = attribute.ValidateProperty(property.GetValue(this, null));
                if (err == null) continue;

                if (Error == null) Error = err;
                else Error += string.Format("{\r\n{0}", err);
            }
            return Error;
        }

Now it’s all wired up, we can simply decorate the ViewModel’s property like this:

        [PropertyCannotBeEmptyString("Please specify a name")]
        public string Name
        {
            get { return _name; }
            set
            {
                if (value == _name) return;
                _name = value;
                RaisePropertyChanged(() => Name);
            }
        }

Source code: Rename file extension from .doc to .zip
ValidationSample.doc

Categories: Uncategorized Tags: , , ,
Follow

Get every new post delivered to your Inbox.