This week we had a requirement to display many differently filtered views of data within a web page. In this particular instance, the data represented a resultset of items from one of our line of business apps. Each view of the data was based on a particular set of logic and needed to be displayed in a grid like so:

The report was for showing various exceptional states of resource scheduling data and so each category represented a different exceptional state, such as:
- Over allocated entries
- Under allocated entries
- Entries where the submitted values were less than the scheduled values
- Entries submitted on days when the sun was shining and where there were 3 or more red cars parked in the carpark
You get the idea
In creating the report I wanted to ensure that we could easily add new category sections quickly and so I went for a processing system where you could easily create and add new rules to a single processing pipeline. So we start with a generic definition of a rule and an item that the rule will act upon:
internal class GenericItem {
private int _id;
public int ID { get { return _id; } set { _id = value; } }
public GenericItem(int id) { this._id = id; }
public override string ToString() { return "Name: " + _id.ToString(); }
}
internal abstract class ItemRule {
List<GenericItem> _matches = new List<GenericItem>();
public List<GenericItem> Matches { get { return this._matches; } set { this._matches = value; } }
abstract public void Evaluate(GenericItem item);
virtual public void Complete() { this.Clear(); }
virtual public void Clear() { }
}
You can see from this code that each rule will receive an item to evaluate and will expose a Matches collection of the items which match the rule definition. The ItemRule class is abstract so the implementation logic for each rule will be contained in the Evaluate method of each of the derived rule classes. Here's a couple of simple rules which filter items based on whether they are odd or even:
internal class IsEvenRule : ItemRule {
public override void Evaluate(GenericItem item) {
if (item.ID % 2 == 0) this.Matches.Add(item);
}
}
internal class IsOddRule : ItemRule {
public override void Evaluate(GenericItem item) {
if (item.ID % 2 == 1) this.Matches.Add(item);
}
}
These rules are very simplistic. They perform a simple piece of logic and then if the item matches that logic they add the item to their Matches collection. Some rules would be much more complex though. Consider a rule where you need to match where there are a certain number of items in the list - meaning that you couldn't fully report until you had done a complete scan of all of the items. Here's a rule for such a situation where the Match collection is populated only where there are greater than 10 instances of odd-numbered items in the list:
internal class TenOddsRule : ItemRule {
List<GenericItem> _evaluatedItems = new List<GenericItem>();
public override void Evaluate(GenericItem item) {
if (item.ID % 2 == 1) {
if (this._evaluatedItems.Count == 9) {
foreach (GenericItem evaluatedItem in this._evaluatedItems) {
this.Matches.Add(evaluatedItem);
}
}
this._evaluatedItems.Add(item);
if (this._evaluatedItems.Count >= 10) {
this.Matches.Add(item);
}
}
}
public override void Complete() {
this._evaluatedItems.Clear();
base.Complete();
}
}
Notice that the rule adds has a private list that it adds items to and only populates its Matches collection when the inner-list grows to beyond 9 items.
Now we can wrap our rules logic within a generic processing pump that will contain the logic for performing iteration and applying each of the rules to the items in the list, like so:
internal class ItemProcessor {
List<ItemRule> _rules = new List<ItemRule>();
public void AddRule(ItemRule rule) {
rule.Clear();
_rules.Add(rule);
}
public void AddRules(IEnumerable<ItemRule> rules) {
foreach (ItemRule rule in rules) { AddRule(rule); }
}
public void ProcessItems(List<GenericItem> items) {
foreach (GenericItem item in items) {
foreach (ItemRule rule in _rules) {
rule.Evaluate(item);
}
}
foreach (ItemRule rule in _rules) {
rule.Complete();
}
}
}
Then we can create some rules and add them to our processor:
IsEvenRule evensRule = new IsEvenRule();
IsOddRule oddsRule = new IsOddRule();
TenOddsRule tenOddsRule = new TenOddsRule();
ItemProcessor processor = new ItemProcessor();
processor.AddRules(new ItemRule[] { evensRule, oddsRule, tenOddsRule });
processor.ProcessItems(items);
With the items processed, we can now use the Matches collections to bind to our UI elements, like so:
rpt1.DataSource = evensRule.Matches ;
rpt2.DataSource = oddsRule.Matches ;
rpt3.DataSource = tenOddsRule.Matches ;
The real beauty of using this approach is that new rules can be created quickly and easily to meet the needs of the business while keeping the processing logic distinct and easy to maintain.