Introduction
A project at work forced me to get up to speed on Windows Workflow Foundation (WF) quickly. My boss wanted to use it as a business rule engine where the end user could edit the rules themselves. They didn't necessarily need full flexibility on the editing, but the core parameters of the rules had to be editable. Now there's probably hundred ways to approach this, and many ways to do it without using Workflow Foundation, but I decided do a proof of concept doing so.I then came over Christophe Geers' Blog and the article Using Rules outside of a Workflow. This was the kind of kick-start I needed - or so I thought, until I noticed it targets WF3 (Workflow for .NET 3.5). Now I could go on and use WF3, but some Googling and reading lead me to believe i would be better prepared for the future using WF4 instead*. In this article I will implement the same application as Christophe Geers did, but in Workflow 4.
*) You may argue against this in the comment field if you like - I'm still not sure this is an obvious choice.
Workflow 3 vs 4
So what has changed? A lot. Microsoft has rewritten the Workflow framework quite extensively from WF3 to WF4, and the way you work with Rules is fundamentally changed. Long story short, I feel they've tried to generalize the concept of an element in a workflow to the point where "everything is an apple". Maybe they've succeeded - I really haven't worked long enough with the framework to comment on this. Anyway, a Rule is now a basically an Activity configured accordingly. The Rule class is obsolete. I will not go into detail on the changes - Microsoft have published a 17 page document comparing how to use the concept of Rules in the document WF Guidance: Rules which is a part of the WF4 Migration guideThe Salutation Resolver Solution
Christophe Geers implemented a rule set in WF3 which basically decides the correct salutation in a letter for a person given some basic properties about that person. I'm not going to go into the same level of detail regarding the problem domain as Geers did, instead I'll focus on how to implement it in WF4. For reference, go to his blog entry at any time. This table will describe the expected result though:| Gender | Age | Married | Salutation |
|---|---|---|---|
| M | >18 | Yes/No | Mr |
| F | >18 | No | Miss |
| F | >18 | Yes | Mrs |
| F/M | <18 | Yes/No | To the parents of |
The solution will be divided into three projects:
- A shared library with the business entities
- A Forms project which take input and allow you to edit rules
- A Workflow Activity project. This is where the juice is.
The Forms project contains two forms. The parameter input form looks like this:
It will allow you to change the input parameters and see how the result of the workflow execution changes accordingly. The second form is for altering the business rules runtime. I will come back to that later.
The shared library contains two important classes - Person.cs, which is basically identical to the one from Geers' blog, and SalutationAssignmentRule.cs which is a wrapper over the rules we define, and the core of what we will expose to the end user.
The Activity project contains logic and data for executing the business rules in one of three ways:
- From a Workflow diagram designed with the Visual Studio Designer.
- From a hard-coded workflow where all the logic is written in C# code
- From a dynamic workflow where the workflow logic is generated from user input by C# code
#1 is cool. The designer produces XAML of the workflow (or "business rules"), which is suited for persitance and to a certain degree modification. I didn't really see my endusers editing XAML or using Visual Studio to change the business rules though, so this option was not for me.
#2 is cooler. Here I prove that the same can be done in code. Articles like DynamicAcitivty Creation and Kushal Shah's Adding Variables, Expressions and Bindings Programmatically helped me accomplish this, but the way I implemented it, the rules are pretty static.
#3: Now we're getting somewhere. By adding a layer on top of the code in #2, we can expose part of the rules to the end users. In this example I go as far as exposing the Visual Basic-style condition statements and the if-then-else-assignments to the user interface. This may be overkill for my end users, but I'm showing that the flexibility is there. See screenshot below.
In this DataGridView you can edit the rules runtime.
The implementation
I will focus on solution #3 from last chapter - where dynamic rules are used. For the other methods, download view the solution yourself (link at bottom).First, the "dumb" business classes:
Here is the gist of Person.cs. -Only syntactically altered from Geers' blog:
public class Person
{
#region Properties
public string Name { get; set; }
public string Firstname { get; set; }
public DateTime BirthDate { get; set; }
public string Gender { get; set; }
public bool Married { get; set; }
public string Salutation { get; set; }
// Read-only properties:
public Int32 Age { get { return CalculateAge(); } }
public Boolean Minor { get { return (Age < 18); } }
// Methods
private bool CalculateAge() { /* Implementation */ }
}
Then there is the class for storing the business rules. I need to wrap the complex structure of activities, sequences and arguments from the framwork in a manner that is easier presented in a user interface. In this solution I bind a list of these objects directly to the datagrid.
public class SalutationAssignmentRule
{
public string Condition { get; set; }
public int Priority { get; set; }
public string ThenValue { get; set; }
public string ElseValue { get; set; }
public string TargetParameter { get; set; }
public SalutationAssignmentRule(int priority, string condition, string thenValue, string elseValue = null)
{
TargetParameter = "Person.Salutation"; // Could be configurable
Condition = condition;
Priority = priority;
ThenValue = thenValue;
ElseValue = elseValue;
}
}
The ActivityLibrary projects is where the magic happens, specifically in SalutationRuleGeneratorDynamic.cs and RuleConverter.cs. The latter converts Rules in the form of SalutationAssignmentRule-objects to WF4 activity objects of type If, which are a subclass of the "everything-is-an-apple"-Action class.
RuleConverter.cs:
public class RuleConverter
{
internal static Activity ToIfActivity(SalutationAssignmentRule inRule)
{
var condition = new VisualBasicValue(inRule.Condition);
var ifActivity = new If(new InArgument(condition));
ifActivity.Then = new Assign
{
To = new OutArgument(new VisualBasicReference(inRule.TargetParameter)),
Value = new InArgument(inRule.ThenValue)
};
if (inRule.ElseValue != null)
{
ifActivity.Else = new Assign
{
To = new OutArgument(new VisualBasicReference(inRule.TargetParameter)),
Value = new InArgument(inRule.ElseValue)
};
}
return ifActivity;
}
}
Note how the condition, which is a string sounding something like Person.Gender = "Male", is input to the If-class directly. By wrapping it in a VisualBasicValue Now there's a caveat here: The root Workflow object needs to have a Workflow-property set, indicating that VB-statements will be present in the Workflow. A method to add such a parameter to an Activity is implemented in Common.cs:
Going further up the implementation chain, we have the core Workflow generator class in SalutationRuleGeneratorDynamic.cs:
internal static void AddVbSetting(Activity activity)
{
var settings = new VisualBasicSettings {
ImportReferences = { new VisualBasicImportReference {
Assembly = typeof(Person).Assembly.GetName().Name,
Import = typeof(Person).Namespace } } };
VisualBasic.SetSettings(activity, settings);
}
If you look at the Visual Studio-generated XAML for the SalutationRules workflow, you will find the same setting generated there, in the tag <mva:VisualBasic.Settings>.
public static Activity CreateSalutationRules(List
Finally there is the invocation code. In WF3, you'd instantiate a RuleExecution class followed by an Execute. In WF4 the equivalent is the static method WorkflowInvoker.Invoke(). Again, this is a general Workflow executer meant for everyone - not just for us weirdos using WF4 as a Business Rule Engine.
private void ExecuteRules(Activity activity, Person person)
{
var input = new Dictionary();
input.Add("Person", person);
WorkflowInvoker.Invoke(activity, input);
}
Persisting code-generated workflows
Before I round up, I'd like to mention that if you want to generate XAML from the code-generated activities for storage or transport purposes, you're just a small step away. I didn't include the code for it in my solution, but the key is to swap the DynamicActivity-instantiation in the CreateSalutationRules-method with a ActivityBuilder-class. The article Serializing workflows and Activities to and from XAML illustrates how to to this. You will have to do some minor syntactical changes in the CreateSalutationRules-method for it to compile, and also probably have to go thorugh a serialization-deserializiation-process before ending up with an executable Workflow.In closing
To summarize - yes you can implement Rules in WF4. Is it easier than in WF3? I don't think so. Is it faster? I don't know. Microsoft says it is. Is it better? Maybe. Depends on what your needs are. In some regards, WF4 have actually less functionality than the Rules in WF3 - forward chaining of rules is an example of functionality you will NOT get by using WF4 as a BRE. In other areas, WF4 will provide you with more functionality though. The "rules" are no longer restricted to the If-then-else-structure of WF3. You can define a rule as any activity in the Workflow namespace. For the example Christophe Geers and I presented, the differences would not matter much though. Others will have to answer for more complex problem domains.Downloads
Either clone the repo by running this command:...or download this ZIP (VS2010 solution): WF4-SalutationRules.zip
Update 25th Nov 2011: Turns out there's a better way to create the dynamic activity when you have static input- and output parameters as in this case. By creating a class that inherits Activity, you get to create the parameters as standard C# properties instead of generating InArgument-objects that must manually be added to the Properties collection of the Activity class.
Updated class for the dynamic activity: DynamicSalutationRulesActivity.cs (replaces SalutationRuleGeneratorSynamic)
Thanks goes out to Willem Meints and his blog for this knowledge :) He has a good three-post article about different ways of creating dynamic activities here: part one - two - three. (How come I find all the good articles about WF4 in the Benelux-lands? :p)
