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 ?

Philippe Lavoie
  • 2,583
  • 5
  • 25
  • 39
  • Here's a [blogpost](http://translate.google.com/translate?sl=fr&tl=en&js=n&prev=_t&hl=en&ie=UTF-8&layout=2&eotf=1&u=http%3A%2F%2Fwww.nmediasolutions.com%2Fblogue%2Fdeveloppement-dapplications%2Fvalidation-dpendante-du-contexte%2F&act=url) about this kind of validation but context dependent (Translated from `french` to `english`) – Philippe Lavoie Oct 28 '11 at 18:40
  • Here's another [blogpost](http://translate.google.com/translate?hl=en&sl=fr&tl=en&u=http%3A%2F%2Fwww.nmediasolutions.com%2Fblogue%2Fdeveloppement-dapplications%2Fvalidation-entite-message-erreur-infobulle-tooltip-wpf%2F) about showing tooltip for this annotation (Translated from `french` to `english`) – Philippe Lavoie Oct 28 '11 at 18:41
  • Look at the Video [Enterprise MVVM in WPF: ViewModel Validation using Data Annotations](http://www.youtube.com/watch?v=lool8Ut58Xw). I think it is a nice solution for your Problem. – Jonas Benz Mar 21 '14 at 14:07

5 Answers5

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.

Philippe Lavoie
  • 2,583
  • 5
  • 25
  • 39
  • 2
    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. – Lonli-Lokli Apr 01 '14 at 07:07
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.

Aaron McIver
  • 24,527
  • 5
  • 59
  • 88
  • It's working fine, but it does use the DataAnnotations. That would be a beauty. – Philippe Lavoie Feb 04 '11 at 21:54
  • @Philippe You can do it in SL http://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... – Aaron McIver Feb 04 '11 at 21:59
  • 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. – Philippe Lavoie Feb 07 '11 at 15:21
  • 1
    @Philippe Yes; the built in behavior is SL; not WPF – Aaron McIver Feb 07 '11 at 15:41
  • The link to the tutorial is dead – Ben Jun 29 '16 at 19:59
  • @Ben Updated link using the web archive – Aaron McIver Jul 14 '16 at 19:36
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; }
}
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 ;)

Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59
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.

jbe
  • 6,976
  • 1
  • 43
  • 34