I’m currently working on an application that uses Windows Workflow Foundation to dynamically create parts of a user interface. I recently came across a situation where I needed to dynamically add and then execute a workflow activity based upon metadata parameters that were passed into the workflow. Mark Schmidt’s: Dynamic Update 101 for Windows Workflow Foundation blog entry was an excellent starting point, but while his example involves modifying the workflow once it starts, he modifies the workflow from within the runtime environment; I need to modify it from within the workflow itself. (Also, on a small side note, Mark’s example is against a previous release of Windows Workflow and a few commands have changed.) While there isn’t a whole lot of difference between Mark’s approach and mine, there was one nuance at the end that I didn’t expect and is worth noting.
To modify the workflow I overrode the Execute method of the workflow. The Execute method code is the following:
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
WorkflowChanges wfChanges = new WorkflowChanges(this);
// get a handle to the transient workflow object
CompositeActivity transientWorkflow = wfChanges.TransientWorkflow;
// create the activity
ObjectHandle dynamicActivityHandle = Activator.CreateInstance(dynamicActivityAssembly, dynamicActivityType);
if (dynamicActivityHandle == null)
{
throw new ApplicationException("Activity: " + dynamicActivityType + " not found in " + dynamicActivityAssembly);
}
Activity dynamicActivity = dynamicActivityHandle.Unwrap() as Activity;
if (dynamicActivity == null)
{
throw new InvalidOperationException("Activity Type: " + dynamicActivityType + " in " + dynamicActivityAssembly + " is not a valid activity");
}
DynamicActivityMarkerActivity dynamicActivityMarkerActivity = FindDynamicActivityMarkerActivity(transientWorkflow);
if (dynamicActivityMarkerActivity != null)
{
int markerPosition = dynamicActivityMarkerActivity.Parent.Activities.IndexOf(dynamicActivityMarkerActivity);
dynamicActivityMarkerActivity.Parent.Activities.Insert(markerPosition + 1, dynamicControlCompositionActivity);
}
// save the changes.
this.ApplyWorkflowChanges(wfChanges);
}
return base.Execute(executionContext);
}
The code is mostly plagiarized from Mark’s example and he does a fine job of explaining it, but let’s review the code anyways.
WorkflowChanges wfChanges = new WorkflowChanges(this);
// get a handle to the transient workflow object
CompositeActivity transientWorkflow = wfChanges.TransientWorkflow;
Creating a WorkflowChanges object creates a copy of the running Workflow. This is a necessary step because if we attempted to modify the running workflow and not a copy of it by say adding an activity to the activities collection (i.e. this.Activities.add(myNewActivity);) we would receive a runtime error stating we can’t modify this collection at runtime. The WorkflowChanges provides the ability to make multiple changes to the workflow and then apply all of those changes at once. Mark relates this to a database transaction and I think that’s a good analogy.
Executing the TransientWorkflow property on the WorkflowChanges just provides a pointer to the root CompositeActivity.
// create the activity
ObjectHandle dynamicActivityHandle = Activator.CreateInstance(dynamicActivityAssembly, dynamicActivityType);
if (dynamicActivityHandle == null)
{
throw new ApplicationException("Activity: " + dynamicActivityType + " not found in " + dynamicActivityAssembly);
}
Activity dynamicActivity = dynamicActivityHandle.Unwrap() as Activity;
if (dynamicActivity == null)
{
throw new InvalidOperationException("Activity Type: " + dynamicActivityType + " in " + dynamicActivityAssembly + " is not a valid activity");
}
This code uses two parameters, dynamicActivityType and dynamicActivityAssembly and uses the System.Activator class to create an instance of the type contained within the assembly. It then does some basic error checking to ensure that the type was found and that it has a base type of System.Workflow.ComponentModel.Activity.
DynamicActivityMarkerActivity dynamicActivityMarkerActivity = FindDynamicActivityMarkerActivity(transientWorkflow);
if (dynamicActivityMarkerActivity != null)
{
int markerPosition = dynamicActivityMarkerActivity.Parent.Activities.IndexOf(dynamicActivityMarkerActivity);
dynamicActivityMarkerActivity.Parent.Activities.Insert(markerPosition + 1, dynamicControlCompositionActivity);
}
// save the changes.
this.ApplyWorkflowChanges(wfChanges);
}
Here, I’m finding the location of a marker activity (DynamicActivityMarkerActivity) within my workflow that I use as a reference point so I can customize where to insert the new activity. FindDynamicActivityMarkerActivity is just a helper function to find the marker activity. If I’ve found my marker activity I proceed to insert the dynamic activity into the activities collection.
Finally, I apply the workflow changes to the running workflow using the ApplyWorkflowChanges command.
At this point I thought that I was done and that my new activity would execute. The weird thing is that nothing happened. Furthermore, it seemed as my workflow stopped running after this point. Knowing that workflow uses an eventing model to communicate with its runtime I started adding event handlers to the workflow runtime events.
m_workflowRuntime.WorkflowAborted += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowAborted);
m_workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
m_workflowRuntime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowIdled);
m_workflowRuntime.WorkflowLoaded += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowLoaded);
m_workflowRuntime.WorkflowStarted += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowStarted);
m_workflowRuntime.WorkflowSuspended += new EventHandler<WorkflowSuspendedEventArgs>(workflowRuntime_WorkflowSuspended);
m_workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);
m_workflowRuntime.WorkflowCreated += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowCreated);
m_workflowRuntime.WorkflowResumed += new EventHandler<WorkflowEventArgs>(m_workflowRuntime_WorkflowResumed);
A good list and description of the events can be found at Moustafa Khalil Ahmed's Space: Managing Workflow’s Lifecycle blog entry. The event of interest in this problem was the WorkflowSuspended event.
Placing a break point on the event handler I noticed that after this.ApplyWorkflowChanges (wfChanges) executed the breakpoint was hit. Diving into the SuspendedWorkflowEventArgs the reason property read: “suspending to apply dynamic update to the instance” It seems that the workflow is automatically suspended whenever changes are applied. Not knowing how to avoid the suspension at the moment, I blindly resume the suspended workflow.
static void workflowRuntime_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs e)
{
e.WorkflowInstance.Resume();
}
The WorkflowSuspendedEventArgs provides a pointer to the WorkflowInstance that is being suspended. I just resume that WorkflowInstance.
I know that this could cause problems as there may be other reasons why a workflow is suspended other than because of the application of changes, but for now this gets me around my problem.
Once the workflow resumed my new activity executed.
Happy coding!
- Joel