Windows Workflow

Build Custom Activities To Extend The Reach Of Your Workflows

Matt Milner

This article is based on a prerelease version of the .NET Framework 3.0. All information herein is subject to change.

This article discusses:

  • Building basic and composite activities
  • Asynchronous and event-based activities
  • Customizing the design experience
  • Validation and error handling
This article uses the following technologies:
.NET Framework 3.0

Code download available at: Work flow 2006_12.exe(178 KB)

Contents

Building a Basic Activity
Dependency Properties
Building Composite Activities
Asynchronous and Event-Based Activities
Handling Errors
Design Experience
Validation
Conclusion

Custom workflow activities are one of the most important aspects of the Windows® Workflow Found­a­tion and there are many features to consider when building them. In their January 2006 article, "Simplify Development with the Declarative Model of Windows Workflow Foundation," Don Box and Dharma Shukla discussed the core ideas of building workflows and the interactions between the workflow runtime and activities (see msdn.microsoft.com/msdnmag/issues/06/01/WindowsWorkflow­Foundation). In this article, I will cover the core components required for building custom activities for your business domain, including runtime responsibilities, design time experience, and asynchronous activity development.

When building a custom activity, the first choice you have to make is whether to build a simple or a composite activity. Simple, or basic, activities are those whose logic and execution are encapsulated within the code of the activity. Examples of simple activities include the SendEmail activity in the Windows SDK or an activity to query data from a database.

Composite activities, on the other hand, achieve their goals by relying on the execution of child activities. You can author composite activities in two different ways depending on your requirements. One option is to build a composite activity that allows users to add child activities at design time and that controls the execution of those children. For example, the While activity included in Windows Workflow Foundation lets you add a child activity and then configure the While activity's looping functionality. The While composite activity focuses on managing the execution of its children. The other option for building a composite activity is to create an activity that derives from an existing composite activity-the Sequence activity, for example-and add a group of activities as children to define a reusable component.

A reusable activity, such as a manager approval activity, has several steps that are most easily modeled with a collection of existing activities. The manager approval activity could use activities such as IfElse to determine whether the manager is online. If she is online, then an instant message could be sent; otherwise an e-mail would suffice. This activity encapsulates all of the complex logic of requesting approval, which is in and of itself a workflow. Users of this activity can be relieved of having to deal with the complexities and details of actually contacting the manager, simply by using this composite activity in their workflows.

To begin to understand the concepts of building activities, it helps to start with a very basic activity and then gradually build it up into something more complex. This article will make use of two primary activities for the purpose of explaining these concepts: an activity to create a user in the ASP.NET membership store, and a custom Switch activity to model execution similar to the code concept of the same name. The full source for these activities and others can be found in the code download for this article.

Building a Basic Activity

In many ways, building an activity is similar to building a component. After all, an activity is a reusable component that happens to take advantage of the features of Windows Workflow Foundation, the framework in which it executes. The first step in building an activity, then, is to determine its function and its interface. In the case of the CreateUser activity, the function is to create a user through the membership provider model of ASP.NET. The interface will require the properties necessary to create a user: namely things like e-mail address, username, and password.

The code to handle the function of a basic activity is written in the overridden Execute method from the base Activity class. In addition to the code specific to the function of the activity, it is your responsibility as the activity developer to return the status of your activity. For basic activities, this will almost always be a status of Closed. Returning Closed is the indication to the workflow runtime that your activity has completed its execution and the runtime is free to schedule the next activity in the workflow. Figure 1 shows a basic activity used to create a user in the Membership system of ASP.NET 2.0. For more details on the activity lifecycle and the contract between the runtime and activities, refer to the article by Don Box and Dharma Shukla mentioned earlier.

Figure 1 Create User Basic Activity

public string Password
{
    get { return _password; } set {_password = value; }
}
 
public string Email
{
    get { return _email; } set{ _email = value; }
}
 
public string UserName
{
    get { return _username; } set{ _username = value; }
}

public string MembershipProviderName
{
    get { return _provider; } set{ _provider = value; }
}

