VerifyPropertyName

Apr 30, 2010 at 8:09 PM

I'm having issues with VerifyPropertyName throwing ArgumentExceptions in a particular scenario. It looks like the Conditional attribute on VerifyPropertyName should keep it from executing, but it doesn't. Is the build type determined by my Silverlight project or by the project the assemblies were built from? If its the MVVMLight project, then it looks like the assemblies you published were built as Debug.

What I'm doing is a little different, so maybe there's a better way and I don't need to worry about this exception.

I decided to handle validation by creating a ValidatingViewModelBase class, which inherits from ViewModelBase and implements INotifyDataErrorInfo. I override RaisePropertyChanged and kick of validations by calling the FluentValidation validator for my view model. This all works fine for basic views and view models.

Today I'm working on a view that contains a list of items. My view model has an ObservableCollection of view models that represent those items. However, in order to get changes to those child view models to validate, I needed to handle their PropertyChanged events, and call RaisePropertyChanged on my parent view model. This is where the VerifyPropertyName throws an exception because the propertyName is from the child view model.

Maybe there's a better way to implement validation on a list of view models?

In case you're interested, here's what my ValidatingViewModelBase class looks like.

public class ValidatingViewModelBase<T> : ViewModelBase, INotifyDataErrorInfo
{
    private readonly IValidator<T> _validator;
    private readonly Dictionary<string, List<ValidationInfo>> _errors;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public ValidatingViewModelBase() : this(null, null)
    {
    }

    public ValidatingViewModelBase(IValidator<T> validator) : this(validator, null)
    {
    }

    public ValidatingViewModelBase(IValidator<T> validator, IMessenger messenger) : base(messenger)
    {
        _validator = validator;
        _errors = new Dictionary<string, List<ValidationInfo>>();
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
            return _errors.Values;
            
        CreateValidationErrorInfoListForProperty(propertyName);
        return _errors[propertyName];
    }

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

    protected virtual void AddValidationErrorForProperty(string propertyName, ValidationInfo validationInfo)
    {
        CreateValidationErrorInfoListForProperty(propertyName);

        if (!_errors[propertyName].Contains(validationInfo))
        {
            _errors[propertyName].Add(validationInfo);
            RaiseErrorsChanged(propertyName);
        }
    }

    protected virtual void ClearValidationErrorsForProperty(string propertyName)
    {
        CreateValidationErrorInfoListForProperty(propertyName);

        if (_errors[propertyName].Count > 0)
        {
            _errors[propertyName].Clear();
            RaiseErrorsChanged(propertyName);
        }
    }

    protected virtual void ClearAllValidationErrors()
    {
        foreach (var propertyName in _errors.Keys)
            ClearValidationErrorsForProperty(propertyName);
        _errors.Clear();
    }

    private void CreateValidationErrorInfoListForProperty(string propertyName)
    {
        if (!_errors.ContainsKey(propertyName))
            _errors[propertyName] = new List<ValidationInfo>();
    }

    protected void RaiseErrorsChanged(string propertyName)
    {
        var handler = ErrorsChanged;
        if (handler != null)
        {
            handler.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }

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

    protected virtual bool Validate()
    {
        if (_validator == null)
            return true;

        ClearAllValidationErrors();

        var results = _validator.Validate(this);

        if (!results.IsValid)
        {
            foreach (var failure in results.Errors)
            {
                AddValidationErrorForProperty(failure.PropertyName, 
                    new ValidationInfo(failure.ErrorMessage, ValidationType.Error));
            }
        }

        return results.IsValid;
    }
}

Coordinator
Apr 30, 2010 at 8:34 PM

Hey Matt,

Yes the assemblies published now are the debug ones. The conditional compiling is always a kind of bet on which assemblies to use in which configuration, not the first time I am burned by this. Unfortunately, there is no easy way to solve this. You are right to remind me that I should publish the release version of V3 though. I will do that ASAP. In the mean time, I will send them to you per email.

A quick fix in your (slightly unusual) situation would be to override RaisePropertyChanged in your ValidatingViewModelBase to avoid calling VerifiyPropertyName. The method is fairly simple, so overriding it seems like a good fix.

What do you think?

Cheers,

Laurent

Apr 30, 2010 at 8:46 PM

I'm already overriding RaisePropertyChanged from the ValidatingViewModelBase, but then I'm calling the base method. I think what you're suggesting is to raise the PropertyChanged event there instead of calling the RaisePropertyChanged method on ViewModelBase. That's certainly an option.

Are you aware of any best practices for validating on controls in an ItemsControl template? I can't seem to get the controls to show an invalid state, or get a ValidationSummary control to show the validation results.

Thanks!