Click here to Skip to main content
Click here to Skip to main content

Delegates and Business Objects

By , 21 May 2006
 

Sample Image - DelegateBusinessObjects3.jpg

Introduction

The aim of this article is to give you an understanding of how I perform business object validation in Trial Balance, a personal accounting project of mine. This article was originally a blog post that featured on my website, which you can see here. My approach borrows largely from the technique published in Rocky Lhotka's Expert Business Objects book (don't worry if you haven't read it), but I've added a Stovellian twist to spice things up. I'm also going to detour into a discussion of IDataErrorInfo, a really neat interface provided as part of the .NET framework to make our lives easier.

A typical implementation

Let's take a very simple example of what I mean when I say business rule:

Account names can't be blank, and can't be longer than 20 characters.

It's not uncommon to see a rule like that implemented like this:

public string Name 
{
    get { return _name; }
    set 
    {
        if (value == null
            || value.Trim().Length == 0
            || value.Trim().Length > 20) 
        {
            throw new ArgumentException("Account names can't be" + 
                  " blank, and can't be longer than 20 characters.");
        }
        _name = value; 
    }
}

Whilst this will stop anyone assigning the name of your account to "Supercallafragelisticexpialadocious", I feel it has a few drawbacks:

  • There may be times where you actually need to have an empty name. For example, as the default value for a "Create an account" form.
  • If you're relying on this to validate any data before saving, you'll miss the cases where the data is already invalid. By that, I mean, if you load an account from the database with an empty name and don't change it, you might not ever know it was invalid.
  • If you aren't using data binding, you have to write a lot of code with try/catch blocks to show these errors to the user. Trying to show errors on the form as the user is filling it out becomes very difficult.
  • I don't like throwing exceptions for non-exceptional things. A user setting the name of an account to "Supercalafragilisticexpialadocious" isn't an exception, it's an error. This is, of course, a personal thing.
  • It makes it very difficult to get a list of all the rules that have been broken. For example, on some websites, you'll see validation messages such as "Name must be entered. Address must be entered. Email must be entered". To display that, you're going to need a lot of try/catch blocks.

Rules in CSLA

Rocky's book approaches the idea of validation in a different manner. In Rocky's CSLA framework, a business object won't usually throw exceptions if you set a property to an invalid value, but instead the object will mark itself as invalid. If you try to attempt to save the business object, then an exception might be thrown.

There are two fundamental principles underlying this:

  1. There is nothing wrong with having an invalid business object, so long as you don't try to persist it.
  2. Any and all broken rules should be retrievable from the business object, so that data binding, as well as your own code, can see if there are errors and handle them appropriately.

When I was implementing rules for Trial Balance, I loved Rocky's idea, but there were a couple of things that concerned me:

  • Again, rules are only marked as being broken in the property setter. This is a problem if you're loading the data via another method.
  • Imagine you have a case where you have a StartDate and an EndDate. Your EndDate business rule would probably say that the EndDate must be greater than your StartDate, right?

    What if the following happens:

    StartDate = DateTime.MaxValue;
    EndDate = DateTime.Now; // EndDate is marked as invalid
    StartDate = DateTime.MinValue;

    So, although now EndDate is technically valid, the property setter hasn't been called, and thus EndDate is still marked as invalid. You can get around this with a little extra code, but it's not something I'd want to think about much personally.

  • We agreed before that there's nothing wrong with a business object being invalid, so long as we don't try to save it. In the case where we're not going to be doing any validation but we do call the property setters, our validation code is still called. This is a bit unnecessary.

Rules in Trial Balance

This led me to the following design goals:

  • A business object should be allowed to be invalid, so long as you don't try to save.
  • Rules should not be implemented in property setters, because it's too limiting for reasons outlined above.
  • A rule should not be checked unless it absolutely has to be.
  • No exceptions should be used, unless the object is trying to save without being validated.
  • Any rules that have been broken should be retrievable from the business object.

A typical Trial Balance property will look like this:

public string Name {
    get { return _name; }
    set { _name = value; }
}

Notice how there are no rules in there?

If you take a look at the Account class and scroll about halfway down, you'll see a method called CreateRules that looks somewhat like this:

protected override List<Rule> CreateRules() {
    List<Rule> rules = base.CreateRules();
    rules.Add(new SimpleRule("Name", "An account name is required" + 
              " and cannot be left blank.", 
              delegate { return this.Name.Length != 0; }));
    rules.Add(new SimpleRule("Name", "Account names cannot be" + 
              " more than 20 characters in length.", 
              delegate { return this.Name.Length <= 20; }));
    return rules;
}

For each rule that applies to an object, I'm using delegates to validate the rule. Since these are generally short validation routines, they can usually be done in just one line of code.

IDataErrorInfo

Before I dive into the details about how I validate a rule, I'd like to talk about a very important interface you should know about if you're a .NET developer. It's called IDataErrorInfo, and it's in the System.ComponentModel namespace.