protected override ActivityExecutionStatus Execute(
    ActivityExecutionContext executionContext)
{
    MembershipProvider mp = String.IsNullOrEmpty(MembershipProviderName) 
        ? Membership.Provider : 
        Membership.Providers[MembershipProviderName];
     
    MembershipCreateStatus status;
    mp.CreateUser(UserName, Password, Email, null, null, true, 
        Guid.NewGuid(), out status);

    return ActivityExecutionStatus.Closed;
}

Dependency Properties

Providing properties on an activity is important to allow consumers of the activity the ability to configure its execution. With standard Microsoft® .NET properties or fields, as shown in the previous example, a consumer of the activity must write code in the workflow to set the values of the properties. But consider a scenario where you want to provide a mechanism for less technical staff to build workflows. You do not want all users to have to write code simply to connect data from one activity to another. The declarative Windows Workflow Foundation model lets you make these scenarios easier, and one of the features that supports this model is dependency properties.

Dependency properties are a means of storing the values of an activity or a workflow's state. Unlike standard properties where the value is stored within the class itself, the value of a dependency property is stored in a dictionary maintained by the Depen­dencyObject base class.

Dependency properties enable several key features, but perhaps the most important is known as activity binding. As the name suggests, activity binding is the act of binding a property on an activity to a property on another activity or on the workflow itself. When you bind a dependency property to another property, the value is automatically propagated for you. For example, if a workflow has a property defined for the user name to be created and you had defined a UserName property as a dependency property, then you could bind the UserName property to the property on the workflow. When the workflow is started with parameters, the value passed into the workflow will automatically be passed to your activity through the binding. Figure 2 portrays this relationship.

Figure 2 Binding with Dependency Properties

Figure 2** Binding with Dependency Properties **

This type of binding brings to components the same powerful model that a great many people have come to depend on in UI development. Much like data binding in ASP.NET and Windows Forms, activity binding makes connecting components much simpler. It also makes the idea of a non-programmer building a workflow much more palatable. With properly designed activities, users should be able to drag them onto a design surface and connect the values between activities without having to write any code.

Defining dependency properties in your activity is a two-step process. The first step consists of specifying and registering the dependency property definition, which you do by creating a static variable and setting its value by calling the Register method of the De­pend­ency­Property class. The second step is to define a standard .NET property with get and set methods, where you rely on the base class to store or retrieve the value of the dependency property. Note that there is a code snippet for Visual Studio® 2005 that makes this process easier. The code in Figure 3 shows the UserName property redefined as a dependency property.

Figure 3 UserName Property as Dependency Property

public static DependencyProperty UserNameProperty = 
    System.Workflow.ComponentModel.DependencyProperty.Register(
        "UserName", typeof(string), typeof(CreateUserActivity));

[Description("The new username for this user")]
[Category("User")]
[Browsable(true)]
[DesignerSerializationVisibility(
    DesignerSerializationVisibility.Visible)]
public string UserName
{
    get { return (string)base.GetValue(
              CreateUserActivity.UserNameProperty); }
    set { base.SetValue(CreateUserActivity.UserNameProperty, value); }
}

Note also that in this example the .NET property itself is attributed with markers indicating how the property should appear in the property browser. This is most noticeable in the Visual Studio designer where the category and description readily appear when the activity is highlighted. These attributes are all standard .NET designer attributes and are well documented in the MSDN® library.

Activity binding is one feature enabled by the use of dependency properties. Other features include property promotion, change notification, and state management. Because the values of the properties are stored in a central dictionary, the state of the activity can more easily be serialized. Likewise, this central storage makes it easier to monitor changes to those properties, allowing other activities or workflow code to register for changes and take action when a particular value is modified.

A common question is, when should a developer use dependency properties instead of standard .NET properties? The simple answer is that dependency properties should be used for any property that would benefit from being bound to another property or field. The truth is, there is no good argument for not using dependency properties, and with the code snippet support they are easy to write.

Building Composite Activities

While basic activities provide a great deal of value, it is often the case that you will want to provide more complex reusable components. One type of such a component is the reusable activity that encompasses several basic activities to represent a reusable interaction or flow of logic. For example, an activity that first requests approval from a manager before creating a user would be a composite activity. Wrapped up in this one activity would be the request for permission, receiving the response, and then creating the user. The goal in building this composite activity would be to allow a user to add it to a workflow without having to worry about the complexities of the approval process or how it works.

