January 2011

Volume 26 Number 01

Windows Foundation 4 - Authoring Custom Control Flow Activities in WF 4

By Leon Welicki | January 2011

Control flow refers to the way in which the individual instructions in a program are organized and executed. In Windows Workflow Foundation 4 (WF 4), control flow activities govern the execution semantics of one or more child activities. Some examples in the WF 4 activity toolbox include Sequence, Parallel, If, ForEach, Pick, Flowchart and Switch, among others.

The WF runtime doesn’t have first-class knowledge of any control flow like Sequence or Parallel. From its perspective, everything is just activities. The runtime only enforces some simple rules (for example, “an activity can’t complete if any child activities are still running”). WF control flow is based on hierarchy—a WF program is a tree of activities.

Control flow options in WF 4 are not limited to the activities shipped in the framework. You can write your own and use them in combination with the ones provided in the box, and that’s what this article will discuss. You’ll learn how to write your own control flow activities following a “crawl, walk, run” approach: we’ll start with a very simple control flow activity and add richness as we progress, ending up with a new and useful control flow activity.  The source code for all of our examples is available for download.

But first, let’s start with some basic concepts about activities to set some common ground.

Activities

Activities are the basic unit of execution in a WF program; a workflow program is a tree of activities that are executed by the WF runtime. WF 4 includes more than 35 activities, a comprehensive set that can be used to model processes or create new activities. Some of those activities govern the semantics of how other activities are executed (such as Sequence, Flowchart, Parallel and ForEach), and are known as composite activities. Others perform a single atomic task (WriteLine, InvokeMethod and so forth). We call these leaf activities. 

WF activities are implemented as CLR types, and as such they derive from other existing types. You can author activities visually and declaratively using the WF designer, or imperatively writing CLR code. The base types available to create your own custom activity are defined in the activities type hierarchy in Figure 1. You’ll find a detailed explanation of this type hierarchy in the MSDN Library at msdn.microsoft.com/library/dd560893.

image: Activity Type Hierarchy

Figure 1 Activity Type Hierarchy

In this article I’ll focus on activities that derive from NativeActivity, the base class that provides access to the full breadth of the WF runtime. Control flow activities are composite activities that derive from the NativeActivity type because they need to interact with the WF runtime. Most commonly this is to schedule other activities (for example, Sequence, Parallel or Flowchart), but it may also include implementing custom cancellation using CancellationScope or Pick, creating bookmarks with Receive and persisting using Persist.

The activity data model defines a crisp model for reasoning about data when authoring and consuming activities. Data is defined using arguments and variables. Arguments are the binding terminals of an activity and define its public signature in terms of what data can be passed to the activity (input arguments) and what data will be returned by the activity when it completes its execution (output arguments). Variables represent temporary storage of data.

Activity authors use arguments to define the way data flows in and out of an activity, and they use variables in a couple of ways:

  • To expose a user-editable collection of variables on an activity definition that can be used to share variables among multiple activities (such as a Variables collection in Sequence and Flowchart).
  • To model the internal state of an activity.

Workflow authors use arguments to bind activities to the environment by writing expressions, and they declare variables at different scopes in the workflow to share data between activities. Together, variables and arguments combine to provide a predictable model of communication between activities.

Now that I’ve covered some core activity basics, let’s start with the first control flow activity.

A Simple Control Flow Activity

I’ll begin by creating a very simple control flow activity called ExecuteIfTrue. There’s not much to this activity: It executes a contained activity if a condition is true. WF 4 provides an If activity that includes Then and Else child activities; often we only want to provide the Then, and the Else is just overhead. For those cases we want an activity that executes another activity based on the value of a Boolean condition.

Here’s how this activity should work:

  • The activity user must provide a Boolean condition. This argument is required.
  • The activity user can provide a body—the activity to be executed if the condition is true.
  • At execution time: If the condition is true and the body is not null, execute the body.

Here’s an implementation for an ExecuteIfTrue activity that behaves in exactly this way:

public class ExecuteIfTrue : NativeActivity
{
  [RequiredArgument]
  public InArgument<bool> Condition { get; set; }
  public Activity Body { get; set; }
  public ExecuteIfTrue() { }  
  protected override void Execute(NativeActivityContext context)
  {            
    if (context.GetValue(this.Condition) && this.Body != null)
      context.ScheduleActivity(this.Body);
  }
}

