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:
WorkFlowContracts
: Defined a ICalculator
Service contract. This contract is exposed to client.WWFAsWCFService
: This is a sequential calculator workflow implementing ICalculator
contract.WorkflowService
: The service which exposes the above workflow.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:
- SqlPersistenceService_Schema.sql
- 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.
<serviceBehaviors>
<behavior name="WorkFlowService.Service1Behavior">
<serviceMetadata httpGetEnabled="true"/>
<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.
_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:
_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);
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.
_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