This simple type of composite activity reuses the execution control of an existing base class, often the Se­quenceActivity class. This is such a common scenario that the Visual Studio Extension for Win­dows Work­flow Foundation comes with a visual designer for this type of activity. Simply add a new activity to your project and you have the start of a composite activity deriving from the Sequence activity, with the ability to define the steps in your reusable component much as you define a workflow. Using the property browser, you can change the base class to any other composite activity to change the execution semantics but continue to use the designer to build the activity logic. Figure 4 shows the activity designer being used to create a new composite activity based on the Sequence activity.

Figure 4

Figure 4

When you are encapsulating activities, the need arises to expose properties of the contained activities to consumers of your composite activity. In the previous example, the user of the composite activity needs to be able to provide values for the username, e-mail address, roles, and other details required to properly configure the contained activities. The Visual Studio Extensions provide a mechanism referred to as property promotion to make this easier. When using the composite activity designer, if you right-click an activity, the context menu contains an option to promote the bindable properties on the activity. When you choose this option, dependency properties are declared on the composite activity and the properties of the contained activity are bound to those values. This exposes the properties you require to be set without forcing you to write the code to connect the values. You can determine what the exposed property will be called to consumers of your composite activity and continue to hide the implementation details of your activity.

In the case where you want to have more control over the execution of the child activities, you write an activity derived from CompositeActivity and, in the Execute method, handle the scheduling of execution for your child activities. As an example, consider the Switch activity. The IfElse activity available as part of the .NET Framework 3.0 provides the model for conditionally executing a single branch of activities. The Parallel activity provides a model for executing all branches. Only the ConditionedActivityGroup (CAG) activity provides a model for conditionally executing multiple branches. Switch is simpler than CAG and provides a good mechanism for showing how to control the execution of child activities to allow for conditionally executing multiple branches of activities based on rules or code decisions. Figure 5 shows the Switch activity being used in a workflow. Note that each branch of the activity has a property named Condition that can be set to a declarative rule condition or a code condition just like the conditions on a branch of an IfElse activity. Only when the condition is present and evaluates to True does that branch of activity execute.

Figure 5 Conditional Execution with the Switch Activity

Figure 5** Conditional Execution with the Switch Activity **(Click the image for a larger view)

As a composite activity, the Switch activity is made of up a number of sequences to be executed conditionally. Therefore, the first step is to create another activity, the SwitchBranch activity, which derives from the Sequence activity and defines an ActivityCondition property named Condition. It is this property that will allow a user to set the code or declarative rule condition that will indicate if the sequence should execute. The SwitchBranch takes full advantage of its base activity and provides no execution logic of its own.

In the Execute method of the Switch activity each of the enabled child activities is checked and the condition evaluated. If the condition has been set and evaluates to True, then that child branch must be scheduled for execution. This is done by calling the Ex­e­cute­Activity method on the ActivityExecutionContext. This is the most common place for composite activities to differ in how they manage the children. Whether you want them to be scheduled in parallel, sequence, or reverse order, the Execute method is where you can identify the schedule and which children should or should not be executed.

The Execute method for the Switch activity returns a status of Executing to the runtime. This indicates that the activity has scheduled its children but it is not finished with its work. This keeps the runtime from executing the next activity until it is notified that this activity has completed. When all of the child activities have completed, the Switch activity must be able to notify the runtime that it is complete. Therefore, before scheduling the child branch for execution, the Switch activity registers for a change notification on the activity so that it knows when the child activity closes. Figure 6 shows the code used to register for this notification. Note that this change notification takes advantage of the ability to monitor changes to dependency properties as noted earlier. The activity must implement the IActivityEventListener interface to get notified of these changes.

Figure 6 Register for Change Notification

foreach(Activity child in EnabledActivities)
{
    SwitchBranchActivity childBranch = child as SwitchBranchActivity;
    if (childBranch != null && 
        childBranch.Condition.Evaluate(childBranch, executionContext))
    {
        childBranch.Closed += OnChildClosed;
        executionContext.ExecuteActivity(childBranch);
    }
}
return ActivityExecutionStatus.Executing;

In the OnEvent method of the IActivityEventListener interface, when the activity is notified that a child branch has closed, it checks to see whether there are any more executing children. If not, then the activity can ask the ActivityExecutionContext to close it. This is the same as returning Closed from the Execute method and lets the runtime know the activity has completed all of its tasks. This is another common place for differences as each activity will have to determine the specific conditions under which it has completed its work.