This code is very simple, but there’s more here than meets the eye. ExecuteIfTrue executes a child activity if a condition is true, so it needs to schedule another activity. Therefore, it derives from NativeActivity because it needs to interact with the WF runtime to schedule children.

Once you’ve decided the base class for an activity, you have to define its public signature. In ExecuteIfTrue, this consists of a Boolean input argument of type InArgument<bool> named Condition that holds the condition to be evaluated, and a property of type Activity named Body with the activity to be executed if the condition is true. The Condition argument is decorated with the RequiredArgument attribute that indicates to the WF runtime that it must be set with an expression. The WF runtime will enforce this validation when preparing the activity for execution:

[RequiredArgument]
public InArgument<bool> Condition { get; set; }
public Activity Body { get; set; }

The most interesting piece of code in this activity is the Execute method, which is where the “action” happens. All NativeActivities must override this method. The Execute method receives a NativeActivityContext argument, which is our point of interaction with the WF runtime as activity authors. In ExecuteIfTrue, this context is used to retrieve the value of the Condition argument (context.GetValue(this.Condition)) and to schedule the Body using the ScheduleActivity method. Notice that I say schedule and not execute. The WF runtime does not execute the activities right away; instead it adds them to a list of work items to be scheduled for execution:

protected override void Execute(NativeActivityContext context)
{
    if (context.GetValue(this.Condition) && this.Body != null)
        context.ScheduleActivity(this.Body);
}

Note as well that the type has been designed following the create-set-use pattern. The XAML syntax is based on this pattern for designing types, where the type has a public default constructor and public read/write properties. This means the type will be XAML serialization-friendly.

The following code snippet shows how to use this activity. In this example, if the current day is Saturday, you write the string “Rest!” to the console:

var act = new ExecuteIfTrue
{
  Condition = new InArgument<bool>(c => DateTime.Now.DayOfWeek == DayOfWeek.Tuesday),
  Body = new WriteLine { Text = "Rest!" }
};
WorkflowInvoker.Invoke(act);

The first control flow activity has been created in 15 lines of code. But don’t be fooled by the simplicity of that code—it’s actually a fully functional control flow activity!

Scheduling Multiple Children

The next challenge is to write a simplified version of the Sequence activity. The goal of this exercise is to learn how to write a control flow activity that schedules multiple child activities and executes in multiple episodes. This activity is almost functionally equivalent to the Sequence shipped in the product.

Here’s how this activity should work:

  • The activity user must provide a collection of children to be executed sequentially through the Activities property.
  • At execution time:
    • The activity contains an internal variable with the index of the last item in the collection that has been executed.
    • If there are items in the children collection, schedule the first child.
    • When the child completes:
      • Increment the index of the last item executed.
      • If the index is still within the boundaries of the children collection, schedule the next child.
      • Repeat.

The code in Figure 2 implements a SimpleSequence activity that behaves exactly as described.

Figure 2 The SimpleSequence Activity

public class SimpleSequence : NativeActivity
{
  // Child activities collection
  Collection<Activity> activities;
  Collection<Variable> variables;
  // Pointer to the current item in the collection being executed
  Variable<int> current = new Variable<int>() { Default = 0 };
     
  public SimpleSequence() { }
  // Collection of children to be executed sequentially by SimpleSequence
  public Collection<Activity> Activities
  {
    get
    {
      if (this.activities == null)
        this.activities = new Collection<Activity>();
      return this.activities;
    }
  }
  public Collection<Variable> Variables 
  { 
    get 
    {
      if (this.variables == null)
        this.variables = new Collection<Variable>();
      return this.variables; 
    } 
  }
  protected override void CacheMetadata(NativeActivityMetadata metadata)
  {
    metadata.SetChildrenCollection(this.activities);
    metadata.SetVariablesCollection(this.variables);
    metadata.AddImplementationVariable(this.current);
  }
  protected override void Execute(NativeActivityContext context)
  {
    // Schedule the first activity
    if (this.Activities.Count > 0)
      context.ScheduleActivity(this.Activities[0], this.OnChildCompleted);
  }
  void OnChildCompleted(NativeActivityContext context, ActivityInstance completed)
  {
    // Calculate the index of the next activity to scheduled
    int currentExecutingActivity = this.current.Get(context);
    int next = currentExecutingActivity + 1;
    // If index within boundaries...
    if (next < this.Activities.Count)
    {
      // Schedule the next activity
      context.ScheduleActivity(this.Activities[next], this.OnChildCompleted);
      // Store the index in the collection of the activity executing
      this.current.Set(context, next);
    }
  }
}

