Windows Workflow Tutorial: Using the Replicator for Windows Workflow Foundation

Ken Getz
MCW Technologies LLC

Published: December, 2008

Articles in this series

Download the code for this article

To provide support for general programming control flow constructs, the Windows Workflow Foundation (WF) provides activities that you can easily recognize: the IfElse and While activities, for example, clearly allow you to make choices, and loop within a workflow. What if you need to execute an action for each element of some set of objects, however? You might look for a ForEach activity, but your search won’t find the activity you’re seeking. The activity you need is in the toolbox, however: It’s the Replicator activity. This activity, the functional equivalent of a ForEach activity, allows you to execute an activity for each member of a collection.

Immediately, you start seeing problems: If the Replicator can only execute a single activity for each element of a collection, how can you execute complex actions for each element? The answer, of course, is that the Replicator can not only execute a simple activity, it can execute a composite activity (an activity that can contain other activities), such as a Sequence activity. To execute multiple activities, you can place a Sequence activity within the Replicator activity, and the workflow runtime executes all the activities within the Sequence activity for each element in the source collection.

Although you can use the While activity to provide the same behavior as the Replicator activity, the Replication activity adds some important functionality that you would need to create yourself, if you were using the While activity. For example:

  • The Replicator activity automatically “visits” each item in its input collection, creating a separate instance of its contained activity for each item in the collection. You don’t need to keep track of the number of items in the collection, as you would if you were using the While activity.
  • The Replicator activity allows you to supply code that it calls before, during, and after processing of each item in the collection.
  • The Replicator activity allows you to specify whether you want to handle the instances of its contained activity in parallel, or serially. If you execute the activity instances in parallel, the Replicator initializes each instance of the child activity one after the other, then executes each child activity, and finally, completes the execution of each instance of the child activity. (This behavior is similar to using a Parallel activity, with one branch for each item in the data collection.) If you execute the activity instances serially, the Replicator activity initializes, executes, and completes each instance of the child activity before moving on to the next. (This behavior is similar to using a While activity.) Based on your needs, you’ll need to decide which technique works best for you.
  • The Replicator activity provides an Until property, which allows you to supply a condition (either a declarative condition, or a code condition). This condition allows your application to “short-circuit” the behavior of the activity. That is, if the Until condition becomes true, the activity stops executing the child activity instances, even if it hasn’t completed working through the entire input collection.

This tutorial walks you through the basics of working with the Replicator activity, demonstrating how you can use this activity to execute another activity (or activities) for each member of some collection of values or objects.

Build the Sample Workflow

In this tutorial, you’ll create a simple workflow that processes all the files in a particular folder. You might, for example, want to monitor the status of the contents of a particular folder in the file system, and at regular intervals, sweep through the files and handle them in some particular way. In this sample workflow, you’ll simply sweep through all the files and report some information about each to the Console window, but you might want to back up each file, delete each file, or take some other action based on the existence of the file.

NOTE     Although Windows Workflow Foundation doesn’t include an activity to monitor the file system for changes, you can find a sample custom activity on Microsoft’s site that demonstrates this behavior. Browse to https://msdn.microsoft.com/en-us/library/ms741707.aspx for more information. If you want to send an email in response to finding a file (or for any other reason), you can make use of the sample email workflow activity. Browse to https://msdn.microsoft.com/en-us/library/ms742097.aspx to download a sample email activity.

To get started, in Visual Studio 2008 select File | New | Project to display the New Project dialog box. In the list of project types, select Workflow, displaying the list of workflow templates. Select the Sequential Workflow Console Application template, name your application ReplicatorDemo, and select an appropriate folder for the project. Click OK to create the project.

The point of this exercise is to create an instance of an activity for each element within a collection, and execute each instance in turn. Therefore, from the Toolbox window, drag an instance of the Replicator activity into the new workflow. Figure 1 shows the workflow, with the activity in place.

Figure 1. The Replicator activity indicates that it can contain multiple activities, but it cannot—it can contain only a single activity.

Although the caption within the Replicator’s designer invites you to drop multiple activities within it, the invitation is misleading—you can only add a single activity. At this point, as you can see in Figure 1, the Replicator currently displays an error alert. Click on the red circle to view the error, and you’ll see a message indicating that the Replicator activity must contain one activity.

To get started, from the Toolbox window, drag a Code activity to the interior of the Replicator activity. (Of course, at this point, the Code activity displays an error, because you haven’t yet supplied it code it needs. Disregard this error, for now.) Attempt to drag a second Code activity within the Replicator activity, to verify that you can only place a single activity within the Replicator. Your attempts to drop a second activity will simply never display any green dots within the activity—these green dots are your indication where you can drop an activity.

TIP:        In order to place multiple activities within the Replicator activity, use the Sequence activity; you can then place multiple activities within the Sequence activity.

Once you’re done, the layout should resemble Figure 2.

Figure 2. Place a single activity within the Replicator activity.

Add Support Code

In order to get started with the sample workflow, select replicatorActivity1. Because you’ll need to add code for each of the activity’s available handlers, the simplest way to set up all the procedures is to let the designer generate them for you. With the Replicator activity selected, you can either click the Generate Handlers item within the Properties window (see Figure 3), or select Workflow | Generate Handlers from Visual Studio’s menu. Either way, once you’ve generated the handlers, Visual Studio displays the workflow’s code window, with newly generated handler stubs for the Initialized, Completed, ChildInitialized, and ChildCompleted events.

Figure 3. Select Generate Handlers in the Properties window.

TIP:        If you don’t see the Generate Handlers link within the Properties window, right-click the Properties window and select the Commands option on the context menu.

At the top of the code file, add the following statement:

Imports System.IO

using System.IO;

Within the Workflow1 class, add the following two properties, which will track the search folder and the backup properties:

Private searchFolderValue As String

Public Property searchFolder() As String

  Get

    Return searchFolderValue

  End Get

  Set(ByVal value As String)

    searchFolderValue = value

  End Set

End Property

Private backupFolderValue As String

Public Property backupFolder() As String

  Get

    Return backupFolderValue

  End Get

  Set(ByVal value As String)

    backupFolderValue = value

  End Set

End Property

public string searchFolder { get; set; }

public string backupFolder { get; set; }

Add the following two private class-level variables, to keep track of the current file, and the array of files in the search folder:

Private currentFile As FileInfo

Public files As FileInfo()

private FileInfo currentFile = null;

public FileInfo[] files;

In the replicatorActivity1_Initialized event handler, add the following code. This code retrieves the array of FileInfo objects corresponding to the files in the search folder, and also creates the backup folder if necessary:

If Not String.IsNullOrEmpty(searchFolder) Then

  files = New DirectoryInfo(searchFolder).GetFiles("*.*")

End If

' This example assumes that it can create the backup

' folder. If it already exists, this code silently

' does nothing. If the code can't create the folder,

' the workflow will fail. You should add fault handling,

' and terminate the workflow if you can't

' create the folder. See the tutorial on fault handling

' for more information on handling exceptions.

If Not String.IsNullOrEmpty(backupFolder) Then

  Directory.CreateDirectory(backupFolder)

End If

Console.WriteLine("Replicator activity initialized.")

if (!String.IsNullOrEmpty(searchFolder))

{

  files = new DirectoryInfo(searchFolder).GetFiles("*.*");

}

// This example assumes that it can create the backup

// folder. If it already exists, this code silently

// does nothing. If the code can't create the folder,

// the workflow will fail. You should add fault handling,

// and terminate the workflow if you can't

// create the folder. See the tutorial on fault handling

// for more information on handling exceptions.

if (!String.IsNullOrEmpty(backupFolder))

{

  Directory.CreateDirectory(backupFolder);

}

Console.WriteLine("Replicator activity initialized.");

To use the Replicator activity, you must also set the InitialChildData property of the activity. This property indicates to the activity which set of data it should iterate through. You can either set this property in code, or you can set it in the designer. You can set the InitialChildData property to any collection that implements the IList interface, and that includes most collections and arrays. In this example, you must set the InitialChildData property to the array of FileInfo objects that you just created in code.

To set the InitialChildData property, select View | Designer. In the Workflow designer, select the Replicator activity. In the Properties window, select the InitialChildData property. Click the ellipsis button to the right of the property value; this action displays a dialog box which allows you to select the value to which you will bind the property. In the dialog box, select the files property, as shown in Figure 4. Click OK when you’re done.

 

Figure 4. Bind the InitialChildData property to the public files variable.

TIP:        If you want to set the InitialChildData property in the Workflow designer, you must ensure that, at runtime, the collection has been filled in before the ReplicatorActivity executes. In this case, you have accomplished this goal by filling in the array during the Initialized event handler for the Replicator activity.

In the replicatorActivity1_Completed event handler, add the following code:

Console.WriteLine("Completed all the items in the data source.")

Console.WriteLine("Completed all the items in the data source.");

In the replicatorActivity1_ChildInitialized event handler, add the following code. This code uses the InstanceData property of the procedure’s ReplicatorChildEventArgs parameter to retrieve the value of the current FileInfo object, from the source array of files:

currentFile = CType(e.InstanceData, FileInfo)

Console.WriteLine("ChildInitialized event: " & currentFile.Name)

currentFile = (FileInfo)e.InstanceData;

Console.WriteLine("ChildInitialized event: " + currentFile.Name);

In the replicatorActivity1_ChildCompleted event handler, add the following code. This code again uses the InstanceData property to display information about the currently processing item:

Console.WriteLine("ChildCompleted event: " & _

                  CType(e.InstanceData, FileInfo).Name)

Console.WriteLine("ChildCompleted event: " +

  ((FileInfo)e.InstanceData).Name);

Before you can test your workflow, you must fix all its errors. To do this, select View | Designer to return to the design surface. Double-click codeActivity1. In the codeActivity1_ExecuteCode procedure, add the following code:

Console.WriteLine("Handling a file: " & currentFile.Name)

Console.WriteLine("Handling a file: " + currentFile.Name);

Note that while executing the activities within the Replicator activity, you don’t have explicit access to the current data item. This example stores the current item into a variable named currentFile, but be warned: This technique doesn’t work if you set the Replicator activity’s ExecutionType property to Parallel (this property is set to Sequence, by default.)

WARNING!         Do not store the current data item into a variable within the Replicator activity’s ChildInitialized event handler, as you’ve done in this example. Although it grants you access to the data item when you set the Replicator activity’s ExecutionType property to Sequence, it will not work if you set the property to Parallel, as you’ll see later in this tutorial.

Configure the Host Application

The workflow’s host application must supply the workflow’s search path, and handle halting the application to wait for user input. In the Solution Explorer window, double-click Module1.vb or Program.cs. Within the class, locate the Main procedure. Within the Main procedure, replace the line of code that calls the CreateWorkflow method, inserting the following code block:

Dim parameters = New Dictionary(Of String, Object)

parameters.Add("searchFolder", "C:\test")

parameters.Add("backupFolder", "C:\backup")

workflowInstance = workflowRuntime.CreateWorkflow( _

  GetType(Workflow1), parameters)

var parameters = new Dictionary<string, object>();

parameters.Add("searchFolder", @"C:\\test");

parameters.Add("backupFolder", @"C:\\backup");

WorkflowInstance instance =

  workflowRuntime.CreateWorkflow(

  typeof(Workflow1), parameters);

This code sets up a dictionary containing the two parameters for your workflow, searchFolder and backupFolder. (Feel free to change the specific folder names if you want to monitor files and back them up to different locations.) When you pass the dictionary to the workflow in the call to CreateWorkflow, the Workflow Runtime sets the value of the matching properties within the workflow instance to the values you specify in the dictionary.

Add the following procedure to the class:

Shared Sub WaitForKeypress()

  Console.WriteLine()

  Console.WriteLine("Press any key to quit...")

  Console.ReadKey()

End Sub

static void WaitForKeypress()

{

  Console.WriteLine();

  Console.WriteLine("Press any key to quit...");

  Console.ReadKey();

}

Modify the Main procedure, adding a call to WaitForKeyPress immediately following the existing call to the WaitHandler.WaitOne method. Once you’re done, the end of the Main procedure should look like the following:

    workflowInstance.Start()

    WaitHandle.WaitOne()

    WaitForKeypress()

  End Using

End Sub

    instance.Start();

    waitHandle.WaitOne();

    WaitForKeypress();

  }

}

Test the Workflow

In order to test the workflow, ensure that the C:\Test folder (or whichever folder you specified in the searchFolder property) exists, and that it contains some old files, and at least one file that you created today. Execute the application, and examine the output. Imagine that the C:\Test folder contains three files, two of which (Document.tif and list.txt) have not been modified today, and one (Demo.txt) was created today. The output in the Console window might look like Figure 4.

 

Figure 5. With three files, one of which is new, the output might look like this.

Given the array of files that the code created by examining the C:\Test folder, the Replicator activity “visited” each file in turn, executing its ChildInitialized and ChildCompleted event handlers. In between the two, the Replicator activity executed its child activity, the Code activity, which displays text as it executes.

As you can see in Figure 4, because the Replicator activity’s ExecutionType property has been set to its default value (Sequence), the replicator handles each instance of its child activity completely before moving on to the next.

NOTE     If the child activity executes more than once, the Replicator activity creates separate instances of the child activity. This allows the instances to execute independently (and also makes it possible for the workflow to execute the child activities in parallel—this wouldn’t be possible if the Replicator activity didn’t create a separate instance of the child activity for each item in the InitialChildData collection). If you want to retrieve a collection of all the child activities, use the Replicator activity’s DynamicActivities property from within the child activity’s code.

So far, you’ve seen that the Replicator activity uses its InitialChildData property as a data source, and iterates through each item in the collection. For each child, the Replicator activity calls ChildInitialized and ChildCompleted events, passing each an event argument that includes a reference to the specific child. The Replicator activity’s child activity, however, has no specific information about the current data item, and you’ll need to take extra steps (as you’ll see later in this tutorial) to use that information from within the child activity.

Compare Parallel and Serial Execution

In the previous exercise, you ran the sample workflow with the Replicator activity’s ExecutionType property set to Sequence. Using that execution type, the Replicator activity initializes, executes, and completes each instance of the child activity before it moves on to the next. How does the execution change if you set the property to Parallel?

It’s simple to demonstrate this behavior. Open Workflow1 in the workflow designer, and select the Replicator activity. In the Properties window, set the ExecutionType property to Parallel, and then save and execute the application. This time, you’ll see output similar to Figure 5. Examine the output carefully, and you’ll find major changes from the previous test:

•             The Replicator activity creates an instance of its child activity for each element of its InitialChildData property’s collection.

•             The Replicator activity raises the ChildInitialized event handler for each instance of the child activity, in turn. Then, it executes the child activity instances, one after the other. Finally, it raises the ChildCompleted event for each instance of the child activity.

•             The local currentFile variable is set during the ChildInitialized event handler. When you execute the Replicator activity using the Parallel execution mode, the current file name contains the last initialized child instance, before the Replicator activity starts executing the child activity instances. As you can see in Figure 5, this means that the currentFile variable contains the same value for each instance, and is therefore meaningless. As it is, there’s no way for the Code activity within the Replicator activity to retrieve information about the current file. The recommended solution for this problem is to create a custom activity that contains information about the current item that the Replicator activity is handling, and to use it in place of the “dumb” activity that you’re currently using within the Replicator. You’ll learn more about this solution later in this tutorial.

 

Figure 6. When you set the Replicator activity to run its child activities in parallel, the behavior changes significantly.

Before continuing, make sure you set the ExecutionType property back to Sequence, its default value.

Add an Until Condition

In addition to executing its child activity for each item in the collection supplied in its InitialChildData property, you can supply an Until condition in the Replicator activity’s UntilCondition property. Just as when you’re working with an IfElse or While activity, this condition can be either a declarative rule condition, or it can be a code condition. Even if the Replicator activity hasn’t finished working through the items in the InitialChildData property, if the Until becomes true, the Replicator activity halts its march through the data items.

WARNING!         If you set the UntilCondition property, you must ensure that at some point, it becomes true. If the Replicator activity completes all its child activity instances and the UntilCondition property returns false, the Replicator activity simply hangs. Although you can solve this problem by programmatically adding new items to the data collection to somehow change the UntilCondition property or make it no longer be false, this is an extreme solution. You must plan for this problem, and ensure that the UntilCondition property is no longer false by the time all the child activity instances have completed executing.

Imagine that you wanted to limit the number of files that your Replicator activity handled—you might want to, for example, limit it to only backing up two files (although this restriction seems arbitrary, it simple allows this tutorial to show off the use of the UntilCondition property). Based on the previous warning, if you set the UntilCondition so that it returns true only when you have handled a fixed number of files, but never reach that specified number of files, your workflow will hang. In this example, verify that you have more than two files in the test folder, and start by adding the following variable to the class-level variables:

Private currentFile As FileInfo

Private files As FileInfo()

Private fileCount As Integer

private FileInfo currentFile = null;

private FileInfo[] files;

private int fileCount;

Within the replicatorActivity1_Initialized event handler, add code that initializes the fileCount variable:

fileCount = 0

fileCount = 0;

Within the replicator1_ChildInitialized event handler, add code that increments the fileCount variable:

fileCount += 1

fileCount++;

Finally, add the UntilCondition property setting. To do this, start by selecting View | Designer. Select the Replicator activity, and in the Properties window, find the UntilCondition property. Set the property’s value to Code Condition, and expand the + sign to the left of the property. Set the Condition sub-property to the name of the new procedure, LimitFiles. Press Enter, and Visual Studio creates the procedure stub. (You could also set the UntilCondition property to Declarative Rule Condition, and specify the rule directly in the designer, in this case). Note that Visual Studio creates a stub for the procedure that includes a ConditionalEventArgs object as the second parameter. This object provides a Result property—set it to a Boolean value that, when True, will cause the Replicator to halt. Modify the procedure, adding code to limit the number of files so that once it has processed two files, the UntilCondition property returns true:

Private Sub LimitFiles(ByVal sender As System.Object, _

 ByVal e As System.Workflow.Activities.ConditionalEventArgs)

  e.Result = (fileCount > 1)

End Sub

private void LimitFiles(object sender, ConditionalEventArgs e)

{

  e.Result = (fileCount > 1);

}

Make sure the ReplicatorActivity’s ExecutionType property has been set to Sequence, and run the application. You’ll find that the output shows only two files, instead of the original number. (For fun, try changing the value in the LimitFiles method so that it executes until you reach a number of files greater than the actual number of files that you have. You’ll see that the workflow simply halts, waiting for the fileCount variable to reach the number you specify. Because there aren’t enough files, it never does. You can simply close the Console window to end the running application, in that case.) Clearly, you need to be careful when using the UntilCondition property, to ensure that this situation never occurs.

Create a Custom Activity

Although the goal of the workflow was to back up each file to a specific folder, it currently doesn’t include this behavior. For this simple example, you could do the work in the ChildInitialized event, but what if you needed to execute workflow activities within the Replicator activity, and you needed to have information about the specific item from the Replicator’s data source from within those activities? At this point, the single Code activity within the Replicator receives no information about the current data item, and because the Replicator activity might be executing its contents in parallel fashion, you can’t simply store the value into a workflow-level variable.

The standard solution to this problem is to create a custom activity which has, as one of its properties, a public property that can receive, and work with, the current data item from the parent Replicator activity. For example, if you were to create a custom activity with an InputFile and BackupFolder property, you could pass information from the Replicator to the custom activity, and it could perform the work of backing up the specified file. In other words, to complete this simple workflow, you’re going to create a custom activity that has information about the file to be backed up, and the location to which to back it up, and you’ll replace the existing Code activity with this new activity.

To get started, in Visual Studio, select Project | Add Activity. In the Add New Item dialog box, set the name to be FileBackupActivity, and click Add. Visual Studio creates a designer for the new activity; select View | Code to view the activity’s code.

By default, the designer creates a new activity that can contain other activities (it inherits from SequenceActivity). Because you need to create a simple activity, start by changing the base class to Activity:

Public class FileBackupActivity

  Inherits Activity

End Class

public partial class FileBackupActivity : Activity

{

  public FileBackupActivity()

  {

    InitializeComponent();

  }

}

Within the activity’s class, you’ll need to create two public properties. Although you could use standard .NET properties for both, you should get in the habit of using Workflow’s dependency properties, which supply extra functionality, when your intention is to bind the properties using the designer.

TIP:        Dependency properties are useful when you intend to use the data binding capabilities in the Windows Workflow Foundation, but they do add extra overhead, when compared to simple .NET properties. As a rule of thumb, use dependency properties only when you intend to use workflow binding. In this example, the BackupFolder property needs to be bound to another property, but the InputFile property will not. For this example, there’s no need to make the InputFile property be a dependency property.

Although a full discussion of dependency properties is beyond the scope of this tutorial, they serve a very important purpose within workflows: They allow the workflow runtime to provide a centralized repository for property instances, keeping track of a workflow’s state. When you create a dependency property, the workflow runtime registers the value of the property, for the particular activity instance, in a hash table within the workflow runtime. If your workflow never uses the property value, it never needs to be loaded from the hash table. This behavior allows the workflow to minimize memory usage. In addition, the workflow designer makes use of dependency properties in its support for property binding, so you’ll find working with the designer easier if you make use of dependency properties. (For more information on dependency properties, browse to this page: https://msdn.microsoft.com/en-us/library/ms734499(VS.85).aspx).

To get started, add the the following statement to the top of the code file:

Imports System.IO

using System.IO;

The easiest way to create a dependency property is to the use the code snippet built into Visual Studio 2008. To do that, within the FileBackupActivity class, create a blank line outside any other procedure, and type wdp followed by the Tab key (press the Tab key twice in C#). Fill in the various fields so that the property looks like the following code fragment (in C#, you do not need to supply the class name; in Visual Basic, you must):

Public Shared BackupFolderProperty As DependencyProperty = _

  DependencyProperty.Register("BackupFolder", GetType(String), _

  GetType(FileBackupActivity))

<Description("BackupFolder")> _

<Category("BackupFolder Category")> _

<Browsable(True)> _

<DesignerSerializationVisibility( _

  DesignerSerializationVisibility.Visible)> _

Public Property BackupFolder() As String

  Get

    Return (CType((MyBase.GetValue( _

      FileBackupActivity.BackupFolderProperty)), String))

  End Get

  Set(ByVal Value As String)

    MyBase.SetValue( _

      FileBackupActivity.BackupFolderProperty, Value)

  End Set

End Property

public static DependencyProperty BackupFolderProperty =

  DependencyProperty.Register("BackupFolder", typeof(string),

  typeof(FileBackupActivity));

[DescriptionAttribute("BackupFolder")]

[CategoryAttribute("BackupFolder Category")]

[BrowsableAttribute(true)]

[DesignerSerializationVisibilityAttribute(

  DesignerSerializationVisibility.Visible)]

public string BackupFolder

{

  get

  {

    return ((string)(base.GetValue(

      FileBackupActivity.BackupFolderProperty)));

  }

  set

  {

    base.SetValue(

      FileBackupActivity.BackupFolderProperty, value);

  }

}

If you examine the code, you’ll see that the snippet creates a public static/shared field named InputFileProperty (and BackupFolderProperty)—therefore, there’s only one instance of this field in memory, no matter how many instances of the activity class get created by the workflow. The code also creates a public standard .NET property named InputFile (or BackupFolder), and this property simply gets and sets the value of the property from the hash table managed by the base class (DependencyObject).

In addition, create a standard .NET FileInfo property named InputFile:

Private inputFileValue As FileInfo

Public Property InputFile() As FileInfo

  Get

    Return inputFileValue

  End Get

  Set(ByVal value As FileInfo)

    inputFileValue = value

  End Set

End Property

public FileInfo InputFile { get; set; }

From the calling code’s perspective, the activity exposes two public properties: InputFile and BackupFolder. At this point, you must add code to cause the custom activity to take some action when it executes. Within the custom activity’s class, add the following code, which overrides the Execute method from the base class:

Protected Overrides Function Execute( _

  ByVal executionContext As ActivityExecutionContext) _

  As ActivityExecutionStatus

  Try

    Dim outputFile As String = _

      Path.Combine(BackupFolder, InputFile.Name)

    File.Copy(InputFile.FullName, outputFile)

    Console.WriteLine("Copied {0} to {1}.", _

      InputFile, outputFile)

  Catch ex As Exception

    ' For this simple example, don't do anything if

    ' the file copy fails.

  End Try

  Return ActivityExecutionStatus.Closed

End Function

protected override ActivityExecutionStatus Execute(

  ActivityExecutionContext executionContext)

{

  try

  {

    string outputFile =

      Path.Combine(BackupFolder, InputFile.Name);

    File.Copy(InputFile.FullName, outputFile, true);

    Console.WriteLine("Copied {0} to {1}.",

      InputFile, outputFile);

  }

  catch (Exception)

  {

    // For this simple example, don't do anything if

    // the file copy fails.

  }

  return ActivityExecutionStatus.Closed;

}

The Execute method requires that you return the current state of the activity—in this example, the activity always returns Closed, indicating that it has finished its processing.

Save and compile the project. This action allows Visual Studio to display your custom activity in the Workflow designer’s Toolbox window. Open the workflow in the designer, and verify that you see FileBackupActivity in the Toolbox window.

In the workflow, delete the existing Code activity, and drag an instance of the FileBackupActivity from the toolbox into the Replicator activity. Now that you’ve placed the activity in the Replicator, you still must set its InputFile and BackupFolder properties. You can’t set the InputFile property until runtime, but you can set the BackupFolder property in the designer. To do that, select the activity, and then in the Properties window, locate the BackupFolder property. Click the ellipsis to the right of the property. In the dialog box, select the workflow’s backupFolder property, as shown in Figure 6. This allows you to declaratively select the binding for the activity’s BackupFolder property, setting its value to the backupFolder property of the workflow.

 

Figure 7. Bind the activity’s property to a property of the workflow.

The ChildInitialized event handler passes your code a ReplicatorChildEventArgs object as its second parameter. This object includes, in addition to the InstanceData property you already saw, an Activity property. This property provides a reference to the current child activity, so that you can work with the child activity (in this case, setting the InputFile property.)

In the workflow’s code, add the following code at the end of the existing replicatorActivity1_ChildInitialized event handler so that it creates a reference to the current FileBackupActivity’s instance, and sets its InputFile property:

Dim fba As FileBackupActivity = _

  CType(e.Activity, FileBackupActivity)

fba.InputFile = currentFile

FileBackupActivity fba = (FileBackupActivity)e.Activity;

fba.InputFile = currentFile;

Because the child activity exposes a public InputFile property, you can set the property value from the ChildInitialized event handler, and the child activity can use this information as it performs its actions. Without creating a custom activity, you couldn’t use that information from within the child activity’s actions.

Save and execute the application. This time, you’ll see output as shown in Figure 7, including the output from the FileBackupActivity’s Execute override.

 

Figure 8. The custom activity can use the information about the individual data item that it’s processing.

You might also want to verify the behavior of the workflow using parallel execution. Because of the way the workflow sets up the file counter, the Until condition won’t operate correctly once you set the ExecutionType property to Parallel. To fix this, back in the Workflow designer, select the Replicator activity. In the Properties window, right-click the UntilCondition property, and select Reset from the context menu.

Change the Replicator activity’s ExecutionType property to Parallel (rather than Sequence). You will see that all the files get copied correctly, because the ChildInitialized event handler sets up the properties of each separate instance of the child FileBackupActivity.

Conclusion

In this tutorial, you have investigated the Replicator activity, which functions within a workflow much like a For Each loop does within code. You set the InitialChildData property (either in code, or in the designer) to some collection that implements the IList interface, and the Replicator activity either executes its child activity once for each item in the collection (if the ExecutionType property is set to Sequence) or creates an instance of the child activity for each data item (if the ExecutionType property is set to Parallel). If you need the child activity to be able to interact with each individual data item, your best bet is to create a custom activity that exposes a property into which you can place the data item at runtime. If you find yourself needing to iterate through a collection, like the array of files in this tutorial, you are better off using the Replicator activity than the similar While activity—using the Replicator, you do not need to keep track of the number of items in the collection. In addition, you can employ the UntilCondition property to exit from the Replicator activity’s loop before it has iterated through all its data items.

About the Author

Ken Getz is a developer, writer, and trainer, working as a senior consultant with MCW Technologies, LLC. In addition to writing hundreds of technical articles over the past fifteen years, he is lead courseware author for AppDev (http://www.appdev.com). Ken has co-authored several technical books for developers, including the best-selling ASP.NET Developer’s Jumpstart, Access Developer’s Handbook series, and VBA Developer’s Handbook series, and he’s a columnist for both MSDN Magazine and CoDe magazine. Ken is a member of the INETA Speakers Bureau, and speaks regularly at a large number of industry events, including 1105 Media’s VSLive, and Microsoft’s Tech-Ed.