Silverlight basics. Validation. Part 2. IDataErrorInfo & INotifyDataErrorInfo
- modified:
- reading: 6 minutes
At previous my blog post I wrote about how to implement validation with DataAnnotations. I this part of my article I will describe interfaces IDataErrorInfoand INotifyDataErrorInfo. I recommend you to read first part before read this part. Because I use example from previous part.
Few words about ValidationOnExceptions
Forgot to tell you, if you want implement validation with exceptions, you may not use DataAnnotations, you can throw your own exceptions from set methods. For example, you can implement check of password confirmation like this:
```csharp [Display(Name = "New password confirmation")] public string NewPasswordConfirmation { get { return _newPasswordConfirmation; } set { _newPasswordConfirmation = value; OnPropertyChanged("NewPasswordConfirmation"); ChangePasswordCommand.RaiseCanExecuteChanged(); if (string.CompareOrdinal(_newPassword, value) != 0) throw new Exception("Password confirmation not equal to password."); } } ```Looks better than CustomValidationAttribute.
IDataErrorInfo
The IDataErrorInfo interface came with Silverlight 4. If we want implement validation with this interface we should implement it in our ViewModel (one property and one method). Usually developers write own class handler, which store validation errors)
```csharp public class ValidationHandler { private DictionaryNow let’s rewrite our BindingModel class, we will inherit it from interface IDataErrorInfo and will implement it:
```csharp public class BindingModel : INotifyPropertyChanged, IDataErrorInfo { private string _newPassword; private string _newPasswordConfirmation; private readonly ValidationHandler _validationHandler = new ValidationHandler(); #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged = delegate { }; private void OnPropertyChanged(string propertyName) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } #endregion #region IDataErrorInfo public string this[string columnName] { get { if (_validationHandler.BrokenRuleExists(columnName)) { return _validationHandler[columnName]; } return null; } } public string Error { get { return throw new NotImplementedException(); } } #endregion } ```The main properties for our BindingModel will look like this:
```csharp [Display(Name = "New password")] public string NewPassword { get { return _newPassword; } set { _newPassword = value; OnPropertyChanged("NewPassword"); if (_validationHandler.ValidateRule("NewPassword", "New password required", () => !string.IsNullOrEmpty(value))) { _validationHandler.ValidateRule("NewPassword", "Max length of password is 80 symbols.", () => value.Length < 80); } ChangePasswordCommand.RaiseCanExecuteChanged(); } } [Display(Name = "New password confirmation")] public string NewPasswordConfirmation { get { return _newPasswordConfirmation; } set { _newPasswordConfirmation = value; OnPropertyChanged("NewPasswordConfirmation"); _validationHandler.ValidateRule("NewPasswordConfirmation", "Password confirmation not equal to password.", () => string.CompareOrdinal(_newPassword, value) == 0); ChangePasswordCommand.RaiseCanExecuteChanged(); } } ```Each call of VaidationRule checks some condition, and if it not true, then write info about this validation error in errors collection. After binding Silverlight infrastructure will call get method of indexing property this[string columnName] and it will return error information for this property (columnName). We should set property ValidatesOnDataErrors in our binding to true if we want to use this kind of validation. Property Error throw NotImplementedException because Silverlight doesn’t use it. Quotation from MSDN: “Note that the binding engine never uses the Error property, although you can use it in custom error reporting to display object-level errors.”
In end we should implement again methods for command:
```csharp public BindingModel() { ChangePasswordCommand = new DelegateCommand(ChangePassword, CanChangePassword); } public DelegateCommand ChangePasswordCommand { get; private set; } private bool CanChangePassword(object arg) { return !string.IsNullOrEmpty(_newPassword) && string.CompareOrdinal(_newPassword, _newPasswordConfirmation) == 0; } private void ChangePassword(object obj) { if (ChangePasswordCommand.CanExecute(obj)) { MessageBox.Show("Bingo!"); } } ```We again should use CanChangePassword method, because we need to set button to disabled state when object is invalid. We can’t check valid state of whole object before binding happened. Another problem with this realization: we should write validation rules twice: in property set methods and at CanChangePassword method. But this is problem of this realization; you can solve it with another realization: you can write some class ValidationHandler, which will store not only validation errors, but validation rules, so you can raise validation check in CanChangePassword method. But we still have a problem, we can’t tell to ValidationSummary or some other control at our interface that some validation errors happened or disappeared without binging.
Also you can use DataAnnotation for this approach, but you should write some helper method for that, I will tell you how in next example.
Result of IDataErrorInfo realization (Silverlight sample):
I think that behavior of this sample the same like in previous part. I want to say that this sample has bug, if user will input Password Confirmation first and then New Password, he will see validation error about password confirmation not equal, because this check happened only on NewPasswordConfirmation binding.
INotifyDataErrorInfo
The INotifyDataErrorInfointerface came with Silverlight 4 too. The main advantage of this interface you can do synchronous (like in previous samples) and asynchronous validation too. You can wait validation from server, and only after that tell to interface that all ok or some validation error occurred. I like this way of validation more than other. I will use some classes and realization from articles of Davy Brion about “MVP In Silverlight/WPF Series”.
First I got class PropertyValidation, with it we will store validation rule for properties and message which should be showed when validation error occurred.
```csharp public class PropertyValidationYou will understand how it works when we will start implement validation rules in our example. This class has generic parameter with base class BindingModelBase<T>, from which we will inherit our main BindingModel class.
Let’s implement BindingModelBase class, we will inherit it from INotifyPropertyChanged and INotifyDataErrorInfo interfaces, and will add two fields, one for store validation rules and one for store validation errors:
```csharp public abstract class BindingModelBaseAlso I want to add one helper method additional to OnPropertyChanged, it will be method which has Expression in parameters and will let us to use it like this:
```csharp public string NewPassword { get { return _newPassword; } set { _newPassword = value; OnCurrentPropertyChanged(); } } ```This method is very useful. Implementation of this method:
```csharp public string NewPassword { get { return _newPassword; } set { _newPassword = value; OnPropertyChanged(() => NewPassword); } } ```Implementation:
```csharp protected void OnPropertyChanged(ExpressionMethod get property name from expression. Next, let’s add some methods which will perform validation:
```csharp public void ValidateProperty(ExpressionMethod ValidateProperty delete information about all validation errors which was happened with current property, then check each rule for current property, and if some rule is false it will add error to errors collection. Also we can raise validation check for each property auto with PropertyChanged event:
```csharp protected BindingModelBase() { PropertyChanged += (s, e) => { if (e.PropertyName != "HasErrors") ValidateProperty(e.PropertyName); }; } ```For easy add validation rules to our collection we will add next method:
```csharp protected PropertyValidationNow we can implement BindingModel class, which we will use in our last example. If we want to implement validation with INotifyDataErrorInfo interface we should set to true property ValidatesOnNotifyDataErrorsin bindings.
BindingModel implementation:
```csharp public class BindingModel : BindingModelBaseIn ctor we describe all three validation rules for properties. Look’s very good (Thanks Davy Brion!). I told you that I don’t like to set disabled button, so from now it will be always enabled. For realization command’s method ChangePassword I need some method which will check all validation rules of current object, it will be ValidateAll method, which I will implement in BindingModelBase class:
```csharp public void ValidateAll() { var propertyNamesWithValidationErrors = _errorMessages.Keys; _errorMessages = new DictionaryThis method deletes all validation errors in collection. Then check validation rules, write errors if rule is false, and then raise OnErrorsChanged event for each property which has changed validation state.
Implementation of method ChangePassword:
```csharp private void ChangePassword(object obj) { ValidateAll(); if (!HasErrors) { MessageBox.Show("Bingo!"); } } ```Result (Silverlight application):
I like this realization. It much more flexible and it can use all advantages of previous variants. What about DataAnnotation? If you like to describe validation rules with data annotation attributes I can give you one more help method for that. This method will get all rules from attributes and convert them to PropertyValidation:
```csharp protected PropertyValidationAnd the last BindingModel variant:
```csharp public class BindingModel : BindingModelBaseSource code of these samples you can download from my assembla.com repository.
See Also
- Silverlight basics. Validation. Part 1. DataAnnotations & ValidatesOnExceptions
- Silverlight. Основы. Валидация. Часть 2. IDataErrorInfo & INotifyDataErrorInfo
- Silverlight. Основы. Валидация. Часть 1. DataAnnotations & ValidatesOnExceptions
- Как часто вы думаете о том, что пользователь может нажать Double-Click, где это не предусмотрено?
- Никогда не делайте классы и пространства имен с одним и тем же именем