Long running workflows and the ReceiveActivity
Using a ReceiveActivity is a great way of exposing a workflow via a WCF proxy.
Get started by creating a new project based upon "Sequential Workflow Service Library". This is found in the WCF projects node instead of the Worflow projects node!
Create a service interface, mine is nice and simple and add a few ReceiveActivity to the workflow and hoop up the service interface to the activities. No big deal so far and we can test things by just pressing F5 and having the WCF Test Client. Actually you can only call functions that are hooked up to an ReceiveActivity with the CanCreateInstance set to True so the WCF Test Client might not be all that useful here if you use multiple ReceiveActivity that are part of a single workflow or conversation.
Next step is to create a simple console application as a client with the following code:
Sub SimpleDemo()
Dim proxy As
New ServiceReference1.DoStuffClient
Console.WriteLine(proxy.AskQuestion("Is this cool?"))
proxy.AllDone()
End
Sub
This code works just fine
until we decide to split it up and recreate the WCF proxy object for part two like this
:
Private
Sub Part1()
Dim proxy As
New ServiceReference1.DoStuffClient
proxy.GetStarted("Maurice")
Console.WriteLine(proxy.AskQuestion("To be or not to be?"))
End
Sub
Private
Sub Part2()
Dim proxy As
New ServiceReference1.DoStuffClient
Console.WriteLine(proxy.AskQuestion("Is this cool?"))
proxy.AllDone()
End
Sub
In this case the second proxy object doesn't work at all and we get a FaultException with the message "There is no context attached to incoming message for the service and the current operation is not marked with "CanCreateInstance = true". In order to communicate with this service check whether the incoming binding supports the context protocol and has a valid context initialized."
So what gives? Well the WCF service needs to know which workflow the request needs to be routed to and uses the WorkflowInstanceId to do so. When you create a proxu and do the first call this WorkflowInstanceId is automatically added and resent with the next request. So we need to retrieve this WorkflowInstanceId and, when we create the second proxy object, add it again. Doing so turns out to be pretty simple and only takes a few extra lines of code:
Private _instanceId As
String
Private
Sub Part1()
Dim proxy As
New ServiceReference1.DoStuffClient
proxy.GetStarted("Maurice")
Dim contextManager As IContextManager = _
proxy.InnerChannel.GetProperty(Of IContextManager)()
Dim context As Dictionary(Of
String, String) = _
contextManager.GetContext()
_instanceId = context("instanceId")
Console.WriteLine(proxy.AskQuestion("To be or not to be?"))
End
Sub
Private
Sub Part2()
Dim proxy As
New ServiceReference1.DoStuffClient
Dim contextManager As IContextManager = _
proxy.InnerChannel.GetProperty(Of IContextManager)()
Dim context As
New Dictionary(Of
String, String)
context.Add("instanceId", _instanceId)
contextManager.SetContext(context)
Console.WriteLine(proxy.AskQuestion("Is this cool?"))
proxy.AllDone()
End
Sub
Basically what we need to do is retrieve the workflow instanceId from the context that is returned with the first call and save that. Next time we create a new proxy object we need to store the same instanceId in the request context before we actually use it and we are good to go
Setting the instaceId is actually done through a ClientContextProtocol object which is returned as an IContextManager inside of the channel properties. Check the bold lines in the previous code sample.
So why is this so important?
Well one of the most powerful features of Workflow Foundation is its capability to have long running workflows. Now long running workflows would not be very useful if the client application needs to keep its proxy alive for as long as it need to communicate with the workflow. Guess that would make "long running" a very relative thing. But with this technique all the client has to do is save the workflow instanceId somewhere, perhaps a database table, and it can reconnect to the same workflow at a later point in time.
Enjoy!