Sunteți pe pagina 1din 43

A Developer's Introduction to Windows

Workflow Foundation (WF) in .NET 4


99 out of 125 rated this helpful
Matt Milner, Pluralsight
November 2009
Updated to release: April 2010
Overview
As software developers know, writing applications can be challenging, and we are constantly
looking for tools and frameworks to simplify the process and help us focus on the business
challenges we are trying to solve. We have moved from writing code in machine languages such
as assembler to higher level languages like C# and Visual Basic that ease our development,
remove lower level concerns such as memory management, and increase our productivity as
developers. For Microsoft developers, the move to .NET allows the Common Language
Runtime (CLR) to allocate memory, cleanup unneeded objects and handle low level constructs
like pointers.
Much of the complexity of an application lives in the logic and processing that goes on behind
the scenes. Issues such as asynchronous or parallel execution and generally coordinating the
tasks to respond to user requests or service requests can quickly lead application developers back
down into the low level coding of handles, callbacks, synchronization etc. As developers, we
need the same power and flexibility of a declarative programming model for the internals of an
application as we have for the user interface in Windows Presentation Foundation (WPF).
Windows Workflow Foundation (WF) provides the declarative framework for building
application and service logic and gives developers a higher level language for handling
asynchronous, parallel tasks and other complex processing.
Having a runtime to manage memory and objects has freed us to focus more on the important
business aspects of writing code. Likewise, having a runtime that can manage the complexities
of coordinating asynchronous work provides a set of features that improves developer
productivity. WF is a set of tools for declaring your workflow (your business logic), activities to
help define the logic and control flow, and a runtime for executing the resulting application
definition. In short, WF is about using a higher level language for writing applications, with the
goal of making developers more productive, applications easier to manage, and change quicker
to implement. The WF runtime not only executes your workflows for you, it also provides
services and features important when writing application logic such persistence of state,
bookmarking and resumption of business logic, all of which lead to thread and process agility
enabling scale up and scale out of business processes.
For more conceptual information on how using WF to build your applications can make you
more productive, I recommend you read "The Workflow Way" by David Chappell, found in the
Additional Resources section.
Whats New in WF4
In version 4 of the Microsoft .NET Framework, Windows Workflow Foundation introduces a
significant amount of change from the previous versions of the technology that shipped as part of
.NET 3.0 and 3.5. In fact, the team revisited the core of the programming model, runtime and
tooling and has re-architected each one to increase performance and productivity as well as to
address the important feedback garnered from customer engagements using the previous
versions. The significant changes made were necessary to provide the best experience for
developers adopting WF and to enable WF to continue to be a strong foundational component
that you can build on in your applications. I will introduce the high level changes here, and
throughout the paper each topic will get more in depth treatment.
Before I continue, it is important to understand that backwards compatibility was also a key goal
in this release. The new framework components are found primarily in the System.Activities.*
assemblies while the backwards compatible framework components are found in the
System.Workflow.* assemblies. The System.Workflow.* assemblies are part of the .NET
Framework 4 and provide complete backward compatibility so you can migrate your application
to .NET 4 with no changes to your workflow code. Throughout this paper I will use the name
WF4 to refer to the new components found in the System.Activities.* assemblies and WF3 to
refer to the components found in the System.Workflow.* assemblies.
Designers
One of the most visible areas of improvement is in the workflow designer. Usability and
performance were key goals for the team for the VS 2010 release. The designer now supports
the ability to work with much larger workflows without a degradation in performance and
designers are all based on Windows Presentation Foundation (WPF), taking full advantage of the
rich user experience one can build with the declarative UI framework. Activity developers will
use XAML to define the way their activities look and interact with users in a visual design
environment. In addition, rehosting the workflow designer in your own applications to enable
non-developers to view and interact with your workflows is now much easier.
Data Flow
In WF3, the flow of data in a workflow was opaque. WF4 provides a clear, concise model for
data flow and scoping in the use of arguments and variables. These concepts, familiar to all
developers, simplify both the definition of data storage, as well as the flow of the data into and
out of workflows and activities. The data flow model also makes more obvious the expected
inputs and outputs of a given activity and improves performance of the runtime as data is more
easily managed.
Flowchart
A new control flow activity called Flowchart has been added to make it possible for developers
to use the Flowchart model to define a workflow. The Flowchart more closely resembles the
concepts and thought processes that many analysts and developers go through when creating
solutions or designing business processes. Therefore, it made sense to provide an activity to
make it easy to model the conceptual thinking and planning that had already been done. The
Flowchart enables concepts such as returning to previous steps and splitting logic based on a
single condition, or a Switch / Case logic.
Programming Model
The WF programming model has been revamped to make it both simpler and more robust.
Activity is the core base type in the programming model and represents both workflows and
activities. In addition, you no longer need to create a WorkflowRuntime to invoke a workflow,
you can simply create an instance and execute it, simplifying unit testing and application
scenarios where you do not want to go through the trouble of setting up a specific environment.
Finally, the workflow programming model becomes a fully declarative composition of activities,
with no code-beside, simplifying workflow authoring.
Windows Communication Foundation (WCF) Integration
The benefits of WF most certainly apply to both creating services and consuming or coordinating
service interactions. A great deal of effort went into enhancing the integration between WCF
and WF. New messaging activities, message correlation, and improved hosting support, along
with fully declarative service definition are the major areas of improvement.
Getting Started with Workflow
The best way to understand WF is to start using it and applying the concepts. I will cover several
core concepts around the underpinnings of workflow, and then walk through creating a few
simple workflows to illustrate how those concepts relate to each other.
Workflow Structure
Activities are the building blocks of WF and all activities ultimately derive from Activity. A
terminology note - Activities are a unit of work in WF. Activities can be composed together into
larger Activities. When an Activity is used as a top-level entry point, it is called a "Workflow",
just like Main is simply another function that represents a top level entry point to CLR
programs. For example, Figure 1 shows a simple workflow being built in code.
Sequence s = new Sequence
{
Activities = {
new WriteLine {Text = "Hello"},
new Sequence {
Activities =
{
new WriteLine {Text = "Workflow"},
new WriteLine {Text = "World"}
}
}
}
};
Figure 1: A simple workflow
Notice in Figure 1 that the Sequence activity is used as the root activity to define the root control
flow style for the workflow. Any activity can be used as the root or workflow and executed, even
a simple WriteLine. By setting the Activities property on the Sequence with a collection of
other activities, I have defined the workflow structure. Further, the child activities can have
child activities, creating a tree of activities that make up the overall workflow definition.
Workflow Templates and the Workflow Designer
WF4 ships with many activities and Visual Studio 2010 includes a template for defining
activities. The two most common activities used for the root control flow are Sequence and
Flowchart. While any Activity can be executed as a workflow, these two provide the most
common design patterns for defining business logic. Figure 2 shows an example of a sequential
workflow model defining the processing of an order being received, saved and then notifications
sent to other services.

Figure 2: Sequential workflow design
The Flowchart workflow type is being introduced in WF4 to address common requests from
existing users such as being able to return to previous steps in a workflow and because it more
closely resembles the conceptual design done by analysts and developers working on defining
business logic. For example, consider a scenario involving user input to your application. In
response to the data provided by the user, your program should either continue in the process or
return to a previous step to prompt for the input again. With a sequential workflow, this would
involve something similar to what is shown in Figure 3 where a DoWhile activity is used to
continue processing until some condition is met.

Figure 3: Sequential for decision branching
The design in Figure 3 works, but as a developer or analyst looking at the workflow, the model
does not represent the original logic or requirements that were described. The Flowchart
workflow in Figure 4 gives a similar technical outcome to the sequential model used in Figure 3,
but the design of the workflow more closely matches the thinking and requirements as originally
defined.

Figure 4: Flowchart workflow
Data flow in workflows
The first thought most people have when they think about workflow is the business process or
the flow of the application. However, just as critical as the flow is the data that drives the
process and the information that is collected and stored during the execution of the workflow. In
WF4, the storage and management of data has been a prime area of design consideration.
There are three main concepts to understand with regard to data: Variables, Arguments, and
Expressions. The simple definitions for each are that variables are for storing data, arguments
are for passing data, and expressions are for manipulating data.
Variables storing data
Variables in workflows are very much like the variables you are used to in imperative languages:
they describe a named location for data to be stored and they follow certain scoping rules. To
create a variable, you first determine at what scope the variable needs to be available. Just as
you might have variables in code that are available at the class or method level, your workflow
variables can be defined at different scopes. Consider the workflow in Figure 5. In this
example, you can define a variable at the root level of the workflow, or at the scope defined by
the Collect Feed Data sequence activity.

