Add validation to business objects

Aug 30, 2006

Recently, I had to implement validation on business objects in an already existing project. The validation should ensure that the different classes in the business object tier would validate them selves based on business rules. One of the rules on the Product class was that the Price property never must be negative and the Name property must be at least one character long.

For some reason I don’t know, none of these validation checks was made and I had to find a simple way to implement the checks without changing the original code too much. I’m a big fan of Rockford Lhotka’s CSLA.NET framework which has a nice way of doing validation, so I used that as an inspiration.

In the spirit of CSLA.NET I wanted the boolean read-only property IsValid, that tells whether or not the object is valid or not. I also wanted a read-only property called ValidationMessage, which tells what rules are broke and how to correct them.

The business objects didn’t have a base class, so I decided to create one and add as much of the validation code there. Here is that abstract class called ValidationBase:

using System;

using System.Text;

using System.Collections.Specialized;

 

public abstract class ValidationBase

{

 

  private StringDictionary _BrokenRules = new StringDictionary();

 

  /// <summary>

  /// Add or remove a broken rule.

  /// </summary>

  /// <param name="propertyName">The name of the property.</param>

  /// <param name="errorMessage">The description of the error</param>

  /// <param name="isBroken">True if the validation rule is broken.</param>

  protected void AddRule(string propertyName, string errorMessage, bool isBroken)

  {

    if (isBroken)

    {

      _BrokenRules[propertyName] = errorMessage;

    }

    else

    {

      if (_BrokenRules.ContainsKey(propertyName))

      {

        _BrokenRules.Remove(propertyName);

      }

    }

  }

 

  /// <summary>

  /// Reinforces the business rules by adding rules to the

  /// broken rules collection.

  /// </summary>

  protected abstract void Validate();

 

  /// <summary>

  /// Gets whether the object is valid or not.

  /// </summary>

  public bool IsValid

  {

    get

    {

      Validate();

      return this._BrokenRules.Count == 0;

    }

  }

 

  /// /// <summary>

  /// If the object has broken business rules, use this property to get access

  /// to the different validation messages.

  /// </summary>

  public string ValidationMessage

  {

    get

    {

      StringBuilder sb = new StringBuilder();

      foreach (string messages in this._BrokenRules.Values)

      {

        sb.AppendLine(messages);

      }

 

      return sb.ToString();

    }

  }


}

Then I hade to change all the business object so they derived from ValidationBase and implement the Validate() method on each of them. That’s all the work needed in order to start using the IsValid property. Here’s a dummy example of a derived class that uses the validation feature in its Save() method:

using System;

 

public class Product : ValidationBase

{

  #region Poperties

 

  private int _Price;

 

  public int Price

  {

    get { return _Price; }

    set { _Price = value; }

  }

 

  private string _Name;

 

  public string Name

  {

    get { return _Name; }

    set { _Name = value; }

  }

 

  #endregion

 

  #region Methods

 

  public void Save()

  {

    // Only save if the object is valid,

    // otherwise throw an exception.

    if (IsValid)

    {

      DoSomething();

    }

    else

    {

      throw new Exception(ValidationMessage);

    }

  }

 

  #endregion

 

  #region Validation

 

  /// <summary>

  /// Reinforces the business rules by adding rules to the

  /// broken rules collection.

  /// </summary>

  protected override void Validate()

  {

    AddRule("Price", "Price must be greater than or equal to 0", this.Price < 0);

    AddRule("Name", "Name must be set", string.IsNullOrEmpty(this.Name));

  }

 

  #endregion

}

It became a very simple implementation in the derived classes that didn’t demand any big changes to the original code.

* Only $4.95/month ASP.NET & Windows 2008 + IIS 7 Hosting! FREE SQL Included

Comments (8) -

 Peter Bell
Peter Bell
8/30/2006 8:01:37 PM #

Hi Mads,

Nice post! I do a lot with base classes as well. Only thing I'd recommend is considering using composition to handle your validation rules. You still expose the object.isValid()  and object.ValidationMessage() (I personally have an object.InvalidAttributeList() with a validation message for each invalid attribute so I can more easily display it next to the appropriate form fields or whatever in the presentation layer). The difference is that the object uses compostion to access the appropriate validation functionality as I decided on balance that objects don't need to know how to validate themseves, just to be able to report whether they're valid or not.

One other thing to consider is grouping collections of validtions and associating them to other things. I have a psuedo abstract data type system which allows me to set an attribute as a US phone and it immediately knows lots of things in terms of displaying form fields, formatting for display and running transformations against inputs and validations against the resulting value without me having to specify them. Removed about 80-85% of the transformation and validation rules for most systems!!!).

Best Wishes,
Peter


Mads Kristensen
Mads Kristensen
8/30/2006 8:07:36 PM #

Peter, that sounds pretty well thought over. Can you give a simple code example of your implementation?

By exposing the _BrokenRules dictionary as a public property, you would be able to retrieve the individual broken validation rules for use in e.g. form fields as you point out would be beneficial.

 Alexey
Alexey
8/31/2006 8:44:54 AM #

Hi Mads,

Thanks for interesting post!

Don’t you think that using of property (setter/getter) is more “natural” for validation?

For example something like:

    private int _Price;

    public int Price
    {
      get
      {
        return _Price;
      }
      set
      {
        if (value < 0)
        {
          throw new ProductValidationException("Price must be greater than or equal to 0");
        }
        else
        {
          _Price = value;
        }
      }
    }

 Mads Kristensen
Mads Kristensen
8/31/2006 9:22:48 AM #

Alexey, I see where you're going with this. However, there are some serious problems with your approach. If the Price property is never set, then no validation will take place. If the Price is set invalid, then you propably want to allow the users to change it to something valid. That cannot be done when you just throw an exception. As I see it, invalid values must not be considered to be an exception. It's what you do with the IsValid property that should decide whether or not to throw the exception.

But you could add this line to the setter instead of throwing the exception:
    AddRule("Price", "Price must be greater than or equal to 0", value < 0);

 Mike GQ
Mike GQ
9/2/2006 12:47:49 PM #

This opens my eyes to a situation I find myself in... I'm using the same objects over and over again on a variety of web forms, applying validation controls over and over again... this is a really nice, centralized way for validators.  Well done.

 Ethel
Ethel
9/4/2006 6:42:36 AM #

You should checkout this article, which implements a very extensive broken rules class with UI error handling: developer.coreweb.com/articles/Default4.aspx

Jelle Hissink
Jelle Hissink Netherlands
12/5/2007 10:21:10 AM #

Another improvement: implement System.ComponentModel.IDataErrorInfo - msdn2.microsoft.com/.../...del.idataerrorinfo.aspx - also, this works great for databinding.

Regards,
Jelle

michael young
michael young United States
5/7/2008 4:04:29 PM #

Ethel,
the link to the validation article developer.coreweb.com/articles/Default4.aspx appears to be broken.  Do you have the article, the code, or another link to share?

Comments are closed

About the author

Mads Kristensen

Mads Kristensen
Program Manager at the Microsoft Web Platform team and founder of BlogEngine.NET.

More...

Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.