Get Validation Results

1.7k Views Asked by At

I am using the EntLib 6 Validation Block with WPF integration. Simple Property in my VM:

    [StringLengthValidator(3, MessageTemplate = "Shorten me!")]
    public String SomeText
    {
        get { return _someText; }
        set
        {
            _someText = value;
            OnPropertyChanged("SomeText");
        }
    }

And a corresponding Binding to a TextBox:

<TextBox ToolTip="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" 
         Text="{Binding SomeText, UpdateSourceTrigger=PropertyChanged}" 
         vab:Validate.BindingForProperty="Text"/>

If you enter more than three characters into the TextBox, the value is rejected and the last valid one is stored. The TextBox is highlighted in red and the corresponding message is displayed as ToolTip.

Within the VM I would like to check if there are any Validation Errors - but since the value is rejected in the View, everything seems to be fine. So how do I determine if there was a Validation Error?

ATTENTION: the VAB does NOT use the IDataErrorInfo Interface!

1

There are 1 best solutions below

1
On

I don't know of a clean and straightforward way to get the validation results from your view model when you're using WPF's built-in validation APIs. However, while VAB may not use IDataErrorInfo out of the box, you could add integration fairly easily, and you would only need to modify your base view model class. You could start with something like this:

public class ValidatingModel : INotifyPropertyChanged, IDataErrorInfo
{
    private readonly Dictionary<string, PropertyInfo> _properties;
    private readonly Dictionary<string, Validator> _propertyValidators;
    private readonly Dictionary<string, ValidationResults> _validationResults;

    private string _compositeMessage;

    public ValidatingModel()
    {
        _properties = new Dictionary<string, PropertyInfo>();
        _propertyValidators = new Dictionary<string, Validator>();
        _validationResults = new Dictionary<string, ValidationResults>();

        PopulateValidators();
    }

    private void PopulateValidators()
    {
        var properties = GetType().GetProperties(
            BindingFlags.Instance |
            BindingFlags.Public);

        foreach (var property in properties)
        {
            var attributes = property.GetCustomAttributes(
                typeof(ValueValidatorAttribute),
                false);

            if (attributes.Length == 0 || _properties.ContainsKey(property.Name))
                continue;

            _properties[property.Name] = property;

            _propertyValidators[property.Name] =
                PropertyValidationFactory.GetPropertyValidatorFromAttributes(
                    property.PropertyType,
                    property,
                    string.Empty,
                    new MemberAccessValidatorBuilderFactory());
        }
    }

    protected IEnumerable<ValidationResult> GetValidationResults()
    {
        foreach (var results in _validationResults.Values)
        {
            foreach (var result in results)
                yield return result;
        }
    }

    protected IEnumerable<ValidationResult> GetValidationResults(string property)
    {
        if (_propertyValidators.ContainsKey(property))
        {
            ValidationResults results;

            if (!_validationResults.TryGetValue(property, out results))
                Validate(property);

            if (!_validationResults.TryGetValue(property, out results))
                yield break;

            foreach (var result in results)
                yield return result;
        }
    }

    protected void Validate(string propertyName)
    {
        if (_propertyValidators.ContainsKey(propertyName))
        {
            _compositeMessage = null;
            _validationResults[propertyName] = Validation.Validate(this);
        }
    }

    string IDataErrorInfo.this[string columnName]
    {
        get
        {
            ValidationResults results;

            if (!_validationResults.TryGetValue(columnName, out results))
                Validate(columnName);

            if (_validationResults.TryGetValue(columnName, out results))
                return CombineMessages(results);

            return null;
        }
    }

    string IDataErrorInfo.Error
    {
        get
        {
            if (_compositeMessage != null)
                return _compositeMessage;

            foreach (var validator in _propertyValidators)
            {
                if (_validationResults.ContainsKey(validator.Key))
                    continue;

                _validationResults[validator.Key] = ValidateProperty(
                    validator.Value,
                    _properties[validator.Key]);
            }

            _compositeMessage = CombineMessages(
                _validationResults.SelectMany(r => r.Value));

            return _compositeMessage;
        }
    }

    private ValidationResults ValidateProperty(
        Validator validator,
        PropertyInfo propertyInfo)
    {
        return validator.Validate(propertyInfo.GetValue(this, null));
    }

    private static string CombineMessages(IEnumerable<ValidationResult> results)
    {
        return results.Aggregate(
            new StringBuilder(),
            (sb, p) => (sb.Length > 0 ? sb.AppendLine() : sb).Append(p.Message),
            sb => sb.ToString());
    }

    protected void OnPropertyChanged(string propertyName)
    {
        Validate(propertyName);

        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

This view model performs validation using the Validation Application Block APIs whenever a property changes, and reports the results via IDataErrorInfo. You'd need to set ValidatesOnDataErrors on your Binding declarations for this to work.