Previous Page
Next Page

The WF Programming Model

Activities are the building blocks of WF programs. An activity in a WF program represents a resumable program statement. The lifecycle of an activityexplored in full in Chapters 3, "Activity Execution," and 4, "Advanced Activity Execution"centers around the fact that activity execution logic can exploit bookmarks. Bookmarks allow the execution of an activity (and, by extension, an entire WF program) to pause while awaiting stimulus, without holding a thread.

Activities

Listing 2.1 defines our first activity, Empty, which functions like an empty C# program statement (a lone semicolon).

Listing 2.1. Empty Activity

using System;
                        using System.Workflow.ComponentModel;
                        namespace EssentialWF.Activities
                        {
                        public class Empty : Activity
                        {
                        protected override ActivityExecutionStatus Execute(
                        ActivityExecutionContext context)
                        {
                        return ActivityExecutionStatus.Closed;
                        }
                        }
                        }
                        

All activities inherit from System.Workflow.ComponentModel.Activity. The Execute method is overridden to define an activity's execution logic. In the case of Empty, the purpose of which is to act like a nop MSIL instruction, the Execute method returns immediately with a value indicating that the execution of the activity is complete.

Listing 2.2 shows the Activity type, the base class for all activities. This listing does not show all of the members of Activity, only those that will be discussed in the current chapter; this is a practice we will generally follow throughout the book.

Listing 2.2. Activity Type

namespace System.Workflow.ComponentModel
                        {
                        public class Activity : DependencyObject
                        {
                        protected internal virtual ActivityExecutionStatus Execute(
                        ActivityExecutionContext context);
                        public event EventHandler<
                        ActivityExecutionStatusChangedEventArgs> Closed;
                        public bool Enabled { get; set; }
                        public string Name { get; set; }
                        public CompositeActivity Parent { get; }
                        /* *** other members *** */
                        }
                        }
                        

Listing 2.3 defines a more interesting activity, PrintKey, which represents the first program statement in the WF version of the Open, Sesame program that we introduced in Chapter 1.

Listing 2.3. PrintKey Activity

using System;
                        using System.Workflow.ComponentModel;
                        namespace EssentialWF.Activities
                        {
                        public class PrintKey : Activity
                        {
                        private string key;
                        public string Key
                        {
                        get { return key; }
                        }
                        protected override ActivityExecutionStatus Execute(
                        ActivityExecutionContext context)
                        {
                        key = DateTime.Now.Millisecond.ToString();
                        Console.WriteLine("here is your key: " + key);
                        return ActivityExecutionStatus.Closed;
                        }
                        }
                        }
                        

The PrintKey activity generates a key, saves the value of the key in its key field, writes the key to the console, and then indicates that its execution is complete. The Empty and PrintKey activities don't create bookmarks because their execution logic is not dependent upon stimulus from an external entity.

Listing 2.4 defines our first activity that does need to use a bookmark. We have called this activity ReadLine, though in fact this activity is not at all dependent upon the console.

Listing 2.4. ReadLine Activity

using System;
                        using System.Workflow.ComponentModel;
                        using System.Workflow.Runtime;
                        namespace EssentialWF.Activities
                        {
                        public class ReadLine : Activity
                        {
                        private string text;
                        public string Text
                        {
                        get { return text; }
                        }
                        protected override ActivityExecutionStatus Execute(
                        ActivityExecutionContext context)
                        {
                        WorkflowQueuingService qService =
                        context.GetService<WorkflowQueuingService>();
                        WorkflowQueue queue =
                        qService.CreateWorkflowQueue(this.Name, true);
                        queue.QueueItemAvailable += this.ContinueAt;
                        return ActivityExecutionStatus.Executing;
                        }
                        void ContinueAt(object sender, QueueEventArgs e)
                        {
                        ActivityExecutionContext context =
                        sender as ActivityExecutionContext;
                        WorkflowQueuingService qService =
                        context.GetService<WorkflowQueuingService>();
                        WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);
                        text = (string) queue.Dequeue();
                        qService.DeleteWorkflowQueue(this.Name);
                        context.CloseActivity();
                        }
                        }
                        }
                        

The execution logic of ReadLine uses a bookmark to wait for stimulus from an external entity. In WF, the data structure chosen to represent a bookmark's capacity to hold data is a queue. This queue, which we shall call a WF program queue, is created by ReadLine using the WorkflowQueuingService. The WorkflowQueuingService, shown in Listing 2.5, is obtained from the ActivityExecutionContext, which acts as a provider of services.