Again, a fully functional control flow activity has been written in just a few lines of codes—in this case, about 50 lines. The code is simple, but it introduces some interesting concepts.

SimpleSequence executes a collection of child activities in sequential order, so it needs to schedule other activities. Therefore, it derives from NativeActivity because it needs to interact with the runtime to schedule children.

The next step is to define the public signature for SimpleSequence. In this case it consists of a collection of activities (of type Collection<Activity>) exposed through the Activities property, and a collection of variables (of type Collection<Variable>) exposed through the Variables property. Variables allow sharing of data among all child activities. Note that these properties only have “getters” that expose the collections using a “lazy instantiation” approach (see Figure 3), so accessing these properties never results in a null reference. This makes these properties compliant with the create-set-use pattern.

Figure 3 A Lazy Instantiation Approach

public Collection<Activity> Activities
{
  get
  {
    if (this.activities == null)
      this.activities = new Collection<Activity>();
    return this.activities;
  }
}
public Collection<Variable> Variables 
{ 
  get 
  {
    if (this.variables == null)
      this.variables = new Collection<Variable>();
    return this.variables; 
  } 
}

There’s one private member in the class that’s not part of the signature: a Variable<int> named “current” that holds the index of the activity being executed:

// Pointer to the current item in the collection being executed
Variable<int> current = new Variable<int>() { Default = 0 };

Because this information is part of the internal execution state for SimpleSequence, you want to keep it private and not expose it to users of SimpleSequence. You also want it to be saved and restored when the activity is persisted. You use an ImplementationVariable for this.

Implementation variables are variables that are internal to an activity. They’re intended to be consumed by the activity author, not the activity user. Implementation variables are persisted when the activity is persisted and restored when the activity is reloaded, without requiring any work on our part. To make this clear—and continuing with the Sequence example—if an instance of SimpleSequence is persisted, when it wakes up it will “remember” the index of the last activity that has been executed.

The WF runtime can’t automatically know about implementation variables. If you want to use an ImplementationVariable in an activity, you need to explicitly inform the WF runtime. You do this during CacheMetadata method execution.

Despite its rather frightening name, CacheMetadata is not that difficult. Conceptually, it’s actually simple: It’s the method where an activity “introduces itself” to the runtime. Think about the If activity for a moment. In CacheMetadata this activity would say: “Hi, I’m the If activity and I have an input argument named Condition, and two children: Then and Else.” In the SimpleSequence case, SimpleSequence is saying: “Hi, I’m SimpleSequence and I have a collection of child activities, a collection of variables and an implementation variable.” There’s no more than that in the SimpleSequence code for CacheMetadata:

protected override void CacheMetadata(NativeActivityMetadata metadata)
{
  metadata.SetChildrenCollection(this.activities);
  metadata.SetVariablesCollection(this.variables);
  metadata.AddImplementationVariable(this.current);
}

The default implementation of CacheMetadata uses reflection to get this data from the activity. In the ExecuteIfTrue example, I didn’t implement CacheMetadata and relied on the default implementation to reflect on public members. For SimpleSequence, in contrast, I did need to implement it because the default implementation can’t “guess” about my desire to use implementation variables.

The next interesting piece of code in this activity is the Execute method. In this case, if there are activities in the collection, you tell the WF runtime: “Please execute the first activity in the collection of activities and, when you’re done, invoke the OnChildCompleted method.” You say this in WF terms using NativeActivityContext.ScheduleActivity. Notice that when you schedule an activity, you supply a second argument that’s a CompletionCallback. In simple terms, this is a method that will be called once the activity execution is completed. Again, it’s important to be aware of the difference between scheduling and execution. The CompletionCallback won’t be called when the activity is scheduled; it will be called when the scheduled activity execution has been completed:

