Using the Workflow Rule Condition Editor for Your Own Rule Conditions
Windows Workflow Foundation has rich support for rule-driven work. The built-in PolicyActivity, for example, features the ability to take a set of rules and execute them. Other activities can make use of the RuleSet, Rule, RuleAction and RuleExpressionCondition types to leverage policy-making facilities inside or outside a workflow. In a nutshell:
- A rule condition is a condition that can be evaluated to a boolean value. It features validation and cloning facilities, as well as a dependency management mechanism.
- A rule action represents an action to be executed.
- A rule is a collection of conditions that is grouped into an IF...THEN...ELSE flow mechanism. The IF clause is associated with the conditions, and the THEN and ELSE clauses are associated with rule actions.
- A rule set is a collection of rules that can be executed and validated as a group.
The Existing Design-Time Experience
As far as the design-time experience is concerned, the primary means of defining rules is the RuleSetDialog and RuleConditionDialog classes. Wherever a rule or a rule condition is involved as a property in an activity or a workflow, the design-time editors for the rule or rule condition use these dialogs. For example, here's the PolicyActivity design-time experience. First, you are prompted to choose a rule set reference or to create a new one; next, prompted to configure a rule set by adding rules to it:

And finally, prompted to configure the rule condition, the THEN actions and the ELSE actions:
If all you need is a single rule condition in your code, that you will use for making some kind of runtime decision, then invoking the whole RuleSet to Rule to RuleCondition design-time chain is a serious overkill. Instead, you're probably looking for something like the following dialog:
In order to actually have a choice, you will need to provide an editor capable of displaying the UI of your choice. The built-in editors (such as the RuleSetDefinitionEditor) are sealed, and therefore a different way must be pursued. Let's take a look at how this would be accomplished.
Customizing Your Design-Time Experience
Assume that I have an activity that defines a rule condition. This rule condition then has to be passed to an entity that is external to my workflow, so that it can be evaluated against some mediating context object. This sounds very general, but it's an enabler for very useful scenarios:
- Define a credit authorization condition and pass it to a credit authorization external service;
- Define a filter for messages that you are interested in and pass it to a routing service;
- Define a subscription predicate for events you want to register for and pass it to a publish/subscribe component . . .
What we need, then, is the ability to store a rule expression condition, and we need to provide an editor that will invoke the rule condition dialog with the context object of our choice. Since the context object could be anything, we're looking at a generic solution. First, we need a wrapper for a RuleExpressionCondition:
[Serializable]
[DesignerSerializer(typeof(MyCodeDomSerializer),
typeof(CodeDomSerializer))]
public abstract class EditableRuleCondition
{
public override string ToString() . . .
public RuleExpressionCondition Condition
{
get;
set;
}
}
Next, we need a generic editor for that wrapper class: (Some implementation omitted for brevity)
public abstract class EditableRuleConditionEditor<
TContext, TCondition> : UITypeEditor
where TCondition : EditableRuleCondition, new()
{
public override object EditValue(...)
{
CodeExpression expression = . . .;
RuleConditionDialog dlg =
new RuleConditionDialog(typeof(TContext),
null, expression);
service.ShowDialog(dlg);
RuleExpressionCondition condition =
new RuleExpressionCondition(dlg.Expression);
return new TCondition {
Condition = condition };
}
}
The beauty of this solution is that now all we need to do is derive from EditableRuleCondition and EditableRuleConditionEditor (which is pretty straightforward) and we have a specialization for our own context type which we can directly use in any activity:
public class CreditCardAuthorization
{
public string CardId { get; set; }
public string Owner { get; set; }
}
[Serializable]
[Browsable(true)]
[Editor(typeof(SampleConditionEditor),
typeof(UITypeEditor))]
public class SampleCondition :
EditableRuleCondition
{
}
public class SampleConditionEditor :
EditableRuleConditionEditor<
CreditCardAuthorization, SampleCondition>
{
}
So let's write a credit card authorization activity that takes this kind of condition and does something external with it. It will expose a simple field that can then be assigned to in the designer (we'll skip validation etc. for today):
public class CreditCardActivity : Activity
{
public SampleCondition CreditCardCondition
{
get;
set;
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
if (CreditCardCondition == null ||
CreditCardCondition.Condition == null)
return ActivityExecutionStatus.Closed;
Console.WriteLine("Condition: " +
CreditCardCondition.ToString());
CreditCardAuthorization creditCardInfo =
new CreditCardAuthorization
{
CardId = "123456789",
Owner = "Sasha"
};
RuleValidation validation =
new RuleValidation(
typeof(CreditCardAuthorization), null);
RuleExecution execution =
new RuleExecution(
validation, creditCardInfo);
if (!CreditCardCondition.Condition.Validate(
validation))
{
throw new ArgumentException(
"Invalid condition.");
}
bool result =
CreditCardCondition.Condition.Evaluate(execution);
Console.WriteLine(
"Condition evaluated to: " + result);
return ActivityExecutionStatus.Closed;
}
}
Note that this activity does all the work internally, but nothing prevents us from taking the CreditCardCondition, serializing it and passing it to an external entity. The workflow instance isn't necessary because the context object isn't the workflow - it's an instance of the CreditCardAuthorization class.
Now, let's put this activity in a workflow:
And let's configure the condition (note the intellisense we get for the context object - the context type here is CreditCardAuthorization):
Our workflow is ready to run, and the condition will evaluate to true because we have set up the credit card number to begin with 1234.
Repeating the process for a rule set editor is quite similar, and is left as an exercise for the reader.
The sample code above can be downloaded as a Visual Studio 2008 solution. Bear in mind that it's sample code, and lots of validation logic has been omitted.