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

Windows Workflow as WCF Service and Workflow Persistence

3.07/5 (8 votes)
19 Jan 2009CPOL3 min read 59.7K   694  
Workflow calculator WCF service, an example of long running workflows

Introduction

Windows Workflow Foundation  is a .NET 3.0 and 3.5 feature. In this article, I am trying to explain how workflow can be exposed as WCF web service.

Persistence of the workflow is also explained in detail. I took sequential calculator workflow service as an example.

Background

If you are new to workflow, please refer to this link on MSDN. It explains all the workflow activities, persistence and  serialization concepts.

Using the Code

The uploaded zip file contains 4 projects.  The solution contains:

  1. WorkFlowContracts: Defined a ICalculator Service contract. This contract is exposed to client.
  2. WWFAsWCFService: This is a sequential calculator workflow implementing ICalculator contract.
  3. WorkflowService: The service which exposes the above workflow.
  4. WinformClient: The consumer of the workflow service.

To set up the whole solution, first you need to create the persistence database. For this, use the Persistence schema and logic scripts given by Microsoft at the following location: ~\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN

Create database, let's say PersistanceDB in the SQL server. Run the following scripts in order:

  1. SqlPersistenceService_Schema.sql
  2. SqlPersistenceService_Logic.sql

After this step, your persistence database is ready for the workflow.

Now you need to set the connection string for the persistence database.

Open web.config file of workflow service. Under Servicemodel tag, go to ServiceBehaviour and specify the connection for your persistence database.

XML
<serviceBehaviors>
    <behavior name="WorkFlowService.Service1Behavior">
        <!-- To avoid disclosing metadata information, 
	set the value below to false and remove the metadata 
	endpoint above before deployment -->
        <serviceMetadata httpGetEnabled="true"/>
        <!-- To receive exception details in faults for debugging purposes, 
	set the value below to true.  Set to false before deployment 
	to avoid disclosing exception information -->
        <serviceDebug includeExceptionDetailInFaults="false"/>
        <workflowRuntime name="WorkflowServiceHostRuntime" 
		validateOnCreate="true" enablePerformanceCounters="true">
            <services>
               <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
		 System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, 
		PublicKeyToken=31bf3856ad364e35"
                     connectionString="Data Source=*******;
			Initial Catalog=*******;user id=*******; 
			password=*******;Pooling=False"
                     LoadIntervalSeconds="1" UnLoadOnIdle= "true" />
            </services>
        </workflowRuntime>
    </behavior>
</serviceBehaviors>

Build the solution. Run the WorkflowService. Make sure that WinformClient references the right URL of the workflow service. The idea here is  to use the same instance of the workflow whenever you run the client.

When client is run for the first time, workflow is created  by calling StartCalculator method as shown below. I am saving the workflowid in appsetting for later use.

C#
_calcChannel = new ChannelFactory<ICalculator>
		("WSHttpContextBinding_ICalculator").CreateChannel();
            _workFlowId = _calcChannel.StartCalculator();
            Configuration config = 
		ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
            config.AppSettings.Settings.Remove(_WORKFLOWID);
            config.AppSettings.Settings.Add(_WORKFLOWID, _workFlowId);
            config.Save();

On the workflow side, the whole workflow is designed using a bunch of ReceiveActivities, each activity implementing one OperationContract of the ICalculator Service Contract.

Only StartCalculator Receive activity's CreateInstance is set to true, i.e. only one Receive activity can create an instance of the workflow.

Once the workflow is created, you will see an entry in the InstanceState table of the persistence database.

Let's say by calling StartCalculator() method of the service, you get back the workflow id:

174bf47b-4c8a-40d8-8432-0ac1f38eec9a 

Now to call the same instance of the workflow next time, you run the client as explained below:

C#
 _calcChannel = new ChannelFactory<ICalculator>
		("WSHttpContextBinding_ICalculator").CreateChannel();
            IContextManager context = 
		(_calcChannel as IClientChannel).GetProperty<IContextManager>();
            IDictionary<string, string> instanceIdContext = 
				new Dictionary<string, string>();
            instanceIdContext.Add
		("instanceId", "174bf47b-4c8a-40d8-8432-0ac1f38eec9a");
            context.SetContext(instanceIdContext);   
C#
 label1.Text = Convert.ToString(_calcChannel.AddNumbers(10, 20));

In the above code , IContextManager object holds the context information of the workflow. Context is nothing but the workflowid which was created using StartCalculator() method.

By creating a  dictionary with key instanceId and value as  174bf47b-4c8a-40d8-8432-0ac1f38eec9a, you can re-instantiate the same workflow which was persisted in the database. Apply the context and call AddNumbers method of the calculator. This is very useful when there are long running workflows. 

About wsHttpContextBinding

For communication with workflow Service, service must use basicHttpbinding or wsHttpBinding. If you want to implement some security like protection for context (Sign, encrypt) you should use wsHttpContextBinding. WorkflowServiceHost which hosts the WorkFlow on the server side uses this binding to communicate with clients. In my example, I returning workflowid on method call StartCalculator(). But you also get the context from the channel itself. It is as shown below. Context is returned only in the case when one of Receive Activity (In my case StartCalculator receive activity) creates a workflow instance.

C#
_calcChannel = new ChannelFactory<ICalculator>
		("WSHttpContextBinding_ICalculator").CreateChannel();
           _calcChannel.StartCalculator();
            IContextManager context = 
		(_calcChannel as IClientChannel).GetProperty<IContextManager>();
            IDictionary<string, string> instanceIdContext = context.GetContext();

Hope this article helps in getting a basic understanding of workflow service and its persistence. 

Points of Interest

I had a hard time figuring out how  to call persisted workflow. 
IContextManager is the trick. By applying the context, you can simply invoke the same workflow Instance.
Now you can persist the workflow. I suggest you read about durable services where you can persist the state of the service itself.  

History

  • 19th January, 2009: Initial post

License

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