Figure 5: Variables scoped to activities
Arguments passing data
Arguments are defined on activities and define the flow of data into and out of the activity. You
can think of arguments to activities much like you use arguments to methods in imperative code.
Arguments can be In, Out, or In/Out and have a name and type. When building a workflow, you
can define arguments on the root activity which enables data to be passed into your workflow
when it is invoked. You define arguments on the workflow much as you do variables using the
argument window.
As you add activities to your workflow, you will need to configure the arguments for the
activities, and this is primarily done by referencing in-scope variables or using expressions,
which I will discuss next. In fact, the Argument base class contains an Expression property
which is an Activity that returns a value of the argument type, so all of these options are related
and rely on Activity.
When using the workflow designer to edit arguments you can type expressions representing
literal values into the property grid, or use variable names to reference an in-scope variable as
shown in Figure 6, where emailResult and emailAddress are variables defined in the workflow.

Figure 6: Configuring arguments on activities
Expressions acting on data
Expressions are activities you can use in your workflow to operate on data. Expressions can be
used in places where you use Activity and are interested in a return value, which means you can
use expressions in setting arguments or to define conditions on activities like the While or If
activities. Remember that most things in WF4 derive from Activity and expressions are no
different, they are a derivative of Activity<TResult> meaning they return a value of a specific
type. In addition, WF4 includes several common expressions for referring to variables and
arguments as well as Visual Basic expressions. Because of this layer of specialized expressions,
the expressions you use to define arguments can include code, literal values, and variable
references. Table 1 provides a small sampling of the types of expressions you might use when
defining arguments using the workflow designer. When creating expressions in code there are
several more options including the use of lambda expressions.
Expression Expression Type
"hello world" Literal string value
10 Literal Int32 value
System.String.Concat("hello", " ", "world") Imperative method invocation
"hello " & "world" Visual Basic expression
argInputString Argument reference (argument name)
varResult Variable reference (variable name)
"hello: " & argInputString Literals and arguments/variables mixed
Table 1: Example expressions
Building your first workflow
Now that I have covered the core concepts around Activity and data flow, I can create a
workflow using these concepts. I will start with a simple hello world workflow to focus on the
concepts rather than the true value proposition of WF. To start, create a new Unit Test project in
Visual Studio 2010. In order to use the core of WF, add a reference to the System.Activities
assembly, and add using statements for System.Activities, System.Activities.Statements, and
System.IO in the test class file. Then add a test method as shown in Figure 7 to create a basic
workflow and execute it.
[TestMethod]
public void TestHelloWorldStatic()
{
StringWriter writer = new StringWriter();
Console.SetOut(writer);
Sequence wf = new Sequence
{
Activities = {
new WriteLine {Text = "Hello"},
new WriteLine {Text = "World"}
}
};
WorkflowInvoker.Invoke(wf);
Assert.IsTrue(String.Compare(
"Hello\r\nWorld\r\n",
writer.GetStringBuilder().ToString()) == 0,
"Incorrect string written");
}
Figure 7: Creating hello world in code
The Text property on the WriteLine activity is an InArgument<string> and in this example I
passed a literal value to that property.
The next step is to update this workflow to use variables and pass those variables to the activity
arguments. Figure 8 shows the new test updated to use the variables.
[TestMethod]
public void TestHelloWorldVariables()
{
StringWriter writer = new StringWriter();
Console.SetOut(writer);
Sequence wf = new Sequence
{
Variables = {
new Variable<string>{Default = "Hello", Name = "greeting"},
new Variable<string> { Default = "Bill", Name = "name" } },
Activities = {
new WriteLine { Text = new VisualBasicValue<string>("greeting"),
new WriteLine { Text = new VisualBasicValue<string>(
"name + \"Gates\"")}
}
};
WorkflowInvoker.Invoke(wf);
}
Figure 8: Code workflow with variables
In this case, the variables are defined as type Variable<string> and given a default value. The
variables are declared within the Sequence activity and then referenced from the Text argument
of the two activities. Alternatively, I could use expressions to initialize the variables as shown in
Figure 9 where the VisualBasicValue<TResult> class is used passing in a string representing the
expression. In the first case, the expression refers to the name of the variable, and in the second
case the variable is concatenated with a literal value. The syntax used in textual expressions is
Visual Basic, even when writing code in C#.
Activities = {
new WriteLine { Text = new VisualBasicValue<string>("greeting"),
TextWriter = writer },
new WriteLine { Text = new VisualBasicValue<string>("name + \"Gates\""),
TextWriter = writer }
}
Figure 9: Using expressions to define arguments
While code examples help illustrate important points and generally feel comfortable to
developers, most people will use the designer to create workflows. In the designer, you drag
activities onto the design surface and use an updated but familiar property grid to set arguments.
In addition, the workflow designer contains expandable regions at the bottom to edit arguments
for the workflow and variables. To create a similar workflow in the designer, add a new project
to the solution, choosing the Activity Library template, and then add a new Activity.
When the workflow designer is first displayed, it does not contain any activities. The first step in
defining the activity is to choose the root activity. For this example, add a Flowchart activity to
the designer by dragging it from the Flowchart category in the toolbox. In the Flowchart
designer, drag two WriteLine activities from the toolbox and add them one below the other to the
Flowchart. Now you have to connect the activities together so the Flowchart knows the path to
follow. You do this by first hovering over the green "start" circle at the top of the Flowchart to
see the grab handles, then click and drag one to the first WriteLine and drop it on the drag handle
that appears at the top of the activity. Do the same to connect the first WriteLine to the second
WriteLine. The design surface should look like Figure 10.

Figure 10: Hello Flowchart layout
Once the structure is in place, you need to configure some variables. By clicking on the design
surface and then clicking on the Variables button near the bottom of the designer, you can edit
the collection of variables for the Flowchart. Add two variables called "greeting" and "name" to
list and set the default values to "Hello" and "Bill" respectively be sure to include the quotes
when setting the values as this is an expression so literal strings need to be quoted. Figure 11
shows the variables window configured with the two variables and their default values.

Figure 11: Variables defined in workflow designer
To use these variables in the WriteLine activities, enter "greeting" (without the quotes) in the
property grid for the Text argument of the first activity and "name" (again without the quotes) for
the Text argument on the second WriteLine. At runtime, when the Text arguments are
evaluated, the value in the variable will be resolved and used by the WriteLine activity.
In the test project, add a reference to the project containing your workflow. Then you can add a
test method like that shown in Figure 12 to invoke this workflow and test the output. In Figure
12, you can see the workflow is being created by instantiating an instance of the created class. In
this case the workflow was defined in the designer and compiled into a class deriving from
Activity which can then be invoked.
[TestMethod]
public void TestHelloFlowChart()
{
StringWriter tWriter = new StringWriter();
Console.SetOut(tWriter);
Workflows.HelloFlow wf = new Workflows.HelloFlow();
WorkflowInvoker.Invoke(wf);
//Asserts omitted
}
Figure 12: Testing the Flowchart
So far I have been using variables in the workflow, and using them to supply values to arguments
on the WriteLine activities. The workflow can also have arguments defined which allows you to
pass data into the workflow when invoking it and to receive output when the workflow
completes. To update the Flowchart from the previous example, remove the "name" variable
(select it and press the Delete key) and instead create a "name" Argument of type string. You do
this in much the same way, except you use the Arguments button to view the arguments editor.
Notice that the arguments can also have a direction and you do not need to supply a default value
as the value will be passed into the activity at runtime. Because you are using the same name for
the argument as you did for the variable, the WriteLine activities Text arguments remain valid.
Now at runtime, those arguments will evaluate and resolve the value of the "name" argument on
the workflow and use that value. Add an additional argument of type string with a direction of
Out and a name of "fullGreeting"; this will be returned to the calling code.

Figure 13: Defining arguments for the workflow
In the Flowchart, add an Assign activity and connect it to the last WriteLine activity. For the To
argument, enter "fullGreeting" (no quotes) and for the Value argument, enter "greeting & name"
(without quotes). This will assign the concatenation of the greeting variable with the name
argument to the fullGreeting output argument.
Now in the unit test, update the code to supply the argument when invoking the workflow.
Arguments are passed to the workflow as a Dictionary<string,object> where the key is the name
of the argument. You can do this simply by changing the call to invoke the workflow as shown
in Figure 14. Notice that the output arguments are also contained in a Dictionary<string, object>
collection keyed on the argument name.
IDictionary<string, object> results = WorkflowInvoker.Invoke(wf,
new Dictionary<string,object> {
{"name", "Bill" } }
);
string outValue = results["fullGreeting"].ToString();
Figure 14: Passing arguments to a workflow
Now that you have seen the basics of how to put together activities, variables and arguments, I
will guide you on a tour of the activities included in the framework to enable more interesting
workflows focused on business logic instead of low level concepts.
Tour of the workflow activity palette
With any programming language, you expect to have core constructs for defining your
application logic. When using a higher level development framework like WF, that expectation
does not change. Just as you have statements in .NET languages such as If/Else, Switch and
While, for managing control flow you also need those same capabilities when defining your
logic in a declarative workflow. These capabilities come in the form of the base activity library
that ships with the framework. In this section, I will give you a quick tour of the activities that
ship with the framework to give you an idea of the functionality provided out of the box.
Activity Primitives and Collection Activities
When moving to a declarative programming model, it is easy to begin wondering how to do
common object manipulation tasks that are second nature when writing code. For those tasks
where you find yourself working with objects and needing to set properties, invoke commands or
manage a collection of items, there are a set of activities designed specifically with those tasks in
mind.
Activity Description
Assign Assigns a value to a location enabling setting variables.
Delay Delays the path of execution for a specified amount of time.
InvokeMethod Invokes a method on a .NET object or static method on a .NET type, optionally with a return type of T.
WriteLine Writes specified text to a text writer defaults to Console.Out
AddToCollection<T> Adds an item to a typed collection.
RemoveFromCollection<T> Removes an item from a typed collection.
ClearCollection<T> Removes all items from a collection.
ExistsInCollection<T> Returns a Boolean value indicating if the specified item exists in the collection.

Control flow activities
When defining business logic or business processes, having control of the flow of execution is
critical. Control flow activities include basics such as Sequence which provides a common
container when you need to execute steps in order, and common branching logic such as the If
and Switch activities. The control flow activities also include looping logic based on data
(ForEach) and Conditions(While). Most important for simplifying complex programming are
the parallel activities, which all enable multiple asynchronous activities to get work done at the
same time.
Activity Description
Sequence For executing activities in series
While/DoWhile Executes a child activity while a condition (expression) is true
ForEach<T> Iterates over an enumerable collection and executes the child activity once for each item in the collection, waiting for the child to complete before starting the next iteration. Provides typed access
to the individual item driving the iteration in the form of a named argument.
If Executes one of two child activities depending on the result of the condition (expression).
Switch<T> Evaluates an expression and schedules the child activity with a matching key.
Parallel Schedules all child activities at once, but also provides a completion condition to enable the activity to cancel any outstanding child activities if certain conditions are met.
ParallelForEach<T> Iterates over an enumerable collection and executes the child activity once for each item in the collection, scheduling all instances at the same time. Like the ForEach<T>, this activity provides
access to the current data item in the form of a named argument.
Pick Schedules all child PickBranch activities and cancels all but the first to have its trigger complete. The PickBranch activity has both a Trigger and an Action; each is an Activity. When a trigger
activity completes, the Pick cancels all its other child activities.
The two examples below show several of these activities in use to illustrate how to compose
these activities together. The first example, Figure 15, includes the use of the
ParallelForEach<T> to use a list of URLs and asynchronously get the RSS feed at the specified
address. After the feed is returned, the ForEach<T> is used to iterate over the feed items and
process them.
Note that the code declares and defines a VisualBasicSettings instance with references to the
System.ServiceModel.Syndication types. This settings object is then used when declaring
VisualBasicValue<T> instances referencing variables types from that namespace to enable type
resolution for those expressions. Also of note is that the body of the ParallelForEach activity
takes an ActivityAction which are mentioned in the section on creating custom activities. These
actions use DelegateInArgument and DelegateOutArgument in much the same way that activities
use InArgument and OutArgument.

Figure 15: Control flow activities
The second example, Figure 16, is a common pattern of waiting for a response with a timeout.
For example, waiting for a manager to approve a request, and sending a reminder if the response
has not arrived in the allotted time. A DoWhile activity causes the repetition of waiting for a
response while the Pick activity is used to execute both a ManagerResponse activity and a Delay
activity at the same time as triggers. When the Delay completes first, the reminder is sent, and
when the ManagerResponse activity completes first, the flag is set to break out of the DoWhile
loop.

Figure 16: Pick and DoWhile activities
The example in Figure 16 also shows how bookmarks can be used in workflows. A bookmark is
created by an activity to mark a place in the workflow, so that processing can resume from that
point at a later time. The Delay activity has a bookmark which is resumed after a particular
amount of time has passed. The ManagerResponse activity is a custom activity which waits for
input and resumes the workflow once the data arrives. The messaging activities, discussed
shortly, are the main activities for bookmarking execution. When a workflow is not actively
processing work, when it is only waiting for bookmarks to be resumed, it is considered idle and
can be persisted to a durable store. Bookmarks will be discussed in more detail in the section
about creating custom activities.
Migration
For developers who are using WF3, the Interop activity can play a vital role in reusing existing
assets. The activity, found in the System.Workflow.Runtime assembly, wraps your existing
activity type and surfaces the properties on the activity as arguments in the WF4 model. Because
the properties are arguments, you can use expressions to define the values. Figure 17 shows the
configuration of the Interop activity to call a WF3 activity. The input arguments are defined
with references to in-scope variables within the workflow definition. Activities built in WF3
had properties instead of arguments, so each property is given a corresponding input and output
argument allowing you to differentiate the data you send into the activity and the data you expect
to retrieve after the activity executes. In a new WF4 project you will not find this activity in the
toolbox because the target framework is set to .NET Framework 4 Client Profile. Change the
target framework in the project properties to .NET Framework 4, and the activity will appear in
the toolbox.

Figure 17: Interop activity configuration
Flowchart
When designing Flowchart workflows there are several constructs that can be used to manage the
flow of execution within the Flowchart. These constructs themselves provide simple steps,
simple decision points based on a single condition, or a switch statement. The real power of the
Flowchart is the ability to connect these node types into the desired flow.
Construct/Activity Description
Flowchart The container for a series of flow steps, each flow step can be any activity or one of the following constructs, but to be executed, it must be connected within the Flowchart.
FlowDecision Provides branching logic based on a condition.
FlowSwitch<T> Enables multiple branches based on the value of an expression.
FlowStep Represents a step in the Flowchart with the ability to be connected to other steps. This type is not shown in the toolbox as it is implicitly added by the designer. This activity wraps other activities
in the workflow and provides the navigation semantics for the next step(s) in the workflow.
It is important to note that while there are specific activities for the Flowchart model, any other
activities can be used within the workflow. Likewise, a Flowchart activity can be added to
another activity to provide the execution and design semantics of a Flowchart, within that
workflow. This means you can have a sequence with a several activities and a Flowchart right in
the middle.
Messaging Activities
One of the major focuses in WF4 is tighter integration between WF and WCF. In terms of
workflows, that means activities to model messaging operations such as sending and receiving
messages. There are actually several different activities included in the framework for
messaging, each with slightly different functionality and purpose.
Activity Description
Send/Receive One way messaging activities to send or receive a message. These same activities are composed into request/response style interactions.
ReceiveAndSendReply Models a service operation that receives a message and sends back a reply.
SendAndReceiveReply Invokes a service operation and receives the response.
InitializeCorrelation Allow for initializing correlation values explicitly as part of the workflow logic, rather than extracting the values from a message.
CorrelationScope Defines a scope of execution where a correlation handle is accessible to receive and send activities simplifying the configuration of a handle that is shared by several messaging activities.
TransactedReceiveScope Enables workflow logic to be included in the same transaction flowed into a WCF operation using the Receive activity.
To invoke a service operation from within a workflow, you will follow the familiar steps of
adding a service reference to your workflow project. The project system in Visual Studio will
then consume the metadata from the service and create a custom activity for each service
operation found in the contract. You can think of this as the workflow equivalent of a WCF
proxy that would be created in a C# or Visual Basic project. For example, taking an existing
service that searches for hotel reservations with the service contract shown in Figure 18; adding a
service reference to it yields a custom activity shown in Figure 19.
[ServiceContract]
public interface IHotelService
{
[OperationContract]
List<HotelSearchResult> SearchHotels(
HotelSearchRequest requestDetails);
}
Figure 18: Service contract

Figure 19: Custom WCF activity
More information on building workflows exposed as WCF services can be found in the
Workflow Services section later in this paper.
Transactions and Error Handling
Writing reliable systems can be difficult, and requires that you do a good job of handling errors
and managing to keep consistent state in your application. For a workflow, scopes for managing
exception handling and transactions are modeled using activities with properties of type
ActivityAction or Activity to enable developers to model error processing logic. Beyond these
common patterns of consistency, WF4 also includes activities which allow you to model long
running distributed coordination through compensation and confirmation.
Activity Description
CancellationScope Used to allow the workflow developer to react if a body of work gets cancelled.
TransactionScope Enables semantics similar to using a transaction scope in code by executing all child activities in the scope under a transaction.
TryCatch/Catch<T> Used to model exception handling and catch typed exceptions.
Throw Can be used to throw an exception from the activity.
Rethrow Used to rethrow an exception, generally one that has been caught using the TryCatch activity.
CompensableActivity Defines the logic for executing child activities that may need to have their actions compensated for after success. Provides a placeholder for the compensation logic, confirmation logic, and
cancellation handling.
Compensate Invokes the compensation handling logic for a compensable activity.
Confirm Invokes the confirmation logic for a compensable activity.
The TryCatch activity provides a familiar way to scope a set of work to catch any exceptions that
may occur, and the Catch<T> activity provides the container for exception handling logic. You
can see an example of how this activity is used in Figure 20, with each typed exception block
being defined by a Catch<T> activity, providing access to the exception via the named argument
seen on the left of each catch. If the need arises, the Rethrow activity can be used to rethrow the
caught exception, or the Throw activity to throw a new exception of your own.

Figure 20: TryCatch activity
Transactions help ensure consistency in short lived work and the TransactionScope activity
provides the same sort of scoping you can get in .NET code using the TransactionScope class.
Finally, when you need to maintain consistency, but cannot use an atomic transaction across the
resources, you can use compensation. Compensation enables you to define a set of work that, if
it completes, can have a set of activities to compensate for the changes made. As a simple
example, consider the Compensable Activity in Figure 21 where an email is sent. If after the
activity has completed, an exception occurs, the compensation logic can be invoked to get the
system back into a consistent state; in this case a follow up email to retract the earlier message.

Figure 21: Compensable Activity
Creating and executing workflows
As with any programming language, there are two fundamental things you do with workflows:
define them, and execute them. In both cases, WF provides you with several options to give you
flexibility and control.
Options for designing workflows
When designing or defining workflows, there are two main options: code or XAML. XAML
provides the truly declarative experience and allows for the entire definition of your workflow to
be defined in XML markup, referencing Activities and types built using .NET. Most developers
will likely use the workflow designer to build workflows which will result in a declarative
XAML workflow definition. Because XAML is just XML, however, any tool can be used to
create it, which is one of the reasons it is such a powerful model for building applications. For
example, the XAML shown in Figure 22 was created in a simple text editor, and can either be
compiled, or used directly to execute an instance of the workflow being defined.
<p:Activity x:Class="Workflows.HelloSeq" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities/design"
xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:Members>
<x:Property Name="greeting" Type="p:InArgument(x:String)" />
<x:Property Name="name" Type="p:InArgument(x:String)" />
</x:Members>
<p:Sequence>
<p:WriteLine>[greeting &amp; " from workflow"]</p:WriteLine>
<p:WriteLine>[name]</p:WriteLine>
</p:Sequence>
</p:Activity>
Figure 22: Workflow defined in XAML
This same XAML is generated by the designer, and can be generated by custom tools, making it
much easier to manage. In addition, it is also possible, as was shown previously, to build
workflows using code. This involves the creation of the activity hierarchy through the use of the
various activities in the framework and your custom activities. You have seen several examples
already of workflows defined entirely in code. Unlike in WF3, it is now possible to create a
workflow in code and easily serialize it to XAML; providing more flexibility in modeling and
managing workflow definitions.
As I will show, to execute a workflow, all you need is an Activity and that can be an instance
built in code, or one created from XAML. The end result of each of the modeling techniques is
either a class deriving from Activity, or an XML representation that can be deserialized or
compiled into an Activity.
Options for executing workflows
In order to execute a workflow, you need an Activity that defines the workflow. There are two
typical ways to get an Activity that can be executed: create it in code or read in a XAML file
and deserialize the content into an Activity. The first option is straightforward and I have
already shown several examples. To load a XAML file you should use the
ActivityXamlServices class which provides a static Load method. Simply pass in a Stream or
XamlReader object and you get back the Activity represented in the XAML.
Once you have an Activity, the simplest way to execute it is by using the WorkflowInvoker class
as I did in the unit tests earlier. The Invoke method of this class has a parameter of type Activity
and returns an IDictionary<string, object>. If you need to pass arguments into the workflow,
you first define them on the workflow and then pass the values along with the Activity, into the
Invoke method as dictionary of name/value pairs. Likewise, any Out or In/Out arguments
defined on the workflow will be returned as the result of executing the method. Figure 23
provides an example of loading a workflow from a XAML file, passing arguments into the
workflow and retrieving the resulting output arguments.
Activity mathWF;
using (Stream mathXaml = File.OpenRead("Math.xaml"))
{
mathWF = ActivityXamlServices.Load(mathXaml);
}
var outputs = WorkflowInvoker.Invoke(mathWF,
new Dictionary<string, object> {
{ "operand1", 5 },
{ "operand2", 10 },
{ "operation", "add" } });
Assert.AreEqual<int>(15, (int)outputs["result"], "Incorrect result returned");
Figure 23: Invoking "Loose XAML" workflow with in and out arguments
Notice in this example that the Activity is loaded from a XAML file and it can still accept and
return arguments. The workflow was developed using the designer in Visual Studio for ease, but
could be developed in a custom designer and stored anywhere. The XAML could be loaded
from a database, SharePoint library, or some other store before being handed to the runtime for
execution.
Using the WorkflowInvoker provides the simplest mechanism for running short-lived
workflows. It essentially makes the workflow act like a method call in your application,
enabling you to more easily take advantage of all the benefits of WF without having to do a lot
of work to host WF itself. This is especially useful when unit testing your activities and
workflows as it reduces the test setup necessary to exercise a component under test.
Another common hosting class is the WorkflowApplication which provides a safe handle to a
workflow that is executing in the runtime, and enables you to manage long running workflows
more easily. With the WorkflowApplication, you can still pass arguments into the workflow in
the same way as with the WorkflowInvoker, but you use the Run method to actually start the
workflow running. At this point, the workflow begins executing on another thread and control
returns to the calling code.
Because the workflow is now running asynchronously, in your hosting code you will likely want
to know when the workflow completes, or if it throws an exception, etc. For these types of
notifications, the WorkflowApplication class has a set of properties of type Action<T> that can
be used like events to add code to react to certain conditions of the workflow execution
including: aborted, unhandled exception, completed, idled, and unloaded. When you execute a
workflow using WorkflowApplication, you can use code similar to that shown in Figure 24 using
actions to handle the events.
WorkflowApplication wf = new WorkflowApplication(new Flowchart1());
wf.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Console.WriteLine("Workflow {0} complete", e.InstanceId);
};
wf.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
Console.WriteLine(e.Reason);
};
wf.OnUnhandledException =
delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Console.WriteLine(e.UnhandledException.ToString());
return UnhandledExceptionAction.Terminate;
};
wf.Run();
Figure 24: WorkflowApplication actions
In this example, the goal of the host code, a simple console application, is to notify the user via
the console when the workflow completes or if an error occurs. In a real system, the host will be
interested in these events and will likely react to them differently to provide administrators with
information about failures or to better manage the instances.
Workflow Extensions
One of the core features of WF, since WF3, has been that it is lightweight enough to be hosted in
any .NET application domain. Because the runtime can execute in different domains and may
need customized execution semantics, various aspects of the runtime behaviors need to be
externalized from the runtime. That is where workflow extensions come into play. Workflow
extensions enable you as the developer writing the host code, if you so choose, to add behavior
to the runtime by extending it with custom code.
Two extension types that the WF runtime is aware of are the persistence and tracking extensions.
The persistence extension provides the core functionality for saving the state of a workflow to a
durable store and retrieving that state when it is needed. The persistence extension shipping with
the framework includes support for Microsoft SQL Server, but extensions can be written to
support other database systems or durable stores.
Persistence
In order to use the persistence extension, you must first setup a database to hold the state, which
can be done using the SQL scripts found in
%windir%\Microsoft.NET\Framework\v4.0.30319\sql\<language> (e.g.
c:\windows\microsoft.net\framework\v4.0.30319\sql\en\). After creating a database, you execute
the two scripts to create both the structure (SqlWorkflowInstanceStoreSchema.sql) and the stored
procedures (SqlWorkflowInstanceStoreLogic.sql) within the database.
Once the database is setup, you use the SqlWorkflowInstanceStore along with the
WorkflowApplication class. You first create the store and configure it, then supply it to the
WorkflowApplication as shown in Figure 25.
WorkflowApplication application = new WorkflowApplication(activity);
InstanceStore instanceStore = new SqlWorkflowInstanceStore(
@"Data Source=.\SQLEXPRESS;Integrated Security=True");
InstanceView view = instanceStore.Execute(
instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));
instanceStore.DefaultInstanceOwner = view.InstanceOwner;
application.InstanceStore = instanceStore;
Figure 25: Adding the SQL Persistence Provider
There are two ways the workflow can get persisted. The first is through direct use of the Persist
activity in a workflow. When this activity executes it causes the workflow state to be persisted
to the database, saving the current state of the workflow. This affords the workflow author
control over when it is important to save the current state of the workflow. The second option
enables the hosting application to persist the workflow state when various events occur on the
workflow instance; most likely when the workflow is idle. By registering for the PersistableIdle
action on the WorkflowApplication, your host code can then respond to the event to indicate if
the instance should be persisted, unloaded, or neither. Figure 26 shows a host application
registering to get notified when the workflow is idle and causing it to persist.
wf.PersistableIdle = (waie) => PersistableIdleAction.Persist;
Figure 26: Unloading the workflow when it idles
Once a workflow has idled and been persisted, the WF runtime can unload it from memory,
freeing up resources for other workflows that need processing. You can choose to have your
workflow instance unload by returning the appropriate enumeration from the PersistableIdle
action. Once a workflow is unloaded, at some point the host will want to load it back out of the
persistence store to resume it. In order to do that, the workflow instance must be loaded using an
instance store and the instance identifier. This will in turn result in the instance store being asked
to load the state. Once the state is loaded, the Run method on the WorkflowApplication can be
used, or a bookmark can be resumed as shown in Figure 27. For more on bookmarks, see the
custom activities section.
WorkflowApplication application = new WorkflowApplication(activity);
application.InstanceStore = instanceStore;
application.Load(id);
application.ResumeBookmark(readLineBookmark, input);
Figure 27: Resuming a persisted workflow
One of the changes in WF4 is how the state of workflows is persisted and relies heavily on the
new data management techniques of arguments and variables. Rather than serializing the entire
activity tree and maintaining state for the lifetime of the workflow, only in scope variables and
argument values, along with some runtime data such as bookmark information, are persisted.
Notice in Figure 27 that when the persisted instance is reloaded, in addition to using the instance
store, the Activity that defines the workflow is also passed. Essentially, the state from the
database is applied to the Activity supplied. This technique enables much greater flexibility for
versioning and results in better performance as the state has a smaller memory footprint.
Tracking
Persistence enables the host to support long running processes, load balance instances across
hosts and other fault tolerant behaviors. However, once the workflow instance has completed,
the state in the database is often deleted as it is no longer useful. In a production environment,
having information about what a workflow is currently doing and what it has done is critical to
both managing workflows and gaining insight into the business process. Being able to track
what is happening in your application is one of the compelling features of using the WF
runtime.
Tracking consists of two primary components: tracking participants and tracking profiles. A
tracking profile defines what events and data you want the runtime to track. Profiles can include
three primary types of queries:
ActivityStateQuery used to identify activity states (e.g. closed) and variables or arguments to extract data
WorkflowInstanceQuery used to identify workflow events
CustomTrackingQuery used to identify explicit calls to track data, usually within custom activities
As an example, Figure 28 shows a TrackingProfile being created which includes an
ActivityStateQuery and a WorkflowInstanceQuery. Notice that the queries indicate both when
to collect information, and also what data to collect. For the ActivityStateQuery, I included a list
of variables that should have their value extracted and added to the tracked data.
TrackingProfile profile = new TrackingProfile
{
Name = "SimpleProfile",
Queries = {
new WorkflowInstanceQuery {
States = { "*" }
},
new ActivityStateQuery {
ActivityName = "WriteLine",
States={ "*" },
Variables = {"Text" }
}
}
};
Figure 28: Creating a tracking profile
A tracking participant is an extension that can be added into the runtime and is responsible for
processing tracking records as they are emitted. The TrackingParticipant base class defines a
property to provide a TrackingProfile and a Track method that handles the tracking. Figure 29
shows a limited custom tracking participant that writes data to the console. In order to use the
tracking participant, it must be initialized with a tracking profile, and then added into the
extensions collection on the workflow instance.
public class ConsoleTrackingParticipant : TrackingParticipant
{
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
ActivityStateRecord aRecord = record as ActivityStateRecord;
if (aRecord != null)
{
Console.WriteLine("{0} entered state {1}",
aRecord.Activity.Name, aRecord.State);
foreach (var item in aRecord.Arguments)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Variable:{0} has value: {1}",
item.Key, item.Value);
Console.ResetColor();
}
}
}
}
Figure 29: Console tracking participant
WF ships with an EtwTrackingParticipant (ETW = Enterprise Trace for Windows) that you can
add into the runtime to enable high performance tracking of data. ETW is a tracing system that
is a native component in Windows and is used by many components and services in the OS,
including drivers and other kernel level code. The data written to ETW can be consumed using
custom code or using tools such as the upcoming Windows Server AppFabric. For users
migrating from WF3, this will be a change as there will not be SQL tracking participant shipping
as part of the framework. However, Windows Server AppFabric will ship with ETW consumers
that will collect the ETW data and store it to a SQL database. Likewise, you can build an ETW
consumer and store the data in whatever format you prefer, or write your own tracking
participant, whichever makes more sense for your environment.
Creating custom activities
While the base activity library includes a rich palette of activities for interacting with services,
objects, and collections, it does not provide activities for interacting with subsystems such as
databases, email servers, or your custom domain objects and systems. Part of getting started
with WF4 will be to figure out what core activities you might need or want when building
workflows. The great thing is that as you build core units of work, you can combine them into
more coarse grained activities that developers can use in their workflows.
Activity class hierarchy
While an activity is an activity is an activity, it is not true that all activities have the same
requirements or needs. For that reason, rather than all activities deriving directly from Activity,
there is a hierarchy of activity base classes, shown in Figure 30, that you can choose from when
building a custom activity.

