Click here to Skip to main content
13,835,880 members
Click here to Skip to main content
Add your own
alternative version


268 bookmarked
Posted 21 May 2006

Delegates and Business Objects

, 21 May 2006
Rate this:
Please Sign up or sign in to vote.
An approach to implementing validation on custom business rules, using delegates.

Sample Image - DelegateBusinessObjects3.jpg


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; }
        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.


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.


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>();
    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) {

    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.


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.


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

You may also be interested in...

Comments and Discussions

GeneralRe: Great article, but a little constructive criticism Pin
Matthew Law28-Oct-06 14:31
memberMatthew Law28-Oct-06 14:31 
GeneralRe: Great article, but a little constructive criticism Pin
twesterd6-Dec-06 22:38
membertwesterd6-Dec-06 22:38 
GeneralRe: Great article, but a little constructive criticism Pin
Tomer Noy12-Dec-06 2:24
memberTomer Noy12-Dec-06 2:24 
GeneralRe: Great article, but a little constructive criticism Pin
twesterd12-Dec-06 12:21
membertwesterd12-Dec-06 12:21 
I did try it and I would have these coments:

1. Try your code with no ErrorProvider: What results do you get?

In other words, the ErrorProvider is not triggering the call to this[colunname], the object you are binding to is retrieving the property; hence, making the call: that's why your code shows the result you get. I suspect you are using a grid for test on your form and there are ways to supress excessive calls, but you were incorrect in linking IDataErrorInfo and the ErrorProvider for such behavior because that behavior is present without an ErrorProvider.

It is improper to call validation in the method for this[columnname], unless you want all properties validated whenever any property is retrieved via databinding. Since your comments indicate otherwise, I assume you do not desire this behavior.

2. I do not belive you implemented the interfaces properly.
a. You raise OnChanged whenever a value is passed into the setter, if the value is not different, OnChanged should not be raised.
b. You have no validation implemented. Validation should be performed when and where you specifically want validation performed. Your implmentation simply notes that the this[columnname] is called often from databinding. Those calls to this[columnname]are not reduced by alternative methods, they are made whether INotifyPropertyChanged & IDataErrorInfo is implemented or not. Its like a click event, events are called/ raised whenever you click, not registering with the click event does not reduce calls to raise click events.

In general, validation should be called whenevr a property value changes and or when a property is loaded (Ie. Validation rules may have changed since a property was last persisted, thus, its usually prudent to validate properties when loaded to display notifcation if any property does not pass current validation rules).

You have not implemented any validation for your test. I suggest a method for RaiseOnChanged(propertyname, oldvalue, newvalue) as well as an IsLoading property. That method should call any validation and RaiseOnchanged should be called whenever a property is loaded or changed. A dictionary of error keyed by property name should be maintained. When a property passes, any existing errors should be cleared, when any fail an error should be added to the dictionary/ list.

Now, within the this[columnname] method, an error string is only returned if a property has already been validated and failed. *NOTE - the validation is NOT performed all over again, that would be a performance drain. In other words:

return this.myErrorDictionary.GetError(propertyname);

There is no way this would cause any significant performance problems. The very, very minor performance hit is majorly justified by "live"validation maintained at the business object level and complately eliminates any need whatsover for validation in the UI. Validation in the UI is simply BAD, BAD, design (IMO).

Again, your comments are misdirected (IMO) - the ErrorProvider and the interfaces are not causing the symptoms you describe and you asserted that implementing the interfaces would be a performance drain. I suggested that it would only be a drain if the interfaces were not implmented properly.

I stand by my comments.


GeneralRe: Great article, but a little constructive criticism Pin
Tomer Noy12-Dec-06 22:49
memberTomer Noy12-Dec-06 22:49 
GeneralRe: Great article, but a little constructive criticism Pin
twesterd12-Dec-06 23:39
membertwesterd12-Dec-06 23:39 
GeneralAbout resources usage Pin
wode29-May-06 22:17
memberwode29-May-06 22:17 
QuestionHow the rules belong to? Pin
GalloAndres29-May-06 5:26
memberGalloAndres29-May-06 5:26 
QuestionAuto-recovering model [modified] Pin
IgorC26-May-06 11:35
memberIgorC26-May-06 11:35 
GeneralRE: "I don't like throwing exceptions for non-exceptional things." Pin
KHadden24-May-06 9:01
memberKHadden24-May-06 9:01 
GeneralRe: RE: "I don't like throwing exceptions for non-exceptional things." Pin
sgeddes11429-May-06 15:39
membersgeddes11429-May-06 15:39 
GeneralNon-Databinding Pin
G35Guy24-May-06 7:57
memberG35Guy24-May-06 7:57 
GeneralRe: Non-Databinding Pin
Paul Stovell24-May-06 8:35
memberPaul Stovell24-May-06 8:35 
QuestionQuestion... Pin
BoneSoft24-May-06 5:46
groupBoneSoft24-May-06 5:46 
AnswerRe: Question... Pin
Koru.nl25-May-06 6:56
memberKoru.nl25-May-06 6:56 
GeneralRe: Question... Pin
BoneSoft25-May-06 8:53
groupBoneSoft25-May-06 8:53 
GeneralError vs. Exception Pin
Mel Grubb II24-May-06 5:25
memberMel Grubb II24-May-06 5:25 
GeneralRe: Error vs. Exception Pin
KHadden24-May-06 9:46
memberKHadden24-May-06 9:46 
GeneralRe: Error vs. Exception Pin
IgorC24-May-06 11:03
memberIgorC24-May-06 11:03 
GeneralRe: Error vs. Exception Pin
Mel Grubb II24-May-06 11:07
memberMel Grubb II24-May-06 11:07 
GeneralRe: Error vs. Exception Pin
twesterd14-Jun-06 10:34
membertwesterd14-Jun-06 10:34 
GeneralCSLA 2.0 Pin
MSoulia24-May-06 5:05
memberMSoulia24-May-06 5:05 
GeneralExcellent Pin
chaldon24-May-06 0:25
memberchaldon24-May-06 0:25 
GeneralThanks! [modified] Pin
Paul Stovell22-May-06 5:43
memberPaul Stovell22-May-06 5:43 
GeneralRe: Thanks! [modified] Pin
davidj623-May-06 9:50
memberdavidj623-May-06 9:50 

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.

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