protected override void Execute(NativeActivityContext context)
{
  // Schedule the first activity
  if (this.Activities.Count > 0)
    context.ScheduleActivity(this.Activities[0], this.OnChildCompleted);
}

The OnChildCompleted method is the most interesting part of this activity from a learning perspective, and it’s actually the main reason I’ve included SimpleSequence in this article. This method gets the next activity in the collection and schedules it. When the next child is scheduled, a CompletionCallback is provided, which in this case points to this same method. Thus, when a child completes, this method will execute again to look for the next child and execute it. Clearly, execution is happening in pulses or episodes. Because workflows can be persisted and unloaded from memory, there can be a large time difference between two pulses of execution. Moreover, these pulses can be executed in different threads, processes or even machines (as persisted instances of a workflow can be reloaded in a different process or machine). Learning how to program for multiple pulses of execution is one of the biggest challenges in becoming an expert control flow activity author:

void OnChildCompleted(NativeActivityContext context, ActivityInstance completed)
{
  // Calculate the index of the next activity to scheduled
  int currentExecutingActivity = this.current.Get(context);
  int next = currentExecutingActivity + 1;
  // If index within boundaries...
  if (next < this.Activities.Count)
  {
    // Schedule the next activity
    context.ScheduleActivity(this.Activities[next], this.OnChildCompleted);
    // Store the index in the collection of the activity executing
    this.current.Set(context, next);
  }
}

The following code snippet shows how to use this activity. In this example, I'm writing three strings to the console ("Hello," "Workflow" and "!"):

var act = new SimpleSequence()
{
  Activities = 
  {
    new WriteLine { Text = "Hello" },
    new WriteLine { Text = "Workflow" },
    new WriteLine { Text = "!" }
  }
};
WorkflowInvoker.Invoke(act);

I’ve authored my own SimpleSequence! Now it’s time to move on to the next challenge.

Implementing a New Control Flow Pattern

Next, I’ll create a complex control flow activity. As I mentioned earlier, you’re not limited to the control flow activities shipped in WF 4. This section demonstrates how to build your own control flow activity to support a control flow pattern that’s not supported out-of-the-box by WF 4.

The new control flow activity will be called Series. Its goal is simple: to provide a Sequence with support for GoTos, where the next activity to be executed can be manipulated either explicitly from inside the workflow (through a GoTo activity) or from the host (by resuming a well-known bookmark).

To implement this new control flow, I’ll need to author two activities: Series, a composite activity that contains a collection of activities and executes them sequentially (but allows jumps to any item in the sequence), and GoTo, a leaf activity that I’ll use inside of Series to explicitly model the jumps.

To recap, I’ll enumerate the goals and requirements for the custom control activity:

  1. It’s a Sequence of activities.
  2. It can contain GoTo activities (at any depth), which change the point of execution to any direct child of the Series.
  3. It can receive GoTo messages from the outside (for example, from a user), which can change the point of execution to any direct child of the Series.

I’ll start by implementing the Series activity. Here’s the execution semantics in simple terms:

  • The activity user must provide a collection of children to be executed sequentially through the Activities property.
  • In the execute method:
    • Create a bookmark for the GoTo in a way that’s available to child activities.
    • The activity contains an internal variable with the activity instance being executed.
    • If there are items in the children collection, schedule the first child.
    • When the child completes:
      • Lookup the completed activity in the Activities collection.
      • Increment the index of the last item executed.
      • If the index is still within the boundaries of the children collection, schedule the next child.
      • Repeat.
  • If the GoTo bookmark is resumed:
    • Get the name of the activity we want to go to.
    • Find that activity in the activities collection.
    • Schedule the target activity in the set for execution and register a completion callback that will schedule the next activity.
    • Cancel the activity that’s currently executing.
    • Store the activity that’s currently being executed in the “current” variable.

The code example in Figure 4 shows the implementation for a Series activity that behaves exactly as described.

Figure 4 The Series Activity

public class Series : NativeActivity
{
  internal static readonly string GotoPropertyName = 
    "Microsoft.Samples.CustomControlFlow.Series.Goto";
  // Child activities and variables collections
  Collection<Activity> activities;
  Collection<Variable> variables;
  // Activity instance that is currently being executed
  Variable<ActivityInstance> current = new Variable<ActivityInstance>();
 