Listing 2.5. WorkflowQueuingService

namespace System.Workflow.Runtime
                        {
                        public class WorkflowQueuingService
                        {
                        //  queueName is the bookmark name
                        public WorkflowQueue CreateWorkflowQueue(
                        IComparable queueName, bool transactional);
                        public bool Exists(IComparable queueName);
                        public WorkflowQueue GetWorkflowQueue(IComparable queueName);
                        public void DeleteWorkflowQueue(IComparable queueName);
                        /* *** other members *** */
                        }
                        }
                        

The WorkflowQueue object that is returned by the CreateWorkflowQueue method offers an event, QueueItemAvailable. Despite the syntactic sugar of the C# event, this event represents the asynchronous delivery of stimulus from an external entity to an activity, and is exactly the same pattern of bookmark resumption presented in Chapter 1. The more refined WF version of the programming model for bookmarks allows a bookmark's payload (a WF program queue) to hold an ordered list of inputs that await processing (instead of a single object as did the bookmark in Chapter 1). The physical resumption point of the bookmark is still just a delegate (ContinueAt, in Listing 2.4) even though in the WF programming model the delegate is indicated using the += event subscription syntax of C#.

The WorkflowQueue type is shown in Listing 2.6.

Listing 2.6. WorkflowQueue

namespace System.Workflow.Runtime
                        {
                        public class WorkflowQueue
                        {
                        public event EventHandler<QueueEventArgs> QueueItemAvailable;
                        public object Dequeue();
                        public int Count { get; }
                        public IComparable QueueName { get; }
                        /* *** other members *** */
                        }
                        }
                        

As seen in Listing 2.4, the return value of the ReadLine activity's Execute method indicates that, at that point in time, the ReadLine has pending bookmarks; its execution is not complete. When an item is enqueued in its WF program queue, perhaps days after the ReadLine began its execution, the bookmark is resumed and, as a result, the ContinueAt method is invoked. After obtaining the item from its queue and setting the value of its text field, the ReadLine activity reports its completion.

The signature of ContinueAt in Listing 2.4 has a return type of void because this method is a System.EventHandler. Therefore, in contrast to the Execute method of PrintKey, an explicit call to the ActivityExecutionContext.CloseActivity method must be made by ReadLine in order to tell the WF runtime that its execution is complete.

Composite Activities

An activity that contains other activities is called a composite activity.

System.Workflow.ComponentModel.CompositeActivity is the base class for all composite activities, and is shown in Listing 2.7.

Listing 2.7. CompositeActivity

namespace System.Workflow.ComponentModel
                        {
                        public class CompositeActivity : Activity
                        {
                        public ActivityCollection Activities { get; }
                        public ReadOnlyCollection<Activity> EnabledActivities { get; }
                        /* *** other members *** */
                        }
                        }
                        

CompositeActivity defines a property called Activities that holds the list of activities contained by the composite activity. The items in this list are called the composite activity's child activities. The Activities property is of type ActivityCollection, which is just a specialized IList<Activity> that is defined in the System.Workflow.ComponentModel namespace.

As shown in Listing 2.2, Activity defines a property called Enabled. If the value of this property is false (the default value is true), then the activity is disabled and is considered commented out of the WF program in which it has been declared, much like a program statement can be commented out of a C# program like this:

// Console.WriteLine("hello, world");
            

If the activity that is disabled is a composite activity, then it and all of its child activities (no matter the values of their Enabled properties) are also disabled.

CompositeActivity defines a property called EnabledActivities that is a read-only list of those activities in its Activities collection that are enabled.

It is not possible in a WF program for an activity to be a child activity of more than one composite activity. Activity defines a readonly property called Parent that is a reference to its parent activitythe composite activity that contains it.

The Parent property will be null for exactly one activity in a WF program. Thus, a WF program has the shape of a tree (it is a hierarchical arrangement of activities) and the activity whose Parent property is null is called the root activity of the tree.

The root activity is the entry point of the WF program; its Execute method is akin to the Main method of a C# program.

The purpose of a composite activity is to provide control flow for WF programs. Composite activities manage the execution of their child activities, and rely upon bookmarks to be informed when child activities complete their execution.

Listing 2.8 defines our first composite activity, Sequence, which executes its child activities one by one sequentially (just like ProgramStatementBlock in Chapter 1).

Listing 2.8. Sequence Activity