Each composite activity will likely have different semantics for how it should execute the child activities to meet the needs of the author and the business case. The example shown here is intended to provide insights into the core concerns for an activity developer when creating composite activities. Keep these in mind as you build your activities and consider whether the example applies to your particular case.

Asynchronous and Event-Based Activities

While some activities can complete their work in a simple synchronous manner, there are many cases where an activity will start some work and then wait for the response. Likewise, you may have a need to create an activity that can listen for an external event such as a new file being created or a Windows Management Instrumentation (WMI) event being raised. Because of the way that workflows are managed in the runtime, you should not directly create event handlers for external resources in your activities. For example, if you registered an event handler in your activity for changes to the file system, and the workflow needs to enter an idle, serialized state, your handler goes away with it. Any events raised will then not get to your activity.

Windows Workflow Foundation provides infrastructure to help alleviate this problem of communicating with a workflow that might be currently persisted. The communication model is built on a system of queues: activities generally register to receive messages on a queue and services in the host application send messages on queues. Your custom activities can use this model both to handle external events as well as to communicate completion of asychronous activity execution. This allows your activity to execute up to a point, then wait for stimulus in order to continue executing. Take a look at Figure 7 which depicts the communication model between code in the host application and code or activities in the workflow.

Figure 7 Communicating with Activities Asynchronously

Figure 7** Communicating with Activities Asynchronously **

To allow your activity to listen for messages arriving on a queue, you need to first make sure the queue exists. If it does not exist, you must create it. This is usually done in the Initialize or Execute methods of the activity depending on when the queue needs to be available to receive messages. The WorkflowQueuingService provides the methods necessary to create, find, or delete workflow queues. Finally, your activity must register to receive these notifications by registering for the QueueItemAvailable event on the workflow queue itself. Once you have ensured that the queue exists and have registered for events, your activity will be notified when there are items available in the queue and you can then dequeue the item and process it.

When creating an asynchronous activity, you use the steps I've just described to prepare your activity to receive messages on the queue, and when your asynchronous activity completes, you enqueue a message to notify your activity and optionally send it the resulting data. Because the OnEvent method receives a reference to the ActivityExecutionContext by way of the Sender parameter, the activity can be closed by calling the CloseActivity method.

As an example, consider a simple ReadLine activity. In this example, the ReadLine activity would create a queue in its execute method and register for notification of data arriving on the queue-implementing the IActivityEventListener interface to receive said notification. The host application or a custom runtime service would retrieve user input from the console and enqueue it for the activity. An example ReadLine activity is included in the code for this article and uses this pattern as its implementation.

To create an activity that can act as an event sink-like the HandleExternalEvent activity-you also implement the IEvent­Activity interface. This interface defines the core responsibilities of your activity if it wants to listen for events:

public interface IEventActivity
{
    void Subscribe(ActivityExecutionContext parentContext,
        IActivityEventListener<QueueEventArgs> parentEventHandler);
    void Unsubscribe(ActivityExecutionContext parentContext, 
       IActivityEventListener<QueueEventArgs> parentEventHandler);

    IComparable QueueName { get; }
}

The QueueName property must return an IComparable value that can uniquely identify your activity when messages are enqueued. This same queue name will need to be used by the code which enqueues the message to the workflow runtime.

This interface allows the activity to be instructed to subscribe to events before it executes and lets the activity know when to unsubscribe. In the subscribe and unsubscribe methods, it is the responsibility of the activity to ensure that a queue is created using the QueueName and deleted at the end of processing. In addition, this is the opportunity for your activity to register information with any local services which will be executing logic on behalf of the activity and responding by enqueuing a message.

A local service is a class that you define and add to the workflow runtime from the host and which can be leveraged by either your host code, the workflow, or your activities. The local service can maintain event handlers or other listeners as long as the host application is running and then ensure that the appropriate data gets to the workflow by enqueuing the message. The information you pass the local service should include information about the queue name, the workflow instance ID, and any information the service requires to initiate work or return a result to your activity.