  // For externally initiated goto's; optional
  public InArgument<string> BookmarkName { get; set; }
  public Series() { }
  public Collection<Activity> Activities 
  { 
    get {
      if (this.activities == null)
        this.activities = new Collection<Activity>();
    
      return this.activities; 
    } 
  }
  public Collection<Variable> Variables 
  { 
    get {
      if (this.variables == null)
        this.variables = new Collection<Variable>();
      return this.variables; 
    } 
  }
    
  protected override void CacheMetadata(NativeActivityMetadata metadata)
  {                        
    metadata.SetVariablesCollection(this.Variables);
    metadata.SetChildrenCollection(this.Activities);
    metadata.AddImplementationVariable(this.current);
    metadata.AddArgument(new RuntimeArgument("BookmarkName", typeof(string), 
                                              ArgumentDirection.In));
  }
  protected override bool CanInduceIdle { get { return true; } }
  protected override void Execute(NativeActivityContext context)
  {
    // If there activities in the collection...
    if (this.Activities.Count > 0)
    {
      // Create a bookmark for signaling the GoTo
      Bookmark internalBookmark = context.CreateBookmark(this.Goto,
                BookmarkOptions.MultipleResume | BookmarkOptions.NonBlocking);
      // Save the name of the bookmark as an execution property
      context.Properties.Add(GotoPropertyName, internalBookmark);
      // Schedule the first item in the list and save the resulting 
      // ActivityInstance in the "current" implementation variable
      this.current.Set(context, context.ScheduleActivity(this.Activities[0], 
                                this.OnChildCompleted));
      // Create a bookmark for external (host) resumption
      if (this.BookmarkName.Get(context) != null)
        context.CreateBookmark(this.BookmarkName.Get(context), this.Goto,
            BookmarkOptions.MultipleResume | BookmarkOptions.NonBlocking);
    }
  }
  void Goto(NativeActivityContext context, Bookmark b, object obj)
  {
    // Get the name of the activity to go to
    string targetActivityName = obj as string;
    // Find the activity to go to in the children list
    Activity targetActivity = this.Activities
                                  .Where<Activity>(a =>  
                                         a.DisplayName.Equals(targetActivityName))
                                  .Single();
    // Schedule the activity 
    ActivityInstance instance = context.ScheduleActivity(targetActivity, 
                                                         this.OnChildCompleted);
    // Cancel the activity that is currently executing
    context.CancelChild(this.current.Get(context));
    // Set the activity that is executing now as the current
    this.current.Set(context, instance);
  }
  void OnChildCompleted(NativeActivityContext context, ActivityInstance completed)
  {
    // This callback also executes when cancelled child activities complete 
    if (completed.State == ActivityInstanceState.Closed)
    {
      // Find the next activity and execute it
      int completedActivityIndex = this.Activities.IndexOf(completed.Activity);
      int next = completedActivityIndex + 1;
      if (next < this.Activities.Count)
          this.current.Set(context, 
                           context.ScheduleActivity(this.Activities[next],
                           this.OnChildCompleted));
    }
  }
}

Some of this code will look familiar from the previous examples. I’ll discuss the implementation of this activity.

Series derives from NativeActivity because it needs to interact with the WF runtime to schedule child activities, create bookmarks, cancel children and use execution properties.

As before, the next step is to define the public signature for Series. As in SimpleSequence, there are Activities and Variables collection properties. There’s also a string input argument named BookmarkName (of type InArgument<string>), with the name of the bookmark to be created for host resumption. Again, I’m following the create-set-use pattern in the activity type.

Series has a private member named “current” that contains the ActivityInstance being executed, instead of just a pointer to an item in a collection, as in SimpleSequence. Why is current a Variable<ActivityInstance> and not a Variable<int>? Because I need to get ahold of the currently executing child later in this activity during the GoTo method. I’ll explain the actual details later; the important thing to understand now is that I’ll have an implementation variable that holds the activity instance being executed:

Variable<ActivityInstance> current = new Variable<ActivityInstance>();

In CacheMetadata you provide runtime information about your activity: the children and variables collections, the implementation variable with the current activity instance and the bookmark name argument. The only difference from the previous example is that I’m manually registering the BookmarkName input argument within the WF runtime—adding a new instance of RuntimeArgument to the activity metadata:

