Tuesday, November 15, 2011

Using WF4 as a Business Rule Engine

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 guide

The 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:





















GenderAgeMarriedSalutation
M>18Yes/NoMr
F>18NoMiss
F>18YesMrs
F/M<18Yes/NoTo the parents of

The solution will be divided into three projects:

  1. A shared library with the business entities
  2. A Forms project which take input and allow you to edit rules
  3. 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:

  1. From a Workflow diagram designed with the Visual Studio Designer.
  2. From a hard-coded workflow where all the logic is written in C# code
  3. 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, it will be interpreted runtime.

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:

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>.

Going further up the implementation chain, we have the core Workflow generator class in SalutationRuleGeneratorDynamic.cs:

public static Activity CreateSalutationRules(List rules)
  {
      var inProperty = new DynamicActivityProperty
      {
          Name = "Person",
          Type = typeof(InArgument)
  
      };
      var activity = new DynamicActivity() { Properties = { inProperty } };
      Common.AddVbSetting(activity);
  
      var sequence = new Sequence();
      activity.Implementation = () => sequence;
  
      // Sort descending - those added first are lowest priority
      var sortedRules = rules.OrderByDescending(p => p.Priority).ToList();
      
      // Convert to if-activities and add to sequence
      foreach (var inRule in sortedRules)
      {
          var outRule = RuleConverter.ToIfActivity(inRule);
          sequence.Activities.Add(outRule);
      }
  
      return activity;
  }

The key thing to notice here is how I order the rules by priority to emulate the Priority property of the Rule class of WF3. When you emulate Rules by using a flat list of If-activities as I do here, you get implicit priority by the order of which you add the If's to the Sequence object.

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:

git clone https://github.com/Nilzor/wf4-rule-engine.git

...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)


