WPF Binding : Use DataAnnotations for ValidationRules
Asked Answered
M

5

13

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 ?

Moat answered 4/2, 2011 at 21:17 Comment(3)
Here's a blogpost about this kind of validation but context dependent (Translated from french to english)Moat
Here's another blogpost about showing tooltip for this annotation (Translated from french to english)Moat
Look at the Video Enterprise MVVM in WPF: ViewModel Validation using Data Annotations. I think it is a nice solution for your Problem.Hammertoe
M
8

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.

Moat answered 7/2, 2011 at 19:48 Comment(1)
You should know, that your implementation of IDataErrorInfo will not be called before value is assigned. So if another object is subscribed to PropertyChanged of your DTO, then they will use value which soon might be marked as invalid by your code.Militarism
B
5

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.

Bedazzle answered 4/2, 2011 at 21:24 Comment(6)
It's working fine, but it does use the DataAnnotations. That would be a beauty.Moat
@Philippe You can do it in SL msdn.microsoft.com/en-us/library/dd901590(VS.95).aspx however not in WPF out of the box; it can be done though...updated answer...Bedazzle
So you're saying that the mechanism to check the DataAnnotation exist only in SilverLight and not in WPF ? I try the blog-post example. It works, but you must manually check all the DataAnnotation in every Properties. I was searching if WPF framework was somehow hable to do like SilverLight and auto check the DataAnnotations on my properties.Moat
@Philippe Yes; the built in behavior is SL; not WPFBedazzle
The link to the tutorial is deadHornbeck
@Hornbeck Updated link using the web archiveBedazzle
L
0

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 ;)

Lector answered 4/2, 2011 at 21:48 Comment(0)
H
0

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.

Hyphen answered 5/2, 2011 at 15:27 Comment(0)
F
0

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; }
}
Fleecy answered 12/12, 2013 at 21:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.