I built a sample (you can download it here) based upon a question asked in the WF forums last night (you can see the post here).
I wanted to post it here so I could point out some of the interesting features it has in it.
First – there is a CallWorkflowActivity – which creates and executes another workflow in a synchronous manner. The WF built-in InvokeWorkflowActivity invokes another workflow – but does it asynchronously. It turns out to do is synchronously you have to build a custom activity – mostly because synchronously you have to make some assumptions about the workflow you are calling. In my example – the assumption I am making is that the "called" workflow will have parameters to pass in, and parameters that are return – but there is no other communication between the host and the workflow.
Now – since my CallWorkflowActivity can't talk to the WorkflowRuntime directly – in order to make it work I implemented a service to add to the WorkflowRuntime. Why can't the activity talk to the WorkflowRuntime? Well that could create some interesting problems – like what if an Activity could ask the WorkflowRuntime to persist the WorkflowInstance the Activity is being executed inside of? The Activity would be executing waiting for persistence, the WorkflowRuntime would be waiting to persist until the Activity completed – classic deadlock.
InvokeWorkflow uses a service that is installed by default – IStartWorkflow – which works create in the async case – but doesn't work in the sync case. So I just created a custom service. One aspect of WF that I try to emphasize to people when I do my WF trainings is that even though there are well-known services inside of the WorkflowRuntime – you can add any number of services which won't be known to the WorkflowRuntime – but can be used by your Activities. In fact this is necessary in some cases (like this one) where the code you want to execute would be "illegal" inside of Activity.Execute (things like spawning threads or anything that would go against the model of WF in terms of what Activities should do). Now in this case I made the Service derive from WorkflowRuntimeService because my service needs the WorkflowRuntime to do its job, and that is the easiest way to get a reference to the WorkflowRuntime. But it isn't a requirement that a service added via WorkflowRuntime.AddService derive from WorkflowRuntimeService.
So here is how my service executes a workflow:
public class CallWorkflowService : WorkflowRuntimeService
{
public void StartWorkflow(Type workflowType,Dictionary<string,object> inparms,Guid caller,IComparable qn)
{
WorkflowRuntime wr = this.Runtime;
WorkflowInstance wi = wr.CreateWorkflow(workflowType,inparms);
wi.Start();
ManualWorkflowSchedulerService ss = wr.GetService<ManualWorkflowSchedulerService>();
if (ss != null)
ss.RunWorkflow(wi.InstanceId);
EventHandler<WorkflowCompletedEventArgs> d = null;
d = delegate(object o, WorkflowCompletedEventArgs e)
{
if (e.WorkflowInstance.InstanceId ==wi.InstanceId)
{
wr.WorkflowCompleted -= d;
WorkflowInstance c = wr.GetWorkflow(caller);
c.EnqueueItem(qn, e.OutputParameters, null, null);
}
};
EventHandler<WorkflowTerminatedEventArgs> te = null;
te = delegate(object o, WorkflowTerminatedEventArgs e)
{
if (e.WorkflowInstance.InstanceId == wi.InstanceId)
{
wr.WorkflowTerminated -= te;
WorkflowInstance c = wr.GetWorkflow(caller);
c.EnqueueItem(qn, new Exception("Called Workflow Terminated", e.Exception), null, null);
}
};
wr.WorkflowCompleted += d;
wr.WorkflowTerminated += te;
}
}
Notice the use of anonymous delegates really helps in this case. No need to keep state about workflow instances around in the service – the anonymous delegates get registered and unregistered for each Workflow execution.
To get the data back to the Activity – the WorkflowQueue name that the Activity created – so the Activity can return ActivityExecutionStatus.Executing from Execute – and it waits for an item to come back on the Queue.
If the Workflow terminates – the service sends an exception into the WorkflowQueue – which causes that exception to promolgate to the workflow itself.
Another interesting part of this sample is the use of the WorkflowParameterBindingCollection – similar to the way that the InvokeWorkflow,CallExternalMethod and other built-in activities do. Make note of the DependencyProperty declaration and the call to base.SetReadOnlyProperty in the constructor – both are necessary to get Collections to serialize correctly into your .designer file.