The sample code for this article includes a custom event activity that creates a queue and registers a handler to get notified when an event arrives on the queue. The host program simply enqueues an item after a short delay to show how items are enqueued; in a real scenario your local service would most likely be responsible for sending the data, but this shows that the host can also enqueue data for an activity.

The general pattern when working with event-based activities is to check for items on the queue in the Execute method and, if there is no data, then subscribe to items being available on the queue and return a status of executing. Then in the handler for items arriving, read the data off of the queue and optionally raise any events on your activity before closing the activity.

By implementing these interfaces your activity can now participate in the workflow in different ways. For example, in a state machine workflow your activity can now be the event that appears as the first activity in an event driven sequence, allowing your event to trigger a sequence of activities and potentially a transition to a new state. For a complete example of this type of activity, take a look at the FileWatcher activity in the Windows SDK.

Handling Errors

Another responsibility of the activity developer is the handling of error conditions. Error management in activities has many of the same qualities as any other code, but there are a few special considerations for managing the activity execution. When an error occurs in your activity execution logic, you should raise an exception, providing as much detail as necessary just as you would in any other component. This signals the runtime that an error has occurred and it can interact with activities in the workflow to allow them a chance to shut down cleanly or the runtime may invoke special handlers for errors.

An activity's HandleFault method is called anytime an exception is raised from your activity execution code. In the HandleFault method, you should write the code required to free up any resources or cancel any asynchronous operations that your activity may have initiated. Note that the exception will still get raised to the runtime; this method is strictly for allowing your activity to clean up after itself. Composite activities also support FaultHandlers, a special attached child which is executed as a post-processing convenience after the composite activity has transitioned from the faulting state to the closed state. You can use the FaultHandlers view to model the handling of faults that arise from child activities, so making your exceptions specific to the problem can allow for better error-handling code in the workflow.

The default behavior for the HandleFault method as defined in the Activity class is to call the Cancel method on the activity. This centralizes the cleaning up of resources but may not be appropriate in all cases as your activity may have specific resources to clean up depending on the type of exception raised. If you have no specific need to handle resources, perform other logging, or clean up, then the default behaviors will work for you in most cases.

Note that, in general, cancellation is used to implement semantics wherein a composite activity has spawned a number of children and in order to meet its desired semantics it needs to cancel the ongoing execution of some of its children. Cancellation is a positive thing and used to support forward execution, whereas faults are negative and are used to implement exceptional cases. Cancellation and fault handling are two completely different things, and the base class implementation of HandleFault calls the Cancel method simply to enable very basic scenarios.

Design Experience

Many activities are designed to be consumed in a model-based user interface such as the designer included with the Visual Studio Extensions for Windows Workflow Foundation. The design experience for activities is in many ways more important than the execution fundamentals since it is the most visible to other developers and consumers of your activity. Windows Workflow Foundation allows the visualization of workflows in many different scenarios, not just development, so it is important that your activity, not only convey the semantics of its responsibilities, but also that it seems logical and useful to users.

There are several components to the design-time experience, including appearance, interactivity, and validation. These design components are themselves .NET classes and are associated to the activity through custom attributes applied to the activity class. The first such class is the activity designer, which provides the core developer entry point for creating a rich design-time experience. The designer class allows you to participate in the design experience through interaction with the property browser, context menus, mouse clicks and movements, and the actual painting of the activity on the design surface.

You create a designer by creating a class derived from the ActivityDesigner base class or any number of composite activity designers available in the Windows Workflow Foundation libraries. Here's the SwitchActivityDesigner being applied to the Switch activity:

[Designer(typeof(SwitchActivityDesigner),
    typeof(IDesigner))]
public partial class SwitchActivity :
    System.Workflow.ComponentModel.CompositeActivity,
    IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
...

I will focus on several key features that are most often used in custom designers. The first of these features is the ability to provide custom context menu items on your activity to allow the user to initiate some action. By overriding the Verbs property in your designer, you can specify a collection of menu items that should be added to the context menu for your activity. This allows a user to right-click or otherwise bring up the context menu and initiate your action. For each verb that you add to the collection, you provide an event handler to be called when a user clicks the item in the menu. Additionally, you pass in a member of the DesignerVerbGroup enumeration indicating how you want the particular verb grouped in the context menu. This gives you the ability to group related activities by their purpose, such as editing or setting options. In the sample code for the Switch activity, a verb gets added that allows the user to reset all of the conditions on the child branches. Here's the code to add the verb to the context menu:

