Validation

MVVM Utils provides a set of classes that helps implementing validation.
  • ErrorContainer stores property errors
  • DataErrorInfoProvider exposes errors taken from IErrorContainer via IDataErrorInfo and INotifyDataErrorInfo
By combining these classes you can easily create your own ValidatiableViewModel that can support your requirements.

Sample

Below I will show a sample ValidatiableViewModel inherting MVVM Light's ViewModelBase which fires validation after invoking RaisePropertyChanged. When the validation sets errors then the ErrorContainer executes OnErrorsChanged which also invokes RaisePropertyChanged for notifying the view about the errors.

public abstract class ValidatableViewModel : ViewModelBase, IDataErrorInfo
{
	public const string ObjectErrorPropertyName = "";

	private readonly DataErrorInfoProvider dataErrorInfoProvider;

	protected ValidatableViewModel(ArrayFormat errorFormat = ArrayFormat.First)
	{
		ErrorsContainer = new ErrorsContainer();
		ErrorsContainer.ErrorsChanged += OnErrorsChanged;

		dataErrorInfoProvider = new DataErrorInfoProvider(ErrorsContainer, errorFormat, ObjectErrorPropertyName);
	}

	protected ValidatableViewModel(IMessenger messenger, ArrayFormat errorFormat = ArrayFormat.First)
		: base(messenger)
	{
		ErrorsContainer = new ErrorsContainer();
		ErrorsContainer.ErrorsChanged += OnErrorsChanged;

		dataErrorInfoProvider = new DataErrorInfoProvider(ErrorsContainer, errorFormat, ObjectErrorPropertyName);
	}

	protected ValidatableViewModel(IErrorsContainer<ValidationError> errorsContainer, ArrayFormat errorFormat = ArrayFormat.First)
	{
		ErrorsContainer = errorsContainer;
		ErrorsContainer.ErrorsChanged += OnErrorsChanged;

		dataErrorInfoProvider = new DataErrorInfoProvider(ErrorsContainer, errorFormat, ObjectErrorPropertyName);
	}

	public IErrorsContainer<ValidationError> ErrorsContainer { get; private set; }

	public virtual bool HasErrors
	{
		get { return ErrorsContainer.HasErrors; }
	}

	public virtual string Error
	{
		get
		{
			return dataErrorInfoProvider.Error;
		}
	}

	public virtual string this[string columnName]
	{
		get
		{
			return dataErrorInfoProvider[columnName];
		}
	}

	public void Validate()
	{
		IEnumerable<ValidationError> errors = Validation();
		ErrorsContainer.ClearAndSetErrors(errors);
	}

	protected abstract IEnumerable<ValidationError> Validation();

	protected override void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
	{
		base.RaisePropertyChanged(propertyExpression);
		Validate();
	}

	protected override void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression, T oldValue, T newValue, bool broadcast)
	{
		base.RaisePropertyChanged(propertyExpression, oldValue, newValue, broadcast);
		Validate();
	}

	protected override void RaisePropertyChanged<T>(string propertyName, T oldValue, T newValue, bool broadcast)
	{
		base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast);
		Validate();
	}

	protected override void RaisePropertyChanged(string propertyName)
	{
		base.RaisePropertyChanged(propertyName);
		Validate();
	}

	private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
	{
		OnErrorsChanged(e.PropertyName);
	}

	private void OnErrorsChanged(string propertyName)
	{
		if (propertyName == ObjectErrorPropertyName)
		{
			// object error
			propertyName = "Error";
		}

		// notify for IDataErrorInfo
		base.RaisePropertyChanged(propertyName);
		base.RaisePropertyChanged("HasErrors");
	}
}

And here is a sample ViewModel that supports validation

    public sealed class PersonViewModel : ValidatableViewModel
    {
        public PersonViewModel()
        {
            // validate on creation
            Validate();
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            set { Set(() => Name, ref _name, value); }
        }

        protected override IEnumerable<ValidationError> Validation()
        {
            if (string.IsNullOrWhiteSpace(Name))
            {
                return new [] { ValidationError.Create(() => Name, "Name cannot be empty") };
            }

            return null;
        }
    }

Last edited Oct 18, 2014 at 12:34 PM by Pellared, version 8

Comments

No comments yet.