What is New in WPF 4.5 (InotifyDataErrorInfo): Part 1

In the .Net Framework 4.5 Windows Presentation Foundation (WPF) introduced some new features and improvements in various areas.

One new interface is introduced named “InotifyDataErrorInfo” that can be found in “System.ComponentModel.INotifyDataErrorInfo ” that supports synchronous and asynchronous data validation.

  • Namespace: System.ComponentModel.
  • Assembly: System (in System.dll).
Note: It can also be found in Silverlight since version 4.

The INotifyDataErrorInfo interface enables data entity classes to implement custom validation rules and expose validation results asynchronously.

InotifyDataErrorInfo also supports: 
    • Custom error objects
    • Multiple errors per property
    • Cross-property errors
    • Entity-level errors
public interface
                                                               Figure 1

properties method event
                                                                          Figure 2

geterror
                                                                                 Figure 3

GetError Method

It returns the IEnumerable object that contains the validation errors for any specific property or all properties.

Error Change Event

Always raise the ErrorChange event whenever the collection is returned by the GetError method.

Note

If the source of a two-way data binding implements the INotifyDataErrorInfo interface and the “ValidatesOnNotifyDataErrors” property of the binding is set to true (which it is by default), the WPF 4.5 binding engine automatically monitors the “ErrorsChanged” event and calls the “GetErrors” method to retrieve the updated errors once the event is raised from the source object, provided that the “HasErrors” property returns true.

Let's have a quick example to see how this interface works in WPF 4.5

Objective

The user needs to validate the Username, Email and Re-type email on user interface (WPF).

aspx page

user detail

Code
  1. public class UserInput : ValidatableModel  
  2. {  
  3.         private string _userName;  
  4.         private string _email;  
  5.         private string _repeatEmail;  
  6.   
  7.         [Required]  
  8.         [StringLength(20)]  
  9.         public string UserName  
  10.         {  
  11.             get { return _userName; }  
  12.             set { _userName = value; RaisePropertyChanged("UserName"); }  
  13.         }  
  14.   
  15.         [Required]  
  16.         [EmailAddress]  
  17.         [StringLength(60)]  
  18.         public string Email  
  19.         {  
  20.             get { return _email; }  
  21.             set { _email = value; RaisePropertyChanged("Email"); }  
  22.         }  
  23.   
  24.         [Required]  
  25.         [EmailAddress]  
  26.         [StringLength(60)]  
  27.         [CustomValidation(typeof(UserInput), "SameEmailValidate")]  
  28.         public string RepeatEmail  
  29.         {  
  30.             get { return _repeatEmail; }  
  31.             set { _repeatEmail = value; RaisePropertyChanged("RepeatEmail"); }  
  32.         }  
  33.   
  34.         public static ValidationResult SameEmailValidate(object obj, ValidationContext context)  
  35.         {  
  36.             var user = (UserInput)context.ObjectInstance;  
  37.             if (user.Email != user.RepeatEmail)  
  38.             {  
  39.                 return new ValidationResult("The emails are not equal"new List<string> { "Email""RepeatEmail" });  
  40.             }  
  41.             return ValidationResult.Success;  
  42.         }  
  43.     }  
  44.   
  45.   
  46.   
  47.   
  48.  public class ValidatableModel : INotifyDataErrorInfo, INotifyPropertyChanged  
  49.     {  
  50.         private ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();  
  51.   
  52.         public event PropertyChangedEventHandler PropertyChanged;  
  53.   
  54.         public void RaisePropertyChanged(string propertyName)  
  55.         {  
  56.             var handler = PropertyChanged;  
  57.             if (handler != null)  
  58.                 handler(thisnew PropertyChangedEventArgs(propertyName));  
  59.             ValidateAsync();  
  60.         }  
  61.   
  62.         public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;  
  63.   
  64.         public void OnErrorsChanged(string propertyName)  
  65.         {  
  66.             var handler = ErrorsChanged;  
  67.             if (handler != null)  
  68.                 handler(thisnew DataErrorsChangedEventArgs(propertyName));  
  69.         }  
  70.   
  71.         public IEnumerable GetErrors(string propertyName)  
  72.         {  
  73.             List<string> errorsForName;  
  74.             _errors.TryGetValue(propertyName, out errorsForName);  
  75.             return errorsForName;  
  76.         }  
  77.   
  78.         public bool HasErrors  
  79.         {  
  80.             get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }  
  81.         }  
  82.   
  83.         public Task ValidateAsync()  
  84.         {  
  85.             return Task.Run(() => Validate());  
  86.         }  
  87.   
  88.         private object _lock = new object();  
  89.         public void Validate()  
  90.         {  
  91.             lock (_lock)  
  92.             {  
  93.                 var validationContext = new ValidationContext(thisnullnull);  
  94.                 var validationResults = new List<ValidationResult>();  
  95.                 Validator.TryValidateObject(this, validationContext, validationResults, true);  
  96.   
  97.                 foreach (var kv in _errors.ToList())  
  98.                 {  
  99.                     if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))  
  100.                     {  
  101.                         List<string> outLi;  
  102.                         _errors.TryRemove(kv.Key, out outLi);  
  103.                         OnErrorsChanged(kv.Key);  
  104.                     }  
  105.                 }  
  106.   
  107.                 var q = from r in validationResults  
  108.                         from m in r.MemberNames  
  109.                         group r by m into g  
  110.                         select g;  
  111.   
  112.                 foreach (var prop in q)  
  113.                 {  
  114.                     var messages = prop.Select(r => r.ErrorMessage).ToList();  
  115.   
  116.                     if (_errors.ContainsKey(prop.Key))  
  117.                     {  
  118.                         List<string> outLi;  
  119.                         _errors.TryRemove(prop.Key, out outLi);  
  120.                     }  
  121.                     _errors.TryAdd(prop.Key, messages);  
  122.                     OnErrorsChanged(prop.Key);  
  123.                 }  
  124.             }  
  125.         }  
  126.     }  
Output

main window

Move the cursor to the Username, Email and Re-type email TextBoxes.

require field

email field require

Re-type the wrong email.

retype email