protected override ActivityDesignerVerbCollection Verbs
{
   get
   {
       ActivityDesignerVerbCollection newVerbs = 
            new ActivityDesignerVerbCollection();
        newVerbs.AddRange(base.Verbs);
       newVerbs.Add(new ActivityDesignerVerb(this, 
            DesignerVerbGroup.Edit, "Clear all conditions", 
           new EventHandler(ClearConditions)));
        return newVerbs;
   }
}

In addition to the Verbs property, you can also override the DesignerVerbs property, which allows you to add verbs to the error context menu to indicate problems with the current configuration. Finally, you can override the SmartTagVerbs property to add items to the smart tag that you will find under the names of most composite activities. This item is usually used only if you need to present an alternate view of your design surface similar to the cancellation and fault handler views.

Probably the most common design-time need for an activity developer is to be able to customize the appearance of an activity on the design surface. Windows Workflow Foundation provides several mechanisms for creating the appearance you want, from very simple options where you set only a few properties, to the most robust that allow you to paint the activity directly using a System.Drawing.Graphics object. I will start by explaining the simplest options, which will suffice in many situations.

The first point to understand about the appearance of a workflow activity is that it takes part in what is known as a designer theme. A theme is simply a definition of what each activity should look like in the designer. If you have the Visual Studio Extension for Win­dows Workflow Foundation installed and select Options from the Tools menu, you will see a category for the Workflow Designer that shows two installed themes. Click the New button and you can get sense of the properties available for each activity. You can save a theme and apply it in the designer in Visual Studio, but you can also use it if you rehost the workflow designer control in your own application. Using themes, you can completely change the workflow design surface and each activity hosted in the designer.

The default theme for the designer inspects an activity and allows it to provide theme information. In your activity development, you accomplish this by associating a custom DesignerTheme with your custom designer using an attribute. In the constructor for your designer theme, you can then set values for properties such as Back­Color­Start, BackColorEnd, ForeColor, and BackgroundStyle to change the coloring of the activity and its text. CreateUserActiv­ity­De­signerTheme provides an example of updating these values:

public class CreateUserActivityDesignerTheme : ActivityDesignerTheme
{
    public CreateUserActivityDesignerTheme(WorkflowTheme theme)
        : base(theme)
    {
        this.BackColorEnd = Color.BurlyWood;
        this.BackColorStart = Color.Bisque;
        this.BackgroundStyle = LinearGradientMode.ForwardDiagonal;
        this.ForeColor = Color.Black;
 }
}

In addition to the color and design, you can specify an icon to be drawn in the default position on the activity. The icon is connected to the activity using the ToolboxBitmap attribute and specifying an image file (a 16×16 bitmap or PNG file generally) that is built into the assembly as an embedded resource. In addition to being used in the designer, this same image is shown when the activity is added to the toolbox manually or by an installer, which makes sense given the name of the attribute. Figure 8 shows the CreateUser activity within a workflow showing the theme and icon support.

Figure 8 Custom Theme and Icon for an Activity

Figure 8** Custom Theme and Icon for an Activity **(Click the image for a larger view)

When creating designers for composite activities, in addition to dealing with issues of themes and icons, you may also have to draw connectors between your child activity designers or otherwise change the appearance of how you contain those children. However, there are several base classes that may provide the functionality needed to reduce the amount of code you have to write. For the Switch activity, for example, the designer derives from the ParallelActivityDesigner, which provides for drawing each of the child activities as branches and creates a designer verb to add a new branch. The Switch activity designer just has to override a method and return a new SwitchBranchActivity. This designer can prove useful for any composite activities you have with branches and is used internally by the IfElse, Parallel, and Listen activities.

The base class for the ParallelActivityDesigner class is the Struc­turedCompositeActivityDesigner class, which provides much of the rendering logic for connectors when your composite activity contains other designers. A sibling class to StructuredComposite­ActivityDesigner is FreeFormActivityDesigner. This designer is the basis for the StateMachine workflow designer and can be used when the child activities can be freely placed in your composite activity as opposed to structurally placed. Use one of these two designers as the basis for your own.