protected override void CacheMetadata(NativeActivityMetadata metadata)
{                        
  metadata.SetVariablesCollection(this.Variables);
  metadata.SetChildrenCollection(this.Activities);
  metadata.AddImplementationVariable(this.current);
  metadata.AddArgument(new RuntimeArgument("BookmarkName",  
                                           typeof(string), ArgumentDirection.In));
}

The next new thing is the CanInduceIdle property overload. This is just more metadata that the activity provides to the WF runtime. When this property returns true, I’m telling the runtime that this activity can cause the workflow to become idle. I need to override this property and return true for activities that create bookmarks, as they’ll make the workflow go idle waiting for its resumption. The default value for this property is false. If this property returns false and we create a bookmark, I’ll have an InvalidOperationException exception when executing the activity:

protected override bool CanInduceIdle { get { return true; } }

Things get more interesting in the Execute method, where I create a bookmark (internalBookmark) and store it in an execution property. Before going further, though, let me introduce bookmarks and execution properties.

Bookmarks are the mechanism by which an activity can passively wait to be resumed. When an activity wishes to “block” pending a certain event, it registers a bookmark and then returns an execution status of continuing. This signals the runtime that although the activity’s execution is not complete, it doesn’t have any more work to do as part of the current work item. When you use bookmarks, you can author your activities using a form of reactive execution: when the bookmark is created the activity yields, and when the bookmark is resumed a block of code (the bookmark resumption callback) is invoked in reaction to the bookmark resumption.

Unlike programs directly targeting the CLR, workflow programs are hierarchically scoped trees that execute in a thread-agnostic environment. This implies that the standard thread local storage (TLS) mechanisms can’t be directly leveraged to determine what context is in scope for a given work item. The workflow execution context introduces execution properties to an activity’s environment, so that an activity can declare properties that are in scope for its sub-tree and share them with its children. As a result, an activity can share data with its descendants through these properties.

Now that you know about bookmarks and execution properties, let’s get back to the code. What I’m doing at the beginning of the Execute method is creating a bookmark (using context.CreateBookmark) and saving it into an execution property (using context.Properties.Add). This bookmark is a multiple-resume bookmark, meaning it can be resumed multiple times and it will be available while its parent activity is in an executing state. It’s also NonBlocking, so it won’t prevent the activity from completing once it’s done with its job. When that bookmark is resumed, the GoTo method will be called because I provided a BookmarkCompletionCallback to CreateBookmark (the first parameter). The reason to save it into an execution property is to make it available to all child activities. (You’ll see later how the GoTo activity uses this bookmark.) Notice that execution properties have names. Because that name is a string, I defined a constant (GotoPropertyName) with the name for the property in the activity. That name follows a fully qualified name approach. This is a best practice:

internal static readonly string GotoPropertyName = 
                                "Microsoft.Samples.CustomControlFlow.Series.Goto";
...
...
// Create a bookmark for signaling the GoTo
Bookmark internalBookmark = context.CreateBookmark(this.Goto,                                         
                       BookmarkOptions.MultipleResume | BookmarkOptions.NonBlocking);
// Save the name of the bookmark as an execution property
context.Properties.Add(GotoPropertyName, internalBookmark);

Once I’ve declared the bookmark, I’m ready to schedule my first activity. I’m already familiar with this, because I did it in my previous activities. I’ll schedule the first activity in the collection and tell the runtime to invoke the OnChildCompleted method when the activity is done (as I did in SimpleSequence). Context.ScheduleActivity returns an ActivityInstance that represents an instance of an activity being executed, which I assign to our current implementation variable. Let me clarify this a bit. Activity is the definition, like a class; ActivityInstance is the actual instance, like an object. We can have multiple ActivityInstances from the same Activity:

// Schedule the first item in the list and save the resulting 
// ActivityInstance in the "current" implementation variable
this.current.Set(context, context.ScheduleActivity(this.Activities[0],  
                                                   this.OnChildCompleted));

Finally, we create a bookmark that can be used by the host to jump to any activity within the series. The mechanics for this are simple: Because the host knows the name of the bookmark, it can resume it with a jump to any activity within the Series:

