Introduction
I like WPF.
I like MVVM.
I like non-repetitive code.
I don’t like how WPF validation fits in this story.
Validation implementation 1: Validation rules in XAML
<TextBox Name="textSandrino">
<Binding Path="Name">
<Binding.ValidationRules>
<MustBeANumberValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
Yeah nice… not.
This has several downsides:
- Your business logic leaks to the UI. Very bad!
- A validation rule inherits from
System.Windows.Controls.ValidationRule
. Even if I could create some validation rules in the BLL, this would still be based on a UI object. Not clean.
- What about existing validation code if I’m migrating? Do I have to rewrite everything to fit the
ValidationRule
logic? Crazy…
- Also, this code is hard to manage.
Validation implementation 2: Exceptions in the setters
public class User
{
private int _age;
public int Age
{
get { return _age; }
set
{
if (value < 21)
throw new ArgumentException("Kid!");
_age = value;
}
}
}
HTML:
<TextBox Name="textSandrino">
<Binding Path="Age">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
I’m almost sure that getters and setters should not throw exceptions (did I read it in Framework Design Guidelines?). Personally, I’m OK with the fact that methods throw exceptions. But setters? They should only set values, with a minimum amount of processing logic (like INotifyPropertyChanged
).
Another downside of this is that a big part of your business logic will be in the setter. That’s not clean enough for me.
Validation implementation 3: IDataErrorInfo
public class User : IDataErrorInfo
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Error
{
get
{
return this[string.Empty];
}
}
public string this[string propertyName]
{
get
{
string result = string.Empty;
if (propertyName == "Name")
{
if (string.IsNullOrEmpty(this.Name))
result = "Name cannot be empty!";
}
return result;
}
}
}
Are you kidding me?
- Based on the property name? That’s so 90’s, not refactor friendly at all.
- How will this integrate with my current validation logic (if I’m migrating from WinForms projects, for example)?
- This code looks dirty. I want clean code.
I have seen that there has been an attempt by Josh Smith to implement this in MVVM. Sorry Josh, I like your work, but this is even worse. I can’t live with the fact that I should implement my validation in the Model and in the ViewModel in such a way. Writing this type of validation once is even too much! Imagine writing this twice.
A cleaner solution
OK. This is what I would call clean:
public class Person
{
[TextValidation(MinLength=5)]
public string Name { get; set; }
[NumberValidation]
public string Age { get; set; }
}
What this solution should provide:
- Centralize all the validation logic in one place.
- Be able to use this on the client side (WPF UI) but also on the server side (BLL).
- No repetitive code.
- Nothing string based.
We will be using the BindingDecoratorBase
from Philipp Sumi. This class is far more better than the native BindingBase
in .NET. Great job!
Writing our own validation logic
interface IValidationRule
{
void Validate(object value, out bool isValid, out string errorMessage);
}
Not much to say about this interface. This will be the base of our validation logic.
Creating the attributes
public class NumberValidationAttribute : Attribute, IValidationRule
{
public NumberValidationAttribute()
{
}
public void Validate(object value, out bool isValid, out string errorMessage)
{
double result = 0;
isValid = double.TryParse(value.ToString(), out result);
errorMessage = "";
if (!isValid)
errorMessage = value + " is not a valid number";
}
}
public class TextValidationAttribute : Attribute, IValidationRule
{
public int MinLength { get; set; }
public TextValidationAttribute()
{
}
public void Validate(object value, out bool isValid, out string errorMessage)
{
isValid = false;
errorMessage = "";
if (value != null && value.ToString().Length >= MinLength)
isValid = true;
if (!isValid)
errorMessage = value + " is not equal to or longer than " + MinLength;
}
}
Nothing magic here either. These two attribute classes will make it possible to decorate our model properties with rules. But, it also makes it possible to configure the validation attributes (see MinLength
in TextValidationAttribute
).
Using the validation attributes
public class Personn
{
[TextValidation(MinLength=5)]
public string Name { get; set; }
[NumberValidation]
public string Age { get; set; }
[NumberValidation]
[TextValidation(MinLength=2)]
public string Money { get; set; }
}
How clean is this? We can:
- Apply the validation in a very clean way.
- Only write the validation attribute once and reuse it multiple times.
- Configure the validation attribute in a clean way!
- Use more than one validation attribute per property.
I like!
But, how can we integrate our custom validation attributes with WPF?
Integrating with WPF
You should first take a look at Philipp’s article. With his BindingDecoratorBase
, we’ll add some custom binding to our solution.
But first, we’ll create a WPF ValidationRule
that can use our own validation attributes.
class GenericValidationRule : ValidationRule
{
private IValidationRule ValidationRule;
public GenericValidationRule(IValidationRule validationRule)
{
this.ValidationRule = validationRule;
this.ValidatesOnTargetUpdated = true;
}
public override ValidationResult Validate(object value,
System.Globalization.CultureInfo cultureInfo)
{
bool isValid = false;
string errorMessage = "";
ValidationRule.Validate(value, out isValid, out errorMessage);
ValidationResult result = new ValidationResult(isValid, errorMessage);
return result;
}
}
OK, just a small word about this class.
If you take a close look, this WPF ValidationRule
will absorb our own IValidationRule
(that is used by the attributes). It will use the Validate
method from the interface and return a WPF compatible response.
Also, have you seen ValidatesOnTargetUpdated
? This is a nice property. This will cause the control to validate immediately when it’s data bound. This means even before the user adds some input.
Now that we have this rule, let’s bind our IValidationRule
s with WPF controls automatically!
public class ValidationBinding : BindingDecoratorBase
{
public ValidationBinding()
: base()
{
Binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
}
public override object ProvideValue(IServiceProvider provider)
{
object bindingExpression = base.ProvideValue(provider);
DependencyObject targetObject;
DependencyProperty targetProperty;
if (TryGetTargetItems(provider, out targetObject, out targetProperty))
{
if (targetObject is FrameworkElement)
{
FrameworkElement element = targetObject as FrameworkElement;
element.DataContextChanged +=
new DependencyPropertyChangedEventHandler(element_DataContextChanged);
ControlTemplate controlTemplate =
element.TryFindResource("validationTemplate") as ControlTemplate;
if (controlTemplate != null)
Validation.SetErrorTemplate(element, controlTemplate);
}
}
return bindingExpression;
}
private void element_DataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
object datacontext = e.NewValue;
if (datacontext != null)
{
PropertyInfo property = datacontext.GetType().GetProperty(Binding.Path.Path);
if (property != null)
{
IEnumerable<object> attributes =
property.GetCustomAttributes(true).Where(o => o is IValidationRule);
foreach (IValidationRule validationRule in attributes)
ValidationRules.Add(new GenericValidationRule(validationRule));
}
}
}
}
OK, let me just run through the code:
- Inherit from Philipp’s class.
- Validation should happen when the property changes (instant validation).
ProvideValue
will help us detect the control that received the binding. On that control, we’ll watch out when the datacontext changes.
Now, once the datacontext is set on the control, we’ll get started. Using Binding.Path.Path
, we know what property we bound to the data context.
Imagine I’m setting Person
as the datacontext of the Window
. Then, DataContextChanged
will also launch on the TextBox
, for example. And, if the binding of that TextBox
is set to a property of that class, we can get the attributes!
This is what happens in element_DataContextChanged
. We get the property that matches the one of the data context. Once we have that property, we try to find the attributes we are looking for, and we add these as validation rules to the control.
Also note that I automatically set the validation template of the control. This again reduces the ugly repetitive code. This could also be implemented with a style.
Implementing it
There isn’t much to do.
public class Person
{
[TextValidation(MinLength=5)]
public string Name { get; set; }
[NumberValidation]
public string Age { get; set; }
[NumberValidation]
[TextValidation(MinLength=2)]
public string Money { get; set; }
}
HTML:
<TextBox Height="23" Margin="38,34,0,0" Name="textBox1"
Text="{local:ValidationBinding Path=Name}" VerticalAlignment="Top"
HorizontalAlignment="Left" Width="170" />
That’s it!
The only small downside is that if you’re using MVVM, you have to add the attributes both in your Model and ViewModel. But there are also ways around that.
The project in the attachment contains the source and a working example.