Figure 30: Activity class hierarchy
For most custom activities, you will either derive from AsyncCodeActivity, CodeActivity, or
NativeActivity (or one of the generic variants), or model your activity declaratively. At a high
level, the four base classes can be described as follows:
Activity used to model activities by composing other activities, usually defined using XAML.
CodeActivity a simplified base class when you need to write some code to get work done.
AsyncCodeActivity used when your activity perform some work asynchronously.
NativeActivity when your activity needs access to the runtime internals, for example to schedule other activities or create
bookmarks.
In the following sections, I will build several activities to see how to use each of these base
classes. In general, as you think about which base class to use, you should start with the Activity
base class and see if you can build your activity using declarative logic as shown in the next
section. Next, the CodeActivity provides a simplified model if you determine that you have to
write some .NET code to accomplish your task and the AsyncCodeActivity if you want your
activity to execute asynchronously. Finally, if you are writing control flow activities like those
found in the framework (e.g. While, Switch, If), then you will need to derive from the
NativeActivity base class in order to manage the child activities.
Composing activities using Activity designer
When you create a new activity library project, or when you add a new item to a WF project and
select the Activity template, what you get is a XAML file with an empty Activity element in it.
In the designer, this presents itself as a design surface where you can create the body of the
activity. To get started with an activity that will have more than one step, it usually helps to drag
a Sequence activity in as the Body and then populate that with your actual activity logic as the
body represents a single child activity.
You can think of the activity much like you would a method on a component with arguments.
On the activity itself, you can define arguments, along with their directionality, to define the
interface of the activity. Variables that you want to use within the activity will need to be
defined in the activities that comprise the body, such as the root sequence I mentioned
previously. As an example, you can build a NotifyManager activity that composes two simpler
activities: GetManager and SendMail.
First, create a new ActivityLibrary project in Visual Studio 2010, and rename the Activity1.xaml
file to NotifyManager.xaml. Next drag a Sequence activity from the toolbox and add it to the
designer. Once the Sequence is in place, you can populate it with child activities as shown in
Figure 31. (Note that the activities used in this example are custom activities in a referenced
project and not available in the framework.)

