WPF Binding : Use DataAnnotations for ValidationRules

19.3k views Asked by At

I have read a lot of Blog post on WPF Validation and on DataAnnotations. I was wondering if there is a clean way to use DataAnnotations as ValidationRules for my entity.

So instead of having this (Source) :

<Binding Path="Age" Source="{StaticResource ods}" ... >
  <Binding.ValidationRules>
    <c:AgeRangeRule Min="21" Max="130"/>
  </Binding.ValidationRules>
</Binding>

Where you must have your

public class AgeRangeRule : ValidationRule 
{...}

I want the WPF Binding to go see the Age property and look for DataAnnotation a bit like this:

[Range(1, 120)]
public int Age
{
  get { return _age; }
  set
  {
    _age = value;
    RaisePropertyChanged<...>(x => x.Age);
  }
}

Any ideas if this is possible ?

5

There are 5 answers

1
Philippe Lavoie On BEST ANSWER

The closest approach I found is :

// This loop into all DataAnnotations and return all errors strings
protected string ValidateProperty(object value, string propertyName)
{
  var info = this.GetType().GetProperty(propertyName);
  IEnumerable<string> errorInfos =
        (from va in info.GetCustomAttributes(true).OfType<ValidationAttribute>()
         where !va.IsValid(value)
         select va.FormatErrorMessage(string.Empty)).ToList();


  if (errorInfos.Count() > 0)
  {
    return errorInfos.FirstOrDefault<string>();
  }
  return null;

Source

public class PersonEntity : IDataErrorInfo
{

    [StringLength(50, MinimumLength = 1, ErrorMessage = "Error Msg.")]
    public string Name
    {
      get { return _name; }
      set
      {
        _name = value;
        PropertyChanged("Name");
      }
    }

public string this[string propertyName]
    {
      get
      {
        if (porpertyName == "Name")
        return ValidateProperty(this.Name, propertyName);
      }
    }
}

Source and Source

That way, the DataAnnotation works fine, I got a minimum to do on the XAML ValidatesOnDataErrors="True" and it's a fine workaround of Aaron post with the DataAnnotation.

0
Patrick Schimmel On

Recently I've had the same idea using the Data Annotation API to validate EF Code First POCO classes in WPF. Like Philippe's post my solution uses reflection, but all necessary code is included in a generic validator.

internal class ClientValidationRule : GenericValidationRule<Client> { }

internal class GenericValidationRule<T> : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  {
    string result = "";
    BindingGroup bindingGroup = (BindingGroup)value;
    foreach (var item in bindingGroup.Items.OfType<T>()) {
      Type type = typeof(T);
      foreach (var pi in type.GetProperties()) {
        foreach (var attrib in pi.GetCustomAttributes(false)) {
          if (attrib is System.ComponentModel.DataAnnotations.ValidationAttribute) {
            var validationAttribute = attrib as System.ComponentModel.DataAnnotations.ValidationAttribute;
            var val = bindingGroup.GetValue(item, pi.Name);
            if (!validationAttribute.IsValid(val)) { 
              if (result != "")
                result += Environment.NewLine;
              if (string.IsNullOrEmpty(validationAttribute.ErrorMessage))
                result += string.Format("Validation on {0} failed!", pi.Name);
              else
                result += validationAttribute.ErrorMessage;
            }
          }
        }
      }
    }
    if (result != "")
      return new ValidationResult(false, result);
    else 
      return ValidationResult.ValidResult;
  }
}

The code above shows a ClientValidatorRule which is derived from the generic GenericValidationRule class. The Client class is my POCO class which will be validated.

public class Client {
    public Client() {
      this.ID = Guid.NewGuid();
    }

    [Key, ScaffoldColumn(false)]
    public Guid ID { get; set; }

    [Display(Name = "Name")]
    [Required(ErrorMessage = "You have to provide a name.")]
    public string Name { get; set; }
}
0
jbe On

You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It uses the DataAnnotations Validation attributes together with WPF Binding.

6
Aaron McIver On

In your model you could implement IDataErrorInfo and do something like this...

string IDataErrorInfo.this[string columnName]
{
    get
    {
        if (columnName == "Age")
        {
            if (Age < 0 ||
                Age > 120)
            {
                return "You must be between 1 - 120";
            }
        }
        return null;
    }
}

You will also need to notify the binding target of the newly defined behavior.

<TextBox Text="{Binding Age, ValidatesOnDataErrors=True}" />

EDIT:

If you only want to use Data Annotations you can follow this blog post which outlines how to accomplish the task.

UPDATE:

Historical representation of the aforementioned link.

0
Youp Bernoulli On

Sounds good Aaron. I'm just into WPF and will study databindings next week at work ;) So cannot completely judge your answer...

But with winforms I have used Validation Application Block from the Entlib and implemented IDataErrorInfo (actually IDXDataErrorInfo because we work with DevExpress controls) on a base entity (business object) and that works pretty fine!

It's a bit more sophisticated than the solution you sketched in this way that you place your validation logic on the object and not in the interface implementation. Making it more OOP and maintainable. At the ID(XD)ataErrorInfo I just call Validation.Validate(this), or even better get the validator for the property that the interface is called for and validate the specific validator. Don't forget to call the [SelfValidation] as well because of validation for combinations of properties ;)