A while back I built a sample of how you might make a synchronous call between a Parent Workflow and a Child Workflow. The OOB InvokeWorkflow Activity is asynchronous, and a number of people on the workflow forums were interested in how to make this work, so I coded up a quick and dirty sample which a lot of people have used.
The problem with the sample (and this is a pretty common problem that is very easy to have with Windows Workflow) is that it didn’t work in the face of persistence. If the host instance went away, the workflows could persist – but the CallWorkflowService wouldn’t respond properly because of all its state was kept in local variables. I knew this was a problem a few months ago, but I need to find a solution (the problem is non-trivial IMO) and needed to find the time to implement the solution.
You can download the persistence friendly CallWorkflow sample here.
The way I solved the problem is based around the use of DependencyProperty. Dependency properties are one area of WF that is still fairly mysterious to workflow developers. Without writing a whole post on DependencyProperty – there are two basic reasons for the use of DependencyProperty (which is really a simple concept – keeping property values in a collection in the base class rather than having individual field storage for each property): first to make serialization more efficient, second to enable easier data binding between properties on unrelated Activities (known as Activity Binding).
Another interesting feature is the use of attached DependencyProperties. An attached DependencyProperty can be “attached” to an Activity instance which can be totally unaware of the property. There are a few interesting usages for this – Dennis talks about one here, and I mentioned one in my post about building a custom WorkflowLoaderService.
Attached DependencyProperties was the way I solved this particular problem as well. The CallWorkflowService does keep a local cache of the relationship between a child workflow and the parent workflow, as it needs this information to send a message to the parent workflow when the child workflow is completed or terminated. At the same time it builds its local cache of this data, the CallWorkflowService embeds this information into the child workflow instance by using attached DependencyProperties. The CallWorkflowService defines two attached DependencyProperties by calling DependencyProperty.RegisterAttached. One property is for the InstanceId of the parent workflow, the other is for the queue name on the parent workflow that the results of the child workflow will be sent to.
By embedded this information into the child workflow instance, when a child workflow persists the data persists with the workflow instance. When the workflow instance is loaded back into a host – the CallWorkflowService can extract that data from the instance and rebuild its local cache of mappings between child and parent workflows.
Now – to make this implementation work there were a couple of issues I had to work around. The first is getting attached DependencyProperties embedded into the child workflow instances. Now – one way to do this would have been to use DynamicChange. Inside of the CallWorkflowService’s StartWorkflow method where the child workflow instances are created using WorkflowRuntime.CreateWorkflow, I could have used the WorkflowChanges object to make a cloned copy of the child workflow and then embed the DependencyProperty values into the instance using Activity.SetValue (which is really DependencyObject.SetValue). In this sample I went to great lengths to use the most efficient means possible to accomplish my tasks. So instead of using WorkflowChanges, I created a custom WorkflowLoaderService (again for other reasons for doing this see my earlier blog post). By creating a custom WorkflowLoaderService I could embed the DependencyProperty values at workflow creation time – avoiding the overhead of DynamicChange.
The next issue of course was how to communicate between the CallWorkflowService and the WorkflowLoaderService. Since the call to WorkflowRuntime.CreateWorkflow doesn’t allow non-Activity parameters to be passed (and the parameters themselves are not passed to the WorkflowLoaderService) I had to come up with an out-of-band way to pass data to the custom WorkflowLoaderService so it would be able to detect when to place these DependencyProperty values into a child workflow (since not all workflows will be child workflows – some will be parent workflows and some presumable wouldn’t be a child or a parent – just standalone workflows in this host). To pass these two pieces of data (the parent’s InstanceId and the queue name) I went with CallContext. CallContext is a useful way to pass data between two methods in the same logical call stack (see TODO for more information about CallContext).
So the CallWorkflowService puts the two needed values into CallContext, and the custom WorkflowLoaderService picks them up from the CallContext (if they are there) and calls Activity.SetValue to embed the data inside of the workflow instance (again this is to make sure the information is persisted if and when the child workflow were to persist).
So the first half of the problem is accomplished – the parent’s instance id and the queue name become embedded inside of the child workflow using attached DependencyProperties by using CallContext to pass the data between the CallWorkflowService and the custom WorkflowLoaderService. Now I needed to solve the second part of the problem – which is to extract the data from instances which are loaded into a host from the persistence store.
My first plan was to build a custom WorkflowPersistenceService and wrap whatever WorkflowPersistenceService the host was originally going to use (like the SqlWorkflowPersistenceService). The rationale I had for try to make this work was again to make this sample as efficient as possible. When an Activity instance is reloaded by a WorkflowPersistenceService, the WorkflowPersistenceService has access to the raw Activity instance (not just through the WorkflowInstance object).
The other option isn’t my preferred option because it involved calling WorkflowInstance.GetWorkflowDefinition. I’ve blogged about GetWorkflowDefinition before in terms of what it is good for and what it shouldn’t be used for. The reason it isn’t my preferred option doesn’t have anything to do with those issues – but rather with the expense of calling GetWorkflowDefinition. GetWorkflowDefinition makes a cloned copy of the Workflow. This means the Activity instance is serialized into a memory stream and then deserialized into a new object instance (this is what Activity.Clone does). This can be an expensive operation for a medium to large workflow. Obviously GetWorkflowDefinition is a very useful API and you can use it, but it should be used sparingly.
Of course you might be able to tell I ended up using GetWorkflowDefinition. The problem with my first plan was that you can’t use a container pattern with any of the well-known Workflow Services like the TrackingService, WorkflowPersistenceService, or WorkflowLoaderService. The reason you can’t is because the methods you’d need to call on the contained instances are marked “protected internal”. This means a derived instance can’t call the methods necessary to delegate functionality to the contained instance. The WorkflowRuntime of course can call the methods since they are marked “internal” and the WorkflowRuntime is in the same assembly as the base class definitions.
So to solve the second half of the problem the CallWorkflowService subscribes to the WorkflowRuntime.WorkflowLoaded event. When a workflow instance is loaded it checks to see if the two DependencyProperties are found inside of the loaded instance. When the properties are found, that indicates to the CallWorkflowService that this is indeed a child workflow, and it uses this information to rebuild its local cache mapping between the parent and the child.
When a Workflow completes or terminates the CallWorkflowService looks to see if the instance is one that it is interested in (i.e. if it is a child workflow). If it is a child workflow, then it extracts the parent id and the queue name from the instance. Now – in the case of the WorkflowCompleted event, I didn’t mind using the WorkflowDefintion property on the WorkflowCompleted event argument – because the WorkflowRuntime pre-populates the WorkflowDefintion so my code isn’t calling GetWorkflowDefinition again (i.e. it isn’t adding additional overhead).
That’s pretty much it – if you missed it earlier – feel free to download the persistence friendly CallWorkflow sample here (I’ve replaced the earlier sample with this one – the earlier sample I named HideChildWorkflow – because it also shows how you can use a composite child activity and hide the designer by using a custom designer). Note – to run this sample you’ll need to create a database named CallWorkflowPersistence and configure the SqlWorkflowPersistence tables and stored procs.