Q: Great article on the WWF Rules engine. I am curious how one would implement a rule that is applied to a collection of items, something akin to "IF request.Payments[n].Amount > 10000 THEN request.Payments[n].RequiresApproval = true"
A: There are quite a few approaches to choose from. As usual - it depends on the application!
One solution is to use a WhileActivity and loop through the collection. This explicit approach would run a PolicyActivity for each item in the collection.
The Windows SDK contains an interesting alternative approach in the "Processing Collections in Rules" entry. This is approach is worthy of note because it uses the features and flexibility of the rules engine to perform the iteration without any outside help. The approach specifically relies on the following features:
With a Payment class defined in your domain model, the workflow class can accept an array of Payment references as a parameter:
Next comes the RuleSet:
|GetEnumerator||3||IF 1=1 THEN|
this._enumerator = this._payments.GetEnumerator()
|MoveEnumerator||2||IF this._enumerator.MoveNext() THEN|
this._currentPayment = (Payment)this._enumerator.Current
|One Or More Biz Rules||1||this._currentPayment.Amount > 10000 THEN|
this._currentPayment.RequiresApproval = True
this._currentPayment.RequiresApproval = False
|Force re-eval||0||IF this._currentPayment == this._currentPayment THEN|
GetEnumerator runs first because it has the highest priority. MoveEnumerator runs second - again because of priority. After these two rules finish, _currentPayment will reference the first Payment object in the array. All the "business" rules could now execute on that payment and decide on an outcome.
The interesting piece happens in the last rule, which always evaluates to true and performs an explicit "Update" on the _enumerator field. "Update" is a rule action that explicitly tells the engine about a side effect. In this rule, we are telling the engine that we've changed _enumerator. Even though we haven't actually changed _enumerator, we've forced the engine to look for previous rules with a dependency on _enumerator.
The rules engine knows the MoveEnumerator rule has a dependency on _enumerator, so it re-executes this rule. If MoveNext returns true in this rule, we update the _currentPayment field. The rules engine also detects this implicit side effect, and will reevaluate all the business rules that depend on _currentPayment. If MoveNext returns false, there are no more side effects and the Policy activity can close.
This is a clever pattern which keeps collection processing inside of a single activity.