IDataErrorInfo is used to inform people of errors on your object. It's got two parts – a string property called Error, where you return a list of all the errors on the object (for example: "Name can't be blank. Email can't be blank. Address can't be blank..."), and an indexer that takes the name of a property and returns an associated error message (for example, account["Name"] might return "Name can't be blank").

In .NET, if a control like a DataGridView is data-bound to an object, and that object implements IDataErrorInfo, the DataGridView is so smart that it will actually call the methods on the business object for you. So, if you've implemented IDataErrorInfo correctly, you might see this:

An image of a DataGridView bound to some business objects that implement IDataErrorInfo.

Alternatively, if you have some data-bound TextBoxes or CheckBoxes or ComboBoxes, you can drag an ErrorProvider onto your form. Simply set the DataSource of the error provider to your business object, and you'll also get the errors reported:

A form with a TextBox and ErrorProvider bound to an object that implements IDataErrorInfo.

The ErrorProvider works by looking at the data source, seeing what other controls are bound to it, and then displaying errors on those controls when there are errors on the data source.

Take note – in the screens above, the code that triggers the error to be shown is in the business object, not in the GUI. I didn't have to write a single line of GUI code – it's already done by data binding and IDataErrorInfo.

In Trial Balance, my DomainObject base class implements the IDataErrorInfo, so all of my business objects inherit this functionality.

Back to rules

Here's the class diagram to the important classes:

A class diagram with the Rule, Simple Rule and DomainObject classes.

In Trial Balance, every business object has a CreateRules method, which it inherits from the DomainObject base class. When any of the validation methods on the object are called for the first time, the CreateRules method is called to get a list of all the rules that apply. This is an important point that I'd like to stress – unless you actually call one of the validation methods or properties, the CreateRules method will never be called.

The CreateRules method returns a generic List of Rules to the base class. The Rule class is an abstract class, that contains two properties and an abstract method:

  • PropertyName – Gets the name of the property that the rule belongs to.
  • Description – Gets the descriptive text about the rule that's been broken.
  • ValidateRule() – This is the abstract method that returns whether or not your code is valid.

SimpleRule

Since most rules are very simple one-line constructs, there's an additional class that inherits from Rule, called SimpleRule. This class takes a delegate in its constructor, and this delegate is called by the ValidateRule() method.

If you take a look at the DomainObject class, about half way down, you'll see a method called GetBrokenRules():

public virtual ReadOnlyCollection<Rule> GetBrokenRules(string property) {
    property = CleanString(property);
   
    // If we haven't yet created the rules, create them now.
    if (_rules == null) {
        _rules = new List<Rule>();
        _rules.AddRange(this.CreateRules());
    }
    List<Rule> broken = new List<Rule>();

   
    foreach (Rule r in this._rules) {
        // Ensure we only validate a rule that
        // applies to the property we were given
        if (r.PropertyName == property || property == string.Empty) {
            bool isRuleBroken = !r.ValidateRule(this);
            // [...Snip...]
            if (isRuleBroken) {
                broken.Add(r);
            }
        }
    }

    return broken.AsReadOnly();
}

First, we check to see whether the CreateRules() method has been called, and if not, we'll call it for the first time. Then, we cycle through every rule that was returned. If the rule applies to the property name that was passed in as a parameter to the method, or if no property name was specified, the abstract ValidateRule() method is called. If that returns false, that rule is added to a list to be returned to the caller.

The property indexer that is there, thanks to IDataErrorInfo, makes use of GetBrokenRules():

public virtual string this[string propertyName] {
    get {
        string result = string.Empty;

        propertyName = CleanString(propertyName);

        foreach (Rule r in GetBrokenRules(propertyName)) {
            result += r.Description;
            result += Environment.NewLine;
        }
        result = result.Trim();
        if (result.Length == 0) {
            result = null;
        }
        return result;
    }
}

After calling GetBrokenRules(), it cycles through each one, and if they apply to it, they're added as a long list of error messages to be returned. So, for example, if the indexer is called with "Name" as a parameter, only the rules that apply to the Name property will be included in the result.

The other property that we have to implement as part of IDataErrorInfo, Error, calls the indexer with no property name specified. In this case, the indexer will validate all rules, which is exactly what the Error property is supposed to return.

Conclusion

I feel that my approach gives developers a lot of power, because they aren't just limited to using delegates as rules. The Rule class is designed to follow the Strategy pattern, so you can simply subclass Rule to create common, reoccurring rules, such as a NotBlankRule, a DateRangeRule, or even very complex rules that use multiple properties that go off and call web services to validate.

I think I'll summarize this post by pointing out that there's really no single best solution when it comes to how you implement your business rules. It's something that always varies from project to project, and it's up to us, as developers, to make the best call. This article was just an attempt to outline one possible alternative that you may consider when it comes to developing your own system of business rules, and a discussion about what I believe are the important properties of any business rule system.

Revision history

  • 24 May 2006 - Uploaded demo project.

One last thing!

