Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Windows Workflow (WF) as a WCF Service

5.00/5 (14 votes)
17 Jul 2009CPOL11 min read 116.3K   3.2K  
Sequential workflow as a WCF service. Create workflow custom activities, invoke child workflow from parent. Configure workflow runtime service using a config file. Basic idea of rules, creating a rule using the rule editor. Applying rules during runtime.

Introduction

While working on Windows Workflow Foundation (WF) on Windows Communication Foundation (WCF), I could not find any “practical” documentation/sample explaining, step by step, what needs to be done along with explaining “why”. This article describes how to create a sequential WF as a WCF service.

Well...so, what are we going to learn here?

  1. Create a sequential workflow service library on Windows Communication Foundation.
  2. Work with “RecieveActivity” – Create contracts, bind properties, etc.
  3. Create a website to expose the library to the outside world (web configuration, etc.).
  4. Create a custom workflow runtime service and configure it on the service model section.
  5. Create a custom activity along with custom events and properties using DependencyProperty.
  6. Create a custom activity to invoke a workflow from another workflow. Also, how to pass parameters from/through.
  7. Basic idea of rules, how to create a ruleset using the rule editor. Apply rule during runtime.

Background

As I mentioned, WCF, WF, Rules are very interesting technologies, but at the same time, it's a bit time consuming task to gather all the required basic information from MSDN and other sites. I just wanted to capture all the necessary functionalities around this area.

Using the code

A simple business scenario will make this more interesting. So, let’s define it:

  1. Pre-order details (or raw order details) come from an external source.
  2. Client invokes “Order Processor” to process data.
  3. “Order Processor” applies business rules on raw data and decides which ones to accept and reject.
  4. It also applies a specific business rule (shipping rate discount for existing customers) on each process (order) and saves the order and shipping details into the database.
  5. Business user can change a rule anytime they want.
  6. Optionally, the invoker also can define whether the input file is to be archived or not. [Just to make our main workflow a bit interesting.]

Business Scenario

In technical terms:

  1. Order processor is our main workflow on WCF. The workflow will be active (alive) till we switch it off explicitly, so that it can process our request on demand.
  2. The main workflow applies rules on file level (on all records). In this example, if an order amount is found as zero, reject the order (Data Quality Rule).
  3. After processing file level rules, it invokes another child workflow (which will be a custom activity) to process the workflow by passing the complete ‘order’ object. [Here we will see how we can define properties using “DependencyProperty” and how to use it.]
  4. ‘Worker’ workflow applies rules on order level (ShipmentCostRule) and shipping rates get calculated. Finally, it saves the order into the DB using LinQ.
  5. Both workflows will use a custom workflow runtime service, ExternalRuleSetService, which will be used to retrieve rules from the database.
  6. We will also create two custom activities, “InvokeWorkflow” - to invoke another workflow, and “PolicyFromService” – to apply a rule, which internally is going to use the workflow runtime service to pull rules from the database. This will also have a custom activity event which will get fired after applying the policy.
  7. We will use the rule editor to create rules.

References

I have used a few samples from Microsoft samples which has the rule editor, PolicyFromService, sample usage, etc.

Getting started

Alright! Let us first decide our folder structure.

Business Scenario

  • Lib – will contain all the binaries which will be used by the various apps during runtime.
  • Src/Libraries – will contain all the libraries (Custom Activities, Custom workflow runtime service, etc.).
  • Src/RuleEditor – rule editor from Microsoft sample, I have not done anything special on this.
  • Src/Services – our main workflow on WCF service, also a website to host the service.
  • Src/TestClient – test client to invoke the service.

Create the main workflow

  1. Create New Project-->WCF-->Sequential Workflow Service Library.
  2. new_wcf_sequential_wf_service_library.JPG

  3. A sequential workflow will get created with a “RecieveActivity”, also an interface “IWorkflow1”. Rename workflow “Workflow1” to “ProcessManager” and the interface “IWorkflow1” to “IProcessManagerServices”, because this will be our service which we are going to expose to the external world.
  4. Open IProcessManagerServices. Rename the existing operation contract to string Start(bool fileToBeArchived).
  5. Open “ProcessManager”, you will notice the recieveActivity with a red exclamation mark. Don’t worry, this is because we have renamed the contract “getData”.
  6. wf_initial

  7. Correct the contract operation name, properties --> ServiceOperationInfo --> select “Start” operation.
  8. We need to bind the input parameter “fileToBeArchived”.
    1. ProcessManager --> view code.
    2. Define a variable public bool FileToBeArchived;
    3. Come back to Design mode.
    4. Click on “Promote Bindable Properties”.
    5. RecieveActivity_prop

    6. You should see the “FaultMessage”, “fileToBeArchived”, and “OperationValidation” properties filled.
    7. Delete the “FaultMessage” and “OperationValidation” properties back to blank. We are going to learn about these in the next section. Go to the code view. Delete all the related code VS IDE has added into your code. You should see something like this.
    8. recieveActivity_notrequired_code

    9. Click on ‘fileToBeArchived” and bind with our defined variable “FileToBeArchived”.
  9. We want our service (main workflow) to be active (always alive). For that, we will use a “While” activity. Before dropping it, let us first create another variable public bool SwitchedOn.
  10. Create a “Code” activity just above our receive activity and name it as “SwitchOn”. Drop a “While” activity just below it, and then drag Start “Receive” activity inside “While” activity. Name it as “WhileSwitchedOn”.
  11. On the execute code method, write this.SwitchedOn = true;.
  12. Let's drop another “Receive” activity, and name it “SwitchOff”. Create another operation contract “SwitchOff” and bind it accordingly.
  13. So, our workflow will be alive till we don’t switch it off explicitly.
  14. The client will invoke the workflow “Start” operation with a parameter whether the input file needs to be archived or not, just to show you a few “ifelse” activities to perform some tasks at the main workflow level.

Create libraries

Alright…now, let’s take a break from here and create some other interesting stuff! Let’s create our workflow library with the required custom activities “InvokeWorkflow” and “PolicyFromService”.

A custom activity needs to be inherited from System.Workflow.ComponentModel.Activity, and override its Execute method, that’s it! So simple!

  1. PolicyFromService – I just took the Microsoft sample, not done anything special on this, but just added a custom event which will get fired after applying the policy (ruleset).
  2. InvokeWorkflow – While overriding the Execute method, you just need to do following:
    1. Get service as IStartWorkflow IStartWorkflow startWorkflow = executionContext.GetService(typeof(IStartWorkflow)) as IStartWorkflow;
    2. Start the workflow this.InstanceId = startWorkflow.StartWorkflow(this.TargetWorkflow, this.Parameters). Targetflow and Parameters will be filled by “calling workflow” or “parent workflow”.

    Custom property using DependencyProperty:

  3. Define a static DepedencyProperty, public static readonly DependencyProperty InstanceIdProperty = DependencyProperty.Register("InstanceId", typeof(Guid), typeof(InvokeWorkflow));.
  4. Create a public property to ‘get’ and ‘set’ the (base) values
  5. C#
    [Category("Activity")]
    [Description("InstanceId of of the invoked workflow")]
    public Guid InstanceId
    {
       get
        {
            return (Guid)base.GetValue(InstanceIdProperty);
        }
        set
        {
            base.SetValue(InstanceIdProperty, value);
        }
    }
  6. Define a static DependencyProperty:
  7. C#
    public static DependencyProperty AfterPolicyAppliedEvent = 
      DependencyProperty.Register("AfterPolicyApplied", 
      typeof(EventHandler), typeof(PolicyFromService));
  8. Create a public event to ‘add’ and ‘remove’ the event handler:
  9. C#
    public event EventHandler AfterPolicyApplied
    {
       add { base.AddHandler(AfterPolicyAppliedEvent, value); }
       remove { base.RemoveHandler(AfterPolicyAppliedEvent, value); }
    }
  10. Raise the event in the required place(s).
  11. C#
    base.RaiseEvent(PolicyFromService.AfterPolicyAppliedEvent,this, EventArgs.Empty);

Custom workflow runtime service

ExternalRuleSetService – I have not done anything special on this, but just used the Microsoft sample as it is. You guys just try to understand what the code does. We are going to configure this workflow runtime service, so that it can be used at run time (by the “PolicyFromService” activity).

Finally, compile the solution “Library.sln” and copy the binaries into the root\Lib folder, which will be referred by our main solution.

Back to the main workflow

To use our custom libraries, go to Toolbox --> right click --> Choose Items. Select our libraries from the Lib folder. You should see our components on the toolbar like this:

toolbar

As for our business scenario, after processing the initial tasks on the file (archive, etc.), we need to apply the business rules on the completed input records, accept, and reject records accordingly.

Drop the “PolicyFromService” activity, name it “DataQuality”, and also set RuleSetName as “DataQuality”. Remind me to create this rule with the rule editor :)

Child Workflow

Okie Tokie! We have filtered the records (pre order details) and we need to process each order by applying the business rules and saving them into the DB. For that, we will create another sequential workflow “Worker” and place the “Code” activity to initialize the worker, then apply the rule, a “PolicyFromService” activity, and finally a “Code” activity to save the processed order into the DB. Name these accordingly and set the “PolicyFromService” activity RuleSetName to “ShipmentCostRule” …Remind me to create this rule as well :)

Worker

We are going to invoke this child “Worker” workflow from our main “ProcessManager” workflow. And, this will keep continuing until all orders are processed. So, let's drop a “While” activity, select the Condition as "Declarative Rule Condition" with an expression as this.OrderQueue.Count != 0. Drop an “InvokeWorflow” activity inside the "While" activity. Set TargetWorkflow to “Worker” (remember we are now using a DependencyProperty) by browsing type.

Finally…our final main workflow looks like…

MainWorkflow

We need to pass the required parameters from the main workflow to the child workflow. In this case, we are going to pass a complete ‘order’ object to “Worker”. And, we do it in the “BeforeInvoke” event of the “worker” workflow.

Dequeue the order from the queue and pass it as a parameter.

C#
OrderDetail thisOrder = this.OrderQueue.Dequeue();
Type targetWorkflow = typeof(Worker);
(sender as InvokeWorkflow).Parameters.Add("Order", thisOrder);

[Note - I have used the .NET Queue to queue all records, and dequeue them one by one to process.]

Creating Rules

Okaayy... :) We are pretty close with our application, just need to create two rule sets using the rule editor. Hope you have been with me till now.

  1. DataQuality – Ignore all orders with a $0 amount. I have created an extension method FilterZeroAmountOrders to filter all such records, which we are going to use in this rule.
  2. Condition – this.RawOrders.FilterZeroAmountOrders().Count > 0.

    Then Action – this.Orders = this.RawOrders.FilterZeroAmountOrders().

  3. ShipmentCostRule – If a member has already bought items of more than $1000 value, we give 50% discount to him. Another extension method TotalAmountOrderedTillDate will help us on this.
  4. Condition – this.Order.TotalAmountOrderedTillDate() > 1000.

    Then Action – this.Order.ShippingCost = this.Order.ShippingCost * 0.5.

We don’t care about else parts, we can keep those as blank. Save the rule.

Hosting

Let’s host our service by adding a blank website into our solution. You can delete all except web.config. I will also suggest deleting all contents of web.config so that we know what exactly is needed to run this solution. This is important… Let’s divide our tasks into two parts..

  1. Configure or expose our service contract on the WCF service.
    1. To do that, we will add a new item “WCF Service” into the website and name it as ProcessManagerService.svc. Delete all other items, and folders if VS IDE creates any.
    2. website solution

    3. In ProcessManagerService.svc, we need to tell which service we are going to expose. Our service is actually a workflow named “ProcessManager”. Mention this along with its Factory which takes care of the internal “things” to expose the workflow as a service.
    4. C#
      Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" 
      Service="BigDs.WF.Services.ProcessManager"
    5. Define its endpoint as per the old WCF “ABC” rule. Look at the web.config for details.
  2. Configure our custom WorkflowRuntime service under the service model, because WCF is the host for our workflow. This config section is at <system.serviceModel> --> <serviceBehaviors> --> <behavior> --> <workflowRuntime>
    1. Add the workflow runtime service as follows.
    2. XML
      <workflowRuntime>
       <services>
        <add type="BigDs.WF.WorkFlow.ExternalRuleSetService, BigDs.WF.WorkFlow" />
       <services>
       <commonParameters>
         <add name="ConnectionStringName" value="cs" />
       </commonParameters>
      </workflowRuntime>

Set the website as the startup project and run it. Click on ProcessManagerService.svc to verify that our service is configured properly.

We are nearly ready, this time I am serious :), just need to create client to invoke.

Client

Create a simple console application, add a service reference, and invoke the WCF method as follows…

C#
ProcessManagerServicesClient psc = new ProcessManagerServicesClient();
psc.Start(true);

That's all folks...Run your website, execute the client to test the application. Please let me know what you think about this article, or how can we improve/enhance it.

Let’s quickly summarize what all things we learnt today

  1. WCF Sequential workflow (WF) service library.
  2. Exposing it as a WCF service by hosting it on a website.
  3. Configuring it along with a workflow runtime service.
  4. Creating a custom workflow runtime service.
  5. Creating custom activities.
  6. Creating custom properties using DependencyProperty.
  7. Creating custom events using DependencyProperty.
  8. A basic idea of the rule set.
  9. How to use a rule editor to create rule sets.
  10. Getting rules from the DB and apply it during runtime using a workflow runtime service.
  11. Creating a custom activity to invoke a child workflow.
  12. How to pass parameters to a child workflow.
  13. How to use extension methods and use it in the rule editor to create rules.

Points of interest

We can define <commonParameters> under <workflowRuntime>. I am still trying to find a way to read this from the config. Huuuh! It's really annoying to search for these small things. May be some other time.

Running the code

  1. Unzip the file.
  2. Execute the scripts under Script folder. This will create two tables with some existing data (two rules and three existing order details).
  3. Update the AssemblyPath of the RuleSet table according to your path. This is important.
  4. Update InputFileLocation in web.config to your path.

History

  • 1st version, released on 16th July 2009.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)