Dynamically start and kill a workflow with Windows Workflow FoundationSerge Luca
Applies to:U2U
The Windows Workflow Foundation is a programming model, a run-time engine, and a set of tools for building workflow functionality into .NET applications. Windows Workflow Foundation (WF) is a core component of the .NET Framework 3.0 and can be used on Windows Vista, Windows XP SP2 and Windows Server 2003SP1.
Downloads: Contents:
InvokeWorkflowActivity at design timeIn the Windows Workflow Foundation forum ( http://www.windowsworkflows.net ), someone recently asked how to dynamically start a workflow, let it work in parallel and how to kill it. Moreover the workflow type couldn’t be identified until runtime. Clearly, the first way to find a solution to this problem was to test the InvokeWorkflowActivity ; indeed, this activity allow us to start asynchronously a workflow from another workflow, but the workflow type to be invoked must be known at design time. We’ll see in the last part of this article, that by using a workaround (dynamic update), we can actually set the InvokeWorkflowActivity TargetWorkflow property at runtime. (For a more complex example, see Tom Lake’s blog http://blogs.msdn.com/tomlake . Tom works for the WF team. For an example of synchronous workflow invocation, see Jon Flanders’s blog http://www.masteringbiztalk.com/blogs/jon ). ![]() IStartWorkflow : some backgroundAccording to the MSDN documentation , the System.Workflow.ComponentModel.IStartWorkflow interface is an interesting way to start a workflow : Guid StartWorkflow ( Type workflowType, Dictionary<string,Object> namedArgumentValues) An undocumented and internal class, StartWorkflow, implements this interface. To reach an instance of this class, we have to call the ActivityExecutionContext (AEC), which represents the execution environment of an Activity, the fundamental building block for every workflow. The concept of the ActivityExecutionContext is similar to the ASP.NET HttpContext in that way that it bridges the activity and the WorkflowRuntime context; it is also an API giving access from the Activity code to some WorkflowRuntime functionalities. In our case we use this system feature allowing us to start a workflow. A classical way to access the ActivityExecution context is to create a custom Activity class and to override its Execute method. class ActivityLauncher {
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {
return ActivityExecutionStatus.Closed;
}
}
Therefore, we can call the StartWokflow object, or more important, its IStartWorkflow interface. This object can be considered as an internal service. class ActivityLauncher {
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
IStartWorkflow startWorkflow =
executionContext.GetService(typeof(IStartWorkflow)) as IStartWorkflow;
LaunchedWFGuid = startWorkflow.StartWorkflow(typeof(WorkflowToStart), null);
return ActivityExecutionStatus.Closed;
}
}
![]() Hands-on: Creating the Activity Launcher Custom ActivityLet’s start Visual Studio 2005 and create a Sequential Workflow Console Application : name the project LauncherAndWorkflowKiller ![]() Fig 1: Sequential Workflow project Add a Custom activity: Right click on the project; select “Add New Activity”. Name the file: WorkflowLauncherActivity.cs . By default, the generated custom activity is derived from SequenceActivity ; in the Solution Explorer, select the activity, click on « View Code ». The generated code looks like this: namespace LauncherAndWorkflowKiller {
public partial class WorkflowLauncherActivity: SequenceActivity {
public WorkflowLauncherActivity() {
InitializeComponent();
}
}
}
Let’s derive our new activity from Activity instead of SequenceActivity . namespace LauncherAndWorkflowKiller {
public partial class WorkflowLauncherActivity: Activity {
public WorkflowLauncherActivity () {
InitializeComponent();
}
}
}
Override the Activity Execute method and change the return value to ActivityExecutionStatus.Closed ; this means that after this method, the Activity will move to the closed state which is the (Activity) final Execution state. protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {
return ActivityExecutionStatus.Closed;
}
Before writing the code for starting the workflow, we need to provide a property that will handle the type of workflow we want to start. For the sake of efficiency, most of the properties defined in the Workflow Foundation will be dependency properties. However they can be classical properties as well. Now, let’s define the WorkflowToStart property. Using the workflow snippet “Dependency Property”; add a new string property WorkflowToStart : public static DependencyProperty WorkflowToStartProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("WorkflowToStart", typeof(string), typeof(WorkflowLauncherActivity));
[Description("This is the workflow to be started")]
[Category("Worklflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string WorkflowToStart {
get {
return ((string)(base.GetValue(WorkflowLauncherActivity.WorkflowToStartProperty)));
} set {
base.SetValue(WorkflowLauncherActivity.WorkflowToStartProperty, value);
}
}
We can now enhance our Execute method. Assembly myAssembly = Assembly.GetExecutingAssembly();
Type workflowType = myAssembly.GetType(WorkflowToStart);
IStartWorkflow wfStarter = executionContext.GetService(typeof(IStartWorkflow)) as
IStartWorkflow;
Dictionary<string, object> wfParams = new Dictionary<string, object>();
this.LaunchedWFGuid = wfStarter.StartWorkflow(workflowType, this.ParamsForWF);
As you noticed, the Guid of the instantiated workflow is stored in the launchedWFGuid variable that we still have to define as a Dependency Property; in this case, we’ll see that this is mandatory because we will have to link this property with another activity property; data binding between activities on the same workflow is a very useful feature provided by Dependency Properties. Let’s define this LaunchedWFGuid property (by using the snippet). public static DependencyProperty LaunchedWFGuidProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("LaunchedWFGuid", typeof(Guid), typeof(WorkflowLauncherActivity));
[Description("Workflow generated Guid")]
[Category("Workflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Guid LaunchedWFGuid {
get {
return ((Guid)(base.GetValue(WorkflowLauncherActivity.LaunchedWFGuidProperty)));
} set {
base.SetValue(WorkflowLauncherActivity.LaunchedWFGuidProperty, value);
}
}
We now have to test the WorkflowLauncherActivity . Add a new workflow in the project; this workflow will act as the invoked workflow; keep the default name ( Workflow2 in my case). Compile the solution; our new WorkflowLauncherActivity will show up on the toolbox. ![]() Fig 2: WorkflowLauncherActivity on the toolbox We can now drag & drop a CodeActivity and our new WorkflowLauncherActivity activity to the sequential workflow window of Workflow1. Next we also add a delay activity with a delayDuration of 10 seconds into Workflow1 (the Caller Workflow). ![]() Fig 3: The caller workflow Now, let’s design Workflow2 (the invoked workflow ) : Drag & Drop a CodeActivity into Workflow2, and in the Execute_Code method we print a simple message to the screen. private void codeActivity1_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine(" This is workflow2");
}
![]() Fig 4: The called workflow After having successfully built we run the application. ![]() Fig 5: Running the caller workflow ![]() Killing the workflow: WorkflowShutdownServiceOur intention is to create a dedicated custom activity for killing a running workflow. There are 2 ways to kill the started workflow: the first way is to call the WorkflowInstance Terminate(), the second to call the Abort() method. When using Terminate(), the runtime engine clears the in-memory workflow instance and informs the persistence service that the instance has been cleared from memory; with the Abort() method, the runtime engine simply clears the in-memory workflow instance, which can then be restarted from the last persistence point. According to the MSDN documentation, getting a workflowInstance from a workflow Guid, is easy: we can use the WorkflowRuntime GetWorkflow() method that takes a wokflow Guid as a parameter : public WorkflowInstance GetWorkflow ( Guid instanceId ) Calling the WorkflowRuntime directly from the Activity code must be avoided: the reason for this is that we cannot cache the WorkflowRuntime in the activity; if it’s cached it can be persisted as well, so how canthe runtime persist itself and reload itself? Caching doesn’t make sense. Let’s create our WorkflowShutdownService by adding a class derived from the WorkflowRuntimeService :
using System.Workflow.Runtime.Hosting;
class WorkflowShutdownService : WorkflowRuntimeService {
public void ShutDownWorkflow(Guid workflowGuidToShutdown) {
WorkflowInstance wi= this.Runtime.GetWorkflow(workflowGuidToShutdown);
wi.Abort();
}
}
Even if we wouldn’t have created a derived class, it’s good to keep in mind that it’s a best practice to create an abstract class derived from WorkflowRuntimeService and to base your activity code (see later) on this abstract class definition; the service class can be specified in the application configuration file and swapped with another class if necessary without changing the code. After defining the service, we have to register it with the WorkflowRuntime in the Main method of our application.
class Program {
static void Main(string[] args) {
using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) {
WorkflowShutdownService wfShutdownService = new WorkflowShutdownService();
workflowRuntime.AddService(wfShutdownService);
...
}
}
}
![]() Killing the workflow: WorkflowKillerActivityAs we did before, let’s add a new Activity class to our project and override the Execute method. public partial class WorkflowKillerActivity: Activity {
public WorkflowKillerActivity() {
InitializeComponent();
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {
return ActivityExecutionStatus.Closed;
}
}
Now, we can access the WorkflowShutdownService from the ActivityExecutionContext (AEC). protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {
WorkflowShutdownService wfShutdonwService =
executionContext.GetService<WorkflowShutdownService>();
wfShutdonwService.ShutDownWorkflow(<variabletospecify>);
return ActivityExecutionStatus.Closed;
}
The Shutdown method requires a Workflow Guid; we can therefore define a Dependency Property , WFGuidToKill ), that will be “data bound” later with a WorkflowLauncherActivity LaunchedWG property. public static DependencyProperty WFGuidToKillProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("WFGuidToKill", typeof(Guid), typeof(WorkflowKillerActivity));
[Description("Guid of the workflow to kill")]
[Category("Workflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Guid WFGuidToKill {
get {
return ((Guid)(base.GetValue(WorkflowKillerActivity.WFGuidToKillProperty)));
} set {
base.SetValue(WorkflowKillerActivity.WFGuidToKillProperty, value);
}
}We can modify our Execute method to take this new property into account: protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
WorkflowShutdownService wfShutdonwService = executionContext.GetService<WorkflowShutdownService>();
wfShutdonwService.ShutDownWorkflow(WFGuidToKill);
return ActivityExecutionStatus.Closed;
}
After successfully building the solution the new WorkflowKillerActivity should show up on the toolbox. ![]() Fig 6: WorkflowKillerActivity on the toolbox ![]() Testing the WorkflowKillerActivityWe now are modifying the invoked workflow (Workflow2) for making it running indefinitely by using a WhileActivity. We also add several CodeActivities . ![]() Fig 7: The enhanced called workflow Here is he associated codeActivity_Execute code : private void codeActivity1_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine(" workflow2 starting...");
}
private void codeActivity3_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine("Workflow 2 finishing...");
}
private void codeActivity2_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine("Worklfow 2 in the loop");
}
Define the WhileActivity condition: -Select the WhileActivity. -Open the properties window and click on Conditions. -Select Declarative Rule Condition as Condition property; the ConditionName property gets the value InfiniteLoop and the Expression property is set to true . Modify the calling workflow (Workflow1) by dragging & dropping an instance of our new WorkflowKillerActivity from the Toolbox and several code activities as illustrated. ![]() Fig 8:The enhanced caller workflow starting and killing a invoked workflow Here is the code behind our CodeActivities : private void codeActivity1_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine("Launcher workflow starting...");
}
private void codeActivity2_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine("Launcher workflow stopping...");
}
We still have to “data bind” the WorkflowKillerActivity.WFGuidToKillProperty with Workflowlauncher LaunchedWFGuid. ; Let’s proceed as follows: In Workflow1, select workflowKillerActivity1 ; in the property page, click on the ellipsis button (…) associated with WFGuidToKill ; the Binding Window will show up; select activityLauncher1 ; at this activity level, select LaunchedWFGuid . Build and run the application. When successful you should see the same as on the picture below: ![]() Fig 9: Invoked workflow started and killed In this test application, we provide the workflow type at design time; to specify it at runtime; you can easily modify the CodeActivity1_ExecuteCode method as follows: private void codeActivity1_ExecuteCode(object sender, EventArgs e) {
activityLauncher1.WorkflowToStart = "LauncherAndWorkflowKiller.Workflow2";
Console.WriteLine("launcher workflow starting”);
}
![]() Workflow ParametersPassing workflow parameters is quite simple: Let’s add a new Dependency Property to our invoked workflow: TestParam (use the snippet). public static DependencyProperty TestParamProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("TestParam", typeof(string), typeof(Workflow2));
[Description("This is the description which appears in the Property Browser")]
[Category("This is the category which will be displayed in the Property Browser")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string TestParam {
get {
return ((string)(base.GetValue(Workflow2.TestParamProperty)));
} set {
base.SetValue(Workflow2.TestParamProperty, value);
}
}
Usually, passing parameters to an invoked workflow means creating a Dictionary(<string,object>) generic class; The <object > part of this generic is actually the value of the parameter and the <string> part is the property name. To make this easier to understand, here is the modified version of our WorkflowLauncherActivity : public partial class WorkflowLauncherActivity: Activity {
...
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {
Assembly myAssembly = Assembly.GetExecutingAssembly();
Type workflowType = myAssembly.GetType(WorkflowToStart);
IStartWorkflow wfStarter = executionContext.GetService(typeof(IStartWorkflow)) as IStartWorkflow;
Dictionary<string, object> wfParams = new Dictionary<string, object>();
wfParams.Add("TestParam", "mytest");
this.LaunchedWFGuid = wfStarter.StartWorkflow(workflowType, wfParams);
return ActivityExecutionStatus.Closed;
}
We can encapsulate this in our WorkflowLauncherActivity by providing a Dictionary<string,object> public property. public static readonly DependencyProperty ParamsForWFProperty = DependencyProperty.Register("ParamsForWF",
typeof(Dictionary<string, object>),
typeof(WorkflowLauncherActivity),
new PropertyMetadata(DependencyPropertyOptions.ReadOnly,
new Attribute[] { new BrowsableAttribute(false),
new DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Content)
}));
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Dictionary<string,object> ParamsForWF {
get {
return ((Dictionary<string,object>)(base.GetValue(WorkflowLauncherActivity.ParamsForWFProperty)));
}
}
The collection must be ready to receive some parameters as soon as the Activity is instantiated: we can modify our constructor (this could also be done in an overridden version of the Activity’s Initialize method) like this: public WorkflowLauncherActivity() {
base.SetReadOnlyPropertyValue(ParamsForWFProperty, new Dictionary<string, object>());
InitializeComponent();
}
We can test our new feature by modifying codeActivity1_Execute code (code behind CodeActivity1) in Workflow1: private void codeActivity1_ExecuteCode(object sender, EventArgs e) {
WorkflowLauncherActivity1.WorkflowToStart = "LauncherAndWorkflowKiller.Workflow2";
WorkflowLauncherActivity1.ParamsForWF.Add("TestParam", "hello");
Console.WriteLine("launcher workflow starting...");
}
After successfully compiling and running the you will see this: ![]() Fig 10: “hello” passed as a parameter ![]() Dynamic generationAlthough less efficient in our specific case, there’s another way to dynamically invoke a workflow. We can also do this by dynamically change the workflow while it is running by adding some code that inserts an InvokeWorkflowActivity in the workflow: In our calling workflow (workflow1), the codeActivity1_Execute method could be changed like this:
InvokeWorkflowActivity invokeNewWorkflow = new InvokeWorkflowActivity();
WorkflowParameterBinding paramBinding = new WorkflowParameterBinding("TestParam");
paramBinding.Value = "hello this is my param";
invokeNewWorkflow.ParameterBindings.Add(paramBinding);
WorkflowChanges changes = new WorkflowChanges(this);
Type type = typeof(Workflow2);
invokeNewWorkflow.Name = "Workflow2";
invokeNewWorkflow.TargetWorkflow = type;
changes.TransientWorkflow.Activities.Add(invokeNewWorkflow);
this.ApplyWorkflowChanges(changes);
In this article, we discussed how to dynamically and asynchrounously start and stop a workflow from within another workflow. We also took a look on how to pass parameters to a workflow, create custom activities and quick touched on the WorkflowRuntime Services. ![]() About the author
| ||||||