40 comments:

  1. Hello Frode! Thank you for your excellent article! Can you check the sample solution? MainForm.cs seems to be missing. Also, the SharedLibrary is targeting .NET 3.5...not sure if this matters though. Thanks again!

    ReplyDelete
  2. You're right. Thanks for pointing that out! WF4-SalutationRules.zip is now updated with MainForm cs.

    ReplyDelete
  3. I've added an update at the bottom of the article as I learned how to improve on the dynamic activity creating

    ReplyDelete
  4. Hi Frode, Thanks for this great article!

    Do you have any idea how should i store this ruleset in the database? if so, can you provide an example of the ruleset format in xml? Thanks

    ReplyDelete
  5. Anon Nov 27:

    Well, since I've wrapped the rules in the business class SalutationAssignmentRule, I would store instances of that class to a database. Why not just create a table called SalutationAssignmentRule, map each property to a column and use Linq2SQL or EF to read/write? Keep it simple.

    The business case I'm outlining assumes that the rules themselves are in a set format (IF/Then/Else-assignment). If you want even more dynamic rules, you would need a more dynamic editor and more dynamic persistence - maybe serializing the workflow directly as XML. See "Serializing Workflows and Activities to and from XAML" http://msdn.microsoft.com/en-us/library/ff458319.aspx . But that is a different business case.

    ReplyDelete
  6. Hi Frode,
    Thanks for your prompt reply. I am actually doing some research on workflow features. trying to apply workflow on my coming project. I will be doing a project for e-forms approval system. There will be many e-forms, each of the eform have different approver and approval routing. do you think workflow can handle this? any advice? thanks in advance.

    ReplyDelete
  7. Yea I don't see why workflow wouldn't handle the case you describe. You would need a different workflow (activity diagram) for each type of e-form then.

    Use workflows when you need your business rules to be dynamic, or when you want your end users to edit them.

    Another reason could be that you simply like the visualization you get from "workflowifying" your logic. It adds clarity and could be used in documentation and dialog with the non-technical stakeholders.

    ReplyDelete
  8. I am thinking one workflow for all the eforms.
    still in doing research. long way to go. :p

    ReplyDelete
  9. Frode,

    Nice article - thanks very much. As you say, this is not a full replacement for a Rete-based BRE, but it does meet some of the frequent requirements within business systems - a simple set of sequential, ordered, If ... Then ... Else conditions.

    One thing that I did want to ask though - if I understand your model correctly, this is designed to be deployed as a code module. Have you given any thought to how this might be deployed as a Workflow Service at all?

    Paul Marriott
    Technical Director
    MKM Health

    ReplyDelete
  10. Hi Paul - I have to admin I have zero experience with Workflow Services. I have deployed a service based on these concepts as a WCF service though, where the workflow is a code module (DLL) referred by the service module. Maybe someone else can shed some light on this?

    ReplyDelete
  11. Hi frode, two quick questions, currently the code will execute the rules until it finds the first match, then stops, what parts would i need to change to make it systematically pass through each rule? Additionally is it possibble to make the output of the rule, the then and else outputs, perform an Add to a property, e.g adding 3 to a property rather than overwriting and setting to a 3.

    Other than that fantastic code, really helped me set up my rule engine, better than most tutorials out there!

    ReplyDelete
  12. Anon July 17: Actually you're wrong. The current code _will_ execute all rules even if the first matches! The scenario I describe work because of the priorization - it executes from the highest number to the lowest. So for a male minor in this example, the rule with priorty 30 will first hit and assign the salutation to "Mr", but then an instant later it will be overwritten with "To the parents of" by the rule with priorty 10. I should probably have outlined this better in the article.

    As for adding a value to a property instead of assigning, you could still use the assignment Action, but use the current value as input to the next. Basically like "theValue = theValue + 1".

    For more complex operations, you can look to the InvokeMethod-Action, which basically allows you to invoke any C#-method. See http://msdn.microsoft.com/en-us/library/dd807388.aspx

    ReplyDelete
    Replies
    1. Frode, do you know of a way to abort checking against the rest of the rules once one of them fails? e.g 4 rules, second one fails so abort and dont attempt number 3 or 4. Something like this i feel would be very helpful as were looking to rack up a good few hundred rules and dont want to keep testing rules when we know the object failed early on.

      Ta in advance, Anon July 17

      Delete
    2. One approach is to attach each rule to the Else-branch of the previous one. You might have to rewrite some rules to accommodate this approach. In the scenario above, you'd have to add a fourth rule saying something like "If Person.Gender <> 'Male' Then 'Miss' "

      Another approach might be to wrap all assignment operations (Then-branch) in a Sequence ending with a TerminateActivity (http://msdn.microsoft.com/en-us/library/ms734509%28v=vs.90%29)

      Delete
  13. Frode, ah yes my mistake, moments after posting i noticed that my test data, ONLY matched ONE rule anyway so never hit any others, my mistake!

    Yes i thought id be able to do that too but it repeatedly failed, i had no way of finding what was already there by the time i was that deep through the code and wasn't in the mood to re-do every level to pass the value down that far. instead i just told the class that if its given an int add it to what its already got by using the set part of the property, was much quicker and works regardless.

    Thankyou for the fast reply and the links! i shall begin scrollign through it all now. Much thanks!

    Anon July 17

    ReplyDelete
  14. Hi Frode
    Extremely useful article thanks. How could I extend the rule conditions beyond simple logic operators to include functions such as "if(Mystring.contains("Priority")) then ..."?

    ReplyDelete
  15. Hello Frode,

    FYI, I noticed that the rule sequence created in Visual Studio does not function correctly for the case of Female and Married. The result is Miss instead of Mrs. It appears to stop evaluation after the Else assignment in the first If condition.

    ReplyDelete
  16. This comment has been removed by the author.

    ReplyDelete
  17. Hello Frode,

    Thanks for that good article. I modified it in a way that you also can have values in the Then and Else clauses. For that I changed in the RuleConverter class the following lines:

    Value = new InArgument(new VisualBasicValue(inRule.ThenValue))

    Value = new InArgument(new VisualBasicValue(inRule.ElseValue))

    Maybe than could be useful to mention too.

    Cheers,
    Bruno

    ReplyDelete
  18. Hey Frode,
    realy nice article.
    I have changed the Person.cs through my own class, which create an objcetreprasentation of an Xml-file.
    Therefore i modified the:
    DefaultRuleGenerator.cs
    SalutationAssigment.cs (the property "TargetParameter" gets now my output-property as a string)
    SalutationRuleGeneratorDynamic.cs (modified the Property)
    RuleSetInvoker (also new Name of the Property as a string)
    Mainform.cs

    I get no error,
    but i dont get expected output string, instead the output Window-field is showing me the "Xml-Classname+SubClassname".

    Hope you can hlp, Regards,
    B.D.

    ReplyDelete
  19. Reviewing the code that I downloaded, it appears youre still using the System.Workflow namespaces, instead of the newer System.Activities namespace.
    I think the main point of this article was to achive the same thing but without using the Rule classes. Im not sure when the System.Workflow namespaces becamde deprecated (I think 4.5 ?), but this article was originally written when VS 2010 was current.

    ReplyDelete
  20. Frode,
    Not sure what happened to my 1st comment, but Ill say again - thanks for this article. I have been banging away trying to figure out why the idea of "rules" are considered depricated in WF4. Basically, the scenario you mentioned at the beginning is where I started.

    Regarding my previous comment, I think the code that still refers to the previous namespaces are just artifacts, as I removed the references to system.workflow.* and the solution still builds successful.
    Im using this approach for the basis for my project.

    Oh and one additional question - what is the Csla reference in the SharedLibrary? Its missing in the zip file. I removed it as well, and the solution builds successful.
    Is it this ? https://github.com/MarimerLLC/csla/wiki

    thanks again for this helpful article

    ReplyDelete
  21. STNE: You are right about the CSLA library. I don't know why I referenced it from the SharedLibrary - it's not relevant for this example. I've removed that reference in addition to System.Workflow.* and updated the ZIP referenced

    ReplyDelete
  22. Can you also post some examples where the rules are not static?

    ReplyDelete
  23. Hi Frode, Thanks for the really useful article and the code. Its great that using this, both developers and business users can create custom rules. We also want to provide some kind of Intellisense on the editing grid to minimize the chances of errors - would you know how this can be achieved? - Sandeep

    ReplyDelete
  24. It can be easy to lose sight of the very reasons why you wanted to open your business startup. You can get wrapped up in the day-to-day operations, leaving little time to focus on what makes your business startup standout in the market. Having a strategy in place that allows you to keep you motivated can ensure your business stays on a path of success city business listing

    ReplyDelete
  25. This web-site is really a walk-through it really is the data you wanted concerning this and didn’t know who to ask. Glimpse here, and you’ll certainly discover it. 먹튀검증

    ReplyDelete
  26. This web-site is really a walk-through it really is the data you wanted concerning this and didn’t know who to ask. Glimpse here, and you’ll certainly discover it. Mega888 game client download

    ReplyDelete
  27. Pretty nice post. I just stumbled upon your weblog and wished to say that I’ve truly enjoyed browsing your blog posts. In any case I’ll be subscribing on your rss feed and I hope you write once more soon! 먹튀검증

    ReplyDelete
  28. Hi there! Good post! Please do tell us when I could see a follow up! 918kiss pussy888 ios

    ReplyDelete
  29. I’d have got to talk with you here. Which isn’t something Which i do! I quite like reading a post that may get people to feel. Also, many thanks for permitting me to comment! 918kiss kiss918 apk download

    ReplyDelete
  30. When do you think this Real Estate market will go back in a positive direction? Or is it still too early to tell? We are seeing a lot of housing foreclosures in Orlando Florida. What about you? I would love to get your feedback on this. 918kiss kiss918 apk download

    ReplyDelete
  31. After study a handful of the websites with your website now, and I truly as if your strategy for blogging. I bookmarked it to my bookmark website list and will be checking back soon. Pls consider my web site likewise and make me aware if you agree. 토토사이트

    ReplyDelete
  32. Dead written subject matter, Really enjoyed reading through . 파워볼사이트

    ReplyDelete
  33. noutati interesante si utile postate pe blogul dumneavoastra. dar ca si o paranteza , ce parere aveti de inchiriere vile vacanta ?. 918kiss

    ReplyDelete
  34. Nice post. I learn something more challenging on different blogs everyday. It will always be stimulating to read content from other writers and practice a little something from their store. I’d prefer to use some with the content on my blog whether you don’t mind. Natually I’ll give you a link on your web blog. Thanks for sharing. 먹튀검증 방법

    ReplyDelete
  35. Since 2007 there has been a massive shift of people buying goods and services online. But success for an online home business doesn't happen by accident. Here are 3 major factors that always lead to failure. استديو تصوير

    ReplyDelete
  36. Technology is the leader of the enterprising world. And it leads using a constitution. Unlike the traditional political structure, this constitution is Algorithms written by engineers, scientists, etc and not congressmen and politicians. Carbon dioxide

    ReplyDelete