// Create a bookmark for external (host) resumption
 if (this.BookmarkName.Get(context) != null)
     context.CreateBookmark(this.BookmarkName.Get(context), this.Goto,
                           BookmarkOptions.MultipleResume | BookmarkOptions.NonBlocking);

The OnChildCompleted method should be straightforward now, as it’s very similar to the one in SimpleSequence: I look up the next element in the activities collection and schedule it. The main difference is that I only schedule the next activity if the current activity successfully completed its execution (that is, reached the closed state and hasn’t been canceled or faulted).

The GoTo method is arguably the most interesting. This is the method that gets executed as the result of the GoTo bookmark being resumed. It receives some data as input, which is passed when the bookmark is resumed. In this case, the data is the name of the activity we want to go to:

void Goto(NativeActivityContext context, Bookmark b, object data)
{
  // Get the name of the activity to go to
  string targetActivityName = data as string;
       
  ...
 }

The target activity name is the DisplayName property of the activity. I look up the requested activity definition in the “activities” collection. Once I find the requested activity, I schedule it, indicating that when the activity is done, the OnChildCompleted method should be executed:

// Find the activity to go to in the children list
Activity targetActivity = this.Activities
                              .Where<Activity>(a =>  
                                       a.DisplayName.Equals(targetActivityName))
                              .Single();
// Schedule the activity 
ActivityInstance instance = context.ScheduleActivity(targetActivity, 
                                                     this.OnChildCompleted);

Next, I cancel the activity instance that’s currently being executed and set the current activity being executed to the ActivityInstance scheduled in the previous step. For both these tasks I use the “current” variable. First, I pass it as a parameter of the CancelChild method of NativeActivityContext, and then I update its value with the ActivityInstance that has been scheduled in the previous block of code:

// Cancel the activity that is currently executing
context.CancelChild(this.current.Get(context));
// Set the activity that is executing now as the current
this.current.Set(context, instance);

The GoTo Activity

The GoTo activity can be used only inside of a Series activity to jump to an activity in its Activities collection. It’s similar to a GoTo statement in an imperative program. The way it works is very simple: It resumes the GoTo bookmark created by the Series activity in which it’s contained, indicating the name of the activity we want to go to. When the bookmark is resumed, the Series will jump to the activity indicated.

Here’s a simple description of the execution semantics:

  • The activity user must provide a string TargetActivityName. This argument is required.
  • At execution time:
    • The GoTo activity will locate the “GoTo” bookmark created by the Series activity.
    • If the bookmark is found, it will resume it, passing the TargetActivityName.
    • It will create a synchronization bookmark, so the activity does not complete.
      • It will be cancelled by the Series.

The code in Figure 5 shows the implementation for a GoTo activity that behaves exactly as described.

Figure 5 The GoTo Activity

public class GoTo : NativeActivity
{
  public GoTo() 
  { }
       
  [RequiredArgument]
  public InArgument<string> TargetActivityName { get; set; }
  protected override bool CanInduceIdle { get { return true; } }
    
  protected override void Execute(NativeActivityContext context)
  {
    // Get the bookmark created by the parent Series
    Bookmark bookmark = context.Properties.Find(Series.GotoPropertyName) as Bookmark;
    // Resume the bookmark passing the target activity name
    context.ResumeBookmark(bookmark, this.TargetActivityName.Get(context));
    // Create a bookmark to leave this activity idle waiting when it does
    // not have any further work to do. Series will cancel this activity 
    // in its GoTo method
    context.CreateBookmark("SyncBookmark");
  }
}

GoTo derives from NativeActivity because it needs to interact with the WF runtime to create and resume bookmarks and use execution properties. Its public signature consists of the TargetActivityName string input argument that contains the name of the activity we want to jump to. I decorated that argument with the RequiredArgument attribute, meaning that the WF validation services will enforce that it’s set with an expression.

I rely on the default implementation of CacheMetadata that reflects on the public surface of the activity to find and register runtime metadata.

The most important part is in the Execute method. I first look for the bookmark created by the parent Series activity. Because the bookmark was stored as an execution property, I look for it in context.Properties. Once I find that bookmark, I resume it, passing the TargetActivityName as input data. This bookmark resumption will result in the Series.Goto method being invoked (because it’s the bookmark callback supplied when the bookmark was created). That method will look for the next activity in the collection, schedule it and cancel the activity that’s currently executing:

