Attaching a Workflow Instance to a Workflow Service Host
As part of WF and WCF integration in .NET 3.5, Workflow Services give us the ability to expose workflows as WCF services (including activation) and to consume WCF services from within workflows. Additional interesting features include explicit context management support, conversation support (multiple parallel send-receive pairs in a workflow), durable services and other interesting issues. (Check out the demos in the Visual Studio 2008 Training Kit, specifically the Demos\03 - WF directory.)
This model seems really cool because it's enough for me to put a ReceiveActivity as the first activity of my WF, set the CanCreateInstance of that activity to true, and create a WorkflowServiceHost for that workflow type. The details of activation, message dispatching, correlation (context management) are taken care of by the infrastructure. (By the way, I have used a custom implementation of almost all of these features first-hand just after the initial .NET 3.0 RTM, so I can't be blamed for being biased in favor of Microsoft's implementation.)
One thing that was immediately missing to me is the ability to create a workflow manually (i.e., not in response to a WCF service call) and then associate it with the WorkflowServiceHost instance. So basically I want to create the workflow myself in response to some external trigger, but from that point on the workflow can use ReceiveActivity without limitations (which means it must be associated with the WorkflowServiceHost). Consider the following workflow:
ReceiveActivity (CanCreateInstance = false) from IMyWorkflow.Start
ReceiveActivity (CanCreateInstance = false) from IMyWorkflow.End
So what I would like to do is create an instance of that workflow manually (using the WorkflowRuntime obtained from the WorkflowRuntimeBehavior on the WorkflowServiceHost), and then provide the endpoint and workflow instance id so that clients (in the broad sense) can now issue the Start and End operations to the workflow.
By the way, the reason for doing something like that is that I'm constructing a framework for executing workflows which I do not control; to do that, I want to construct the service host dynamically* and execute the workflow in response to my trigger and not in response to a WCF service call over an endpoint that initially is known only to me.
After spending lots of time, I started whipping up an implementation of a custom activity that I would dynamically insert into the workflow, and that activity would contain a ReceiveActivity listening on an internal contract I define; this poses problems because I can't dynamically modify a workflow before it's running, unless that workflow is a XOML-only workflow; so this means that my workflow developers will have to embed that activity in their workflow, YUCK!
Fortunately, there is a way to accomplish what I wanted, and it's phenomenally simple. Unfortunately, I couldn't find any documentation or reference to it, but here it comes. Create the workflow yourself using the WorkflowRuntime obtained from the host, and start the workflow (WorkflowInstance.Start). That's it. The workflow can now be addressed using the workflow instance id, if you set up the context correctly. (By the way, it's not that I didn't think of the first step. What I forgot to do is call the Start method, which is so damn stupid.)
Here's what the code looks like (except for the workflow definition itself):
You can find the sample code here. It demonstrates setting up the workflow outlined above as a workflow service, but creating an instance of that workflow manually and then communicating with it by manually setting up the context to specify that particular instance as a GUID. (Note that the "instanceId" in the example is case-sensitive.)
* This in itself is interesting because you need to add an endpoint to the host for each service contract your WF is using a ReceiveActivity on. To do that, I created an instance of the workflow (as a template) and recursed over all composite activities looking for ReceiveActivity instances (from which you can extract the contract information using the ServiceOperationInfo property).