using System;
                        using System.Workflow.ComponentModel;
                        namespace EssentialWF.Activities
                        {
                        public class Sequence : CompositeActivity
                        {
                        protected override ActivityExecutionStatus Execute(
                        ActivityExecutionContext context)
                        {
                        if (this.EnabledActivities.Count == 0)
                        return ActivityExecutionStatus.Closed;
                        Activity child = this.EnabledActivities[0];
                        child.Closed += this.ContinueAt;
                        context.ExecuteActivity(child);
                        return ActivityExecutionStatus.Executing;
                        }
                        void ContinueAt(object sender,
                        ActivityExecutionStatusChangedEventArgs e)
                        {
                        ActivityExecutionContext context =
                        sender as ActivityExecutionContext;
                        e.Activity.Closed -= this.ContinueAt;
                        int index = this.EnabledActivities.IndexOf(e.Activity);
                        if ((index + 1) == this.EnabledActivities.Count)
                        context.CloseActivity();
                        else
                        {
                        Activity child = this.EnabledActivities[index + 1];
                        child.Closed += this.ContinueAt;
                        context.ExecuteActivity(child);
                        }
                        }
                        }
                        }
                        

The Sequence activity executes its list of enabled child activities in a linear order, and therefore provides the same linear control flow as a { } statement block that demarcates a list of program statements within a C# program.

Sequence cannot directly execute its child activities since the Activity.Execute method (see Listing 2.2) has accessibility of protected internal. Instead, Sequence requests the execution of a child activity via ActivityExecutionContext.

Sequence subscribes to the Activity.Closed event (defined in Listing 2.2) before it requests the execution of a child activity. When the child activity completes its execution, the execution of the Sequence is resumed at the ContinueAt method. The Sequence activity's subscription to the Closed event of a child activity is syntactic sugar for the creation of a bookmark that is managed internally, on behalf of Sequence, by the WF runtime.

The ActivityExecutionContext type is effectively an activity-facing abstraction on top of the WF runtime, and is shown in Listing 2.9.

Listing 2.9. ActivityExecutionContext

namespace System.Workflow.ComponentModel
                        {
                        public class ActivityExecutionContext : System.IServiceProvider
                        {
                        public void ExecuteActivity(Activity activity);
                        public void CloseActivity();
                        public T GetService<T>();
                        public object GetService(Type serviceType);
                        /* *** other members *** */
                        }
                        }
                        

ActivityExecutionContext will be discussed in great detail in Chapters 3 and 4 because it plays a substantial role in the realization of the automaton that governs the lifecycle of all activities.

WF Programs

A WF program is a hierarchy of activities. To be precise, a WF programas managed by the WF runtimeis actually just an object whose type is a derivative of Activity and whose Parent property is null (the object is a root activity). If this object is a composite activity, as is typically the case, then it may contain other activities.

Before going further, we need to clarify some terminology. When we use the term activity we usually mean activity declarationthe declaration of a program statement within a WF program. Listing 2.10 shows a WF program that has one declaration of a Sequence and two PrintKey declarations.

Listing 2.10. A Simple WF Program Represented as XAML

<Sequence xmlns="http://EssentialWF/Activities">
                        <PrintKey />
                        <PrintKey />
                        </Sequence>
                        

It is natural to describe the program in Listing 2.10 as a Sequence activity that contains two PrintKey activities (instead of as a Sequence activity declaration containing two PrintKey activity declarations), and we will follow this convention. As a consequence, we will refer to a type that derives from Activity (for example, the PrintKey class) as an activity type.

It is possible to write and run a WF program that consists of a single activity declaration, like this:

<PrintKey xmlns="http://EssentialWF/Activities">
            

The program in Listing 2.10, and the single-activity PrintKey program, are represented using XAML. The choice of XAML is just thatone option among many for representing our WF programs. When it comes time to run a WF program, we can hand it over to the WF runtime as XAML or in any other format, provided that a hierarchy of Activity objects can be materialized from that format. XAML is a pragmatic choice for the format of our program because the WF runtime supports loading from XAML by default. In Chapter 5, "Applications," we will see how custom formats are accommodated by the WF runtime.

The XAML in Listing 2.10 contains an XML namespace definition that helps locate the Sequence and PrintKey types. This is similar to a using statement in C#.

To map the XML namespace "http://EssentialWF/Activities" to a CLR namespace, the assembly containing the Sequence and PrintKey types must carry a special attribute:

using System.Workflow.ComponentModel.Serialization;
            [assembly: XmlnsDefinition("http://EssentialWF/Activities",
            "EssentialWF.Activities")]
            