// Get the bookmark created by the parent Series
Bookmark bookmark = context.Properties.Find(Series.GotoPropertyName) as Bookmark; 
// Resume the bookmark passing the target activity name
context.ResumeBookmark(bookmark, this.TargetActivityName.Get(context));

The final line of code is the trickiest one: creating a bookmark for synchronization that will keep the GoTo activity running. Hence, when the GoTo.Execute method is complete, this activity will still be in executing state, waiting for a stimulus to resume the bookmark. 
When I discussed the code for Series.Goto, I mentioned that it cancelled the activity being executed. In this case, Series.Goto is actually canceling a Goto activity instance that’s waiting for this bookmark to be resumed.

To explain in more detail: The instance of GoTo activity was scheduled by the Series activity. When this activity completes, the completion callback in Series (OnChildCompleted) looks for the next activity in Series.Activities collection and schedules it. In this case I don’t want to schedule the next activity—I want to schedule the activity referenced by TargetActivityName. This bookmark enables this because it keeps the GoTo activity in an executing state while the target activity is being scheduled. When GoTo is cancelled, there’s no action in the Series.OnChildCompleted callback because it only schedules the next activity if the completion state is Closed (and, in this case, is Cancelled):

// Create a bookmark to leave this activity idle waiting when it does
// not have any further work to do. Series will cancel this activity 
// in its GoTo method
context.CreateBookmark("SyncBookmark");

Figure 6 shows an example using this activity. In this case, I’m looping back to a previous state according to the value of a variable. This is a simple example to illustrate the basic use of Series, but this activity can be used to implement complex, real-world business scenarios where you need to skip, redo or jump to steps in a sequential process.

Figure 6 Using GoTo in a Series

var counter = new Variable<int>();
var act = new Series
{
  Variables = { counter},
  Activities =
  {
    new WriteLine 
    {
      DisplayName = "Start",
      Text = "Step 1"
    },
    new WriteLine
    {
      DisplayName = "First Step",
      Text = "Step 2"
    },
    new Assign<int>
    {
      To = counter,
      Value = new InArgument<int>(c => counter.Get(c) + 1)
    },
    new If 
    {
      Condition = new InArgument<bool>(c => counter.Get(c) == 3),
      Then = new WriteLine
      {
        Text = "Step 3"
      },
      Else = new GoTo { TargetActivityName = "First Step" }
    },
    new WriteLine 
    {
      Text = "The end!"
    }
  }
};
WorkflowInvoker.Invoke(act);

References

Windows Workflow Foundation 4 Developer Center
msdn.microsoft.com/netframework/aa663328

Endpoint.tv: Activities Authoring Best Practices
channel9.msdn.com/shows/Endpoint/endpointtv-Workflow-and-Custom-Activities-Best-Practices-Part-1/

Designing and Implementing Custom Activities
msdn.microsoft.com/library/dd489425

ActivityInstance Class
msdn.microsoft.com/library/system.activities.activityinstance

RuntimeArgument Class
msdn.microsoft.com/library/dd454495


Go with the Flow

In this article I presented the general aspects of writing custom control flow activities. In WF 4, the control flow spectrum is not fixed; writing custom activities has been dramatically simplified. If the activities provided out-of-the-box don’t meet your needs, you can easily create your own. In this article I started with a simple control flow activity and then worked my way up to implement a custom control flow activity that adds new execution semantics to WF 4. If you want to learn more, a Community Technology Preview for State Machine is available at CodePlex with full source code. You’ll also find a series of Channel 9 videos on activity authoring best practices. By writing your own custom activities, you can express any control flow pattern in WF and accommodate WF to the particulars of your problem.


Leon Welicki is a program manager in the Windows Workflow Foundation (WF) team at Microsoft working on the WF runtime. Prior to joining Microsoft, he worked as lead architect and dev manager for a large Spanish telecom company and as an external associate professor on the graduate computer science faculty at the Pontifical University of Salamanca at Madrid.

Thanks to the following technical experts for reviewing this article: Joe Clancy, Dan Glick, Rajesh Sampath, Bob Schmidt and Isaac Yuen