Introduction
Silverlight 4 has some new ways for validating input values (some new approaches to implement validation in your application). The first approach is DataAnnotation. In this case, you should describe validation rules with attributes. Two other ways (both of them came with Silverlight 4) are: you should implement one of these interfaces for your ViewModel: IDataErrorInfo
or INotifyDataErrorInfo
. I want to talk about all of these approaches, and about the pros and cons of using each of them. The goal of this article is to find the best way to implement validation of input values in my and your applications.
Background
I have an example. I want to describe all of these approaches in a simple control: "change password".
It has twoPasswordBox
controls, a button, and a ValidationSummary
. Each sample will have its own ViewModel, but the XAML of the UserControl will be the same:
<UserControl x:Class="SilverlightValidation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:DataAnnotations="clr-namespace:SilverlightValidation.DataAnnotations"
xmlns:SilverlightValidation="clr-namespace:SilverlightValidation"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<DataAnnotations:BindingModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot"
Background="White" Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Right">New Password:</TextBlock>
<PasswordBox Grid.Column="1"
Password="{Binding Path=NewPassword, Mode=TwoWay,
ValidatesOnNotifyDataErrors=True, ValidatesOnExceptions=True,
ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
<TextBlock Grid.Row="1"
HorizontalAlignment="Right">New Password Confirmation:</TextBlock>
<PasswordBox Grid.Row="1" Grid.Column="1"
Password="{Binding Path=NewPasswordConfirmation, Mode=TwoWay,
ValidatesOnNotifyDataErrors=True, ValidatesOnExceptions=True,
ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
<Button Grid.Row="2" Grid.ColumnSpan="2"
HorizontalAlignment="Right" Content="Change"
Command="{Binding Path=ChangePasswordCommand}" />
<sdk:ValidationSummary Grid.Row="3" Grid.ColumnSpan="2" />
</Grid>
</UserControl>
I have set four properties referred to in the validation in bindings for the password boxes. For now, I will tall only about the NotifyOnValidationError
property. I use it for notifying ValidationSummary
that some validation errors exist.
PasswordBox
has only OneWay binding from the control to the source (security reasons). Binding works only when focus changes (the same as with a TextBox
). In WPF, you can change this behavior; you can set that binding should happen when the user presses some key (key down event). In Silverlight, you can do the same with an Attached Property:
public static class UpdateSourceTriggerHelper
{
public static readonly DependencyProperty UpdateSourceTriggerProperty =
DependencyProperty.RegisterAttached("UpdateSourceTrigger",
typeof(bool), typeof(UpdateSourceTriggerHelper),
new PropertyMetadata(false, OnUpdateSourceTriggerChanged));
public static bool GetUpdateSourceTrigger(DependencyObject d)
{
return (bool)d.GetValue(UpdateSourceTriggerProperty);
}
public static void SetUpdateSourceTrigger(DependencyObject d, bool value)
{
d.SetValue(UpdateSourceTriggerProperty, value);
}
private static void OnUpdateSourceTriggerChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && d is PasswordBox)
{
PasswordBox textBox = d as PasswordBox;
textBox.PasswordChanged -= PassportBoxPasswordChanged;
if ((bool)e.NewValue)
textBox.PasswordChanged += PassportBoxPasswordChanged;
}
}
private static void PassportBoxPasswordChanged(object sender, RoutedEventArgs e)
{
var frameworkElement = sender as PasswordBox;
if (frameworkElement != null)
{
BindingExpression bindingExpression =
frameworkElement.GetBindingExpression(PasswordBox.PasswordProperty);
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
}
}
If you want to use it, you should set this Attached Property for the password box:
<PasswordBox Grid.Column="1"
Password="{Binding Path=NewPassword, Mode=TwoWay, ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True, ValidatesOnDataErrors=True,
NotifyOnValidationError=True}"
SilverlightValidation:UpdateSourceTriggerHelper.UpdateSourceTrigger="True"/>
I will not use any framework, so I need my own DelegateCommand
(a class which implements the ICommand
interface):
public class DelegateCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public DelegateCommand(Action<object> execute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
}
public DelegateCommand(Action<object> execute,
Func<object, bool> canExecute)
: this(execute)
{
if (canExecute == null)
throw new ArgumentNullException("canExecute");
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute != null)
return _canExecute(parameter);
return true;
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
public event EventHandler CanExecuteChanged = delegate {};
}
This is not good, as you can get memory leaks with it (Memory Leak caused by the DelegateCommand.CanExecuteChanged Event), so it will be better to use Prism version 2.1 or higher.
In my example, I need these validation rules:
- New Password is a required field;
- New Password has limited length - 20 symbols (sad, but many developers forget about it, and get production errors from DB like 'string truncated');
- New Password Confirmation should be the same as New Password.
#1 DataAnnotations and ValidatesOnExceptions
We have had this approach before Silverlight 4. I started working with Silverlight only from the third version, so I can say that in the third version, we had this approach. The only advantage of this type of validation is that most .NEt technologies has it (ASP.NET, WPF, WinForms). The basic idea is - show validation errors by throwing exceptions. In Silverlight 3, validation by throwing exceptions was only one way to implement validation (except your own implementation). If you want to use this kind of validation, you should set ValidatesOnExceptions
to true at binding.
Let's create our binding model. All examples will start with the implementation of the BindingModel
class, which has an implementation of INotifyPropertyChanged
and three fields: two string fields which will store passwords, and a command. I will bind this command to a button in the user interface; this command will do the changing of the password (I will initialize this command at ctor).
public class BindingModel : INotifyPropertyChanged
{
private string _newPassword;
private string _newPasswordConfirmation;
public DelegateCommand ChangePasswordCommand { get; private set; }
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I also need two helper methods:
private bool IsValidObject()
{
ICollection<ValidationResult> results = new Collection<ValidationResult>();
return Validator.TryValidateObject(this, new ValidationContext(this, null, null),
results, true) && results.Count == 0;
}
private void ValidateProperty(string propertyName, object value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = propertyName });
}
First, we will check that the whole object BindingModel
is valid. The second method checks that some property is valid. Both methods use the class Validator
, this is a Silverlight infrastructure. This class gets all validation rules from the attributes and check that rules have a valid state. The IsValidObject
method returns a boolean result. The ValidateProperty
method throws an exception if a validation rule has an invalid state. You can describe validation rules with attributes which inherit from ValidationAttribute
(you can create your own attributes inheriting from this): StringLengthAttribute
, RequiredAttribute
, RegularExpressionAttribute
, RangeAttribute
, DataTypeAttribute
, CustomValidationAttribute
. You can learn about all of these attributes from their links. I will describe the rules for my properties:
[Required]
[StringLength(20)]
[Display(Name = "New password")]
public string NewPassword
{
get { return _newPassword; }
set
{
_newPassword = value;
OnPropertyChanged("NewPassword");
ChangePasswordCommand.RaiseCanExecuteChanged();
ValidateProperty("NewPassword", value);
}
}
[CustomValidation(typeof(BindingModel), "CheckPasswordConfirmation")]
[Display(Name = "New password confirmation")]
public string NewPasswordConfirmation
{
get { return _newPasswordConfirmation; }
set
{
_newPasswordConfirmation = value;
OnPropertyChanged("NewPasswordConfirmation");
ChangePasswordCommand.RaiseCanExecuteChanged();
ValidateProperty("NewPasswordConfirmation", value);
}
}
The NewPassword
property is simple. It has two validation rules implemented using the two attributes Required
and StringLength
. It also has a DisplayAttribute
which sets the property name for controls like ValidationSummary
. If the property does not have the current attribute, than we will see "NewPassword" in ValidationSummary
. Both properties have in set methods an equal sequence of operands: set value, notify that value is changed, raise command's CanExecute
method (if using it), and the last operand checks the validation rules for the current property. The NewPassword
property has the CustomValidationAttribute
. This attribute has information about the method and class type which contains this method. This method should perform validation. The attribute expects a public static method, and should return a ValidationResult
; first the parameter should have the property's type. You can set the second parameter to ValidationContext
. In my case, I have:
public static ValidationResult CheckPasswordConfirmation(string value,
ValidationContext context)
{
var bindingModel = context.ObjectInstance as BindingModel;
if (bindingModel == null)
throw new NotSupportedException("ObjectInstance must be BindingModel");
if (string.CompareOrdinal(bindingModel._newPassword, value) != 0)
return new ValidationResult("Password confirmation not equal to password.");
return ValidationResult.Success;
}
We should also implement a command and the command's methods:
public BindingModel()
{
ChangePasswordCommand =
new DelegateCommand(ChangePassword, CanChangePassword);
}
private bool CanChangePassword(object arg)
{
return IsValidObject();
}
private void ChangePassword(object obj)
{
if (ChangePasswordCommand.CanExecute(obj))
{
MessageBox.Show("Bingo!");
}
}
For both PasswordBox
controls, I set UpdateSourceTrigger="True"
in XAML. You can try this sample at my blog.
Let's speak about the problems of this approach. The main problem - we throw exceptions in set methods. This can be a mess. If you run your application with a debugger, you will see a lot of excess information (especially if you set the binding for each key down). Also, you can't set null
or string.Empty
(dump property) to the NewPassword
property without any backend property or method (you can't use the default property, because it will throw an exception). In my case, I can't set null
s to properties NewPassword
and NewPasswordConfirmation
at control loading, because I will get an exception. So I need to write separate methods for each control, like Set[Property]Value. So these properties can be used only for binding. Really, this is a mess.
Another problem - implementation. I don't like to raise the CanExecute
event on each property set. I don't like to set button disabled if validation fails. I think this is unintelligent. It is always better when a button is active. So when the user clicks the button and all controls are empty, he will get information about all the errors on the control, and he can solve the problems step by step. But with DataAnnotation and validation by exceptions, you can't do it easily (I don't know how to do it easily). You can't validate all ViewModel objects and notify ValidationSummary
and the controls about validation errors. You can do it hardly: you can raise UpdateBinding
for each control on your control with some special class like ValidationScope
, which you should write.
Also, I use UpdateSourceTrigger
in my sample because binding works only on focus change. So when the user inputs NewPassword
and then NewPasswordConfirmation
, he will see that the button is still disabled (because binding occurs for NewPasswordConfirmation
only when he changes focus). This is a mess. But when I use UpdateSourceTrigger
I can see any validation error on each key down - this is not good either.
A 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 a check for password confirmation like this:
[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 to implement validation with this interface, we should implement it in our ViewModel (a property and a method). Usually, developers write their own class handler which stores the validation errors)
public class ValidationHandler
{
private Dictionary<string, string> BrokenRules { get; set; }
public ValidationHandler()
{
BrokenRules = new Dictionary<string, string>();
}
public string this[string property]
{
get { return BrokenRules[property]; }
}
public bool BrokenRuleExists(string property)
{
return BrokenRules.ContainsKey(property);
}
public bool ValidateRule(string property,
string message, Func<bool> ruleCheck)
{
bool check = ruleCheck();
if (!check)
{
if (BrokenRuleExists(property))
RemoveBrokenRule(property);
BrokenRules.Add(property, message);
}
else
{
RemoveBrokenRule(property);
}
return check;
}
public void RemoveBrokenRule(string property)
{
if (BrokenRules.ContainsKey(property))
{
BrokenRules.Remove(property);
}
}
public void Clear()
{
BrokenRules.Clear();
}
}
Now, let's rewrite our BindingModel
class; we will inherit it from the IDataErrorInfo
interface:
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:
[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 to VaidationRule
checks for some condition, and if not true, will write info about the validation error in the Errors
collection. After binding, the Silverlight infrastructure will call the get method of the indexing property this[string columnName]
and it will return the error information for this property (columnName
). We should set the property ValidatesOnDataErrors
in our binding to true if we want to use this kind of validation. A property error throws 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 the end, we should implement methods for the command:
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!");
}
}
Again, we should use the CanChangePassword
method because we need to set the button to disabled state when the object is invalid. We can't check the valid state of the whole object before the binding happens. Another problem with this implementation: we should write validation rules twice: in property set methods and in the CanChangePassword
method. But this is a problem of this implementation; you can solve it with another one: you can write a ValidationHandler
class, which will store not just validation errors, but validation rules as well, so you can raise a validation check in the CanChangePassword
method. But we still have a problem, we can't tell ValidationSummary
or some other control in 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 the next example.
The result of the IDataErrorInfo
implementation (Silverlight sample) can be seen in this article from my blog.
I think the behavior of this sample is the same as that in the previous part. I also want to say that this sample has a bug: if the user inputs Password Confirmation first and then New Password, he will see a validation error about password confirmation not being equal, because this check happens only in the NewPasswordConfirmation
binding.
INotifyDataErrorInfo
The INotifyDataErrorInfo
interface came with Silverlight 4 too. The main advantage of this interface is you can do both synchronous (like in the previous samples) and asynchronous validation. You can wait for validation from the server, and only after that, tell the interface that all is OK or some validation error occurred. I like this way of validation more than the other. I will use some classes and implementation from the articles of Davy Brion about "MVP In Silverlight/WPF Series".
First, I got the class PropertyValidation
; with it, we will store the validation rule for properties and the message which should be shown when a validation error occurs.
public class PropertyValidation<TBindingModel>
where TBindingModel : BindingModelBase<TBindingModel>
{
private Func<TBindingModel, bool> _validationCriteria;
private string _errorMessage;
private readonly string _propertyName;
public PropertyValidation(string propertyName)
{
_propertyName = propertyName;
}
public PropertyValidation<TBindingModel>
When(Func<TBindingModel, bool> validationCriteria)
{
if (_validationCriteria != null)
throw new InvalidOperationException(
"You can only set the validation criteria once.");
_validationCriteria = validationCriteria;
return this;
}
public PropertyValidation<TBindingModel> Show(string errorMessage)
{
if (_errorMessage != null)
throw new InvalidOperationException(
"You can only set the message once.");
_errorMessage = errorMessage;
return this;
}
public bool IsInvalid(TBindingModel presentationModel)
{
if (_validationCriteria == null)
throw new InvalidOperationException(
"No criteria have been provided for this " +
"validation. (Use the 'When(..)' method.)");
return _validationCriteria(presentationModel);
}
public string GetErrorMessage()
{
if (_errorMessage == null)
throw new InvalidOperationException(
"No error message has been set for " +
"this validation. (Use the 'Show(..)' method.)");
return _errorMessage;
}
public string PropertyName
{
get { return _propertyName; }
}
}
You will understand how it works when we start to implement the validation rules in our example. This class has a generic parameter with the base class BindingModelBase<T>
from which we will inherit our main BindingModel
class.
Let's implement the BindingModelBase
class; we will inherit it from the INotifyPropertyChanged
and INotifyDataErrorInfo
interfaces, and will add two fields: one for storing validation rules and one for storing validation errors:
public abstract class BindingModelBase<TBindingModel> :
INotifyPropertyChanged, INotifyDataErrorInfo
where TBindingModel : BindingModelBase<TBindingModel>
{
private readonly List<PropertyValidation<TBindingModel>>
_validations = new List<PropertyValidation<TBindingModel>>();
private Dictionary<string, List<string>>
_errorMessages = new Dictionary<string, List<string>>();
#region INotifyDataErrorInfo
public IEnumerable GetErrors(string propertyName)
{
if (_errorMessages.ContainsKey(propertyName))
return _errorMessages[propertyName];
return new string[0];
}
public bool HasErrors
{
get { return _errorMessages.Count > 0; }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I want to add a helper method to OnPropertyChanged
, which has Expression in parameters and will let us use it like this:
public string NewPassword
{
get { return _newPassword; }
set { _newPassword = value; OnCurrentPropertyChanged(); }
}
This method is very useful. Here is the implementation of this method:
public string NewPassword
{
get { return _newPassword; }
set { _newPassword = value; OnPropertyChanged(() => NewPassword); }
}
More implementation:
protected void OnPropertyChanged(Expression<Func<object>> expression)
{
OnPropertyChanged(GetPropertyName(expression));
}
private static string GetPropertyName(Expression<Func<object>> expression)
{
if (expression == null)
throw new ArgumentNullException("expression");
MemberExpression memberExpression;
if (expression.Body is UnaryExpression)
memberExpression =
((UnaryExpression)expression.Body).Operand as MemberExpression;
else
memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("The expression is not " +
"a member access expression", "expression");
var property = memberExpression.Member as PropertyInfo;
if (property == null)
throw new ArgumentException("The member access expression " +
"does not access a property", "expression");
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
throw new ArgumentException("The referenced property " +
"is a static property", "expression");
return memberExpression.Member.Name;
}
The method gets the property name from the expression. Next, let's add some methods which will perform validation:
public void ValidateProperty(Expression<Func<object>> expression)
{
ValidateProperty(GetPropertyName(expression));
}
private void ValidateProperty(string propertyName)
{
_errorMessages.Remove(propertyName);
_validations.Where(v => v.PropertyName ==
propertyName).ToList().ForEach(PerformValidation);
OnErrorsChanged(propertyName);
OnPropertyChanged(() => HasErrors);
}
private void PerformValidation(PropertyValidation<TBindingModel> validation)
{
if (validation.IsInvalid((TBindingModel) this))
{
AddErrorMessageForProperty(validation.PropertyName,
validation.GetErrorMessage());
}
}
private void AddErrorMessageForProperty(string propertyName, string errorMessage)
{
if (_errorMessages.ContainsKey(propertyName))
{
_errorMessages[propertyName].Add(errorMessage);
}
else
{
_errorMessages.Add(propertyName, new List<string> {errorMessage});
}
}
The method ValidateProperty
deletes information about all validation errors which happened with the current property, then checks each rule for the current property, and if some rule is false, it will add an error to the Errors
collection. Also, we can raise a validation check for each property automatically with the PropertyChanged
event:
protected BindingModelBase()
{
PropertyChanged += (s, e) => { if (e.PropertyName
!= "HasErrors") ValidateProperty(e.PropertyName); };
}
For easily adding validation rules to our collection, we will add this method:
protected PropertyValidation<TBindingModel>
AddValidationFor(Expression<Func<object>> expression)
{
var validation =
new PropertyValidation<TBindingModel>(GetPropertyName(expression));
_validations.Add(validation);
return validation;
}
Now we can implement the BindingModel
class which we will use in our last example. If we want to implement validation with the INotifyDataErrorInfo
interface, we should set the ValidatesOnNotifyDataErrors
property to true
in the bindings.
Here is the BindingModel
implementation:
public class BindingModel : BindingModelBase<BindingModel>
{
private string _newPassword;
private string _newPasswordConfirmation;
public DelegateCommand ChangePasswordCommand { get; private set; }
public BindingModel()
{
ChangePasswordCommand = new DelegateCommand(ChangePassword);
AddValidationFor(() => NewPassword)
.When(x => string.IsNullOrEmpty(x._newPassword))
.Show("New password required field.");
AddValidationFor(() => NewPassword).When(
x => !string.IsNullOrEmpty(x._newPassword) &&
x._newPassword.Length > 80).Show(
"New password must be a string with maximum length of 80.");
AddValidationFor(() => NewPasswordConfirmation).When(
x => !string.IsNullOrEmpty(x._newPassword) &&
string.CompareOrdinal(x._newPassword,
x._newPasswordConfirmation) != 0).Show(
"Password confirmation not equal to password.");
}
[Display(Name = "New password")]
public string NewPassword
{
get { return _newPassword; }
set
{
_newPassword = value;
OnPropertyChanged(() => NewPassword);
}
}
[Display(Name = "New password confirmation")]
public string NewPasswordConfirmation
{
get { return _newPasswordConfirmation; }
set
{
_newPasswordConfirmation = value;
OnPropertyChanged(() => NewPasswordConfirmation);
}
}
private void ChangePassword(object obj)
{
throw new NotImplementedException();
}
}
In the ctor, we describe all three validation rules for the properties. Looks very good (thanks Davy Brion!). I told you that I don't like to set the disabled button, so from now, it will always be enabled. For implementing the command's method ChangePassword
, I need some method which will check all the validation rules of the current object. This will be the ValidateAll
method, which I will implement in the BindingModelBase
class:
public void ValidateAll()
{
var propertyNamesWithValidationErrors = _errorMessages.Keys;
_errorMessages = new Dictionary<string, List<string>>();
_validations.ForEach(PerformValidation);
var propertyNamesThatMightHaveChangedValidation =
_errorMessages.Keys.Union(propertyNamesWithValidationErrors).ToList();
propertyNamesThatMightHaveChangedValidation.ForEach(OnErrorsChanged);
OnPropertyChanged(() => HasErrors);
}
This method deletes all the validation errors in the collection. Then it checks the validation rules, writes errors if the rule is false, and then raises the OnErrorsChanged
event for each property which has changed the validation state.
Here is the implementation of the method ChangePassword
:
private void ChangePassword(object obj)
{
ValidateAll();
if (!HasErrors)
{
MessageBox.Show("Bingo!");
}
}
The result (Silverlight application) can be seen at this article on my blog.
I like this implementation. It is much more flexible and can use all the advantages of the 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 the rules from the attributes and convert them to PropertyValidation
:
protected PropertyValidation<TBindingModel>
AddValidationFor(Expression<Func<object>> expression)
{
return AddValidationFor(GetPropertyName(expression));
}
protected PropertyValidation<TBindingModel> AddValidationFor(string propertyName)
{
var validation = new PropertyValidation<TBindingModel>(propertyName);
_validations.Add(validation);
return validation;
}
protected void AddAllAttributeValidators()
{
PropertyInfo[] propertyInfos =
GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
Attribute[] custom = Attribute.GetCustomAttributes(propertyInfo,
typeof(ValidationAttribute), true);
foreach (var attribute in custom)
{
var property = propertyInfo;
var validationAttribute = attribute as ValidationAttribute;
if (validationAttribute == null)
throw new NotSupportedException("validationAttribute " +
"variable should be inherited from ValidationAttribute type");
string name = property.Name;
var displayAttribute = Attribute.GetCustomAttributes(propertyInfo,
typeof(DisplayAttribute)).FirstOrDefault() as DisplayAttribute;
if (displayAttribute != null)
{
name = displayAttribute.GetName();
}
var message = validationAttribute.FormatErrorMessage(name);
AddValidationFor(propertyInfo.Name)
.When(x =>
{
var value = property.GetGetMethod().Invoke(this, new object[] { });
var result = validationAttribute.GetValidationResult(value,
new ValidationContext(this, null, null)
{ MemberName = property.Name });
return result != ValidationResult.Success;
})
.Show(message);
}
}
}
And the last BindingModel
variant:
public class BindingModel : BindingModelBase<BindingModel>
{
private string _newPassword;
private string _newPasswordConfirmation;
public DelegateCommand ChangePasswordCommand { get; private set; }
public BindingModel()
{
ChangePasswordCommand = new DelegateCommand(ChangePassword);
AddAllAttributeValidators();
AddValidationFor(() => NewPasswordConfirmation).When(
x => !string.IsNullOrEmpty(x._newPassword) &&
string.CompareOrdinal(x._newPassword,
x._newPasswordConfirmation) != 0).Show(
"Password confirmation not equal to password.");
}
[Display(Name = "New password")]
[Required]
[StringLength(80, ErrorMessage =
"New password must be a string with maximum length of 80.")]
public string NewPassword
{
get { return _newPassword; }
set
{
_newPassword = value;
OnPropertyChanged(() => NewPassword);
}
}
[Display(Name = "New password confirmation")]
public string NewPasswordConfirmation
{
get { return _newPasswordConfirmation; }
set
{
_newPasswordConfirmation = value;
OnPropertyChanged(() => NewPasswordConfirmation);
}
}
private void ChangePassword(object obj)
{
ValidateAll();
if (!HasErrors)
{
MessageBox.Show("Bingo!");
}
}
}
Source code for these samples can be downloaded from my assembla.com repository, or use the link from the top of this article.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.