Rethinking your business validation rules

Last night Tom Hollander and co. announced the release of the December CTP of Enterprise Libraries 3.0 (on CodePlex no less!). I've been watching and waiting for this (even bugging Tom to get a "sneak peek") but it's reality now and something you can play with. After all, opening Christmas presents isn't the only thing geeks do over the holidays. Don't use this for production apps, but if you've got something going into production in the next 6 months, its something to consider.

One of the main features they wanted to deliver (based on user feedback) was a validation engine. Something that would allow you to validate business entities with little effort. Last night I gave the validators a whirl to see how they worked. It's pretty slick and ends up being a nice cleanup of your domain from clutter (which is always a good thing).

Let's say I have an Account class and the name of the account has some rules around it. Names are required and can only be between 5 and 8 characters. This is a simple example, but one you should get the idea from.

First I'll write a set of quick tests for my Account class. Four tests, one creates a valid Account, two test the length bounds, and one creates an Account with a null name.

    6 [TestClass]

    7 public class AccountTests

    8 {

    9     private Account _account;

   10 

   11     [TestInitialize]

   12     public void SetUp()

   13     {

   14         _account = new Account();   

   15     }

   16 

   17     [TestMethod]

   18     public void CanCreateValidAccountWithName()

   19     {

   20         _account.AccountName = "C123456";

   21         Assert.AreEqual(true, _account.IsValid);

   22     }

   23 

   24     [TestMethod]

   25     public void AccountNameMustCannotBeLessThanFiveCharacters()

   26     {

   27         _account.AccountName = "A";

   28         Assert.AreEqual(false, _account.IsValid);

   29     }

   30 

   31     [TestMethod]

   32     public void AccountNameCannotExceedEightCharacters()

   33     {

   34         _account.AccountName = "123456789";

   35         Assert.AreEqual(false, _account.IsValid);

   36     }

   37 

   38     [TestMethod]

   39     public void AccountNameIsRequired()

   40     {

   41         Account account = new Account();

   42         account.AccountName = null;

   43         Assert.AreEqual(false, account.IsValid);

   44     }

   45 }

Now we'll create the Account class in a more "traditional" sense, checking for business rules validation during the setter and setting a flag called IsValid along the way.

    3 public class Account

    4 {

    5     private string _accountName;

    6     private bool _isValid;

    7 

    8     public string AccountName

    9     {

   10         set

   11         {

   12             if(string.IsNullOrEmpty(value))

   13                 _isValid = false;

   14             else if(value.Length < 5)

   15                 _isValid = false;

   16             else if(value.Length > 8)

   17                 _isValid = false;

   18             else

   19             {

   20                 _accountName = value;

   21                 _isValid = true;

   22             }

   23         }

   24         get { return _accountName; }

   25     }

   26 

   27     public bool IsValid

   28     {

   29         get

   30         {

   31             return _isValid;

   32         }

   33     }

   34 }

Great, our tests pass and all is good in the world. However our setter looks kind of ugly and could be improved. Maybe we could create some private methods called from our setter, or you could throw a BusinessRulesException but it's still ugly no matter how you refactor this.

Enter the new hotness of the Validation Application Block of Enterprise Library 3.0. Rather than writing all those "if" statements and conditional checking, we can specify our validation through attributes decorated on the property. And our IsValid property can be changed to use the new ValidationFactory classes:

    6 public class Account

    7 {

    8     private string _accountName;

    9 

   10     [NullValidator]

   11     [StringLengthValidator(5, 8)]

   12     public string AccountName

   13     {

   14         set { _accountName = value; }

   15         get { return _accountName; }

   16     }

   17 

   18     public bool IsValid

   19     {

   20         get

   21         {

   22             IValidator<Account> validator = ValidationFactory.CreateValidator<Account>();

   23             ValidationResults results = validator.Validate(this);

   24             return results.IsValid;

   25         }

   26     }

   27 }

Makes for reading your domain code much easier and it's a breeze to write. Also (at least IMHO) it makes for reading the intent of the business rules much easier. You can also create your own validators and there are a set of built-in ones in the December CTP drop (String Length, String Range, Date Range, Valid Number, etc. more coming later) so there's much more to it than this simple example but I find it all very slick and easy to use.

Published Saturday, December 23, 2006 8:45 AM by Bil Simser

Comments

# re: Rethinking your business validation rules

Saturday, December 23, 2006 10:36 PM by Vikram

Good article and well explained

# re: Rethinking your business validation rules

Sunday, December 24, 2006 3:09 PM by Nadeem

Is it actually necessary to create a new instance of the validator every time you need to validate?  Or can you simply keep a private validator instance and then simply call the Validate method when you need validation results?

What other information is available in the ValidationResults object?

Code that is easier to read is good and all, but what would really be nice is a new ASP.NET validator control that leverages the validation criteria present in any business object that uses this validation technique (you would have an IValidatable interface).  This way you don't have to code your own UI elements to prevent invalid form submission and describe the validation errors to users.  On top of this, the DetailsView control should have the ability to automatically attach these validators with no code.  Sounds easy enough to do...