Developing an Iterative Activity

To illustrate the concepts related to activity execution contexts, the following code shows a sample activity that behaves like the C# foreach construct. Activities that possess the properties of a loop are referred to as iterative activities. The ForEachActivity class has a property of type IEnumerable, which means that basically any .NET collection can be provided for iteration. This activity was modeled after the out-of-the-box While activity, and like the While activity, the sample implements the IActivityEventListener <ActivityExecutionStatusChangedEventArgs> interface. This provides the ability to listen for events raised on an activity. In this case, the activity is interested in listening for events on itself, specifically the Closed event.

  public class ForEachActivity : CompositeActivity,     IActivityEventListener<ActivityExecutionStatusChangedEventArgs> {     private IEnumerator enumerator = null;     // *** Dependency properties removed for brevity ***     protected override ActivityExecutionStatus Execute(         ActivityExecutionContext context)     {         if (context == null)             throw new ArgumentNullException("context");         // perform the first "loop"         if (this.Iterate(context))         {             // if there are no items in the collection, we're done             return ActivityExecutionStatus.Executing;         }                  // nothing to do, the activity is done         return ActivityExecutionStatus.Closed;     }     private bool Iterate(ActivityExecutionContext context)     {         if (this.enumerator == null)             this.enumerator = this.Collection.GetEnumerator();         // make sure there is another item and there is a child activity         if (!this.enumerator.MoveNext() || this.EnabledActivities.Count != 1)             return false;         // set the CurrentItem property         this.CurrentItem = this.enumerator.Current;         ActivityExecutionContextManager aecManager =             context.ExecutionContextManager;         // create a new context for the child activity         ActivityExecutionContext newContext =             aecManager.CreateExecutionContext(this.EnabledActivities[0]);         // register for the child activity's Closed event         newContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this);         // execute the child activity         newContext.ExecuteActivity(newContext.Activity);         return true;     }     void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(         object sender, ActivityExecutionStatusChangedEventArgs e)     {         if (e == null)             throw new ArgumentNullException("e");         if (sender == null)             throw new ArgumentNullException("sender");         // get the execution context of the child activity         ActivityExecutionContext context = sender as ActivityExecutionContext;         if (context == null)         {             throw new ArgumentException(                 "sender should be an ActivityExecutionContext instance");         }         // unsubscribe from the Closed event         e.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);         ActivityExecutionContextManager aecManager =             context.ExecutionContextManager;         // complete the execution context         aecManager.CompleteExecutionContext(             aecManager.GetExecutionContext(e.Activity));         // do the next "loop"         if (!this.Iterate(context))         {             // no more loops, close the for each activity             context.CloseActivity();         }     } } 

The overridden Execute method basically just calls an Iterate helper method that holds the looping logic. In reality, there isn’t a loop to be found anywhere in this code. It is simulated by using the enumerator object, executing a lone child activity, and calling Iterate again after the child activity is closed. When the enumerator.MoveNext method no longer returns true, the ForEachActivity is closed.

In the Iterate method in this example, an ActivityExecutionContextManager instance is used to create a new execution context based on the ForEachActivity’s child. The new context is then able to execute the child activity using its ExecuteActivity method. This pattern ensures that the child activity executes in its own separate context for every iteration. In the OnEvent method, which captures the Closed event, the execution context is completed with ActivityExecutionContextManager.

Figure 8-1 shows an example workflow that a ForEachActivity method called forEachDate. The first Code activity initializes a List<T> collection, which is bound to the ForEachActivity. The Code activity inside the loop then prints out information about the current item.

image from book
Figure 8-1

The following code represents the code-beside file for the workflow in Figure 8-1:

  private void initCollection_ExecuteCode(object sender, EventArgs e) {     DateTime startDate = DateTime.Now;     this.listOfDates.Add(startDate.AddHours(-5));     this.listOfDates.Add(startDate.AddHours(-4));     this.listOfDates.Add(startDate.AddHours(-3));     this.listOfDates.Add(startDate.AddHours(-2));     this.listOfDates.Add(startDate.AddHours(-1)); } private void printInfo_ExecuteCode(object sender, EventArgs e) {     CodeActivity codeActivity = sender as CodeActivity;     if (codeActivity != null)     {         ForEachActivity.ForEachActivity fea =             codeActivity.Parent as ForEachActivity.ForEachActivity;         if (fea != null)          {             Console.WriteLine("Current date: " +                 ((DateTime)fea.CurrentItem).Hour.ToString());         }     } } 

This workflow uses a collection of DateTime instances just to show that the activity can handle anything that implements IEnumerable. The printInfo event handler obtains a reference to the current item by first using the sender parameter to access the CodeActivity instance and then accessing its parent. The property of ForEachActivity’s CurrentItem is of type object, so it needs to be cast as a DateTime before its Hour property can be accessed.

Although this is similar to how you develop other activity types, developing an iterative activity has its own set of considerations that must be addressed - the biggest of which is managing activity execution contexts. More specifically, the children of the iterative activity must be executed in their own execution contexts for each iteration so that each execution context can be persisted and treated separately for transactional purposes. The activity execution context is a great feature of Windows Workflow Foundation and really shows its benefits in iterative activities.

Professional Windows Workflow Foundation
Professional Windows Workflow Foundation
ISBN: 0470053860
EAN: 2147483647
Year: 2004
Pages: 118
Authors: Todd Kitta

Similar book on Amazon
Pro WF: Windows Workflow in .NET 4 (Expert's Voice in .NET)
Pro WF: Windows Workflow in .NET 4 (Expert's Voice in .NET)
Pro WF: Windows Workflow in NET 3.5 (Expert's Voice in .NET)
Pro WF: Windows Workflow in NET 3.5 (Expert's Voice in .NET)
Microsoft  Windows  Workflow Foundation Step by Step (Microsoft Windows Step by Step)
Microsoft Windows Workflow Foundation Step by Step (Microsoft Windows Step by Step)
Essential Windows Workflow Foundation
Essential Windows Workflow Foundation © 2008-2017.
If you may any questions please contact us: