Introduction
Implementing multiple validation rules on a single control is bit difficult but not impossible. Every form has one or more controls which required to be validation on different set of logic. Since this is a very basic dependency of code that every developer has to do, this tip is dedicated only to this.
It would be an easy task if we have set of multiple validation like required, numeric, minimum character length, folder exists, numeric range rule and we just apply one or more than one rule just by putting comma or | between the rules in our XAML file. To elaborate more, the issue lets see a situation.
Assume one textbox control value needs to be validated with the below conditions:
- It has to be a required field.
- It has to be a numeric field.
- It should be between ranges of 1 to 100.
Or:
- It has to be a required Field
- Input value should have minimum 3 characters.
Or:
- It has to be a required field.
- Input value should be a valid directory.
Now one way is to create a class and club all rules into one and then use that one rule, but isn't it is a time consuming job and difficult to manage at the later stage of project? Imagine how many combination of rules we will have to make and if there is any logic change, we need to go back and manage each rule with the new changes.
Background
Continue to my validation segment, previously I wrote a tip where I highlighted how to implement maximum length validation on controls, now I moved to other validation but with addition of how to implement multiple validation on the same control.
Using the Code
Would it be nice to have our XAML allow assigning these rules with some kind of separator and then XAML parser would handle this list of rules on the control.
Well yes, this is possible and I will show you in the below steps how we can achieve this.
Single Validation
<TextBox x:Name="titleBox" MaxLength="100" Grid.Column="1" Margin="0,11,0,0" HorizontalAlignment="Stretch">
<Binding
Path="Book.Title"
ValidatesOnDataErrors="True"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<rules:RequiredRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
Multiple Validation
<TextBox Text="{binding:RuleBinding Path=Book.Pages,
ValidationList=RequiredRule|NumericRule|RangeRule, MinValueRange=0, MaxValueRange=999, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True , Mode=TwoWay}"
Grid.Column="1" Grid.Row="6" HorizontalAlignment="Stretch"/>
First, we will introduce our two generic classes which would allow us to bind these multiple rules and then these rules would be set at run time.
[MarkupExtensionReturnType(typeof(object))]
public abstract class BindingDecoratorBase : MarkupExtension
{
/// <summary>
/// The decorated binding class.
///
private Binding binding = new Binding();
public override object ProvideValue(IServiceProvider provider)
{
//create a binding and associate it with the target
return binding.ProvideValue(provider);
}
protected virtual bool TryGetTargetItems(IServiceProvider provider, out DependencyObject target, out DependencyProperty dp)
{
}
}
Now our second class would be RuleBinding
Class which will be inherited from our 1st class BindingDecoratorBase
class. This class has an override of ProvideValue()
method. In this method, we call the below RegisterRule()
method:
public override object ProvideValue(IServiceProvider provider)
{
//In case multiple rules are bound then it would come like "Required|Numeric
var validationRules = ValidationList.Split(new string[] { "|", }, StringSplitOptions.RemoveEmptyEntries);
foreach (var rule in validationRules)
{
RegisterRule(rule);
}
//delegate binding creation etc. to the base class
object val = base.ProvideValue(provider);
return val;
} ....
private void RegisterRule(string ruleName)
{
ValidationRule rule;
switch (ruleName)
{
case "RequiredRule":
{
rule = new RequiredRule();
Binding.ValidationRules.Add(rule);
break;
}
case "RangeRule":
{
rule = new MinNumericRule()
{ MinValue = MinValueRange, MaxValue = MaxValueRange};
Binding.ValidationRules.Add(rule);
break;
}
case "NumericRule":
{
rule = new NumericRule();
Binding.ValidationRules.Add(rule);
break;
}
case "NumericNotEmpty":
{
rule = new NumericNotEmptyRule();
Binding.ValidationRules.Add(rule);
break;
}
case "FolderExistRule":
{
rule = new FolderExistRule();
Binding.ValidationRules.Add(rule);
break;
}
case "MinLengthRule":
{
rule = new MinLengthRule();
Binding.ValidationRules.Add(rule);
break;
}
}
}
That's it, very simple implementation but very helpful and effective, when you would run this project you would find that tooltips are changing based on error for the same control.
Points of Interest
Working on WPF is fun and doing things in a simple way in WPF is like cherry on the cake. It is always important that we write code in a simple way so that it can be managed by other people in your absence.
Validation plays a very important role and eliminates possibilities of all those silly errors which are enough to annoy an end user. Every minute spent to create basic structure of validation is worth it and this leads a project to an exception free successful project and saves lots of productivity.
Hope you enjoyed reading this tip.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)