Figure 31: Notify manager activity
This activity needs to take arguments for the employee name and the message body. The
GetManager activity looks up the employees manager and provide the email as an
OutArgument<string>. Finally, the SendMail activity sends a message to the manager. The next
step is to define the arguments for the activity by expanding the Arguments window at the
bottom of the designer and entering the information for the two required input arguments.
In order to compose these items, you need to be able to pass the input arguments specified on the
NotifyManager activity to the individual child activities. For the GetManager activity, you need
the employee name which is an in argument on the activity. You can use the argument name in
the property dialog for the employeeName argument on the GetManager activity as seen in
Figure 31. The SendMail activity, needs the managers email address, and the message. For the
message, you can enter the name of the argument containing the message. However, for the
email address, you need a way to pass the out argument from the GetManager activity to the in
argument for the SendMail activity. For this you need a variable.
After highlighting the Sequence activity, you can use the Variables window to define a variable
named mgrEmail. Now you can enter that variable name both for the ManagerEmail out
argument on the GetManager activity, and the To in argument on the SendMail activity. When
the GetManager activity executes, the output data will be stored in that variable, and when the
SendMail activity executes, it will read data from that variable as the in argument.
The approach just described is a purely declarative model for defining the activity body. In some
circumstances, you might prefer to specify the body of the activity in code. For example, your
activity may include a collection of properties that in turn drive a set of child activities; a set of
named values that drive the creation of a set of Assign activities would be one case where using
code would be preferred. In those cases, you can write a class that derives from activity, and
write code in the Implementation property to create an Activity (and any child elements) to
represent the functionality of your activity. The end result is the same in both cases: your logic
is defined by composing other activities, only the mechanism by which the body is defined is
different. Figure 32 shows the same NotifyManager activity being defined in code.
public class NotifyManager : Activity
{
public InArgument<string> EmployeeName { get; set; }
public InArgument<string> Message { get; set; }
protected override Func<Activity> Implementation
{
get
{
return () =>
{
Variable<string> mgrEmail =
new Variable<string> { Name = "mgrEmail" };
Sequence s = new Sequence
{
Variables = { mgrEmail },
Activities = {
new GetManager {
EmployeeName =
new VisualBasicValue<string>("EmployeeName"),
Result = mgrEmail,
},
new SendMail {
ToAddress = mgrEmail,
MailBody = new VisualBasicValue<string>("Message"),
From = "test@contoso.com",
Subject = "Automated email"
}
}
};
return s;
};
}
set { base.Implementation = value; }
}
}
Figure 32: Activity composition using code
Notice that the base class is Activity and the Implementation property is a Func<Activity> to
return a single Activity. This code is very similar to that shown earlier when creating workflows,
and that should not be surprising as the goal in both cases is to create a Activity. Additionally, in
the code approach arguments can be set with variables, or you can use expressions to connect
one argument to another as is shown for the EmployeeName and Message arguments as they are
used on the two child activities.
Writing custom Activity classes
If you determine that your activity logic cannot be accomplished by composing other activities,
then you can write a code based activity. When writing activities in code you derive from the
appropriate class, define arguments, and then override the Execute method. The Execute method
gets called by the runtime when it is time for the activity to do its work.
To build the SendMail activity used in the previous example I first need to choose the base type.
While it is possible to create the functionality of the SendMail activity using the Activity base
class and composing TryCatch<T> and InvokeMethod activities, for most developers it will be
more natural to choose between the CodeActivity and NativeActivity base classes and write code
for the execution logic. Because this activity does not need to create bookmarks or schedule
other activities, I can derive from the CodeActivity base class. In addition, because this activity
will return a single output argument, it should derive from CodeActivity<TResult> which
provides an OutArgument<TResult>. The first step is to define several input arguments for the
email properties. Then you override the Execute method to implement the functionality of the
activity. Figure 33 shows the completed activity class.
public class SendMail : CodeActivity<SmtpStatusCode>
{
public InArgument<string> To { get; set; }
public InArgument<string> From { get; set; }
public InArgument<string> Subject { get; set; }
public InArgument<string> Body { get; set; }
protected override SmtpStatusCode Execute(
CodeActivityContext context)
{
try
{
SmtpClient client = new SmtpClient();
client.Send(
From.Get(context), ToAddress.Get(context),
Subject.Get(context), MailBody.Get(context));
}
catch (SmtpException smtpEx)
{
return smtpEx.StatusCode;
}
return SmtpStatusCode.Ok;
}
}
Figure 33: SendMail custom activity
There are several things to note about the use of arguments. I've declared several standard .NET
properties using the types InArgument<T>. This is how a code-based activity defines its input
and output arguments. However, within the code, I cannot simply reference these properties to
get the value of the argument, I need to use the argument as a sort of handle to retrieve the value
using the supplied ActivityContext; in this case a CodeActivityContext. You use the Get method
of the argument class to retrieve the value, and the Set method to assign a value to an argument.
Once the activity completes the Execute method, the runtime detects this and assumes the
activity is done and closes it. For asynchronous or long running work, there are ways to change
this behavior which are explained in an upcoming section, but in this case, once the email is sent,
the work is complete.
Now let us examine an activity using the NativeActivity base class to manage child activities. I
will build an Iterator activity that executes some child activity a given number of times. First, I
need an argument to hold the number of iterations to perform, a property for the Activity to be
executed, and a variable to hold the current number of iterations. The Execute method calls a
BeginIteration method to start the initial iteration and subsequent iterations are invoked the same
way. Notice in Figure 34 that variables are manipulated the same way as arguments using the
Get and Set method.
public class Iterator : NativeActivity
{
public Activity Body { get; set; }
public InArgument<int> RequestedIterations { get; set; }
public Variable<int> CurrentIteration { get; set; }
public Iterator()
{
CurrentIteration = new Variable<int> { Default = 0 };
}
protected override void Execute(NativeActivityContext context)
{
BeginIteration(context);
}
private void BeginIteration(NativeActivityContext context)
{
if (RequestedIterations.Get(context) > CurrentIteration.Get(context))
{
context.ScheduleActivity(Body,
new CompletionCallback(ChildComplete),
new FaultCallback(ChildFaulted));
}
}
}
Figure 34: Iterator native activity
If you look closely at the BeginIteration method, you will notice the ScheduleActivity method
call. This is how an activity can interact with the runtime to schedule another activity for
execution, and it is because you need this functionality that you derive from NativeActivity.
Also note, when calling the ScheduleActivity method on the ActivityExecutionContext, two
callback methods are provided. Because you do not know which activity will be used as the
body, or how long it will take to complete, and because WF is a heavily asynchronous
programming environment, callbacks are used to notify your activity when a child activity has
completed, allowing you to write code to react.
The second callback is a FaultCallback which will get invoked if the child activity happens to
throw an exception. In this callback, you receive the exception and have the ability, via the
ActivityFaultContext, to indicate to the runtime that the error has been handled , which keeps it
from bubbling up and out of the activity. Figure 35 shows the callback methods for the Iterator
activity where both schedule the next iteration and the FaultCallback handles the error to allow
execution to continue to the next iteration.
private void ChildComplete(NativeActivityContext context,
ActivityInstance instance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
BeginIteration(context);
}
private void ChildFaulted(NativeActivityFaultContext context, Exception ex,
ActivityInstance instance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
context.HandleFault();
}
Figure 35: Activity callbacks
When your activity needs to do work that may be long running, ideally that work could be
handed off to another thread to allow the workflow thread to be used to continue processing
other activities, or used to process other workflows. However, simply starting up threads and
returning control to the runtime can cause problems as the runtime does not monitor the threads
you create. If the runtime determined that the workflow was idle, the workflow might unload,
disposing of your callback methods, or the runtime could assume your activity is done and move
to the next activity in the workflow. To support asynchronous programming in your activity you
derive from the AsyncCodeActivity or AsyncCodeActivity<T>.
Figure 36 shows an example of an asynchronous activity that loads an RSS feed. This signature
of the execute method is different for the asynchronous activities in that it returns an
IAsyncResult. You write the code in the execute method to begin your asynchronous operation.
Override the EndExecute method to handle the callback when your asynchronous operation is
complete and return the result of the execution.
public sealed class GetFeed : AsyncCodeActivity<SyndicationFeed>
{
public InArgument<Uri> FeedUrl { get; set; }
protected override IAsyncResult BeginExecute(
AsyncCodeActivityContext context, AsyncCallback callback,
object state)
{
var req = (HttpWebRequest)HttpWebRequest.Create(
FeedUrl.Get(context));
req.Method = "GET";
context.UserState = req;
return req.BeginGetResponse(new AsyncCallback(callback), state);
}
protected override SyndicationFeed EndExecute(
AsyncCodeActivityContext context, IAsyncResult result)
{
HttpWebRequest req = context.UserState as HttpWebRequest;
WebResponse wr = req.EndGetResponse(result);
SyndicationFeed localFeed = SyndicationFeed.Load(
XmlReader.Create(wr.GetResponseStream()));
return localFeed;
}
}
Figure 36: Creating asynchronous activities
Additional activity concepts
Now that you have seen the basics of creating activities using the base classes, arguments and
variables, and managing execution; I will touch briefly on some more advanced features you can
use in activity development.
Bookmarks enable an activity author to create a resumption point in the execution of a
workflow. Once a bookmark has been created, it can be resumed to continue the workflow
processing from where it left off. Bookmarks are saved as part of the state, so unlike the
asynchronous activities, after a bookmark has been created, it is not an issue for the workflow
instance to be persisted and unloaded. When the host wants to resume the workflow, it loads the
workflow instance and calls the ResumeBookmark method to resume the instance from where it
left off. When resuming bookmarks, data can be passed into the activity as well. Figure 37
shows a ReadLine activity that creates a bookmark to receive input and registers a callback
method to be invoked when the data arrives. The runtime knows when an activity has
outstanding bookmarks and will not close the activity until the bookmark has been resumed. The
ResumeBookmark method can be used on the WorkflowApplication class to send data to the
named bookmark and signal the BookmarkCallback.
public class ReadLine : NativeActivity<string>
{
public OutArgument<string> InputText { get; set; }
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark("ReadLine",
new BookmarkCallback(BookmarkResumed));
}
private void BookmarkResumed(NativeActivityContext context,
Bookmark bk, object state)
{
Result.Set(context, state);
}
}
Figure 37: Creating a bookmark
Another powerful feature for activity authors is the concept of an ActivityAction.
ActivityAction is the workflow equivalent of the Action class in imperative code: describing a
common delegate; but for some, the idea of a template may be easier to understand. Consider
the ForEach<T> activity which allows you to iterate over a set of data and schedule a child
activity for each data item. You need some way to allow the consumer of your activity to define
the body, and be able to consume the data item of type T. Essentially, you need some activity
that can accept an item of type T and act on it, ActivityAction<T> is used to enable this scenario,
and provides a reference to the argument and the definition of a Activity to process the item.
You can use ActivityAction in your custom activities to enable consumers of your activity to add
their own steps at appropriate points. This allows you to create workflow or activity templates of
a sort, where a consumer can fill in the relevant parts to customize the execution for their use.
You can also use the ActivityFunc<TResult> and the related alternatives when the delegate your
activity needs to invoke will return a value.
Finally, activities can be validated to ensure they are configured properly by checking individual
settings, constraining allowable child activities, etc. For the common need to require a particular
argument, you can add a RequiredArgument attribute to the property declaration in the activity.
For more involved validation, in the constructor of your activity, create a constraint and add it to
the collection of constraints surfaced on the Activity class. A constraint is an activity written to
inspect the target activity and ensure validity. Figure 38 shows the constructor for the Iterator
activity, which validates that the RequestedIterations property is set. Finally, overriding the
CacheMetadata method, you can invoke the AddValidationError method on the ActivityMetdata
parameter to add validation errors for your activity.
var act = new DelegateInArgument<Iterator> { Name = "constraintArg" };
var vctx = new DelegateInArgument<ValidationContext>();
Constraint<Iterator> cons = new Constraint<Iterator>
{
Body = new ActivityAction<Iterator, ValidationContext>
{
Argument1 = act,
Argument2 = vctx,
Handler = new AssertValidation
{
Message = "Iteration must be provided",
PropertyName = "RequestedIterations",
Assertion = new InArgument<bool>(
(e) => act.Get(e).RequestedIterations != null)
}
}
};
base.Constraints.Add(cons);
Figure 38: Constraints
Activity designers
One of the benefits of WF is that it allows you to program your application logic declaratively,
but most people do not want to write XAML by hand, which is why the designer experience in
WF is so important. When building custom activities, you will also likely want to create a
designer to provide the display and visual interaction experience for consumers of your activity.
The designer for WF is based on Windows Presentation Foundation which means you have all
the power of styling, triggers, databinding and all of the other great tools for building a rich UI
for your designer. In addition, WF provides some user controls that you can use in your designer
to simplify the task of displaying an individual child activity, or a collection of activities. The
four primary controls are:
ActivityDesigner root WPF control used in activity designers
WorkflowItemPresenter used to display a single Activity
WorkflowItemsPresenter used to display a collection of child Activities
ExpressionTextBox used to enable in place editing of expressions such as arguments
WF4 contains an ActivityDesignerLibrary project template and ActivityDesigner item template
that can be used to initially create the artifacts. Once you have the designer initialized, you can
begin using the above elements to customize the look and feel of your activity by laying out how
to display data and visual representations of child activities. Within the designer you have access
to a ModelItem that is an abstraction over the actual activity, and surfacing all properties of the
activity.
The WorkflowItemPresenter control can be used to display a single Activity that is part of your
activity. For example, to provide the ability to drag an activity onto the Iterator activity shown
earlier and display the activity contained within the Iteractor activity, you can use the XAML
shown in Figure 39, binding the WorkflowItemPresenter to the ModelItem.Body. Figure 40
shows the designer in use on a workflow design surface .
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.IteratorDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
<Grid>
<Border BorderBrush="MidnightBlue" BorderThickness="3" CornerRadius="10">
<sap:WorkflowItemPresenter MinHeight="50"
Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"
HintText="Add body here" />
</Border>
</Grid>
</sap:ActivityDesigner>
Figure 39: WorkflowItemPresenter in activity designer

