WF Rules: Unleashing The Rule Engine Within .NET

October 18, 2010

I have written previously about WF Rules, the rule engine that ships in .NET. As I have described, this rule engine is bundled in .NET versions 3.0 and higher, and is included in the default installation of Windows Vista and Windows 7. (If you are using .NET 4.0, you need the Extended / Full install – the WF Rules assemblies are not in the Client install.)

Getting Access To WF Rules
The average rule author who is looking to try out WF Rules will ask where to start. At they point, they may become lost in the sea of WF documentation or find that they are told to use Visual Studio, which they may not even have installed. It is indeed the case, that WF Rules is typically used as part of Windows Workflow Foundation, and the rules are typically authored within Visual Studio. However, WF Rules can also be used without Visual Studio or Workflow Foundation if one is willing to read through the documentation, download some samples and write a bit code.

A Simple Example
Inspired by a co-worker, I have put together this quick example to lower the barrier to entry a bit. This is a very bare-bones example in order to highlight the rule-specific code that is needed to utilize WF Rules without Visual Studio or Workflow Foundation. So, the code will not feature command-line arguments, a mutable file name, factoring for re-use, etc. (If you are reading this, you most likely already know how to do those things in C#.) The emphasis here is upon demonstrating the scenario with the minimum amount of code. You should be able to paste these three code snippets into three files and have a working example.

The Target Type
In order to author rules, we require a target type to write the rules against. Let’s start with a simple data class:

using System;

class Person
{
  double age;
  public double Age { 
    get
    {
      return age;
    }
    set
    {
      age = value; 
    }
  }
}

For this example, I placed this code in a file named Person.cs. You may choose to place this type within the same file as the other code below. As I said, this example is geared for simplicity.

The RuleSetDialog
Now, let’s write some short code that will instantiate the RuleSetDialog and allow us to author a rule. This code needs to do three things:

  1. Open an existing or create a new .rules file – the WorkflowMarkupSerializer is used if we load an existing RuleSet
  2. Instantiate the RuleSetDialog, while supplying the target type
  3. Save the .rules file if the rules have been updated – using the WorkflowMarkupSerializer

Here is a minimal program to do this:

using System;
using System.IO;
using System.Windows.Forms;
using System.Workflow.Activities.Rules;
using System.Workflow.Activities.Rules.Design;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Xml;

class Program
{
  static string filename = "Test.rules";

  static void Main()
  {
    RuleSet ruleset = null;
	
    // Obtain or create ruleset
    if (File.Exists(filename))
    {
      // load file
      ruleset = Load(filename);
    }
    else
    {
      ruleset = new RuleSet();
    }

    RuleSetDialog dialog = new RuleSetDialog(typeof(Person), null, ruleset);
    DialogResult result = dialog.ShowDialog();

    if (result == DialogResult.OK)
    {
      // save the file
      Save(filename, dialog.RuleSet);
    }
  }

   static RuleSet Load(string filename)
  {
    XmlTextReader reader = new XmlTextReader(filename);
    WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
    object results = serializer.Deserialize(reader);
    RuleSet ruleset = (RuleSet)results;

    if (ruleset == null)
    {
       Console.WriteLine("The rules file " + filename + " does not appear to contain a valid ruleset.");		
    }
   return ruleset;
  }

  static void Save(string filename, RuleSet ruleset)
  {     
    XmlTextWriter writer = new XmlTextWriter(filename, null);
    WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
    serializer.Serialize(writer, ruleset);
    Console.WriteLine("Wrote rules file: " + filename);
  }
}

Authoring A Simple Rule
You will need to save each of these files before you can compile them – for example, I named them Person.cs and RuleEditor.cs.

Now we need to compile these files. You will typically find the C# compiler wherever your .NET installation is, for example:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe

Let me emphasize this – you don’t need Visual Studio for this example. If you have .NET on your machine, you should have the C# compiler already available.

If you saved the files as Person.cs and RuleEditor.cs and pass them to the compiler at the same time, you should end up with RuleEditor.exe – which we can then use to author a rule against a Person. Depending upon your environment settings, you may need to provide the following reference assemblies to the compiler at the same time: System.Workflow.Activities.dll, System.Workflow.ComponentModel.dll, and System.Workflow.Runtime.dll.

Now you should be able to execute RuleEditor.exe and author a rule such as this (click the picture to see full details):
Creating a simple rule in the rule editor dialog

Firing The Rules
Once we have our example rule authored, we need a simple program to load the ruleset and call the rule engine so we can fire the rules. (Note that the code here for loading the ruleset is the same as above.)

using System;
using System.IO;
using System.Workflow.Activities.Rules;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Xml;

class Program
{
  static string filename = "Test.rules";

  static void Main()
  {
    RuleSet ruleset = null;

    Person person = new Person();
    person.Age = 70;
		
    if (File.Exists(filename))
    {
      // load file
      ruleset = Load(filename);
    }
	
    if (ruleset != null)
    {
      RuleValidation validation = new RuleValidation(person.GetType(), null);
      RuleExecution engine = new RuleExecution(validation, person);
      ruleset.Execute(engine);
    }
  }

  static RuleSet Load(string filename)
  {
    XmlTextReader reader = new XmlTextReader(filename);
    WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
    object results = serializer.Deserialize(reader);
    RuleSet ruleset = (RuleSet)results;

    if (ruleset == null)
    {
      Console.WriteLine("The rules file " + filename + " does not appear to contain a valid ruleset.");		
    }
    return ruleset;
  }
}

This program should build much the same as the previous program. You will need to include the Person class again. In a real application, we would factor the business objects into a shared library.

Closing
I hope that this bit of code has demonstrated how readily WF Rules can be used by itself. I find it neat to see a rule engine included in a default OS installation.

Resources