One final note: when you create a composite activity using the activity designer, the activity will show up on the design surface rendering all of its child activities. Thus, a consumer of your composite activity will see all of the internal activities in your implementation. This may be your intention, but if not you can overcome this by creating a designer for your composite activity that derives from the base ActivityDesigner class. This provides the simple rendering of a single activity. You can attach a designer theme and ToolboxBitmap attributes to further customize the look, providing true encapsulation.

Validation

When creating an activity, or any component for that matter, it is good practice to validate the input of the user and make sure your component is configured correctly. In fact, it would be even better if you could stop the user from incorrectly configuring your component in the first place. There are two key areas of development where you get the chance to make sure the activity is being configured correctly: in the designer and with a custom validator for your activity.

When creating a custom designer for your activity you control the activities that can be added to your composite activity, or the activities to which your activity can be added. It is important to note that this behavior only applies to the design environment and does not come into play if your activity is added through code.

Overriding the CanBeParentedTo method on the base Activity­De­signer class allows you to filter the types of activities your activity may be added to. If you return false from this method, users see a red slash, the international symbol for No, indicating that they cannot add your activity. The SwitchBranch activity, for example, will only allow itself to be parented to a SwitchActivity, ensuring that it is only run in the correct context. Overriding the CanInsertActivities method from the CompositeActivityDesigner class lets you control whether a particular activity or set of activities may be added to your composite activity. Using these two methods gives you the power to control the activity structure related to your activity. This becomes very important when you begin looking at the execution needs of your activity as it provides the opportunity to ensure you are set up for successful execution.

The other option for validating your activity is to create a custom validator. The validator class is responsible for inspecting your activity and making sure it is configured according to your requirements, and this validation occurs regardless of whether you are using a designer to add your activity into a workflow. By this point you can probably guess how to create a validator: you create a class that derives from ActivityValidator and then override the Validate method. You then use the ActivityValidatorAttribute to connect your validator to your activity.

The CreateUser activity has a property to hold the password. However, it's not a good idea to configure the password statically in the workflow. Instead, the validator component ensures that the password property is bound to some other value so that its value is set at runtime. Figure 9 shows the CreateUserValidator class testing the property to make sure it is bound.

Figure 9 CreateUserValidator

public override ValidationErrorCollection Validate(
    ValidationManager manager, object obj)
{
    ValidationErrorCollection baseErrors = base.Validate(manager, obj);

    CreateUserActivity user = obj as CreateUserActivity;
    if (user == null) throw new ArgumentException(
        "Activity must be a CreateUser activity");

    // make sure we are in a valid designer experience
    if (user.Parent != null &&
        !user.IsBindingSet(CreateUserActivity.PasswordProperty))
    {
        baseErrors.Add(new ValidationError(
            "Password must be set using activity binding",
            1, false, "Password"));
        baseErrors.Add(new ValidationError("Oops", 2, true));
    }
    return baseErrors;
}

The code first validates that the activity being validated is of the correct type and that it is being validated in a context other than its own compilation. If you do not check the parent property for null, then you will not be able to build your activity in the first place as the validator will run when your activity is compiled. The Valida­tion­ErrorCollection is used to pass back a collection of errors to the designer so that the appropriate error messages can be added to the user interface.

Conclusion

As you can see, Windows Workflow Foundation provides a rich environment for you to build reusable, easily designed activities for use in your workflows, whether they're leaf activities (key to implementing domain specific logic) or composite activities (key to creating control flow patterns reflective of real world interactions between entities). In fact, Windows Workflow Foundation does not treat any of its out-of-the-box activities any differently than the custom activities you write.

From the activity execution to the design experience, you have access to the same resources as the Microsoft team that built the base activity library. Building robust activities enables you to expose your applications and services to the processing model of Windows Workflow Foundation and to take advantage of all the services it has to offer. I encourage you to take advantage of the community Web site (wf.netfx3.com) to find activities and submit your own activities when you build them.?

Matt Milner is an independent software consultant specializing in Microsoft technologies including .NET, Web services, Windows Workflow Foundation, Windows Communication Foundation, and BizTalk Server. As an instructor for Pluralsight, Matt teaches courses on Workflow, BizTalk Server, and Windows Communication Foundation. Matt lives in Minnesota with his wife Kristen and his two sons.