Figure 40: Iterator designer
WorkflowItemsPresenter provides similar functionality for displaying multiple items such as the
children in a sequence. You can define a template and orientation for how you want the items
displayed, as well as a spacer template to add visual elements between activities such as
connectors, arrows, or something more interesting. Figure 41 shows a simple designer for a
custom sequence activity that displays the child activities with a horizontal line between each.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.CustomSequenceDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
<Grid>
<StackPanel>
<Border BorderBrush="Goldenrod" BorderThickness="3">
<sap:WorkflowItemsPresenter HintText="Drop Activities Here"
Items="{Binding Path=ModelItem.ChildActivities}">
<sap:WorkflowItemsPresenter.SpacerTemplate>
<DataTemplate>
<Rectangle Height="3" Width="40"
Fill="MidnightBlue" Margin="5" />
</DataTemplate>
</sap:WorkflowItemsPresenter.SpacerTemplate>
<sap:WorkflowItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</sap:WorkflowItemsPresenter.ItemsPanel>
</sap:WorkflowItemsPresenter>
</Border>
</StackPanel>
</Grid>
</sap:ActivityDesigner>
Figure 41: Custom sequence designer using WorkflowItemsPresenter

Figure 42: Sequence designer
Finally, the ExpressionTextBox control enables in-place editing of activity arguments within the
designer in addition to the property grid. In Visual Studio 2010 this includes intellisense support
for the expressions being entered. Figure 43 shows a designer for the GetManager activity
created earlier, enabling editing of the EmployeeName and ManagerEmail arguments within the
designer. The actual designer is shown in Figure 44.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.GetManagerDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;
assembly=System.Activities.Presentation">
<sap:ActivityDesigner.Resources>
<sapc:ArgumentToExpressionConverter
x:Key="ArgumentToExpressionConverter"
x:Uid="swdv:ArgumentToExpressionConverter_1" />
</sap:ActivityDesigner.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center">Employee:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="1"
Expression="{Binding Path=ModelItem.EmployeeName, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50"
HintText="&lt;Employee Name&gt;"/>
<TextBlock Grid.Row="1" VerticalAlignment="Center"
HorizontalAlignment="Center">Mgr Email:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="2" Grid.Row="1"
UseLocationExpression="True"
Expression="{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50" />
</Grid>
</sap:ActivityDesigner>
Figure 43: Using ExpressionTextBox to edit arguments in the designer