The use of XmlnsDefinition is not required, but without it we must employ a different way of mapping element names in XAML to CLR type names; this approach is discussed in Chapter 7, "Advanced Authoring." Throughout this book, we assume that all example activities are defined in the EssentialWF.Activities CLR namespace and reside in an assembly, EssentialWF.dll, that is decorated with the XmlnsDefinition attribute as shown previously.

XAML is a language for specifying the initialization of objects. You can think of the XAML in Listing 2.10 as the equivalent of the following C# code (the using statement required to identify the CLR namespace for the Sequence and PrintKey types is omitted):

Sequence s = new Sequence();
            s.Activities.Add(new PrintKey());
            s.Activities.Add(new PrintKey());
            

The WF program in Listing 2.10 just prints a couple of keys to the console. Listing 2.11 shows a WF program that is more interestingit is the familiar Open, Sesame program.

Listing 2.11. XAML-Based Open, Sesame Program

<Sequence x:Name="OpenSesame" xmlns="http://EssentialWF/Activities"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
                        <PrintKey x:Name="pk1" />
                        <ReadLine x:Name="r1" />
                        <PrintGreeting x:Name="pg1" Key="{wf:ActivityBind pk1,Path=Key}"
                        Input="{wf:ActivityBind r1,Path=Text}" />
                        </Sequence>
                        

The Key property of PrintGreeting is bound to the Key property of PrintKey, and the Input property of PrintGreeting is bound to the Text property of ReadLine. In order to make this work, we must implement a PrintGreeting activity type that supports activity databindingthe declarative flow of data from one activity to another.

The PrintGreeting activity type is shown in Listing 2.12.

Listing 2.12. PrintGreeting Activity That Supports Databinding

using System;
                        using System.Workflow.ComponentModel;
                        namespace EssentialWF.Activities
                        {
                        public class PrintGreeting : Activity
                        {
                        public static readonly DependencyProperty KeyProperty
                        = DependencyProperty.Register("Key",
                        typeof(string), typeof(PrintGreeting));
                        public static readonly DependencyProperty InputProperty
                        = DependencyProperty.Register("Input",
                        typeof(string), typeof(PrintGreeting));
                        public string Key
                        {
                        get { return (string) GetValue(KeyProperty); }
                        set { SetValue(KeyProperty, value); }
                        }
                        public string Input
                        {
                        get { return (string) GetValue(InputProperty); }
                        set { SetValue(InputProperty, value); }
                        }
                        protected override ActivityExecutionStatus Execute(
                        ActivityExecutionContext context)
                        {
                        if (Key.Equals(Input))
                        Console.WriteLine("hello, world");
                        return ActivityExecutionStatus.Closed;
                        }
                        }
                        }
                        

The GetValue and SetValue methods called in the implementations of the Key and Input properties are inherited from DependencyObject, which is the type from which Activity derives (see Listing 2.2). The details of activity databindingincluding complete discussion of the DependencyObject, DependencyProperty, and ActivityBind types upon which this feature restsare covered in Chapter 7.

As you can see in Listing 2.12, activity databinding relies upon the fact that activities in a WF program are named: Activity defines a property of type string called Name (see Listing 2.2). Activity.Name is a metadata property, which means that its value is immutable at runtime. Activity.Enabled and CompositeActivity. Activities are also metadata properties; they are given values by the developer of a WF program but cannot be changed during the execution of instances of that program. The management of WF program metadata (the values of all metadata properties of all activities in a program) by the WF runtime is discussed in Chapter 5; the implementation of metadata properties by activities is discussed in Chapter 7.

XAML defines a special XML namespace for its core constructs. This namespace is "http://schemas.microsoft.com/winfx/2006/xaml" and by convention the prefix "x" is employed in its XML namespace definition. You will typically find the following namespace definition in the root element of a XAML document (such as in Listing 2.12) that describes a WF program:

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            

The WF programming model also defines a special XML namespace. This namespace is "http://schemas.microsoft.com/winfx/2006/xaml/workflow" and it is mapped to CLR types that are defined in the System.Workflow namespaces. This namespace is by convention either specified as the default XML namespace in a XAML document containing a WF program, or using the prefix "wf" as shown here:

xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow"
            

In this book, we favor the use of the "wf" prefix because it allows us to use the default XML namespace for our custom activity types. All activity types defined as examples in this book reside in the "EssentialWF.Activities" CLR namespace that is mapped to the XML namespace "http://EssentialWF/Activities" using the XmlnsDefinition attribute.


Previous Page
Next Page