Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / WPF

Automatically validating business entities in WPF using custom binding and attributes

Rate me:
Please Sign up or sign in to vote.
4.75/5 (17 votes)
21 Aug 2009CPOL4 min read 70.1K   1.4K   40   15
Validate your business entities in a maintainable way.

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

XML
<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

C#
public class User
{
    private int _age;
    public int Age
    {
        get { return _age; }
        set
        {
            if (value < 21)
                throw new ArgumentException("Kid!");
            _age = value;
        }
    }
}

HTML:

XML
<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

C#
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:

C#
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

C#
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

C#
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

C#
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.

C#
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 IValidationRules with WPF controls automatically!

C#
/// <summary>
/// Binding that will automatically implement the validation
/// </summary>
public class ValidationBinding : BindingDecoratorBase
{
    public ValidationBinding()
        : base()
    {
        Binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
    }
 
    /// <summary>
    /// This method is being invoked during initialization.
    /// </summary>
    /// <param name="provider">Provides access to the bound items.</param>
    /// <returns>The binding expression that is created by the base class.</returns>
    public override object ProvideValue(IServiceProvider provider)
    {
        // Get the binding expression
        object bindingExpression = base.ProvideValue(provider);
 
        // Bound items
        DependencyObject targetObject;
        DependencyProperty targetProperty;
 
        // Try to get the bound items
        if (TryGetTargetItems(provider, out targetObject, out targetProperty))
        {
            if (targetObject is FrameworkElement)
            {
                // Get the element and implement datacontext changes
                FrameworkElement element = targetObject as FrameworkElement;
                element.DataContextChanged += 
                  new DependencyPropertyChangedEventHandler(element_DataContextChanged);
 
                // Set the template
                ControlTemplate controlTemplate = 
                  element.TryFindResource("validationTemplate") as ControlTemplate;
                if (controlTemplate != null)
                    Validation.SetErrorTemplate(element, controlTemplate);
            }
        }
 
        // Go on with the flow
        return bindingExpression;
    }
 
    /// <summary>
    /// Datacontext of the control has changed
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    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.

C#
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:

XML
<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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead RealDolmen
Belgium Belgium
I'm a Technical Consultant at RealDolmen, one of the largest players on the Belgian IT market: http://www.realdolmen.com

All posts also appear on my blogs: http://blog.sandrinodimattia.net and http://blog.fabriccontroller.net

Comments and Discussions

 
QuestionValidate on button click? Pin
mpgjunky3-May-10 11:54
mpgjunky3-May-10 11:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.