Figure 44: GetManager activity designer
The example designers presented here were simple to highlight the specific features but the full
power of WPF is at your disposal to make your activities easier to configure and more natural to
use for your consumers. Once you have a designer created, there are two ways to associate it
with the activity: applying an attribute to the activity class, or implementing the
IRegisterMetadata interface. To let the activity definition drive the choice of designer, simply
apply the DesignerAttribute to the activity class and supply the type for the designer; this works
exactly as it did in WF3. For a more loosely coupled implementation, you can implement the
IRegisterMetadata interface and define the attributes you wish to apply to the activity class and
properties. Figure 45 shows an example implementation to apply the designers for two custom
activities. Visual Studio will discover your implementation and invoke it automatically, whereas
a custom designer host, discussed next, would call the method explicitly.
public class Metadata : IRegisterMetadata
{
public void Register()
{
AttributeTableBuilder builder = new AttributeTableBuilder();
builder.AddCustomAttributes(typeof(SendMail), new Attribute[] {
new DesignerAttribute(typeof(SendMailDesigner))});
builder.AddCustomAttributes(typeof(GetManager), new Attribute[]{
new DesignerAttribute(typeof(GetManagerDesigner))});
MetadataStore.AddAttributeTable(builder.CreateTable());
}
}
Figure 45: Registering designers for activities
Rehosting the workflow designer
In the past, developers have often wanted to provide their users with the ability to create or edit
workflows. In previous versions of Windows Workflow Foundation, this was possible, but not a
trivial task. With the move to WPF comes a new designer and rehosting was a prime use case
for the development team. You can host the designer in a custom WPF application like that
shown in Figure 46 with a few lines of XAML and a few lines of code.

Figure 46: Rehosted workflow designer
The XAML for the window, Figure 47, uses a grid to layout three columns, then puts a border
control in each cell. In the first cell, the toolbox is created declaratively, but can also be created
in code. For the designer view itself, and the property grid, there are two empty border controls
in each of the remaining cells. These controls are added in code.
<Window x:Class="WorkflowEditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:sapt="clr-namespace:System.Activities.Presentation.Toolbox;
assembly=System.Activities.Presentation"
Title="Workflow Editor" Height="500" Width="700" >
<Window.Resources>
<sys:String x:Key="AssemblyName">System.Activities, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
<sys:String x:Key="MyAssemblyName">CustomActivities</sys:String>
</Window.Resources>
<Grid x:Name="DesignerGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="7*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Border>
<sapt:ToolboxControl>
<sapt:ToolboxControl.Categories>
<sapt:ToolboxCategory CategoryName="Basic">
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource AssemblyName}" >
<sapt:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Sequence
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource MyAssemblyName}">
<sapt:ToolboxItemWrapper.ToolName>
CustomActivities.SendMail
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
</sapt:ToolboxCategory>
</sapt:ToolboxControl.Categories>
</sapt:ToolboxControl>
</Border>
<Border Name="DesignerBorder" Grid.Column="1"
BorderBrush="Salmon" BorderThickness="3" />
<Border x:Name="PropertyGridExpander" Grid.Column="2" />
</Grid>
</Window>
Figure 47: Designer rehosting XAML
The code to initialize the designer and property grid is shown in Figure 48. The first step, in the
OnInitialized method, is to register the designers for all of the activities that will be used in the
workflow designer. The DesignerMetadata class registers the associated designers for the
activities that ship with framework and the Metadata class registers the designers for my custom
activities. Next, create the WorkflowDesigner class and use the View and
PropertyInspectorView properties to access the UIElement objects needed to render. The
designer is initialized with an empty Sequence, but you could also add menus and load the
workflow XAML from a variety of sources including the file system or a database.
public MainWindow()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e) {
base.OnInitialized(e);
RegisterDesigners();
PlaceDesignerAndPropGrid();
}
private void RegisterDesigners() {
new DesignerMetadata().Register();
new CustomActivities.Presentation.Metadata().Register();
}
private void PlaceDesignerAndPropGrid() {
WorkflowDesigner designer = new WorkflowDesigner();
designer.Load(new System.Activities.Statements.Sequence());
DesignerBorder.Child = designer.View;
PropertyGridExpander.Child = designer.PropertyInspectorView;
}
Figure 48: Designer rehosting code
With this little bit of code and markup, you can edit a workflow, drag and drop activities from
the toolbox, configure variables in the workflow and manipulate arguments on the activities.
You can also get the text of the XAML by calling Flush on the designer, then referencing the
Text property of the WorkflowDesigner. There is much more you can do, and getting started is
much easier than before.
Workflow Services
As mentioned previously, one of the large areas of focus in this release of Windows Workflow
Foundation is tighter integration with Windows Communication Foundation. The example in the
activity walkthrough showed how to call a service from a workflow, and in this section I will
discuss how to expose a workflow as a WCF service.
To begin, create a new project and select the WCF Workflow Service project. Once the template
is complete, you will find a project with a web.config file and a file with a XAMLX extension.
The XAMLX file is a declarative service or workflow service and like other WCF templates,
provides a functioning, albeit simple, service implementation that receives a request and returns
a response. For this example, I will change the contract to create an operation to receive an
expense report and return an indicator that the report has been received. To start, add an
ExpenseReport class to the project like the one shown in Figure 49.
[DataContract]
public class ExpenseReport
{
[DataMember]
public DateTime FirstDateOfTravel { get; set; }
[DataMember]
public double TotalAmount { get; set; }
[DataMember]
public string EmployeeName { get; set; }
}
Figure 49: Expense report contract type
Back in the workflow designer, change the type of the "data" variable in the variables to the
ExpenseReport type just defined (you may need to build the project for the type to show up in
the type picker dialog). Update the Receive activity by clicking the Content button and changing
the type in the dialog to the ExpenseReport type to match. Update the activity properties to
define a new OperationName, and ServiceContractName as shown in Figure 50.

Figure 50: Configuring the receive location
Now the SendReply activity can have the Value set to True to return the indicator of receipt.
Obviously, in a more complicated sample, you could use the full power of workflow to save
information to the database, do validation of the report, etc. before determining the response.
Browsing to the service with the WCFTestClient application shows how the activity
configuration defines the exposed service contract as shown in Figure 51.

Figure 51: Contract generated from workflow service
Once you have created a workflow service, you need to host it, just as you would with any WCF
service. The previous example used the simplest option for hosting: IIS. To host a workflow
service in your own process, you will want to use the WorkflowServiceHost class found in the
System.ServiceModel.Activities assembly. Note that there is another class by this same name in
the System.WorkflowServices assembly which is used for hosting workflow services built using
the WF3 activities. In the simplest case of self-hosting, you can use code like that shown in
Figure 52 to host your service and add endpoints.
WorkflowServiceHost host = new
WorkflowServiceHost(XamlServices.Load("ExpenseReportService.xamlx"),
new Uri("http://localhost:9897/Services/Expense"));
host.AddDefaultEndpoints();
host.Description.Behaviors.Add(
new ServiceMetadataBehavior { HttpGetEnabled = true });
host.Open();
Console.WriteLine("Host is open");
Console.ReadLine();
Figure 52: Self-hosting the workflow service
If you want to take advantage of the managed hosting options in Windows, you can host your
service in IIS by copying the XAMLX file into the directory and specifying any necessary
configuration information for behaviors, bindings, endpoints, etc. in the web.config file. In fact,
as seen previously, when you create a new project in Visual Studio using one of the Declarative
Workflow Service project templates, you will get a project with a web.config file and the
workflow service defined in a XAMLX, ready to deploy.
When using the WorkflowServiceHost class to host your workflow service, you still need to be
able to configure and control the workflow instances. To control the service there is a new
standard endpoint called the WorkflowControlEndpoint. A companion class,
WorkflowControlClient, provides a pre-built client proxy . You can expose a
WorkFlowControlEndpoint on your service, and use the WorkflowControlClient to create and
run new instances of workflows, or control running workflows. You use this same model to
manage services on the local machine, or you can use it to manage services on a remote
machine. Figure 53 shows an updated example of exposing the workflow control endpoint on
your service.
WorkflowServiceHost host = new
WorkflowServiceHost("ExpenseReportService.xamlx",
new Uri("http://localhost:9897/Services/Expense"));
host.AddDefaultEndpoints();
WorkflowControlEndpoint wce = new WorkflowControlEndpoint(
new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/Expense/WCE"));
host.AddServiceEndpoint(wce);
host.Open();
Figure 53: Workflow Control Endpoint
Because you are not creating the WorkflowInstance directly, there are two options for adding
extensions to the runtime. The first is that you can add some extensions to the
WorkflowServiceHost and they will initialize correctly with your workflow instances. The
second is using configuration. The tracking system, for example, can be entirely defined in the
application configuration file. You define the tracking participants and the tracking profiles in
the configuration file, and using a behavior, you apply a particular participant to a service as
shown in Figure 54.
<system.serviceModel>
<tracking>
<participants>
<add name="EtwTrackingParticipant"
type="System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
profileName="HealthMonitoring_Tracking_Profile"/>
</participants>
<profiles>
<trackingProfile name="HealthMonitoring_Tracking_Profile">
<workflow activityDefinitionId="*">
<workflowInstanceQuery>
<states>
<state name="Started"/>
<state name="Completed"/>
</states>
</workflowInstanceQuery>
</workflow>
</trackingProfile>
</profiles>
</tracking>
. . .
<behaviors>
<serviceBehaviors>
<behavior name="SampleTrackingSample.SampleWFBehavior">
<etwTracking profileName=" HealthMonitoring_Tracking_Profile" />
</behavior>
</serviceBehaviors>
</behaviors>
. . .
Figure 54: Configuring tracking for services
There are many new improvements to hosting and configuration for WCF services that you can
take advantage of in your workflows such as ".svc-less" services, or services that don't need a file
extension, default bindings and default endpoints just to name a few. For more information on
these and other features in WCF4, refer the companion paper on WCF found in the Additional
Resources section.
In addition to hosting improvements, Windows Workflow Foundation takes advantage of other
features in WCF; some directly and others indirectly. For example, message correlation is now a
key feature in building services which allows you to relate messages to a given workflow
instance based on data in the message such as an order number or employee id. Correlation can
be configured on the various messaging activities for both the request and response and supports
correlating on multiple values in the message. Again, more details on these new features can be
found in the companion paper on WCF4.
Conclusion
The new Windows Workflow Foundation technology that ships with .NET Framework 4
represents a major improvement in performance and developer productivity and realizes a goal
of declarative application development for business logic. The framework provides the tools for
short units of complicated work to be simplified, and for building complex long running services
with correlated message, persisted state, and rich application visibility through tracking.
About the Author
Matt is a member of the technical staff at Pluralsight, where he focuses on connected systems
technologies (WCF, WF, BizTalk, AppFabric, and the Azure Services Platform). Matt is also an
independent consultant specializing in Microsoft .NET application design and development. As a
writer Matt has contributed to several journals and magazines including MSDN Magazine where
he currently authors the workflow content for the Foundations column. Matt regularly shares his
love of technology by speaking at local, regional and international conferences such as Tech Ed.
Microsoft has recognized Matt as an MVP for his community contributions around connected
systems technology. Contact Matt via his
blog: http://www.pluralsight.com/community/blogs/matt/default.aspx.
Additional Resources
The Workflow Way by David Chappell
A Developers Introduction to WCF in .NET 4 by Aaron Skonnard
Guidance for migrating from WF in .NET 3.5 to WF in .Net 4.
Windows Server AppFabric

S-ar putea să vă placă și