I'd also like to say that the data binding features in Windows Forms are very rich, and it doesn't take much to use them to your advantage and create a very rich GUI. If you haven't heard of IDataErrorInfo, INotifiesPropertyChanged, or IEditableObject, you would really be doing yourself a favour by heading over to MSDN and having a read through the list of interfaces related to data binding.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Paul Stovell
Octopus Deploy
Australia Australia
Member
My name is Paul Stovell. I live in Brisbane and develop an automated release management product, Octopus Deploy. Prior to working on Octopus I worked for an investment bank in London, and for Readify. I also work on a few open source projects. I am a Microsoft MVP for Client Application Development.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionDynamic rule description strings?memberblah23815 Oct '11 - 17:00 
Hello,
 
The concepts in your article are pretty intriguing and I am looking to implement them in my Windows Forms application. Currently I am doing validation on the presentation layer and want to move it into the business layer. In my validation I report errors that include the actual value of the property being validated, or some other property of the business object that is relevant to the property being validated.
 
For example:
String.Format("The file {0} does not exist!", this.filePath)
Or
String.Format("The column {0} does not exist in table {1}!", this.columnName, this.tableName)
 
Can you suggest how this type of reporting can be implemented in your design?
AnswerRe: Dynamic rule description strings? [modified]memberTroy Tamas24 Oct '11 - 20:32 
What about writing a new Rule type that, instead of taking a string for the description, like in simple rule, takes a delegate returning a string.
 
i.e.
public class DelegateRule : Rule
    {
        public DelegateRule(string propertyName, Func<string> description, Func<bool> validation)
        {
//...
 

So instead of writing
new SimpleRule("FileName", 
              "The file does not exist!",
              IOService.FileExists);
 
It would be something more like
new DelegateRule("FileName", 
              () => String.Format("The file {0} does not exist!", this.filePath),
              IOService.FileExists);
 
EDIT:
 
Here's the entire DelegateRule class, inheriting from the abstract Rule class (based on, but slightly different from this Rule class) from the Cinch Framework by Sacha Barber.
 
public class DelegateRule : Rule
    {
        Func<object, string> _descriptionDelegate;
        Func<object, bool> _validationDelegate;
        public DelegateRule(string propertyName, Func<object, string> descriptionDelegate, Func<object, bool> validationDelegate) : base(propertyName, string.Empty)
        {
            _descriptionDelegate = descriptionDelegate;
            _validationDelegate = validationDelegate;
        }
 
        public override bool ValidateRule(object domainObject)
        {
            bool isValid = _validationDelegate(domainObject);
            if (!isValid)
                this.Description = _descriptionDelegate(domainObject);
            return isValid;
        }
    }
 
I like this Rule class better because you can define all the rules once per type, statically, rather than once per object. Then you would pass the "this" parameter to ValidateRule inside your validation object. See Sacha's article for details...
 
Though, there could be a potential conflict here if you were validating a bunch of objects with the same instance of this class in between validating and getting the description, since it would mean your description would be changing to that of another object...

modified 25 Oct '11 - 4:42.

QuestionnicememberCIDev14 Jul '11 - 4:15 
A well written and useful article.
Just because the code works, it doesn't mean that it is good code.

GeneralVB.NET versionmemberwalther19855 Jan '10 - 17:18 
Please a VB.NET version , i need it ,anyone can help me ?
GeneralRe: VB.NET versionmemberGerhardKreuzer11 Jan '10 - 10:14 
Just look down the list 8 posts
There is a link.
 
Gerhard
GeneralRe: VB.NET versionmemberGoDeep18 Feb '10 - 22:20 
Just convert it yourself using http://www.developerfusion.com/tools/convert/csharp-to-vb/ or using the Telerik Code Converter http://converter.telerik.com/[
QuestionHow do you deal with warnings, not errors?memberJiltedCitizen5 Oct '09 - 8:51 
I want to notify people of warnings which can still be saved but not have a separate validation for warnings. The real problem is the UI, IDataErrorInfo doesn't really account for warning's. Any idea's?
QuestionI want using it in ASP.NET (validation in Web form)?memberMember 15187125 Sep '09 - 16:23 
Great article!
 
I want using it in ASP.NET (validation in Web form, i'm using ObjectDataSource and FormView)?
 
Thanks for rely!
GeneralRulesmemberAshk_10 Aug '09 - 21:35 
Hi
 
I have some problem like this, but i have a different architect.
This is a good article, but you create a list of rules for each single instance of a business class, at this article "Customer".
 
Thanks
QuestionHow about Business CollectionsmemberBoutemine Oualid31 May '08 - 8:23 
Hello,
thnx for this helpfull article..
 
This implementation of business rules is simple for understand, but not adaptable to business collections,
 
For example, if i have a set of customers and i'd like that the user name must be unique for all elements of this collection(this a rule like i belive), i belive that the implementation of the rule as a class will not helpfull unlike the use of Delegates??(PropertyName Property has no +value on the rule's logic)
 
My question:
 
How i implement this logic (with classes) in the case of a business collection (BusinessListBase in rocks aproach)???
 
with best regards
 
with best regards
vb4arab.com

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 21 May 2006
Article Copyright 2006 by Paul Stovell
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid