Вы находитесь на странице: 1из 638

www.it-ebooks.

info
For your convenience Apress has placed some of the front
matter material after the index. Please use the Bookmarks
and Contents at a Glance links to access them.

www.it-ebooks.info
Contents at a Glance

About the Author���������������������������������������������������������������������������������������������������������������� xv


About the Technical Reviewer������������������������������������������������������������������������������������������ xvii
Acknowledgments������������������������������������������������������������������������������������������������������������� xix
Introduction����������������������������������������������������������������������������������������������������������������������� xxi

■■Chapter 1: Why Workflows������������������������������������������������������������������������������������������������1


■■Chapter 2: Introducing Windows Workflow Foundation��������������������������������������������������21
■■Chapter 3: Windows Workflow Activities������������������������������������������������������������������������63
■■Chapter 4: State Machine Workflows����������������������������������������������������������������������������109
■■Chapter 5: Flowchart Workflows�����������������������������������������������������������������������������������159
■■Chapter 6: Versioning and Updating Workflows������������������������������������������������������������205
■■Chapter 7: Building Custom Workflow Activities�����������������������������������������������������������257
■■Chapter 8: Persisting Workflows�����������������������������������������������������������������������������������295
■■Chapter 9: Tracking Workflows�������������������������������������������������������������������������������������357
■■Chapter 10: Rehosting the Workflow Designer��������������������������������������������������������������399
■■Chapter 11: Stateful WCF Services Using Workflow������������������������������������������������������451
■■Chapter 12: Workflows in Windows Azure��������������������������������������������������������������������501
■■Chapter 13: Hosting Workflows in Windows Server������������������������������������������������������563

Index���������������������������������������������������������������������������������������������������������������������������������617

www.it-ebooks.info
Introduction

Now that you have picked up this book and are curious enough to read this introduction, let me share with you how
Windows Workflow Foundation (WF) can help you to be a better developer. WF is a Microsoft .NET technology that
provides a fascinating way to develop software by defining workflows instead of writing conventional code.
Building workflows is an exercise in which visual models or diagrams represent how logic will flow. The first
chapter quickly explains why workflows are important and walks through different ways of modeling scenarios
outside of WF. Since building workflows is quite different from writing code, this chapter will give you a visual
grounding in modeling processes if you are new to modeling.
My passion for Windows Workflow (WF) started when I watched it being demoed (for the very first time) by
Microsoft. Hopefully that passion will infect you too, as you begin to understand how WF fits within your development
toolbox.
With the appearance of Visual Studio 2012 and .NET Framework 4.5, a new version of WF has been released,
referred to as WF4.5. Whether you are familiar with WF or not, this book will help you understand the new features in
WF4.5 and how they can be used in real-world scenarios. I have taken pains to make sure that this book does not leave
WF beginners in the dark, while showing experienced developers how to use its very latest features to accomplish
practical tasks.

xxi

www.it-ebooks.info
Chapter 1

Why Workflows

This chapter explains why workflows are important for developing software, how they can provide a visual
understanding of user requirements and design blueprints, and the benefits of using workflow technology like
Windows Workflow Foundation (WF).

■■Tip  The first time I visited Microsoft’s campus for a software design review (SDR) I referred to Windows Workflow
Foundation as “WWF.” I was graciously informed by one of the original Workflow Team members that it should be called
WF (pronounced “dub eff”) to avoid any possible confusion with the World Wrestling Federation or even the World Wildlife
Fund. For the remainder of the book I will refer to Windows Workflow Foundation as WF.

A workflow is a visual representation of the logical flow of steps for accomplishing a goal or task. Writing software
that integrates with a workflow technology is a paradigm shift for most developers, who are used to writing traditional
code. So whenever I teach WF, I have found it helps if I explain how workflows can be used to model daily events like
buying groceries or getting an oil change, before discussing the characteristics of workflows, such as
• Different types of workflows used for modeling.
• Flow behavior of workflows like sequential or parallel.
• How a workflow can be reused within other workflows.
Before I dig into the technical features of WF, this chapter will explain how workflows help developers thoroughly
understand processes so that they can develop better solutions. Once you have grasped the basics of workflows and
the processes they model, you will find it much easier to understand when (and why) WF is the right framework for
developing software solutions.

Business Processes
A process is a series of steps that must be completed to perform a desired unit of work and can be modeled using
workflows. Modeling processes as workflows is nothing new: in fact, humans have been modeling processes for
centuries. It seems that as our ancestors learned how to think, they also learned how to model their ideas. Models
provide a representation for an existing artifact or concept. After a model is built it can be used for studying and
collecting valuable information about the artifact it represents.
Without modeling, what would the world be like today? We would not have airplanes or be able to cross over
large bodies of water via bridges or ships. Medical science would not be quite as far advanced as it is today without
people like Leonardo Da Vinci, who drew the first concepts of human anatomy.

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Mathematical equations are also considered models. Consider equations that model supply and demand in
economics, or the stock market. Models are the transport for learning more about everyday life, and this simple concept
is what makes modeling processes within businesses so natural. Transitioning from concepts around the laws of
physics and biology, models are also used to learn about how businesses process everyday work as well. By studying
how processes are built we can make recommendations for making inefficient processes more efficient.
Modeling business processes has become so important that many process management strategies have stemmed
from it. Because time is money, organizations rely on process management strategies that help them improve their
processes for effectively doing business. The Industrial Revolution pioneered the concept of displacing raw human
labor with automation. The methodology used to drive automation gave birth to industrial engineering (IE), which
is an example of a process management strategy that uses modeling techniques to optimize complex processes
around managing time, energy, and resources. Industrial engineers mainly focus on supply-chain manufacturing
and distribution operations and use mathematical equations to optimize one or more department’s processes for
managing and processing work more effectively.
One example of how industrial engineering has made an impact is in the entertainment world of amusement

Waiting in line for an amusement ride models the same characteristics around First In, First Out (FIFO), which

Today, workflow technologies like WF are available for aligning process management methodologies. A workflow

• Process parameters: Information required for starting a process. Processes sometimes require
information to be entered so it has data to process by making decisions.
• Business rules: These rules drive how a process makes decisions. Being able to manage
business rules while a process is running is important for implementing changes and
improving overall optimization over time.
• Data-driven: Data sometimes drives the decisions for a business process because of the
state of the data. An example of a data-driven process are extract, transform, and load (ETL)
processes that make decisions on where to load extracted data from a source.
• Event-driven: Events drive processes by providing actions that a process can use for making
decisions. An event can be fired externally or internally within a process.
• State machine: These are processes that rely on external events for transitioning between
states for making decisions. State machine processes provide a mechanism for receiving
external events usually fired by human decisions.
• Process agility: The flexibility within processes to adapt to continually changing environment of an
organization as it adapts to new trends and goals for processing business.
Once these behavior characteristics are understood, software can be written to target functionality around closing the gap
between the technical side of programming and the requirements software is created to fulfill, thereby providing a level
of abstraction and automation within business processes. This has sparked the birth of additional process management
methodologies that also focus on modeling business processes within organizations.
Business process management (BPM) has been a significant player as a methodology within the business process
and technology scene. BPM helps manage business processes within an organization that affect one or more divisions
or departments and focuses on building effective business processes with the aid of technology. There are other

www.it-ebooks.info
Chapter 1 ■ Why Workflows

business process methodologies that also focus on optimizing business processes, but BPM stands out because it
primarily relies on using technology when recommending solutions. Just like software development, BPM has its own
life cycle it uses to optimize processes within an organization (see Table 1-1).

Table 1-1.  Business Process Management Life Cycle


Phase Description
Design Defining the stakeholder’s goals and requirements for effectiveness around how processes should
be executed within an organization.
Model Building a representation of a business process to visually understand and recommend changes
for how it should process. This usually includes recommendations for the logical flow, external/
internal events, tracking metrics, and human interaction.
Execute Physically adding a new process into an organization’s environment so the changes to the process
can be evaluated.
Monitor Tracking metrics for a process while it is executing to evaluate the logic and performance.
Optimize Making modifications to business processes based on provided metrics and environmental
changes.

An important observation based on Figure 1-1 is that the lifecycle never ends. This pattern is a reminder that
business processes are continuously changing and always have room for improvement. The pattern is called continual
process improvement and it is not only important for ever-changing business processes, but also promotes the
adoption of innovative ideas around technology that increase process effectiveness and quality.

Design

Optimize Model

Monitor Execute

Figure 1-1.  BPM phase order

Workflow Activities
At the beginning of this chapter I mentioned that a workflow is a list of predefined steps that are executed in a specific
order to perform an outcome and that you can use them to model processes. Each step of a workflow is called an
activity and one or more activities makes up a workflow. Just as the atom plays a role as the building block of the
universe, activities are considered the basic building blocks that define a workflow. To demonstrate how activities are

www.it-ebooks.info
Chapter 1 ■ Why Workflows

used and to show how easy it is to model as a workflow using activities, let’s look at an example of a simple process,
such as going to the movies. When planning to go to a movie, the first steps are as follows:
1. Check the times when the movie is showing.
2. Order tickets, either at the theater or online.
3. Pick up the tickets in order to enter the theater to see the movie.
Figure 1-2 models each of these steps as activities within a workflow. These are the basic steps that need to be taken
for seeing a movie. By following them, you execute a workflow every time you want to see a movie. All workflows have
a starting and ending point, and within this workflow each activity must be processed in sequential order. However, to
maintain a level of flexibility for modeling processes, this is not a requirement for all workflows. A major benefit of the
workflow is that others can also use it for seeing a movie, too. The concept of reuse does not have any real significance
in this example, but the familiar analogy of movie-going helps to illustrate the principle of reusing code, where a

Order Tickets

Gain Admission

Figure 1-2.  Workflow for going to a movie

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Defining Requirements
Another benefit gained by modeling a process as a workflow is transparency, which grants the ability to see a process
as a two dimensional model, illustrating the logic within the process. Have you ever heard that a picture is worth
a thousand words? It’s the easiest way to communicate a process to others. Let’s look at modeling a workflow for a
business process that transfers money from one bank account to another. In this case, there are no other requirements
available for how this business process should work other than past experiences of transferring money. Figure 1-3
represents a workflow for transferring funds from a saving account to a checking account.

Access Savings
Account

Check Funds

Balance >$1 No Deposit Money

Yes

Transfer to Checking

Figure 1-3.  Workflow for transferring money

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Figure 1-3 demonstrates that funds will be transferred from a savings account (once it is determined that more
than one dollar is available within the account) into a checking account. If there is less than one dollar in the savings
account, the transfer of funds activity will not execute within the workflow.
Workflows can also be used to flush out additional requirements by gaining transparency into a business process.
For example, the bank might have additional rules around a mandatory minimal account balance that must be met
before a certain amount of money can be transferred. Also, what credentials must be authenticated against before
gaining access to the savings account?
I learned the importance of using workflows as a way to communicate requirements the first time I lead a team of
developers on a project. We decided as a team that we would use workflows as way to communicate requirements not
only to each other but with the client, too. This way we could make sure that the team had a clear understanding of
what the client needed.
This became a real world exercise one day when I hit a brick wall while trying to understand the requirements
being communicated to me from the client. For whatever reason, communicating verbally with the client was not
working, so I finally drew what I thought were the requirements. By drawing the steps and decisions around the logic

The most important part of creating software is not actually writing the code, as most developers tend to think.

Sometimes a software project’s sponsors (those who drive the initiative and the direction of the software project)

The best practice for developing software enlists the SDLC to guide the process of development. Table 1-2
represents the phases that are most commonly used within a SDLC. Each phase of the cycle is equally important and
depends on the previous phase. Therefore, the success for a software project primarily relies on how well each phase
is executed.

Table 1-2.  System Development Life Cycle


Phase Description
Planning Building a case for initiating a software project to exceed the goals for project sponsors.
Discovery Understanding the stakeholder’s business requirements so the project can be successful.
Analysis Gathering and documenting user requirements around how the software should work and
perform.
Design Defining and documenting both physical and logical architecture based on gathered user
requirements.
Testing Testing the software to make sure it functions the way it should from the client’s perspective.
Deployment Implementing the developed solutions within a production platform.

www.it-ebooks.info
Chapter 1 ■ Why Workflows

The first two phases, Planning and Discovery, focus on understanding stakeholder goals and how goals will be
met or even exceeded for the overall project.
The next phase, Analysis, focuses on gathering the requirements based on the stakeholder’s goals and how
the software will function and perform. Many development teams struggle with the Analysis phase. Projects fail
because development teams cannot communicate effectively or understand the process for defining requirements.
A development team can have the best engineers on it, but a failure to explain to them what needs to be built can be
catastrophic.
It is important to understand the types of requirements needed for architecting and developing a solution.
Software requirements can be broken up into four areas.
• Business requirements: Goals defined by project sponsors against which the success of the
project can be measured.
• User requirements: Functionality that must be implemented, allowing users to accomplish
their objectives.
• Functional requirements: Detailed representation usually provided by the technical leadership
to provide guidance through models and serve as the blueprints for how the software should
be developed collectively by the team.
• Quality of service: Standards agreed upon for how developed software should scale and
perform based on predefined metrics. These requirements are important when determining
the overall architecture for the solution.
The key objective gained through modeling a process is to understand and learn more about the process while
building a visual representation. Workflows are a natural tool for defining the different types of requirements
previously mentioned.

Model Driven Development


If you are consistently building models for the requirements gathered before writing any code for the software projects
you develop, you are applying model-driven engineering (MDE) or model-driven development (MDD)1. The models
created are then used to drive the business logic that is written as code.
If you prefer a more agile approach, there is also agile model-driven development (AMDD). It builds models but
applies an iterative approach for driving features of prioritized requirements to a deeper level, with iteration until all
functionality is flushed out. Critics of MDD feel that the models generated become stale or obsolete as processes change;
however, this is where BPM comes to the rescue by always adapting to changes within processes.
There are many tools available to model processes as workflows, and these give developers and architects the comfort of
easily building diagrams without having to leave Visual Studio. Before the rich diagramming features released with
Visual Studio 2010 (VS2010) Ultimate, developers had to look outside of Visual Studio for other tools for modeling workflows.
Most used Microsoft Visio (and rightfully so as Visio’s templates cover just about every possible workflow). However,
VS2010 Ultimate supports many diagrams, and these are covered in the next sections.

Component Diagrams
Component diagrams illustrate the tiers included within the physical architecture for a solution. Figure 1-4 illustrates
a rental service and the components that make up the rental service’s architecture. It also illustrates how the
components interact with each other. For instance, the ClientBrowser component’s HTTP interface requires services
from the rental site to be able to use the rental service.

Model-driven architecture (MDA) is an industry standard maintained by the Object Management Group (OMG).
1 

www.it-ebooks.info
ws

Figure 1-4. Component diagram for a rental service

Use Case Diagrams


Use cases model interaction between users (referred to as actors) and a logical grouping of functionality, sometimes
referred to as a subsystem. Figure 1-5 illustrates a Jeep parts web site where customers can order parts for their car.
First a customer must either create a profile or log in; then the customer gets extended functionality for creating
an order. Creating the order includes processing the order, which also includes processing payment and later even
refunding money if a customer wants to return part or all of an order.

www.it-ebooks.info
Chapter 1 ■ Why Workflows

<<subsystem>>
Jeep Parts Website

Create Order
<<extend>>

Login
Customer <<include>>
<<extend>>

Process
Create Order
Profile

<<include>>
<<subsystem>>
Payment Service

Process
Payment

Issue
Parts Employee Refunds

Figure 1-5.  Component diagram for a rental service

Class Diagrams
Class diagrams model relationships for objects defined with code. Entities defined within a business domain are
usually modeled in code to closely relate their role within the business. Figure 1-6 illustrates three classes that
make up a part order. There is a composite relationship between the order and the order line item because an order
contains an order line item. An order line item shows it has a relationship with an auto part based on the part’s ID and
indicates that there can only be one part ordered per line item; however, many order line items can have the same
part ordered.

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Figure 1-6.  Component diagram for a rental service

Sequence Diagrams
Sequence diagrams show how processes interact within a system. Sequence diagrams can illustrate a deeper
representation than a use case because they represent a full sequence for a process from beginning to end and
provide clarity regarding the interaction of the participants involved. Figure 1-7 illustrates four participants and how
they interact with each other when creating and processing a parts order.
• Customer
• Parts Order
• Inventory
• Credit Card Processing

10

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Figure 1-7.  Processing a parts order

Activity Diagrams
Activity diagrams model business logic and work well for discovering additional user requirements that might not
have been considered or thought through completely. Since activity diagrams can be used for modeling, they are a
great tool for building workflows. Table 1-3 explains the symbols that are available within Visual Studio for diagraming
activity diagrams.

11

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Table 1-3.  Activity Diagram Symbols


Diagraming Symbols Description
Initial Node Indicates the beginning of the workflow.
Activity Final Node Indicates the end of the workflow.
Action A step within a workflow that is primarily used to model activity.
Object Node Used to demonstrate transmission, buffering, filtering, and transformation of
objects.
Comment Used for commenting on the flow of the workflow.
Decision Node Indicates more than one flow driven by a decision within the workflow.
Merges more than one flow into one outgoing flow.
Divides one thread into more than one concurrent thread.
Joins concurrent threads into one outgoing thread.
Sends a signal to another system or activity.
Waits for a signal or event.
Action that calls another activity.
Action that calls an operation.
Allows data to flow into an action.
Allows data to flow out of an action.
Activity Parameter Node Parameters used to push data in and out of an activity.
Connector Connects the flow between activities.

Building an Activity Diagram


To build diagrams in Visual Studio you will need Visual Studio 11 Ultimate. Here are the steps for building diagrams in
Visual Studio 11 Ultimate.
1. Open a new instance of VS11 and create a new project by clicking File ➤ New ➤ Project.
Name the project “Apress.Example” and the solution “Apress.” It is common practice for the
solution and project names to be different so the hierarchy from solution to project is easily
recognized. By default the “Create directory for solution” checkbox is checked, which means
that the file directory for the solution will automatically be created. Within the Installed
Templates directory is a template called Modeling Projects. This is the type of project you will
use to building diagrams (see Figure 1-8).

12

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Figure 1-8.  Creating a new modeling project

2. Add a new diagram to the project by right-clicking Apress.Example.Diagramming within


the Solution Explorer. Add a new diagram by clicking Add ➤ New Item. Figure 1-9 shows
all of the diagrams that can be added to the project. Since activity diagrams are closely
related to the type of workflows you will be building using WF, select UML Activity
Diagram as the type of diagram to build. Change the name for the new activity diagram to
“CustomerOrder” and leave the extension as .activitydiagram.

13

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Figure 1-9.  Adding a UML Activity Diagram

Before you start building the workflow for processing a customer order, let’s walk through the logic of processing a
customer’s order. First, make sure the product ordered is in stock by checking the inventory.
• When a customer orders a product, there are two inventories that need to be checked.
• Local store
• Warehouse
• If the product is not in either of the inventories, get the product from the supplier’s inventory.
• Once the inventory is found, process payment.

■■Tip  When adding new items to a project, it is good practice to give the item a representative name. For instance, if
you add a new activity diagram for a customer order, you could name it “actCustomerOrder." (However, there’s no need to
do so in this case because its extension is descriptive enough.)

3. Click the Initial Node symbol within the toolbox (see Figure 1-10), and then click the
canvas for the activity diagram to add it as part of the diagram.

14

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Figure 1-10.  Activity diagraming activities

4. Click the Action symbol and then click the canvas of the activity diagram to add an action.
Double-click within the Action symbol so the name can be changed to “Check Store
Inventory.”
5. To connect the two symbols placed on the canvas, click the Connector symbol and then
hover the mouse over the Initial Node that was already added to the canvas. While the
mouse is hovering over the Initial Node, the mouse icon will change so the connection can
be anchored. Click once to anchor the connection arrow and then click the Check Store
Inventory action to add the connection.
6. Follow step 4 and add two more steps to the workflow. Name them “Check Warehouse
Inventory” and “Check External Supplier.” At this point the diagram should look like
Figure 1-11.

15

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Check Store
Inventory

Check Warehouse
Inventory

Check External
Supplier

Figure 1-11.  Activity diagram with steps

7. Next, add the logic that models the decisions for the workflow. Click the Decision
Node symbol and then click the canvas between the Check Store Inventory and Check
Warehouse Inventory steps. Follow the same steps to add a Decision Node between the
Check Store Inventory and Check External Supplier steps.
8. Logic and decisions can now be added between the existing steps by using the Connector
symbols. The connectors can be added quickly by clicking a Connector symbol and then
clicking the step and decision that should be connected (see Figure 1-12).

16

www.it-ebooks.info
Chapter 1 ■ Why Workflows

Check Store
Inventory

Check Warehouse
Inventory

Check External
Supplier

Figure 1-12.  Connecting steps and decision symbols

9. Descriptions for a Connector symbol can be added by clicking a connector arrow within
the workflow. Add the description, “Not In Inventory” for the Connector arrow between
the Decision symbol and the Action symbol Check Warehouse Inventory. This indicates
that if inventory can’t be found for a customer order, the next available inventory should
be checked.
10. If there is inventory from one of the locations in the workflow, based on the order for
checking inventory, the order gets processed. This type of logic can be modeled using the
Merge Node symbol. Add a Merge symbol to the workflow and place it on the right side of
the workflow.
11. Add connections between the existing Decision symbols and the Merge Node. Add one
more Connector symbol between the Check External Supplier step and the Merge Node
symbol.
12. Add the description “Inventory Exists” for each connection to the Merge Node symbol.
13. Now that you have a flow for processing a customer’s order when inventory exists, add
another Action symbol to the workflow and change its name to “Process Order.” Add
another Connector symbol between the Merge Node symbol and Process Order step. This
logic indicates that it is ok to process the order when the inventory exists. The finished
workflow should now resemble the complete business logic represented in Figure 1-13.

17

www.it-ebooks.info
ws

Check Store
Inventory

[Inventory Exists]

[Not In Inventory]

Check Warehouse
Inventory

[Inventory Exists]
Process Order

Check External [Inventory Exists]


Supplier

Complete process for customer order

  Tip Symbols can also be added to the workflow by right-clicking in the canvas of the workflow and selecting add.
a list of symbols will appear. Clicking any of them will automatically add them to the canvas.

Workflow Technology
There is much to gain when applying workflows with a software development methodology, but the real power of
workflows is building software from workflows. Technically workflow logic can still be done just by writing code, and
sometimes simply using code is the best solution, but there are obstacles that a workflow technology like WF can help
developers address.
Once a software project has completed the Analysis phase and has entered the Design phase, important decisions
have to be made about the technologies and architecture of the solution. This is why understanding the requirements
of a project are so important. Once developers understand the requirements, educated decisions can be made about
the technologies that will help the project be successful and the architecture the team will use together to implement
the solution.
WF was built to address certain requirements that were painstakingly complicated to implement.
• Long-running processes can be extremely complicated and may require to be executed
continuously or within a certain schedule. An example of a long-running process is ordering
something over the Internet and having the item shipped to a home address. Another example is
the service maintenance required for hardware that can span over years.

18

www.it-ebooks.info
Chapter 1 ■ Why Workflows

• Declarative workflows allow developers to build workflows visually that perform complicated
conditional logic and actions to reduce the amount of code and the complications for how
code is implemented.
• Business domain activities are custom activities that are built to focus on an organization’s
proprietary business.
• Rules-driven logic can also be added within the workflow or even modeled in a workflow.
It can be modified during runtime or while the workflow is being processed by an
application
• Human automation can integrate within the workflow so humans can make decisions for
how the logic of the workflow should flow.
• Service-oriented architecture can be applied by building services from workflow rather
than complex code.
• Workflow persistence provides the mechanism for releasing processing memory from
workflows that are idle from either waiting for events or by logic that dictates that the workflow
should go idle.
• Business processes monitoring provides automation for how information is gathered and
stored, about the logic being processed, and for pertinent data being monitored.

Summary
Workflows are great for modeling business processes. However, to really gain value from using them, they should be
applied with a methodology like business process management that helps guide the steps for modeling workflows and
focuses on continuously making improvement to processes so they do not become stagnant.
As developers model business processes with workflows, they come to understand requirements quicker and
can thus plan architecture and write code that is efficiently designed to meet or exceed the goals of stakeholders.
Workflows also provide the transparency for the complicated business logic needed within software. Tools like Visio
and Visual Studio ease the experience of designing and documenting workflows. By using a workflow technology
like WF, code can be represented as business logic that is abstracted through declaratively building workflows that
can be executed as code. Workflows running within an application can also be consistently changed at runtime and
throughout the lifespan of the business processes they model.
Now that you understand why it is important to use workflows during software development, the rest of the book
digs deeper into WF to show you how to gain the aforementioned benefits. Using WF within applications is truly a
more effective way of architecting and developing software. The next chapter will focus on the components that make
up the WF.

19

www.it-ebooks.info
Chapter 2

Introducing Windows
Workflow Foundation

Before jumping into how WF is used, it is important to understand the capabilities that it provides. This chapter gives
a brief introduction to WF and how it has changed over the years. Important components of WF will be introduced,
such as some of the out-of-box activities that model coding constructs and how activities use the workflow designer
for workflow orchestration or arranging activities within a workflow for modeling business logic. Next, WF data
modeling will be covered; this is how workflows receive, store, and return data. Each of the WF components covered
in this chapter will contribute to building a foundation of how WF works; if you’re already familiar with WF, this
chapter will serve as a review of important concepts.
WF establishes a software framework so developers can model code declaratively as workflows for supporting
event-driven and long-running processes. Although many developers consider WF to be solely a workflow engine
technology because of its built-in functionality for processing workflows, it is actually a software framework. That is,
it comprises a set of reusable code “building blocks” that can be assembled and extended to build custom software.
When a developer uses a software framework, he does not need to write software from scratch. Another example
of a software framework you are probably familiar with is the Microsoft .NET Framework; it contains a collection of
runtime libraries that can be used to develop software that is compiled to run within the Microsoft .NET runtime. Just
as with any new technology, the goal for WF is to address strategic shortcomings that were difficult to meet using a
“code only” approach.
As developers, we develop software by writing code. However, the code itself is not an ideal reference for
understanding its functionality because it is written in a special format and uses syntax that usually only developers
understand. If there is a lack of standards in documenting the code, it has to be “reverse engineered,” which is the
process of using code to understand what functionality it performs. This makes software code difficult to maintain,
depending on how many programmers were involved in developing the code and the different technologies and
architectures they used.
For example, with the release of ASP.NET Model View Controller (MVC), which introduced a completely different
architecture compared to ASP.Net and web forms, web applications can now combine both technologies, making
them harder to manage. Another example is the overlapping of data access technologies like ADO.NET, LINQ to
SQL, and Entity Framework. It is quite common to see applications that utilize all three of these technologies, making
the code very hard to manage. WF provides a natural interpretation of code by representing it through transparent
workflows as an alternative to just viewing code alone.
Most software is developed to process information quickly. Users create, read, update, and delete data through
manual events and the data is immediately processed. However, there are times when business processes execute
over days, months, and even years. These are defined as long-running processes, and implementing them through
software poses unique challenges. WF provides a framework (which would otherwise have to be custom built)
to address common design goals and characteristics associated with implementing long-running processes. WF
functionality includes memory management for persisting the current state or snapshot of a process and tracking
custom events for the duration of a process.

21

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Sometimes the complexity of business rules for processes within organizations can be difficult to comprehend,
which leads to a huge effort in implementing complicated logic within software. WF provides out-of-the-box
workflow activities as the basic building blocks for a workflow that processes a unit of work and defines the flow of
logic within the workflow. Workflow activities can be used together within a workflow for modeling the complexity
of logic declaratively, making it easier to implement. Workflow activities can also be custom built to define logic for a
particular business domain.
Finally, because business processes usually change many times over the period of an application’s life span, it
is hard to make changes to the logic within an application to adapt to business process changes. Traditionally, it is a
good practice for developers to implement an architectural pattern called “layering” that promotes the segregation
of code into designated layers. For example, code that performs data access is usually separated out into its own
“Data Access” layer. Business logic is separated out into a “Business” layer. By separating out related code by its
functional role, managing code becomes easier because changes are isolated within the layer; this reduces the
number of regression bugs. WF takes the layering approach further because it layers business logic through workflows
and reduces the responsibility for the application down to simply hosting the workflows so the workflow can execute.

As WF has matured over the years, so has the integration of workflows and Windows Communication Services

Today we see another trend emerging where industry leaders like Microsoft and Amazon provide the hardware
and software infrastructure that is strategically located within geographic locations around the world, so the same
infrastructure does not have to be provided locally within data centers, nor does it require leasing hardware for
running software applications. This trend is called cloud computing and the advantage of subscribing to cloud
computing is that you only have to pay for the memory space for holding data and processing utilization for the
software applications running within the cloud. Microsoft’s cloud solution is a technology called Azure. Azure
provides the infrastructure and server technology so developers can focus on what they do best, which is developing
the business logic. When developing for the cloud, developers no longer have to worry about setting up, configuring,
and supporting servers. Instead, cloud computing providers extend service level agreements (SLAs) for handling the
managing the infrastructure and providing a level of uptime for the servers so developers can focus on deploying
software and configuring how applications will perform and run. Since cloud computing was a new concept,
most developers felt that it seemed a great place to host small applications but not intense line-of-business (LOB)
applications. After all, what businesses were going to allow their LOB applications to be run remotely, without the
protection of a local or remote private data center?
At first, the most seasoned developers were concerned that they would have a hard time or even fail trying to
move or write custom software for clients in the cloud. The fear receded as Microsoft provided more resources for
Azure, not only for its infrastructure but also its technology. So today we can take advantage of quickly building
workflows that run for long periods of time and expose them as services that are pushed out to the cloud via WCF,
without first having to worry about setting up the hardware or configuring the servers used to host them.

WF History
Windows Workflow Foundation made its first appearance in September of 2005 at a Microsoft-hosted event called
Professional Developers Conference (PDC). It was announced as an extension of the next release of Microsoft’s

22
4
www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

.NET Runtime 2.0. Over the years there have been two other releases within the .NET Framework: 3.0 and 3.5.
We commonly refer to these releases as WF3.x. Microsoft was planning for the next release of WF within the .NET
Framework 4.0, but they ultimately decided to do a complete planning overhaul of WF. However, the retooling of
WF4 was not as painful as expected. On the contrary, it was much easier than originally learning WF3.x, but there
was much confusion around the why Microsoft decided to do a complete rewrite for such a young technology as WF.
Below are the main factors that Microsoft considered important enough to rewrite in WF4:

• Feedback from developers struggling with the complexity for building and hosting workflows
in WF3.x.
• Release of Visual Studio 2010 and the .NET 4.0 Framework, which was one of the most
significant releases in Visual Studio history, so WF could take advantage of the .NET runtime’s
new features.
• Improved performance by re-engineering WF’s runtime engine.

Even though Microsoft decided to rewrite WF, the WF product team still had the responsibility of maintaining
interoperability with the existing software that was written using the relatively young WF3.x framework. Therefore the
goal for the rebuild also needed to include the functionality from the previous version of WF3.x, with only a couple of
“acceptable” caveats around interopting WF4.0 with existing implementations of WF3.x.
Many developers questioned the rewrite of WF4.0 and felt the pain for retooling from WF3.x. However,
after cracking open the WF4.0 box, they quickly discovered that the WF Team did a great job in listening to the
development community and built a much leaner WF for better performance gains and richer functionality for
building workflow solutions, plus an easier learning curve for a better developer experience.

Platform Update 1
And then there’s the Microsoft .NET Framework 4 Platform Update 1, which was released in April of 2011. It was the
last major release in between WF4 and WF4.5, and the majority of the features came from customer requests around
WF and building state-machine workflows. Building state-machine workflows was a functionality that the WF team
omitted with the release of WF4 because they figured state-machine workflows were no longer needed with the
release of flowchart-style workflows. The Microsoft .NET Framework 4 Platform Update 1 contains three packages
(see Figure 2-1):
• Microsoft .NET Framework 4 Platform Update 1 (KB2478063), which loads the runtime files
for the platform update.
• Multi-Targeting Pack (KB2495638, which loads reference assemblies and IntelliSense files for
the platform update.
• Design-time Package for Visual Studio 2010 SP1, which installs the other packages and
configures Visual Studio 2010 SP1 with the new targeting profiles and IntelliSense, plus it loads
state-machine activities.

23

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-1.  Installing Platform Update 1

Once Platform Update 1 is installed, the next time VS 2010 is opened to create a new workflow project, there will
be two new choices for frameworks that a project can be compiled against (see Figure 2-2).

Figure 2-2.  Configuring Platform Update 1 for a new project

24

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

If a workflow solution already exists, after loading the workflow solution in VS2010, right-click on the solution
and change the properties for the Application tab, as shown in Figure 2-3.

Figure 2-3.  Configuring Platform Update 1 for an existing project

The only difference between .NET Framework 4 Platform Update 1 and .NET Framework 4 Client Profile Platform
Update 1 is the Client Profile is a smaller set of assemblies than what .NET 4.0 provides, so it is much leaner when
running within client applications.
Once Platform Update 1 is chosen for the project to be compiled against, a new category of out-of-box activities is
included for building state-machine workflows (see Figure 2-4).

Figure 2-4.  State machine activities


25

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Now that workflows are making their way into Azure, another key feature that was released with the platform
update was support for the database that handles persistence for workflows and its compatibility when running
within SQL Azure. SQL Azure is a relational database that runs within Azure’s cloud for storing data.
Obviously, with the release of WF4.5 and the new .NET Framework 4.5, this platform update is irrelevant because
all the capabilities it provides are already included within WF4.5; however, I wanted to make sure it was covered as
part of the complete history of WF.

WF Components
There are different components within WF that play significant roles in executing and managing workflows:
• Workflow Runtime
• Activities
• Workflows
• Workflow Designer
• Workflow DataModel
• Persistence
• Monitoring

of the WF runtime is to schedule and coordinate workflows asynchronously or within its own process. Compared
to the WF runtime in WF3.5, there are significant changes that are focused around improving the WF runtime’s
performance. Also, for WF3.5, the WF runtime was required to be hosted within a .NET executable. This changed
with WF4 because it was not practical for some scenarios. For instance, setting up the WF runtime within testing
environments was challenging. In WF4, directly calling the WF runtime for hosting workflows is no longer necessary.

Workflow Hosts
There are now better options for hosting workflows, ones that are tailored around the characteristics of the solutions
hosting the workflows. Here is a list of some additional options for managing the WF runtime at an abstracted level
in WF4:
• WorkflowApplication: Used for hosting workflows within .NET applications that take full
advantage of the workflow runtime for managing a workflow instance asynchronously.
WorkflowApplication allows applications that host workflows to manage the execution like
persisting or cancelling of a workflow and subscribe to notifications on a workflow instance
lifecycle. A .NET application can kick off a workflow using WorkflowApplication and then
continue processing on a different thread.
• WorkflowServiceHost: Used for managing endpoints and configuration for hosting workflows
as WCF services.
• WorkflowInvoker: A sought-after change from the community for easily hosting workflows
synchronously, just like a call to a method. Workflows can be spun up quickly and processed on
the same thread as the hosting application. WorkflowInvoker is a great way to unit test activities
and workflows before they are implemented; however, it can also be used to run asynchronously.

26

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

One of the main functions that the WF runtime performs is managing a workflow’s life cycle. When a
workflow is spun up within the WF runtime, it creates an instance of that workflow called a WorkflowInstance.
A WorkflowInstance is a single instance of a workflow running within the WF runtime. So if an e-commerce site uses
WF to process its orders, each order that is created will create a WorkflowInstance. As workflow instances are created
one right after the next, it is important to know the state they are in at any given time while they are being processed.
This is done by subscribing to events through the WF runtime as the lifecycle of the WorkflowInstance changes over
time. The events the WF runtime provides are shown in Table 2-1.

Table 2-1.  WorkflowInstance Events


Event Description
WorkflowCreated Occurs when workflow instance is created.
WorkflowStarted Occurs when workflow instance is started.
WorkflowLoaded Occurs when workflow instance is loaded into memory.
WorkflowAborted Occurs when a workflow instance is aborted.
WorkflowIdled Occurs when workflow instance enters the idle state, like waiting on an external event.
WorkflowUnLoaded Occurs when workflow instance is unloaded into memory.
WorkflowSuspended Occurs when a workflow instance is suspended.
WorkflowResumed Occurs when execution of a workflow instance is resumed following a suspension.
WorkflowTerminated Occurs when workflow instance is terminated.
WorkflowPersisted Occurs when the state of a workflow instance is persisted, removing it from memory.
WorkflowCompleted Occurs when workflow instance is completed.

Activities
WF activities are the basic unit of work and are used to execute code within a workflow. Understanding the
functionality that each activity provides within WF is half the learning curve. By getting to know each activity, the skill
for building efficient workflows becomes easier. WF4.x activities are categorized into 10 areas:
• Control Flow: Models how business processes can flow within a workflow.
• Flowchart: Provides the most transparency for modeling decision-making processes.
Flowchart activities were introduced in WF4.
• State-machine: Models possible transitions between the states of a workflow indicating human
interaction and events within workflows.
• Messaging: Provides communication functionality for building workflows that are exposed as
services and communicate over transport protocols.
• Runtime: Provides instructions to the WF runtime for how to manage workflow behavior.
• Primitives: Provides general functionality around execution.
• Transaction: Provides functionality for allowing activities to execute within a transaction so
unwanted results can be reversed.
• Collection: Provides basic functionality for managing data represented as collections
within a workflow.

27

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

• Error Handling: Provides functionality for hardening workflows by providing logic for
managing unanticipated exceptions.
• Migration: Provides functionality for allowing workflows built using WF3.x to execute in WF4.
Tables 2-2 through 2-11 list the activities included in each of these areas.

Table 2-2.  Control Flow Activities


Activity Description
DoWhile Keeps executing child activities until the conditional expression is met.
ForEach Executes child activities for each iteration of an enumerable collection.
Executes child activities within a single branch based on a condition.
Executes more than one branch of child activities at the same time asynchronously.

asynchronously within each iteration.


Used to receive events outside of a workflow and serves as the trigger for when events
occur. Usually a delay is used to wait for an external event for a given time.
Branches for a Pick activity that contain a trigger for handing external events and an Action
activity for processing logic.
Holds a collection of child activities that are executed sequentially.
Executes child activities based on a predetermined expression.
While Continuously executes child activities while a condition is met.

Table 2-3.  Flowchart Activities


Activity Description
Flowchart Provide the canvas for modeling a flowchart workflow.
FlowDecision Occurs when workflow instance is started.
FlowSwitch<T> Executes child activities based on a predetermined expression.

Table 2-4.  State Machine Activities


Activity Description
StateMachine Provides the canvas and a default state for a state-machine workflow.
State States represent the transitions that can be made within a state-machine workflow.
Child activities can be added to the state activity when a state activity becomes active and
when there is a transition to another state.
FinalState Represents the last state for a state-machine workflow. Its icon is different from the other
state’s icons, so that it how you identify it as the final state.

28

www.it-ebooks.info
Chapter 2 ■ IntroduCIng WIndoWs WorkfloW foundatIon

Table 2-5. Messaging Activities


Activity Description
CorrelationScope Provides correlation management for child activities. Correlation is way to relate
messaging activities.
InitializeCorrelation Usually correlation is established when messages are sent or received; however,
sometimes correlation needs to be established before actually sending or receiving
a message.
Receive Receives incoming messages sent to a workflow.
ReceiveAndSendReply Follows the same pattern as a common web service that receives an incoming
message and produces a reply that is sent back.
Send Sends an outgoing message to a service.
SendAndReceiveReply Correlation is used to send an outgoing message to a service and anticipates
receiving a reply from the external service.
TransactionReceiveScope Allows message received to be processed within transactions.

Table 2-6. Runtime Activities


Activity Description
Persist Tells the WF runtime that the workflow should be persisted and removed from memory.
TerminateWorkflow Tells the WF runtime that the workflow should terminate.

Table 2-7. Primitives Activities


Activity Description
Assign Assigns values to objects within a workflow.
Delay Temporary pauses execution of a workflow based on a predetermined amount of time.
InvokeMethod Allows existing code that was written outside of the workflow to be executed within a workflow.
WriteLine Writes a defined string of text that is written to a console window.

Table 2-8. Transaction Activities


Activity Description
CancellationScope Allows cancellation logic to be executed to reverse unwanted execution results.
CompensableActivity Defines work that needs to be done during normal execution of logic and work that
needs to be done to compensate or reverse completed execution logic.
Compensate Allows the workflow to start compensation.
Confirm Allows confirmation to occur for normal or compensated work.
TransactionScope Allows work that is executed within child activities to be executed within a transaction so
it can be rolled back if needed.

29

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Table 2-9.  Collection Activities


Activity Description
AddToCollection<T> Adds objects within a predefined collection.
ClearToCollection<T> Clears all objects within a predefined collection.
ExistsInCollection<T> Checks to see if an object is contained within a predefined collection.
RemoveFromCollection<T> Removes an object contained within a predefined collection.

Table 2-10.  Error Handling


Activity Description
Rethrows or raises an exception that occurs within a workflow.
Throws or raises an exception that occurs within a workflow.
Allows unanticipated exceptions to be trapped and handled.

  Migration

Provides a place for WF3.x child activities to execute within a workflow built in WF4.

Let’s get a little more familiar with the out-of-box activities that are most commonly used. Since workflows are
a visualization tool, I included a simple visual representation for the out-of-box activities used the most for building
workflows. You will see that the logic patterns follow the same coding patterns used in writing code—except with
WF you can declaratively build the logic, rather than using code. Therefore, there is a common pattern for each
implementation of a workflow that makes managing workflows much easier than managing code. Activities in WF4.5
also allow C# expressions so the expressions used within the book will focus on using C# instead of VB expressions.

WriteLine Activity
The WriteLine activity works well for pushing text to readable sources like the console window to notify users of a
workflows progress. The WriteLine activity can also be used for debugging other activities and workflows, which will
be demonstrated while walking through some of the out-of-box activities (see Figure 2-5).

Figure 2-5.  Simple WriteLine activity for displaying text

DoWhile Activity
This activity models the code statement DoWhile and has a Condition property that must use an expression resulting
in either a True or False value. Expressions used in WF4 must be VB expressions, so instead of using a C# expression
like 1==1, a VB expression expressed as 1=1 is required. Figure 2-6 illustrates a DoWhile activity that uses

30

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

the C# expression 1==1. While the Condition property of the DoWhile activity is met, the WriteLine activity that is
contained within the DoWhile activity will write “Execute Me” to the console window as an infinite loop because 1==1
will always result in True. The DoWhile activity is guaranteed to execute its child activities as least once and thereafter
until the condition results to False.

Figure 2-6.  DoWhile activity

ForEach<T> Activity
The ForEach activity models the code statement ForEach. It iterates through an object that implements the
interface IEnumerable and executes child activities for each iteration. Figure 2-7 shows the activity iterating
through a collection of customers and using the WriteLine child activity with its Text property set to
cust.FirstName for writing each customer’s FirstName to the console window. The TypeArgument property defines
the type of object that will hold the value for each iteration. Figure 2-7 has its TypeArgument set to Customer.

Figure 2-7.  ForEach activity


31

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

If Activity
The If activity models the code statement If and uses an expression condition to decide the workflow’s flow.
Figure 2-8 uses the condition 1==1, and since this will always result to True, the WriteLine child activity will write
“Condition Met” to the console window each time.

  If activity

Parallel activity provides a standard way of implementing a logical pattern for scheduling more than one

Parallel activity, the workflow activities will be executed on the same thread as
the workflow. Workflow activities within a Parallel activity are scheduled to execute, and multiple activities can
be scheduled in sequence. The WF runtime schedules the execution of activities within a Parallel activity, so the
activities will execute in an order from left to right. When a sequence of workflow activities are used within branches
of a Parallel activity, the first activity within the Sequence activity for each branch is executed from
left to right and then the same pattern is used for each activity thereafter. Figure 2-9 shows two
WriteLine activities that will be executed, so “Execute 1” will be written first and then “Execute 2” will be written
next to the console window. If execution needs to stop during the execution of the Parallel activity, there is also a
CompletionCondition property that accepts an expression for when the activity should stop executing.

Figure 2-9.  Parallel activity

ParallelForEach<T> Activity
The ParallelForEach<T> activity models the code statement ForEach and iterates through an object that implements
the interface IEnumerable and executes child activities for each iteration, except it processes each iteration
asynchronously. However, unless the InvokeMethod activity, messaging activities, or workflow activities that are
built using the base object AsyncCodeActivity are used within the Body section of the ParallelForEach activity,

32

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

the same thread processing the workflow will be used for processing the ParallelForEach activity. This pattern is
appropriate when an iteration could cause the workflow to go idle; however, iterations after it can’t wait and still
need to be processed. Figure 2-10 shows the workflow activity iterating through a customers collection and using one
WriteLine child activity with the expression "Started " + cust.FirstName to indicate the start of an iteration.
A Delay activity is used to cause the workflow to go idle and another WriteLine activity with the expression “Finished”
+cust.FirstName is used to indicate to the console window when each of the iterations finishes. When the Delay
activity is hit and the iteration goes idle, another iteration is then executed.

Figure 2-10.  ParallelForEach activity

Pick Activity
The Pick activity is a way for workflows to handle external events. This pattern is appropriate when a workflow is
waiting on feedback and goes idle. Figure 2-11 shows the activity waiting for feedback using a Delay activity, which
is set to wait for 30 seconds. The Pick activity also contains a custom Bookmark activity that listens for a predefined
external event. If the 30-second timer runs out, the child WriteLine activity writes “External event never happened”
to the console. If the external event is received, the other WriteLine child activity writes “External event happened”
to the console.

33

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

  Pick activity

PickBranch activities can be added to the Pick activity for handling additional external events, as
 2-12.

Figure 2-12.  PickBranch activity

Sequence Activity
The Sequence activity is considered a composite activity, which means it is used as a container for holding child
activities. Each child activity within a Sequence activity executes in sequence and is primarily used as the base for

34

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

sequential style of workflows. The Sequence activity in Figure 2-13 has three WriteLine child activities, and each one
executes in the order from top down, writing “Process 1”, “Process 2”, “Process 3” to the console.

Figure 2-13.  Sequence activity

Switch<T> Activity
The Switch activity models the code statement Switch and uses an object type as a prescribed condition to decide
the workflow’s flow. In Figure 2-14, the condition is a integer value of a CustomerType. When the Case statement is
matched, child activities within the Case are executed. If a Case cannot be matched, there is also a Default flow that
executes its child activities. For example, if CustomerType=1, the WriteLine child activity will write “Executing Case 1”
to the console window. If there is no match, another WriteLine activity writes “No Cases match”.

Figure 2-14.  Switch activity

35

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

While Activity
The While activity models the code statement While. It has an expression condition of 1==1 for demonstration
purposes, which means it is an infinite loop; for each loop that 1==1, the child activity WriteLine will write
“Execute Me” to the console window. The While activity will not execute its child activities unless the condition
results to true. This is what differentiates it from the DoWhile activity (see Figure 2-15).

  While activity

The Flowchart activity sets the canvas for modeling detailed decision-making flows. After adding a Flowchart activity
to the workflow, it sets the stage for modeling a flowchart workflow by providing a starting point (see Figure 2-16).

Figure 2-16.  Flowchart activity

FlowDecision Activity
The FlowDecision activity provides the magic for building flowchart workflows by providing a rich visualization
for deciding conditional flow of a workflow. After adding the Flowchart activity, an expression condition can be
built that results in true or false. The FlowDecision activity in Figure 2-17 shows that if the condition is false, a
WriteLine activity is executed that writes “Condition is False” to the console window; if true, another WriteLine
activity writes “Condition is True” to the console window. When adding a FlowDecision activity, make sure to
connect it to the Start activity.

36

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-17.  FlowDecision activity

FlowSwitch<T> Activity
The FlowSwitch activity is much like the Switch<T> activity; however, it represents the visual flow much better and
can only be used within the canvas of flowchart workflows. After adding a Flowchart activity, an object type is used
as a prescribed condition that is matched to execute a single flow. If the predefined condition can’t be met, it also has
a Default flow that is executed. Figure 2-18 shows that if the condition matches “0” or “1”, the WriteLine activity is
executed and writes to the console window; if the condition does not match, the Default flow is executed and writes
to the console window.

Figure 2-18.  FlowSwitch<T> activity

37

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

StateMachine Activity
The StateMachine activity sets up the workflow canvas as a state-machine style of workflow and includes a Start and a
default state activity. Additional state activities can be dragged to the designer as they are needed, like the State2 and
FinalState activities illustrated in Figure 2-19.

Figure 2-19.  StateMachine activity

StateMachine activities can be drilled into deeper by double-clicking on them to reveal Entry and Exit triggers,
as illustrated in Figure 2-20. Transitions can also be created to other states to model human interaction with
the workflow.

38

www.it-ebooks.info
Chapter 2 ■ IntroduCIng WIndoWs WorkfloW foundatIon

Figure 2-20. State activity

FinalState Activity
The FinalState activity serves as the last state executed within a state machine workflow. Child activities are
executed within it on the Entry trigger for the state (see Figure 2-21).

Figure 2-21. FinalState activity

Assign Activity
Figure 2-22 illustrates the Assign activity used for assigning values to other objects within a workflow. It shows an
output argument that will be returned from the workflow after it has been assigned a new Customer object. The
output argument can then be returned to the application hosting the workflow through the WF runtime.

39

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

  Assign activity

In addition to the out-of-box activities, activities can also be custom built to handle business logic that can’t be
modeled using existing workflow activities. Note that out-of-box activities are not designed to be inherited from using
code because their classes are sealed, so they cannot be used as a base class. The next chapter will explain more
about building custom activities; until then, let’s take a quick tour around some of the workflow activities WF provides.

Workflows
Workflows in WF are an orchestration of work modeled from business processes, and there are three different types of
workflows that can be built within WF for modeling different types of business process flows based on characteristics
of a business process. The three types of workflows within WF4 are
• Sequential: Models flows that are procedural in nature and follow a predictable pattern
of starting from the top and following sequential order of steps down. Characteristics of
sequential workflows include
• Predetermined flow
• Systematic
• Lack of flexibility
• State-machine: Models flows that cannot be predicted and represent the current state of
execution at any given time, while relying on external events based on human decisions
for guiding its flow through transitions among other possible states. Characteristics of
state-machine workflows include

40

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

• Flexible
• Models human behavior
• Reactive
• Flowchart: Models a decision-making process flow that is neither predictable nor requires
external events based on human decisions for processing. Characteristics of flowchart
workflows include
• Transparent decision options
• Highest level of transparency for decision making
• Natural flow back to previously executed workflow activities
Sequential and state-machine workflows were introduced in WF3.x; however, the flowchart style of workflow was
a new feature introduced in WF4. Modeling workflows as flowcharts removed the boundaries of having different types
of workflows within WF because it is more natural way for modeling processes. Flowchart workflows provide a higher
level of transparency because the goal of a flowchart is to flush out all possible flows within a process. This stems back
to the concepts around using workflows, as demonstrated in Chapter 1. WF can now utilize the features of flowchart
workflows by executing work based on detailed decision-making flows modeled within the workflow.
To see an example of each of the three workflows, let’s model a process around the legal age for voting within the
United States.
The example in Figure 2-23 shows how this logic could be modeled within a sequential workflow. Figure 2-24
shows the voting process as a transitional state using a state-machine workflow.

Figure 2-23.  Voting modeled as a sequential workflow


41

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

  Voting modeled as a state-machine workflow

Figure 2-25 shows the logic within the transition Voted, which can be seen by double-clicking on the transition.

42

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-25.  State-machine Voting transition logic

Figure 2-26 shows a more natural way of modeling the logic for the legal age of voting; however, in some cases it
will make more sense to use sequential or state-machine workflows for modeling business processes. Understanding
the characteristics of the process that needs to be modeled is key so they can be matched up with the characteristics
mentioned earlier for the type of workflow that should be used to model the process.

43

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

  Voting modeled as a flowchart workflow

Defining Workflows
Workflows in WF4.x can be represented as XML or defined solely using code. Listing 2-1 shows how to build a
workflow using C#.

Listing 2-1.  Sequentially Modeled Workflow Represented as C# Code


static void VotingWorkflow()
{
Variable<int> varAge = new Variable<int>
{
Name = "varAge",
Default = 18
};

Activity wfVoting = new Sequence
{
Variables = {varAge},
Activities =
{
new ifIf
{
Condition = new InArgument<bool>((e)=>varAge.Get(e)>=18),
Then = new WriteLine

44

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

{
Text = "You can vote!"
},
Else= new Sequence
{
Activities =
{
new WriteLine
{
Text = "Sorry, too young to vote"
},
new TerminateWorkflow
{
Exception = new InArgument<Exception>((ex)=>new
ApplicationException("Too Young"))
}
}
}
},

new WriteLine
{
Text = "Thanks for voting"
}
}
};

WorkflowInvoker.Invoke(wfVoting);
Console.Read();
}

This is a different approach than the originally defined workflows represented in WF3.x. WF3.x workflows
required the combination of both code and XML when authored. WF4.x workflows can either be built from code as
represented in Listing 2-1 or built using Extensible Application Markup Language (XAML), which also introduces a
very powerful feature within WF environments. XAML files are XML-formatted files defined by Microsoft for working
directly with objects within the .NET Framework. This allows workflows to be authored using tools outside of WF and
executed within any WF environment. Listing 2-2 represents the XML that was used to build the sequential voting
workflow in Figure 2-23.

Listing 2-2.  Sequentially Modeled Workflow Represented as XML


<Activity mc:Ignorable="sap" x:Class="Apress.Example.Chapter2.WorkflowType.wfVotingAge"
sap:VirtualizedContainerService.HintSize="531,618" mva:VisualBasic.Settings="Assembly references
and imported namespaces for internal implementation" xmlns="http://schemas.microsoft.com/
netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.
VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml"
xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:s4="clr-namespace:System;assembly=System.
ServiceModel" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-
namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.
microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.

45

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.
ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core"
xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-
namespace:System.Data;assembly=System.Data" xmlns:sl="clr-namespace:System.Linq;assembly=System.
Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/
winfx/2006/xaml">
<Sequence sad:XamlDebuggerXmlReader.FileName="\\WIN-83I06C1NH3R\SharedWithVM\Apress\Apress\
Apress.Example.Chapter2.WorkflowType\wfSequentialVoting.xaml" sap:VirtualizedContainerService.
HintSize="491,578">
<Sequence.Variables>
<Variable x:TypeArguments="x:Int32" Name="varAge" />
</Sequence.Variables>
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<If Condition="[varAge &gt;= 18]" sap:VirtualizedContainerService.HintSize="469,353">
<If.Then>
<WriteLine sap:VirtualizedContainerService.HintSize="211,247" Text="You can vote!" />
</If.Then>
<If.Else>
<Sequence sap:VirtualizedContainerService.HintSize="233,247">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<WriteLine sap:VirtualizedContainerService.HintSize="211,61" Text="Sorry, too young
to vote!" />
<TerminateWorkflow Exception="[New ArgumentException(&quot;Too young&quot;)]"
sap:VirtualizedContainerService.HintSize="211,22" />
</Sequence>
</If.Else>
</If>
<WriteLine sap:VirtualizedContainerService.HintSize="469,61" Text="Thanks for voting!" />
</Sequence>
</Activity>

■■Note State-machine workflows were not included in the release of WF4. The WF team thought that flowchart
workflows would be a better alternative for modeling state-machine processes. However, a huge developer response for
adding the style of state-machine workflows into WF4 eventually convinced the WF team to add them to WF4 with the
release of the .NET 4 platform update. State-machine workflows are included within WF4.5.

Workflow Designer
Significant improvement was made to the workflow designer in Visual Studio 2010, not only visually but in
performance as well. The workflow designer uses Windows Presentation Foundation (WPF) technology for a much

46

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

richer experience of designing workflows. The workflow designer provides the canvas for building workflows. Let’s get
a better look at the workflow designer by seeing it firsthand in Visual Studio 2012.

WORKFLOW DESIGNER WALKTHROUGH

1. Open up Visual Studio 2012 and create a new Workflow Project. The next pop-up screen
asks the type of project.
2. Make sure that .NET Framework 4.5 is chosen for the framework for the project. If you
choose .NET Framework 3 or 3.5, you will see different project types then represented in
Figure 2-1 for creating a new project using the original workflow designer.

Figure 2-27.  Creating a workflow console application

Because the project is a workflow console application, the project by default provides everything it needs for
building and executing a workflow. If .NET Framework 3 or 3.5 is selected, the designers for WF3.x also have a
template called sequential workflow console application and state machine workflow console; however, each
project specifically builds sequential or state-machine workflows (see Figure 2-28 and 2-29).

47

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-28.  WF3.x sequential workflow

Figure 2-29.  WF3.x state machine workflow

3. View the Solution Explorer, and open the file Program.cs by double-clicking on it. The
code in Listing 2-3 shows that the workflow console application template uses the
WorkflowInvoker to easily process the workflow, much like a call to a method in code.
The WorkflowInvoker processes the workflow by passing it the workflow object built
using the designer as an argument.

Listing 2-3.  Default Code within Program.cs, Used for Executing a Workflow
using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;

namespace WorkflowConsoleApplication1
{

class Program
{
static void Main(string[] args)
{
WorkflowInvoker.Invoke(new Workflow1());
}
}
}
48

www.it-ebooks.info
Chapter 2 ■ IntroduCIng WIndoWs WorkfloW foundatIon

figure 2-30 shows that the designer canvas in Wf4.x is empty because there is no longer a boundary between
templates for different workflows like sequential and state-machine workflows in Wf3.x. Combining different
types of workflows in Wf3.x was a difficult task, but in Wf4.x, different types of workflows can be combined
within the design of one workflow.

Figure 2-30. Creating a workflow console application

4. expand the primitives tab within the toolbox, and click and drag the WriteLine activity to
the designer canvas.
5. add “hello World” to the WriteLine activity’s Text property. If you run the project at this
point, the only thing that might happen is maybe a console window will pop up and then
go away as quickly as it appeared. this is because of the code in the Program.cs file
(see figure 2-31).

49

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-31.  Adding “Hello World” to the WriteLine Activity

6. Open the Program.cs file and add Console.Read(); right under the WorkflowInvoker.
Invoke The code within the Main method should look like Listing 2-4.

Listing 2-4.  Adding Console.Read()


static void Main(string[] args)
{
WorkflowInvoker.Invoke(new Workflow1());
Console.Read();
}

Next, compile the project to make sure everything compiles. Press F5 to run the workflow. The console window
will stay open, and within the window it should read, “Hello Workflow”. The line of code Console.Read() tells the
program to wait for feedback from the keyboard before closing.
7. Close the console window by making sure it is active and pressing the Enter key.

50

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-32.  “Hello Workflow” Console

This exercise demonstrated the workflow designer and showed how to build a basic workflow and have it run
within a console application. The example also showed how a workflow is hosted using the WF runtime by
providing the code for calling a workflow using the WorkflowInvoker host. This type of project can be used for
building workflows to make sure they are working as expected.

WF Data Model
In order for workflows to process information, the workflow obtains data in three different ways.
• When the workflow is started: Data is supplied to the workflow as it is started within the WF
runtime. Input arguments are passed to the workflow through the WF runtime, and the name
for each input argument must be explicitly assigned to an argument within the workflow so
data can be passed to the workflow.
• Read into the workflow from external events: Workflows can be passed data through external events
that occur at random times. An example is a workflow that processes orders. An order-processing
workflow is started and then waits to process orders as they are made by customers.
• Received from the workflow through an external event: Workflows can have predefined logic for
checking other data sources to see if data exists for being processed. Workflows can read data
from pre-configured databases and make calls to services.
Just as any other programming language, WF uses variables, arguments, and expressions for processing data and
communicating data with the WF runtime (see Table 2-12).

Table 2-12.  Workflow Data Model


Data Model Description
Variable Used as storage areas defined by object type and optionally the name. Values for variables are
assigned at runtime and are stored as a part of the state of a workflow instance. Variables are
declared as part of the definition of a workflow.
Argument Controls how data flows by accepting and returning data in and out of an activity.
The specified directions are In, Out, or InOut.
Expression An activity with an elevated return value used in argument bindings.

51

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

WORKFLOW DATA MODEL

Arguments, variables, and expressions can be built into a workflow using the WF designer. Using the same
project from the workflow designer walkthrough, this exercise will walk through how to add an Age argument to a
workflow and how to add a WF expression using the argument.
1. Open the project used in the workflow designer walkthrough. Within the Solution Explorer,
double-click the workflow called Workflow.xaml. By default, the workflow designer
will open, revealing the workflow and the single WriteLine activity you added to
the designer.
2. At the bottom of the workflow designer you will notice the words “Variables,”
“Arguments,” and “Imports”. These are actually tabs, so by clicking on Arguments, the
user interface expands for creating a new workflow argument. It is good practice to
use a naming convention for identifying arguments by adding ”arg” to the beginning of
the argument name assigned. The naming convention could be taken a step further by
indicating the flow of data for the argument. For example, instead of using “arg”, use
“argIn” as the predicate for the name when defining an argument.

Figure 2-33.  Creating an argument

52

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

3. Click within the Name textbox for the new argument and add argInAge for the argument
name. The direction for the new argument defaults to In, which is the direction of the
argument needed for passing data into the workflow from the application hosting
the workflow.

■■Tip The process for removing unwanted arguments is not really clear; however, arguments can be removed once
they are highlighted within the argument editor by pressing the Delete key.

The argument type defaults to String and since the new argument is meant to pass in an age, the type needs to
be changed to an integer. Click on the argument type drop-down and select Int32. If the logic in the workflow
called for a default age to be passed in, that would be set by using the Default value textbox.

Figure 2-34.  Argument types

Now that the workflow has an argument for receiving data, simple logic using a C# expression needs to be added
to the workflow that uses the argument to write a message to the console window from the workflow.
4. Click the WriteLine activity within the workflow, and the Properties window for the
activity will display. The WriteLine activity has a property called Text that will be used
for building the expression.
5. Click the ellipses(“…”) button on the Text property for the WriteLine activity.
An Expression Editor will appear that allows an expression to be built for the activity
(see Figure 2-35).

53

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-35.  WriteLine Activity Properties

6. Change the text expression from “Hello Workflow” to the following, as shown in Figure 2-36:
String.Format("Hello Workflow I am {0} years old!", argInAge)

Figure 2-36.  Expression Editor

This code formats the string by including the parameter argAge, which is converted to a string since the
argument is an integer. There is one more step that needs to happen in order to pass the age argument into the
workflow using the WF runtime hosted within the console application.
7. View the Solution Explorer, and open the file Program.cs by double-clicking on it. Add the
code in Listing 2-5.

Listing 2-5.  Adding Console.Read()


 using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;

54

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

namespace Apress.Example.Chapter2
{

class Program
{
static void Main(string[] args)
{
var wfArg = new Dictionary<string,Object>();
wfArg.Add("argInAge",39);
WorkflowInvoker.Invoke(new Workflow1(), wfArg);
Console.Read();
}
}
}

This code adds the namespace System.Collections.Generic so a Dictionary generic object with the
signature of <string,object> can be passed in as an argument using WorkflowInvoker.Invoke.
8. Press F5 to run the workflow. The workflow will print “Hello Workflow I am 39 years old!”
out to the console, as shown in Figure 2-37.

Figure 2-37.  Displaying results from the workflow

■■Note  “Browse for Types” allows defined arguments to have custom types. For example, if there is a defined
Customer type, it can also be used as the argument type so customer objects can be passed in and out of workflows.

This exercise demonstrated how to implement an argument within a workflow and how to pass the value of
an argument through the WF runtime. Finally, an expression was built to show how the workflow could display
information about the argument as output within a console window.

55

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Persistence
Have you ever watched a sci-fi movie where astronauts travel years to other planets and must fall into a deep sleep to
reduce aging by slowing the body’s energy expenditure? Workflow persistence is rather like that, in that a workflow is
put to sleep to save memory consumption when the workflow becomes idle. The WF runtime realizes that a workflow
has become idle and converts the workflow into a data format that can be loaded into a data store like SQL Server.
The current state of the workflow becomes much like the astronaut because it has been cryogenically frozen in time
with the intention of being thawed at a later point to perform more work. When a business solution implements WF
for processing orders, every time a business transaction for an order is started, a workflow presenting the order will
be created through the WF runtime as a workflow instance. Each workflow instance that runs requires an allocation
of memory and the CPU processing power to run it—just like any other process. However, WF supports running
workflows for longer periods, and because there may be times when a workflow needs input and decides to go idle
while it waits for feedback, memory is still being held.
Instead of holding on to the allocated memory, which is essentially being wasted while a workflow is idle, WF

Since the WF runtime manages workflow scheduling and execution, persistence can be associated to the WF

One of the greatest challenges we face as developers is creating ways of understanding what is going on under the
hood of running applications. If we could gather custom feedback and certain metrics based on each process to
understand how they run, we would be able to build better software. Another key feature building workflows provides
is a pattern of how code executes and the flow pattern executed; this provides developers with a visual model of where
to track certain events. WF tracking contains tools for gathering data about a workflow’s execution so developers
can have insight into the health of how a workflow executes. By default, data retrieved while tracking a workflow is
stored in the Event Tracing for Windows (ETW) log, which is provided through the operating system. Once tracking is
configured to use a data store, a default tracking profile is provided and can be tailored to return a subset of data that
is important to track (see Figure 2-38).

56

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Figure 2-38.  WF Tracking physical overview

A Lap Around WF4.5


Now that you have a good basic understanding of WF’s components, let’s look at the cool new features in WF4.5.
WF4.5 was released with .NET Framework 4.5. As developers continually use WF to develop workflow applications,
we are constantly discovering better ways of making our day-to-day processes easier as we become more comfortable
with our development tools. Microsoft has once again done a fantastic job of addressing developer concerns.

Activities
Some major improvements have been made around working with activities in WF4.5. The following sections cover
some areas that make the experience better while managing activities within workflows.

Multi-Select
Multiple activities can now be multi-selected and dragged from one workflow and dropped within another workflow.

Tree-Style Workflow Outline


Workflows can now be associated with a corresponding outline defined as a hierarchy tree. When an item in the tree is
selected, the view is navigated to the corresponding activity within the workflow designer.

Expression Extensibility
Expressions can now be customized so developers can author their own expression experience within the
workflow designer.

Annotations
Annotations can now be added to narrate information about activities on a workflow. Annotations can be added by
right-clicking on an activity (see Figure 2-39).

57

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

  Adding annotations

modeled around. WF4.5 provides new versioning features where multiple versions of a workflow can be hosted side
by side. WorkflowServiceHost can now process many versions of a workflow by reflecting the new changes for a
deployed workflow. New workflow instances that are spun up use the latest workflow definition. Dynamic updates can
also be applied to persisted workflows.

Build-Time Validation
Building workflow projects sometimes succeed without any validation errors, while there are still validation errors for
a workflow. WF4.5 validation errors will now cause the project to fail during the build process.

Centralized View State in XAML


View state for a workflow is now serialized and contained within an element of the XAML file. Developers can now
easily find the view state so it can be customized.

Debugging State-Machine States


In WF4, there was no indication when a state activity was being executed during runtime. In WF4.5, breakpoints can
be added to state activities themselves, indicating that a state activity is about to be executed.

Auto-Connect and Auto-Insert


As mentioned earlier while demonstrating the FlowDecision activity, you had to make sure to connect the
FlowDecision activity to the Start activity. This no longer has to be done because in WF4.5, as you drag activities on

58

www.it-ebooks.info
Chapter 2 ■ IntroduCIng WIndoWs WorkfloW foundatIon

to the canvas for building flowchart and state-machine, the new activities can automatically be connected by dragging
the new activity on an existing activity’s attachment points. Activities will also be automatically connected by adding
new activities between existing activity nodes on a workflow (see Figure 2-40).

Figure 2-40. Attachment points

C# Expressions
In WF4, the decision was made to use Visual Basic syntax for writing expressions that add logic to workflows because
of its English-like vocabulary and syntax. This was a pain for developers who primarily use C# as their language of
choice, so in WF4.5. C# expressions can now be used.

NoPersistScope
Sometimes it is important that child activities are not persisted in a logical flow. The concept is that although the
workflow has been persisted, something could happen in between to cause the workflow to fail, which would cause
the persisted actions of the workflow to be obsolete. By obsolete, I don’t mean that the workflow does not get persisted
correctly; it just means that the logic flow of the workflow is compromised because the next time the workflow
instance loads from being persisted, logic has already occurred that will cause the process to be invalid. In some cases,
WF4.5 provides a No-Persist Scope or No Persist Zone that prevents child activities from persisting.

ValidateUnconnectedNodes
ValidateUnconnectedNodes is a property included in WF4.5 to check when flowchart workflows have disconnected
nodes. When this property is set to True, validation errors occur, indicating that the nodes are not connected.

Flowchart Capabilities
There is no descriptive way to address the decision-making role it is playing within the workflow. A new DisplayName
property can now be added to both the FlowSwitch and FlowDecision activities so that it shows more information
around a decision that is being made.

Auto-surround Sequence
When there is already an activity within a workflow, you often need to use a Sequence activity as a container for
holding the already existing activity because other activities need to be added. In this scenario, the existing activity has
to be deleted. Alter the existing activity to indicate that the new activity can be placed either before or after the existing
WriteLine activity.
59

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

  Without a sequence activity

Figure 2-39 shows that a new Sequence activity is automatically added, which now serves as a parent activity to
WriteLine activities.

Figure 2-42.  Adding a sequence activity

Workflow Search
Sometimes workflows are so big that it is hard to find a particular part of the workflow that needs to be identified so
changes or maintenance can be applied. WF4.5 offers designer search functionality that allows the workflow to be
searched on a keyword. Note that searching is not available when the designer is rehosted. There are two searches.

60

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

• Quick search: Ctrl+F or Edit, Find and Replace, or Quick Find.


Searches on properties for
• Activity
• FlowNode
• State
• Transition
• Custom flow-control items
• Variables
• Arguments
• Expressions
• Find in files: Ctrl+Shift+F or Edit, Find and Replace, Find in Files

Deleting Variables and Arguments


Variables and arguments could only be deleted by clicking over the argument or variable and clicking Delete. This
sometimes caused issues because when you click on a variable or argument, the designer thinks you want to edit it.
The first time I saw this, I was not sure how to delete them and instead removed them from the XAML. WF4.5 provides
the more intuitive solution of a Delete command on the Context menu (see Figure 2-43).

Figure 2-43.  Attachment points

Contract-First
When building WF services using the messaging activities, the contract that gets implemented is built on the fly.
WF4.5 allows pre-existing contracts to be used and implemented within a workflow service. This is an extremely
powerful feature because it works well with code generation tools like T4, Microsoft’s template code generator.

State-Machine
Of course state-machine workflows are also included within WF4.5, so if you skipped the Platform Update 1, which
released state-machine workflows, you will still automatically get them in WF4.5.

61

www.it-ebooks.info
Chapter 2 ■ Introducing Windows Workflow Foundation

Summary
This chapter explained the benefits of using a technology like WF. It addressed all of the significant components in
WF, including the WF designer, runtime, and activities, by showing how they are used. Key comparisons were made
to WF3.x and how some of the pain points were solved by features new to WF4. There was a brief section on the
Platform Update 1, released for the .NET Framework and Visual Studio 2012 designer, which offered a much richer
way to implement state-machine workflows (which was left out of WF4). Finally, this chapter covered the changes in
the latest release of Workflow Foundation, WF4.5 and how these new features continue to make WF the wonderful
technology that it has become. The next chapter will focus on activities and how to build them from the ground up.
From this point on, we start getting more technical as we dive into the .NET libraries and namespaces of WF4.5.

62

www.it-ebooks.info
Chapter 3

Windows Workflow Activities

A workflow models a business process and coordinates the flow of work to be performed, but it is the role of the
workflow’s activities to implement the actual execution of that work. A WF activity provides the most basic unit of
work for building execution logic through code and provides a consistent approach rather than just defining logic with
standard code.
Chapter 2 covered a couple of activities just to get you familiar with how they work, but if you are used to writing
code, you will have noticed that most of the activities implemented many of the same logical patterns as code
constructs. The only difference is that activities provide a more declarative way of implementing logic because they
visually represent the way they are to be used within a workflow. And just like code, activities are re-usable in the
sense that once they are defined, they can be used to model behavior for more than one workflow.
Activities also have an exception handling very similar to how programming languages handle exceptions.
I also mentioned that the WF activity is the basic building block for defining a workflow, so understanding activities
allows developers to express custom workflows that target specific business processes. This chapter focuses on
understanding the different types of WF activities, how they are defined, and how they can be used for building
workflows for modeling complex business process scenarios. I will also cover how to build custom activities that are
built to be domain specific, meaning that they are geared to focus on a certain type of business model. See Figure 3-1.

Code
Activity

Native Dynamic
Activity
Activity Activity

AsyncCode
Activity

Figure 3-1.  Activity hierarchy

63

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Activity Basics
WF activities are nothing more than objects defined within the .NET Framework, and they are derived from the
namespace System.Object. The namespace for WF activities is System.Activities and it contains the components
used to define and implement activities. The class Activity contained within System.Activities provides the
abstract class for all activities (see Table 3-1).

Table 3-1.  System.Activities.Activity


Namespace Description
System.Activities.CodeActivity Mimics WF3.x base activity. Simple execution of code for an activity to
execute within a workflow.

of activities asynchronous.

Activities can be built to take advantage of one of the namespaces represented in Table 3-1. Here are the details:
• CodeActivity: The closest way to writing basic code within a workflow. CodeActivity was
introduced in WF3.x as an “out-of-box” activity. The activity in WF4 is now an abstract class
that developers can use as a base class for custom activities that strictly need to execute code
synchronously. There are no child activities for CodeActivity.
• NativeActivity: An abstract class that provides the same coding capabilities of the
CodeActivity but introduces interaction with the WF runtime for managing an activity’s
communications through bookmarks and scheduling other activities. Activities that derive
from NativeActivity can have child activities and are usually geared for long-running
workflows. If you do not need the features of the WF runtime, it is better to simply derive a
custom activity from CodeActivity.
• AsyncCodeActivity: An abstract class that provides the same coding capabilities of the
CodeActivity but extends it by providing an asynchronous coding ability that is not intended
to be persisted through the workflow. AsyncCodeActivity is a good candidate for trying to
make multiple service call-outs to services or making web requests at the same time.
• DynamicActivity: A sealed class, so it is not intended to be used for deriving custom activities.
Instead, DynamicActivity allows developers to literally declare an activity and execute it
through code or XAML during runtime.
Although most of the activities defined will inherit from System.Activities.Activity and will be defined using
the workflow designer, other custom activities will derive from the base classes, such as:
• CodeActivity
• NativeActivity
• AsyncCodeActivity

64

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Data Management
The concept for how WF activities process data is similar to regular code. Activities require the following:
• Variables
• Arguments
• Expressions

Variables
Just as a method written in code can have variables defined within it, activities also have variables that they use.
Variables are used as placeholders for holding temporary data that can be used as part of the overall results for
executing business logic. Variables contain a variable type that represents the type of data that it will house and an
optional default value that can be used in case there is no value passed into the variable.
One of the key benefits WF4 added to variables is scope availability. A variable’s activity scope determines
what activities can reference the variable within a workflow. Figure 3-2 demonstrates how two different variables,
variable1 and variable2, can have limited scope within an activity by using the Scope drop-down menu and
selecting the desired parent activity. You will notice that variable2 has a scope of the Sequence activity, which is the
container for the Pick activity; however, if the variable is not needed in both branches of the Pick activity, it would be
better to reduce the variables scope like the other variable, variable2, which only gives scope to Branch2 within the
Pick activity rather than the entire Sequence activity.

Figure 3-2.  Variable scoping

65

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Arguments
Arguments are used for receiving and returning data to the WF runtime. Arguments provide functionality within a
workflow much like a function coding construct. Data can be passed into a function, and a function can also return
data back to the line of code that called it. When workflows expect to receive data as input, the argument is defined as
an InArgument object. When data is returned from a workflow, an OutArgument object is used, and when data needs to
be received and returned within the same argument for a workflow, an InOutArgument object is used. Therefore, WF
arguments can be one of three types of directions within an activity:
• In: Sends an argument into an activity through the WF runtime.
• Out: Returns an argument value set within an activity to the WF runtime.
• In/Out: Works like a referenced value that can be passed into an activity, set with a value, and
then returned back from the activity to the WF runtime.
Arguments also have a type attribute, just as variables do, that represents the data that they can hold as well as a

 3-3 demonstrates three different string arguments that can be used within the workflow.

Figure 3-3.  Variable scoping

66

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Expressions
Expressions specify the execution of logic that an activity will perform. Most of the time expressions take advantage
of the variable and arguments for processing information; other times expressions use data received from external
sources that originate outside of the workflow. Expressions in WF4 use Visual Basic syntax for modeling logic but
WF4.5 provides an alternative to this by allowing C# syntax to be used as well. Expressions can also be extended, so a
custom expression’s syntax can be used instead of requiring workflow authors to use C# or VB syntax. Chapter 5 will
cover how expressions can be customized and used within activities.
As you read further, you will see how variables, arguments, and expressions assist in activity execution through
the activities in the chapters.

■■Note  You may notice that the name of the arguments use the prefix “arg” and variables use the prefix “var” for
workflows in the examples. This is solely done to minimize confusion between arguments and variables.

Activity Life Cycle


Just as a workflow instance running within the WF runtime has a lifecycle, so does a workflow activity. An activity is
started in the executing state, which means that the activity has been invoked to execute. Once all work has completed
within the activity, including any work with its child activities or outside communication through bookmarks, the
activity changes to the closed state. When an activity is requested to be cancelled, the requested activity resolves
to a canceled state. If there is an exception thrown during its execution, the activity then goes into a faulted state.
See Figure 3-4.

Executing

Faulted Canceled

Closed

Figure 3-4.  Activity lifecycle

Authoring Activities
One of the really powerful features introduced in WF4 is the ability to author activities through code and XAML, an
XML representation for a WF activity. WF3.x had activities that were used declaratively and had a code back-end for
implementing how the activity executed. Activities could also be built using XML, noted as “.xoml”, but there was still
code associated with the workflow. WF4 helped clear up the confusion by clearly drawing a line between activities
that are authored through code and activities that are strictly authored through XAML.

67

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Imperative Code
The real power of WF is designing workflows declaratively using the WF designer, but workflows can also be declared
strictly through code.
Listing 3-1 demonstrates the code for implementing a basic WriteLine activity by instantiating the variable
wfActivity, declared as a System.Activities.Activity and setting it to a new WriteLine activity. The
WriteLine activity has a Text property that is used to write “Hello from Workflow” to a console window. Finally,
WorkflowInvoker.Invoke is used to call the wfActivity just as a simple method call.

Listing 3-1.  Simple Hello from Workflow


using System;
using System.Collections.Generic;
using System.Linq;

3
{
public partial class ImperativeCodeWorkflow
{
public void SimpleHelloWorld()
{
Activity wfActivity = new WriteLine
{
Text = "Hello from Workflow."
};
WorkflowInvoker.Invoke(wfActivity);
}
}
}

This next example goes a little more in depth by using data within a workflow and performing a simple addition.
Three variables are used to hold data that will be used during the addition calculation (see Listing 3-2).

Listing 3-2.  Declaring Variables


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Activities.Statements;
using Microsoft.VisualBasic.Activities;

namespace Apress.Example.Chapter3
{
public partial class ImperativeCodeWorkflow
{
public void AdditionActivity()
{
Variable<int> Add1 = new Variable<int>

68

www.it-ebooks.info
Chapter 3 ■ WindoWs WorkfloW aCtivities

{
Name = "Add1",
Default = 5
};

Variable<int> Add2 = new Variable<int>


{
Name = "Add2",
Default = 5
};

Variable<int> Sum = new Variable<int>


{
Name = "Sum"
};

• Add1: Default value of 5


• Add2: Default value of 5
• Sum: Holds the calculated value of Add1 + Add2
After the variables are declared, a Sequence activity is instantiated and used as a container for holding child activities.
The three variables declared in Listing 3-2 are then added to the Sequence activity, along with an Assign activity
that will be used to assign an argument declared as an integer type to the Sum variable that is calculated from the
expression of adding variables Add1 and Add2. The calculated value is then written to the console window by adding
the WriteLine activity to the Sequence activity. See Listing 3-3.

Listing 3-3. Sequence Activity in Code


Activity wfSequence = new Sequence
{
Variables = { Add1,Add2,Sum },
Activities =
{
new Assign<int>
{
To = Sum,
Value = new InArgument<int>(ad) => Add1.Get(ad) + Add2.Get(ad))
},
new WriteLine
{
Text = new InArgument<string> ((sm) =>string.Format("The sum of {0} and {1}
is {2} ",Add1.Get(sm),Add1.Get(sm),Sum.Get(sm)))
}
}
};

WorkflowInvoker.Invoke(wfSequence);
}

69

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

This code can be called simply using the following code:

var imperativeCode = new ImperativeCodeWorkflow();


imperativeCode.AdditionActivity();

Dynamic Activities
Declaring activities as DynamicActivity instead of through imperative code provides a flexible way for executing
activities because they allow the creation of arguments and values for the arguments to be created externally from the
workflow. Dynamic activities provide properties that can be set instead for processing logic within the activity (see
Table 3-2).

  Important DynamicActivity Properties

System.Activities.Activity.

activity.
Name of the activity that the WF designer displays.

System.Collections.ObjectModel.KeyedCollection(Of String, DynamicActivityProperty)

Let’s take a look at how you can take advantage of building a dynamic activity to provide the flexibility for
enlisting outside arguments. Let’s use the same logic that was built using imperative code activity for adding two
integers together. To get started, the first thing to do is to add the arguments and variables that the dynamic activity
will use. The code in Listing 3-4 demonstrates adding two in arguments, argAdd1 and argAdd2 and one out argument,
AdditionResult. Two variables, varAdd1 and varAdd2, are also included to hold the argument values passed in to the
workflow; however, they are not really needed in this simple example. Notice that each variable is given a default value
of 5, just in case arguments are not passed to the workflow activity.

Listing 3-4.  Sequence Activity in Code


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Activities.Statements;
using Microsoft.VisualBasic.Activities;
using System.Activities.Expressions;

namespace Apress.Example.Chapter3
{
public partial class DynamicCodeActivity
{
public Activity AdditionActivity()

70

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

{
var argAdd1 = new InArgument<int>();
var argAdd2 = new InArgument<int>();
var AdditionResult = new OutArgument<int>();

Variable<int>varAdd1=new Variable<int>
{
Name = "varAdd1",
Default = 5
};

Variable<int>varAdd2 = new Variable<int>


{
Name = "varAdd2",
Default = 5
};

The implementation for the workflow activity is pretty much the same as the addition activity from Listing 3-3, except
that arguments can now be passed into the activity externally so that any two numbers can be added together through
the hosted WF runtime. After the activity is instantiated, the following properties are set for the activity:
• DisplayName
• Properties
Listing 3-5 demonstrates adding the DisplayName property, “Add two Integers”, which the WF runtime will
use to associate the activity to any exceptions that may occur. There are also two arguments of type InArgument,
argAdd1 and argAdd2, and one OutArgument type for returning data back to the WF runtime that are added using the
DynamicActivityProperty. The name given to each argument is important because this is what the WF runtime will
use for referencing the arguments with the activity.

Listing 3-5.  Instantiating the DynamicActivity


var MathAddActivity = new DynamicActivity()
{
DisplayName = "Add two Integers",
Properties =
{
new DynamicActivityProperty
{
Name = "argAdd1",
Type = typeof(InArgument<int>),
Value = argAdd1
},
new DynamicActivityProperty
{
Name = "argAdd2",
Type = typeof(InArgument <int>),
Value = argAdd2
},

71

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

new DynamicActivityProperty
{
Name = "argAdditionResult",
Type = typeof(OutArgument<int>),
Value = argAdditionResult
}
},

After the Properties property for the activity has been set, the next property that needs to be set for defining the
execution logic for the activity is Implementation.
The Implementation property creates a new Sequence activity with two new variables, varAdd1 and varAdd2,
that were defined in Listing 3-4. The Sequence activity adds three Assign activities and one WriteLine activity to its
Activities property, which represents included child activities. The first Assign activity assigns the variable varAdd1
to the value passed into the activity through the Add1 InArgument. The second Assign activity assigns the variable
to the value also passed into the activity through the Add2 InArgument. The last Assign activity assigns

OutArgument so it can be returned as output from the activity. Finally, the WriteLine activity simply confirms the

  Implementing the Dynamic Activity


 new Sequence
{
Variables =
{
varAdd1,
varAdd2
},
Activities =
{
new Assign<int>
{
To = varAdd1,
Value = new ArgumentValue<int>
{
ArgumentName = "argAdd1"
}
},
new Assign<int>
{
To = varAdd2,
Value = new ArgumentValue<int>
{
ArgumentName = "argAdd2"
}
},
new Assign<int>

72

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

{
To = new ArgumentReference<int>
{
ArgumentName =  "argAdditionResult"
},
Value = new InArgument<int>(ad) =>varAdd1.Get(ad)+varAdd2.Get(ad))
},
new WriteLine
{
Text = new InArgument<string>((env) =>string.Format("The sum of {0} and
{1} is {2} "
,varAdd1.Get(env)
,varAdd2.Get(env)
,argAdditionResult.Get(env)))
}
}
}
};
return MathAddActivity;
}
}
}

Next let’s review the code for passing in the arguments and getting the results from the activity. The code in
Listing 3-7 instantiates a DynamicCodeActivity class defined in Listing 3-4 and calls the activity AdditionActivity.

Listing 3-7.  Executing the Dynamic Activity


using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
namespace Apress.Example.Chapter3
{

class Program
{
static void Main(string[] args)
{
CallDynamicAdditionActivity();
}

private static void CallDynamicAdditionActivity()


{
var dynamicActivity = new DynamicCodeActivity();
var actAddition = dynamicActivity.AdditionActivity();

var result = WorkflowInvoker.Invoke(actAddition,


new Dictionary<string, object>{ { "argAdd1", 3 }, { "argAdd2", 5 } });

73

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Console.WriteLine(string.Format("The workflow returned {0}",


result["argAdditionResult"]));
Console.ReadKey();

var imperativeCode = new ImperativeCodeWorkflow();


imperativeCode.AdditionActivity();
}
}
}

The dynamic activity returned is then executed by executing WorkflowInvoker.Invoke and passing in two
arguments, argAdd1 and argAdd2, that were defined in Listing 3-5. The integers that are passed in will be added
together. Notice the Dictionary object’s signature <string, object> that is used for passing arguments into
workflows. This string part of the signature is used to set the name of the arguments, which must be known so the
object part of the signature sets the values 3 and 5 which are hard-coded

Tip There might be some confusion between building workflows through imperative code and defining dynamic

Dynamic activities provide dependency injection by

XAML
WF4 introduced activities authored using code plus another powerful feature for creating workflows entirely from
XML. Workflows authored in XML can now be created and executed without being compiled; they can also be
modified during runtime. So what strategies does this provide for developers? If you are familiar with the WF Rules
Engine that was made available in WF3.x, it allowed rules to be created and then stored within a central location like
SQL Server. Rules could be retrieved at a later time from storage and compiled so they could process business logic for
.NET applications. The real advantage in implementing a rules engine so rules can be processed within applications
is so rules can be changed during runtime. The same concept applies when authoring dynamic activities through
XAML. However, instead of building rules that are defined through code, workflows can be created; this provides a
better approach for executing business logic. Workflows can be built not only by developers but also non-technical
users that model the logic declaratively instead of having to learn the rules syntax for building rules. Workflows can
now be run on the fly using the System.Activities.XamlIntegration.ActivityXamlServices object and changed
during runtime.
ActivityXamlServices is a static object that allows XML to be loaded and returns an Activity type that can
be processed as if the workflow activity was created through code, but without the workflow having to be compiled.
Figure 3-5 demonstrates how a workflow can be created using the WF designer declaratively that models the dynamic
activity authored through code.

74

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-5.  Adding a sequence activity and arguments

The first activity that is added to the WF designer is a Sequence activity that will serve as the container for the
other child activities (see Figure 3-5). The workflow also uses the three arguments found in Listing 3-6.
There are also the two variables that will be used for executing the logic for adding the two input arguments (see
Figure 3-6).

Figure 3-6.  Adding WF variables

75

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

The completed workflow defined in code is completely demonstrated within the designer in Figure 3-7.

Figure 3-7.  Complete workflow

At this point, you can review the XAML it has produced using the WF designer. Browsing through the XAML, you
can easily pick out different activities, declared arguments, variables, and the expressions.

Listing 3-8.  XAML for the Custom Addition Activity


<Activity mc:Ignorable="sap sap2010 sads" x:Class="Apress.Chapter3.Activity1"
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mca="clr-namespace:Microsoft.CSharp.Activities;assembly=System.Activities"
xmlns:sads="http://schemas.microsoft.com/netfx/2010/xaml/activities/debugger"
xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation"
xmlns:sap2010="http://schemas.microsoft.com/netfx/2010/xaml/activities/presentation"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
xmlns:sco = "clr-namespace:System.Collections.ObjectModel;assembly = mscorlib"

76

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml">
<x:Members>
<x:Property Name = "argAdd1" Type = "InArgument(x:Int32)" />
<x:Property Name = "argAdd2" Type = "InArgument(x:Int32)" />
<x:Property Name = "argAdditionResult" Type = "OutArgument(x:Int32)" />
</x:Members>

< sap2010:ExpressionActivityEditor.ExpressionActivityEditor > C#</sap2010:ExpressionActivity
Editor.ExpressionActivityEditor>
<sap2010:WorkflowViewState.IdRef > Apress.Chapter3.Activity1_1</sap2010:WorkflowViewState.IdRef>
<TextExpression.NamespacesForImplementation>
<sco:Collection x:TypeArguments = "x:String">
<x:String> System</x:String>
<x:String> System.Collections.Generic</x:String>
<x:String> System.Data</x:String>
<x:String> System.Linq</x:String>
<x:String> System.Text</x:String>
</sco:Collection>
</TextExpression.NamespacesForImplementation>
<TextExpression.ReferencesForImplementation>
<sco:Collection x:TypeArguments = "AssemblyReference">
<AssemblyReference> Microsoft.CSharp</AssemblyReference>
<AssemblyReference> System</AssemblyReference>
<AssemblyReference> System.Activities</AssemblyReference>
<AssemblyReference> System.Core</AssemblyReference>
<AssemblyReference> System.Data</AssemblyReference>
<AssemblyReference> System.Runtime.Serialization</AssemblyReference>
<AssemblyReference> System.ServiceModel</AssemblyReference>
<AssemblyReference> System.ServiceModel.Activities</AssemblyReference>
<AssemblyReference> System.Xaml</AssemblyReference>
<AssemblyReference> System.Xml</AssemblyReference>
<AssemblyReference> System.Xml.Linq</AssemblyReference>
<AssemblyReference> mscorlib</AssemblyReference>
<AssemblyReference> Apress.Chapter3</AssemblyReference>
</sco:Collection>
</TextExpression.ReferencesForImplementation>
<Sequence sap2010:WorkflowViewState.IdRef = "Sequence_1">
<Sequence.Variables>
<Variable x:TypeArguments = "x:Int32" Default = "5" Name = "varAdd1" />
<Variable x:TypeArguments = "x:Int32" Default = "5" Name = "varAdd2" />
</Sequence.Variables>
<Assign>
<Assign.To>
<OutArgument x:TypeArguments = "x:Int32">
<mca:CSharpReference x:TypeArguments = "x:Int32" > varAdd1</mca:CSharpReference>
</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments = "x:Int32">
<mca:CSharpValue x:TypeArguments = "x:Int32" > argAdd1</mca:CSharpValue>
</InArgument>
</Assign.Value>

77

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

<sap2010:WorkflowViewState.IdRef > Assign_1</sap2010:WorkflowViewState.IdRef>
</Assign>
<Assign>
<Assign.To>
<OutArgument x:TypeArguments = "x:Int32">
<mca:CSharpReference x:TypeArguments = "x:Int32" > varAdd2</mca:CSharpReference>
</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments = "x:Int32">
<mca:CSharpValue x:TypeArguments = "x:Int32" > argAdd2</mca:CSharpValue>
</InArgument>
</Assign.Value>
<sap2010:WorkflowViewState.IdRef > Assign_2</sap2010:WorkflowViewState.IdRef>
</Assign>
<Assign>
<Assign.To>
<OutArgument x:TypeArguments = "x:Int32">
<mca:CSharpReference x:TypeArguments = "x:Int32" > argAdditionResult</mca:CSharpReference>
</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments = "x:Int32">
<mca:CSharpValue x:TypeArguments = "x:Int32" > varAdd1 + varAdd2</mca:CSharpValue>
</InArgument>
</Assign.Value>
<sap2010:WorkflowViewState.IdRef > Assign_3</sap2010:WorkflowViewState.IdRef>
</Assign>
<WriteLine>
<InArgument x:TypeArguments = "x:String">
<mca:CSharpValue x:TypeArguments = "x:String" > string.Format("The sum of {0} and {1} is {2}",var
Add1,varAdd2,argAdditionResult)</mca:CSharpValue>
</InArgument>
<sap2010:WorkflowViewState.IdRef > WriteLine_1</sap2010:WorkflowViewState.IdRef>
</WriteLine>

<sads:DebugSymbol.Symbol > d1JjOlx1c2Vyc1xid2hpdGVcZG9jdW1lbnRzXHZpc3VhbCBzdHVkaW8
gMTFcUHJvamVjdHNcQXByZXNzLkNoYXB0ZXIzXEFjdGl2aXR5MS54YW1sDiwDXw4CAQEuMy42AgEDLzMvNgIBA
jEFPQ4CASU + BUoOAgEYSwVXDgIBC1gFXRECAQQ5CzlPAgEsNAs0VwIBJkYLRk8CAR9BC0FXAgEZUwtTVw
IBEk4LTmECAQxaCVqXAQIBBQ==</sads:DebugSymbol.Symbol>
</Sequence>
<sap2010:WorkflowViewState.ViewStateManager>
<sap2010:ViewStateManager>
<sap2010:ViewStateData Id = "Assign_1" sap:VirtualizedContainerService.HintSize = "242,62" />
<sap2010:ViewStateData Id = "Assign_2" sap:VirtualizedContainerService.HintSize = "242,62" />
<sap2010:ViewStateData Id = "Assign_3" sap:VirtualizedContainerService.HintSize = "242,62" />
<sap2010:ViewStateData Id = "WriteLine_1" sap:VirtualizedContainerService.HintSize = "242,62" />
<sap2010:ViewStateData Id = "Sequence_1" sap:VirtualizedContainerService.HintSize = "264,492">
<sap:WorkflowViewStateService.ViewState>
<scg:Dictionary x:TypeArguments = "x:String, x:Object">
<x:Boolean x:Key = "IsExpanded" > True</x:Boolean>

78

www.it-ebooks.info
Chapter 3 ■ WindoWs WorkfloW aCtivities

</scg:Dictionary>
</sap:WorkflowViewStateService.ViewState>
</sap2010:ViewStateData>
<sap2010:ViewStateData Id = "Apress.Chapter3.Activity1_1"
sap:VirtualizedContainerService.HintSize = "304,572" />
</sap2010:ViewStateManager>
</sap2010:WorkflowViewState.ViewStateManager>
</Activity>

Now the XAML can be run without being compiled by finding the path of the XAML file and using the following
code:

var act = ActivityXamlServices.Load(@"Activity1.xaml");


var retArg = WorkflowInvoker.Invoke(act, new Dictionary < string, object>
{
{ "argAdd1", 3 }, { "argAdd2", 5 }
});
var result = Convert.ToInt32(retArg["argAdditionResult"]);

In this case, the Activity1.xaml file is located within the same file path as the executing assembly, and because
of the implementation of the WriteLine activity within the XAML, the console window also opens and displays the
processed equation for adding two numbers (see Figure 3-8).

Figure 3-8. Activity processed as XAML

79

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

■■Caution  XAML activities cannot serialize Lambda expressions (syntax used for defining a nameless function).
Therefore, if they exist, a LambdaSerializationException will be thrown with the following message: “This workflow
contains lambda expressions specified in code. These expressions are not XAML serializable. In order to make your
workflow XAML-serializable, either use VisualBasicValue/VisualBasicReference or ExpressionServices.Convert(lambda).
This will convert your lambda expressions into expression activities.”

Testing Activities
Let’s now explore how to make sure the WF activities authored will work before they are actually added into a
workflow. Unit testing is a methodology commonly used by developers for testing their code before it is implemented

to execute an activity, the same way a C# method is executed through code. Visual Studio also
 3-9) that can be added to a Visual Studio solution.

Figure 3-9.  Adding a test project

80

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

A test project includes by default a file called UnitTestProject1. Sometimes it is a good practice to add another
unit test file if the functionality being tested can be categorized into more than one category. So let’s say that you are
building functionality around adding inventory into a system, and later you need to build functionality for managing
users within the system. You might have one unit test dedicated to testing the functionality for inventory and another
unit test for testing user management.

Listing 3-9.  Default Unit Test Code


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Apress.Example.Chapter3.Activity.Test
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}

Listing 3-9 shows the default code that a new unit test contains by default. Essentially it is a boilerplate for
building your own unit tests for testing custom code. The code in Listing 3-9 contains a test class and test method; an
obvious giveaway is the attribute TestClass given for the class UnitTest1 and the attribute TestMethod given to the
method TestMethod1. To test the addition activity, the System.Activity namespace needs to be referenced within the
test project (see Figure 3-10).

Figure 3-10.  Test project System.Activities reference

81

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

As code is being tested, there are ways to confirm different types of results to make sure the right results are
returned or not returned. Anticipating different results requires different types of tests. The most common are the
following:
• Positive Testing: Test that proves that code does work.
• Negative Testing: Test that proves that code does not work. An example is testing a scenario
where a file is required so code can read its internals, and testing the scenario for what
happens when the file is available.
• Regression Tests: Goes through a sequence of tests verifying that as a unit each test within the
sequence works.
To verify that tests are returning the correct results, it is important to use an Assert statement to confirm certain
result patterns. Table 3-3 illustrates the different types of Asserts that are available.

  Assert Types
Description
Provides methods for verifying a pass/fail result.
Used for testing collections of objects.
Provides methods for testing strings.
Exception thrown when a test fails.

when the results cannot be defined as a pass or fail.

The following two test methods show how to use Asserts. TestMethod2 compares an empty collection of strings to a
StringBuilder, but because the Assert is AreNotEqual, the test will pass.

[TestMethod]
public void TestMethod2()
{
Assert.AreNotEqual(new List < string > (), new System.Text.StringBuilder());
}

[TestMethod]
public void TestMethod3()
{
Assert.AreEqual("123", "123");
}

TestMethod2 will pass as well because it asserts that “123” equals “123”. To test the activity in Listing 3-8, the same
code can be used for invoking the activity.

[TestMethod]
public void TestMethod1()
{
var act = ActivityXamlServices.Load(@"C:\
Apress\Chapter3\Solution\Apress.Example.Chapter3\Apress.Example.Chapter3\bin\Debug\Workflow1.xaml");

82

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

var retArg = WorkflowInvoker.Invoke(act, new Dictionary < string, object>


{
{ "argAdd1", 3 },
{ "argAdd2", 5 }
});
var result = Convert.ToInt32(retArg["AdditionResult"]);

Assert.AreEqual(result, 3 + 5);
}

The only thing that needs to change is the file path for loading the XAML from, because with the new test project, the
XAML that defines the activity is still located within the build directory for the project that was used for authoring
the activity. After running a test, the results can be viewed within the Test Results window. Figure 3-11 shows that the
Addition activity results were good because it successfully passed its basic task of adding 3 and 5 together.

Figure 3-11.  Test results

Communicating with Activities


Chapter 2 briefly touched on ways of communicating with workflows, but because the execution of logic takes place
inside of an activity, communication to activities flows from the WF runtime, either through arguments or bookmarks.

Bookmarks
Bookmarks allow event-driven communication to occur to an activity within a workflow, from an outside source,
using the WF runtime as the channel of communication. Bookmarks were introduced in WF4 to settle the complexity
around defining what WF3.x referred to as “external events.” The concept of bookmarks is pretty simple as it closely
resembles how a real bookmark works for keeping track of what page of a book was the last to be read.
Bookmarks in WF apply the same meaning except that a bookmark in WF holds the last place a workflow
executed, usually because the workflow is waiting on some external event to happen so it can start back up. The cool
thing about using bookmarks, though, is when a workflow stops and waits for an external event to fire (for example,
a manager needs to approve a work order), the workflow is considered idle and can therefore be persisted within
SQL Server. Then a day later, when the manager decides he or she is ready to approve the work order, the workflow is
loaded back into memory and the workflow picks up exactly where it left off, which is from the bookmark.
There is no out-of-box activity for handling bookmarks, but one can easily be created that handles most of the
functionality needed for implementing a bookmark. Listing 3-10 demonstrates how to build a custom activity that
works with bookmarks within a workflow. The WaitForResponse activity inherits from NativeActivity <TResult>,
which allows any object to be returned from the WF runtime using a Bookmark. Going back to the work order scenario,
once the manager approves the work order, a work order object is returned back through the WF runtime to the
Bookmark so the work order can continue to be processed. The most important information that defines a Bookmark

83

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

object is a bookmark’s name. The name is used to reflect on a Bookmark from the WF runtime. Instead of building
a bookmark activity for every Bookmark needed within a workflow, building an activity that handles bookmarks is a
generic approach, so the name of the Bookmark can be defined through code or using the WF designer rather than
a hard-coded value. Once a Bookmark is created, it can pass with it a .NET object, which can be used as data and
processed within a workflow. The ResponseName property is used to define the actual bookmark name, so that the WF
runtime can associate with the same name to correspond with the activity for external events (see Figure 3-12).

Listing 3-10.  Bookmark Activity


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

3
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{

public string ResponseName { get; set; }

protected override bool CanInduceIdle


{ //override when the custom activity is allowed to make he workflow go idle
get
{
return true;
}
}

protected override void Execute(NativeActivityContext context)


{
context.CreateBookmark(this.ResponseName, new BookmarkCallback(this.ReceivedResponse));
}

void ReceivedResponse(NativeActivityContext context, Bookmark bookmark, object obj)


{
this.Result.Set(context, (TResult)obj);
}
}
}

84

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-12.  WaitForResponse Bookmark Activity

A Pick activity is the ideal place for setting up a bookmark because it has a Trigger and Action signature. Figure 3-13
shows the WaitforResponse activity that was added within the Trigger of Branch1 of the Pick activity. A Delay activity
is brought into Branch2 to set how long to wait for an external event. If the Delay activity interval expires, then whatever
activity is provided in Branch2’s Action container will execute. If the Bookmark is triggered within the timer interval, then
whatever activities are added within the Action container for Branch1 will be executed.

Figure 3-13.  WaitForResponse Bookmark activity

85

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Once the WaitforResponse activity is configured with ResponseName (name of the Bookmark) and Result (usually a
variable within the scope of the activity for the object that is passed with the bookmark), a Bookmark is set and ready to
receive an external event. Recall that bookmarks cannot be used with the WF host, WorkflowInvoker.Invoke. Instead,
the WorkflowApplication host provides a ResumeBookmark method, which is called for initiating an external event
from the hosted WF runtime. When ResumeBookmark is called, two arguments can be passed, indicating the following:
• Bookmark Name: Name of the bookmark set as the ResponseName of the WaitforResponse
activity.
• Object: Value passed in that will be set within the Result property of the WaitforResponse
activity. This is important because the Bookmark starts the execution of the workflow up
again; therefore the input provided from the Bookmark for the workflow should be used within
processing the workflow going forward.

1. Open Visual Studio 2012, and create a new Project.


2. Select the Workflow template to see a list of installed workflow templates.
3. Select Workflow Console Application, and name it Chapter3.Activities.
This project will be used for walking through the rest of the labs in this chapter.

Debugging Activities
After activities have been thoroughly unit tested, workflows can be built using the tested activities and executed, but
developers will also want to step through workflow’s to debug them even further and before they go into production.
WF has first-class experience similar to debugging C# code with using breakpoints, except the breakpoints are applied
to activities within a workflow rather than lines of code. Breakpoints can be set on an activity, and once the activity
receives scope, the workflow execution pauses on the breakpoint. Execution can then be controlled for step-by-step
interaction for visually seeing the execution pattern of execution for the workflow.
WF also has a handy out-of-the-box activity called WriteLine, which is great for debugging. The WriteLine
activity writes custom information to a console, which can also be used for understanding activity flow. There is also
a monitoring extension called Tracking that can be added to the WF runtime to monitor custom information on a
workflow; however, WF tracking has its own dedicated chapter later within the book.

86

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

DEBUGGING ACTIVITIES 

This lab walks through a simple workflow that is used to demonstrate debugging a workflow using breakpoints
and WriteLine activities. The workflow will loop 10 times, each time grabbing a different random number
between 0–10. A condition will check if the random number is greater or less than 5. There will be various places
where breakpoints will be applied and WriteLine activities will be used to indicate the paths for each random
number that is generated.
To walk through these activities, use the new solution called Chapter3.Activities created in Visual Studio
2012.
1. Open the solution Chapter3.Activities.
2. Add another Workflow Console Application project to it and name it
Chapter3.Activities.Debugging.

3. Open the Program.cs file and add Console.ReadKey() underneath


WorkflowInvoker.Invoke(workflow1);

4. Open up the Workflow1.xaml and drag a DoWhile activity to the designer canvas.
5. Create two new variables, one called varRandomNumber and the other called varCounter.
Both of these variable types will be Int32. Set the default value for varCounter to 0. Set
the scope for the variable varRandomNumber to Sequence (see Figure 3-14).

Figure 3-14.  Setting the variables for debugging the workflow

6. Set the Condition of the DoWhile activity to varCounter < 10. This indicates to the
DoWhile activity to loop 10 times.

■■Tip As workflows become larger, it is smart to name some of the DisplayName properties for workflows that contain
more than one of the same activities. If not, variable scopes can start getting confusing, as illustrated in Figure 3-15.

87

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-15.  Confusing scope

7. Drag an Assign activity to the WF designer and place it within the body of the DoWhile
activity. Assign the varRandomNumber variable to a random number each time the
DoWhile executes a loop by setting the assign To property to varRandomNumber and the
Value property to new Random().Next(0,10).

8. Drag an If activity to the WF designer and place it beneath the Assign activity. Set
the Condition property to varRandomNumber <5. The condition will check whether the
random number returned less or greater than 5.
9. To make sure that random numbers are being created, place a breakpoint on the Assign
activity by right-clicking the activity Breakpoint > Insert Breakpoint (see Figure 3-16).

Figure 3-16.  Adding an activity breakpoint


88

www.it-ebooks.info
Chapter 3 ■ WindoWs WorkfloW aCtivities

10. drag another Assign activity the Wf designer and place it beneath the If activity.
increase the varCounter variable by 1 each time the DoWhile activity executes a loop by
setting the assign To property to varCounter and the Value property to varCounter + 1.
right-click on the project and click Build. after everything builds correctly, press f5 to run
the workflow. as the workflow runs, it will pause and highlight the Assign activity, which
contains the breakpoint. in this debugging state, the locals window loads the resources
from the workflow and the immediate window is able to retrieve resources on the
workflow as well. pressing f10 will step through the rest of the workflow 10 more times,
getting a total of 10 random numbers. pressing f5 will process each of the 10 iterations.
this demonstrates how to add breakpoints within a workflow, but using the WriteLine
activity in conjunction with breakpoints helps with the debugging a workflow even more.
see figure 3-17.

Figure 3-17. Debugging an activity using a breakpoint

89

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

11. Drag a WriteLine activity to the WF designer and add it within the Then container of the
If activity. Set the Text property to string.Format("Random number {0} is less
than 5",varRandomNumber).

12. Drag another WriteLine activity to the WF designer and add it within the Else container
of the If activity. Set the Text property to string.Format("Random number {0} is
greater than 5",varRandomNumber).

13. Right-click on the project and click Build. After everything builds correctly, press F5 to run
the workflow. Each time the debugger breaks on the Assign activity, press F5 to finish
the executing iteration.
Each iteration will write information to the console screen about the random number that was
selected and the path of execution performed by the If activity. The problem is that the breakpoint
is not showing execution on the WriteLine activities. Breakpoints need to be added to each of the
WriteLine activities. See Figure 3-18.

Figure 3-18.  Debugging flow using WriteLine activities

14. Remove the breakpoint set on the Assign activity by right clicking on the activity
Breakpoint > Delete Breakpoint (see Figure 3-19).

90

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-19.  Removing a breakpoint

15. Add a breakpoint to each of the WriteLine activities by right-clicking each of the
WriteLine activities and selecting Breakpoint ➤ Insert Breakpoint.

16. Right-click on the project and click Build. After everything builds correctly, press F5 to
run the workflow. As the workflow runs, it will pause and highlight the WriteLine activities
that contain the breakpoints, as the condition of the If activity directs the execution of the
workflow. Pressing F5 will show the flow for each iteration. See Figure 3-20.

91

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-20.  Breakpoints on WriteLine activities

This lab has demonstrated how to apply breakpoints to a workflow to debug its execution visually. This level of
debugging a process is much more natural using a declarative workflow compared to debugging plain code. The
lab also used WriteLine activities to send debug information to the console and how to set breakpoints on the
WriteLine activities to see the flow of execution with an If activity.

Error Handling
Designing a good error handling strategy within code is just as important as developing the functional code it
supports. Exception management is a proactive approach for anticipating when exceptions occur and how they are
handled during the execution of code. There are different types of exceptions that can occur and usually they are
handled differently based on the exception type. WF has a first-class implementation for handling exceptions within
workflows, and WF’s exception management closely resembles the constructs used within standard code.

Listing 3-11.  Try, Catch, Finally


private static void DoSomeThing()
{
try
{

}
92

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

catch (Exception ex)


{

}
finally
{

}
}

Listing 3-11 shows an example for how to handle exceptions in code, and its verbiage pretty much describes how
the try, catch and finally blocks are executed.
1. Code is executed within the try block.
2. If an exception is thrown within the try block, the exception is sent or caught within the
catch block so it can be managed.
3. The finally block is the last section of code that gets executed, regardless if an exception
is thrown or not. This is a great place to add code that either needs to release resources like
memory or to close connections to external sources like databases or files.

■■Caution The finally block of the TryCatch does not perform like regular C# code. If an error occurs within the
catch block, the finally block will not fire. This is not the case with C# code, as the finally block will fire regardless.

WF is declarative, so in order to implement the same functionality for handling exceptions, WF uses out-of-box
activities to declaratively add exception management. There are three activities that are used for handling errors with
in WF:
• Rethrow
• Throw
• TryCatch
By default, a workflow will throw an error that will bubble up to the application hosting the workflow, as long as it
is running the same execution thread. So using WorkflowInvoker.Invoke to host a workflow that has an unmanaged
exception will have the exception bubbled up to the hosting application; however, using WorkflowApplication, which
runs asynchronously to its hosted application, will not receive the error unless the hosted application subscribes to
the WF runtime’s OnUnhandledException event.

HANDLING EXCEPTIONS IN WF 

To demonstrate this, use the same workflow solution, Chapter3.Activities.


1. Open the solution Chapter3.Activities that you have been using for the other activities
2. Add another Workflow Console Application project to it and name it
Chapter3.Activities.ErrorHandling.

93

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

3. Right-click on the project and add a new item.


4. Click on the installed template Workflow and add a new code activity, naming it
ExceptionActivity. All this activity is going to do is throw an exception, so remove it
like so:

public InArgument <string> Text {get; set; }

and

string text = context.GetValue(this.Text);

5. Within the Execute method, add throw new Exception("Here is an exception"); .


The ExceptionActivity code should look like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;

namespace Apress.Example.Chapter3
{
public sealed class ExceptionActivity : CodeActivity
{
protected override void Execute(CodeActivityContext context)
{
throw new Exception("Here is an exception");
}
}
}

6. Right-click on the project and click Build. This will build the new ExceptionActivity.
7. Double-click on the Workflow.xaml, and you will notice the ExceptionActivity is now
added within the ToolBox under the solution.
8. Drag and drop the ExceptionActivity on the WF designer for the workflow. See
Figure 3-21.

94

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-21.  Exception Activity

The project is ready to run. The Program.cs contains code that will automatically invoke Workflow1.xaml, but
first add a Try/Catch block and a Console.ReadKey(), after the WorkflowInvoker call. The catch block will
catch the error within the workflow hosted application.

using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;

namespace Chapter3.Activities
{

class Program
{
static void Main(string[] args)
{
try
{
Activity workflow1 = new Workflow1();
WorkflowInvoker.Invoke(workflow1);
Console.ReadKey();
}

95

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

catch (Exception ex)


{
throw ex;
}
}
}
}

9. Right-click on the project and click Build. After everything builds correctly, press F5 to run
the workflow using the console application as the host. The debugger will break within the
ExceptionActivity showing that an exception has occurred. Pressing F10 causes the
debugger to break on the error as it bubbles back up, this time within the catch block of
the console application hosting the workflow. This simulates what can happen when a
workflow fails for an unknown reason. Now let’s prevent this exception from happening.
10. Stop the application if it is still running and open up the Workflow.xaml file. One
of the cool new features of WF4.5 is called Auto surround; basically it handles
situations for adding activities to a workflow when there is already an activity, like the
ExceptionActivity, and no activity container.

11. To illustrate Auto surround, drag the TryCatch activity to the WF designer and hover
either over or under the ExceptionActivity. The bars indicate that the new activity will
be placed either above or below the ExceptionActivity. Dropping the activity under
the ExceptionActivity causes a new Sequence activity appear, placing the TryCatch
activity directly below the ExceptionActivity and all contained within the Sequence
activity.
12. Drag the existing ExceptionAactivity from below the TryCatch activity and place
it within the Try block of the TryCatch activity. The workflow should now look like
Figure 3-22.

96

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-22.  TryCatch activity

13. The TryCatch activity is showing a design time Exception and that is because there are
no Catches established. Click on “Add new catch” and select System.Exception. You are
telling the TryCatch activity that it needs to catch exceptions that are of type System.
Exception when it runs the above activities within the Try block (see Figure 3-23).

97

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-23.  Catches System.Exception

14. After the catch has been added, activities can also be added within the catch for handling
the exception caught.
15. Add a WriteLine activity within the catch, indicating that the activity has caught
an exception. Add “TryCatch has caught the exception!” to the Text property of the
Writeline activity (see Figure 3-24).

98

www.it-ebooks.info
Chapter 3 ■ WindoWs WorkfloW aCtivities

Figure 3-24. Handling the error

16. Click on the Finally block of the TryCatch activity and drag a Writeline activity,
placing it within the Finally container of the activity. set the Text property to “finally
has executed!”
17. right-click on the project and click Build. after everything builds correctly, press f5 to
run the workflow. You will notice that the debugger still stops on the exception thrown from
the ExceptionaActivity, but the exception is not bubbled up to the application hosting
the workflow. instead, the workflow acknowledges that there was an error and sends the
message to the console that the workflow has handled the exception.
Just because exceptions bubble up to the application hosting the workflow does not mean they are not being
managed. if the ExceptionActivity produced an exception that needed to provide feedback that invalid
data was supplied to the workflow, then the exception might need to be relayed to the application hosting the
workflow. Usually System.ApplicationException types of exceptions are used to notify the user of application
information within the context of the operation being performed. the next steps will build on current workflow
built for handling an exception that needs to be sent to the hosting application.
18. open up the ExceptionActivity.cs file and change the line of code from
throw new Exception("Here is an exception");

to
throw new ApplicationException("Here is an Application Exception");

99

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

19. Right-click on the project and then click Add > New Item > Activity to add a new workflow
and name it ApplicationExceptionWorkflow.xaml.
20. Open the Workflow1.xaml, copy the workflow from the WF designer, and paste it into the
ApplicationExceptionWorkflow.xaml.

21. With the ApplicationExceptionWorkflow.xaml open, add another catch to the


Catches block of the activity by clicking on “Add new catch.”

22. System.ApplicationException is not a selectable choice for the type of Exception to


choose so you have to browse for it (see Figure 3-25).

Figure 3-25.  Catching another exception type (ApplicationException)

23. The Exception dropdown also allows .Net types to be browsed. Type
System.ApplicationException. See Figure 3-26.

100

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-26.  Browsing .Net types

24. Click on OK to select the ApplicationException.


25. There are now two catch blocks for the TryCatch activity. Select on the
ApplicationException block and it will open allowing child activities to be added.

26. Drag a WriteLine activity to the ApplicationException catch block and for the
text property add “Just caught an ApplicationException”. At this point, you have
done the same thing as the SystemException catch, except this block now catches
ApplicationException type of exceptions. The exception will not be thrown to the
hosting application, so you need to add a new ReThrow activity.
27. Drag a ReThrow activity from the toolbox and place it under the WriteLine activity. You
use the ReThrow because it is intended to be used within the catch block for re-throwing
an Exception (see Figure 3-27).

101

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-27.  Adding the Rethrow activity

28. Open up the Program.cs file and change


Activity workflow1 = new Workflow1();

to
Activity workflow1 = new ApplicationExceptionWorkflow();

Add another Catch block above the existing catch block. It needs to be added above the
System.Exception catch block because all exceptions inherit from System.Exception, and every
exception thrown by the code will always cause this Catch block to fire, unless another catch block
above it matching the type of exception fires first.
29. Add a finally block and add the following code:
Console.ReadKey();

102

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

30. Right-click on the project and click Build. After everything builds correctly, press F5 to run
the workflow. Your code should look like the following:
using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;

namespace Chapter3.Activities
{

class Program
{
static void Main(string[] args)
{
try
{
Activity workflow1 = new ApplicationExceptionWorkflow();
WorkflowInvoker.Invoke(workflow1);
}
catch(ApplicationException ex)
{
Console.WriteLine(string.Format("Application Exception --{0}-- has
fired!", ex.Message));
}
catch (Exception ex)
{
Console.WriteLine("Exception {0} has been bubbled up!",
ex.Message);
}
finally
{
Console.ReadKey();
}
}
}
}

As the workflow runs, the debugger stops as an ApplicationException is thrown from the ExceptionAactivity.
Pressing F10 allows the catch block to accept the exception type of ApplicationException. The WriteLine
writes to the console, “Just caught an ApplicationException”, and then the Rethrow activity re-throws the
exception to the hosting application. The exception is then caught within the new catch block that also catches
ApplicationException types of exceptions, and writes to the console, “Application Exception–Here is an
Application Exception!—has fired!”
The Rethrow activity is used for re-throwing an exception within a catch block, but what if you want to throw an
error during the execution of a workflow? This is the work that the Throw activity can accomplish. It is probably
not too different than the ExceptionActivity you built as a Code activity; the only difference is it allows you to
choose the type of Exception that needs to be thrown. These next steps will show how to implement the Throw
activity. You will change the workflow into a timer that will alert the console after a preset of seconds has passed.

103

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

31. Open up the ThrowExceptionWorkflow.xaml and add a new WF variable, and set its
scope to the TryCatch activity. This will allow all activities access to the variable. Name
the variable varSeconds and set its type to Int32.
32. Add another variable and name it varApplicationException. Select the drop-down for
the variable type and select “Browse for Types” to browse for the System.Application
type. Once you find it, select it and then set its scope to the TryCatch activity. This
variable will hold the exception that will be thrown.
33. Add a new WF argument and name it argInSeconds. Make sure the direction is set to In
and it’s type is Int32.
34. Drag an If activity onto the WF designer and add the following condition:
argInSeconds > 0.

35. Drag the ExceptionActivity and place it in the Else container of the If activity.
36. Drag a Delay activity and place it in the Then container of the If activity. The Delay
activity requires a duration of expiration. Click on the Delay activity and use the property
window to set the value for the duration to TimeSpan.FromSeconds(argInSeconds).
37. Drag an Assign activity and place it right above the Delay activity. Assign the varSeconds
variable to the argInSeconds argument by setting the assign To property varSeconds
and the Value property argSeconds.
38. Drag another Assign activity and place it right beneath the Delay activity. Assign the
varApplicationException variable to a new ApplicationException that will be
alert the console that the number of seconds entered has expired. Set the Assign To
property to :varApplicationException and the Value property to new System.
ApplicationException(string.Format("{0} seconds has expired! You have
been alerted",varSeconds))

39. Drag a Throw activity and drop it just below the Assign activity just created. Click on
the Throw activity and in the property window, click on the ellipse button, and add the
following code within the Expression Editor:
varApplicationException

40. Click on the catch block that handles the ApplicationException type of exceptions,
and delete the Rethrow activity. You do not want this Exception bubbling to the hosting
application. Instead you want to write a message to the console to alert that the entered
number of seconds is up from when the workflow started.
41. Click on the Text property of the WriteLine activity and set it to
varApplicationException.Message. See Figure 3-28.

104

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

Figure 3-28.  Alert workflow

42. The last part is to set up is the hosting application. Add an extra line of code right above
the WorkflowInvoker call. Add var wfArgs = new Dictionary <string,object >{ {
"argInSeconds", 5 } };

The extra code added will have a red squiggly under the Dictionary object. This is because the
using statement is missing. Press Ctrl and “.” at the same time and the using statement will pop up,
allowing it to be selected and added. The number of seconds for how long the workflow will run is
set to 5 seconds.
105

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

43. Right-click on the project and click Build. After everything builds correctly, press F5 to run
the workflow (see Figure 3-29).

Figure 3-29.  Success!

This lab has demonstrated how to use the TryCatch activity for handling two different types of exceptions. The
Rethrow activity was also introduced, showing how to rethrow an exception, once it had been managed within a
catch block. Finally, you used the Throw activity to throw an ApplicationException that was caught and used
as a message alerting the hosting application that the number of seconds entered had expired. All three of these
activities are used for managing exceptions within a workflow and for controlling what action takes place after
an exception occurs. More importantly, these activities can help prevent a workflow from failing during runtime
when unanticipated exceptions happen. Just like writing code, exception management is important and should be
strategically implemented.

Summary
This chapter focused on describing WF activities and the namespaces that establish the base classes that all activities
inherit from when created. It also covered how WF4 activities could author workflows, either through code or the
XML file format called XAML. By building workflows from XAML, workflows can be changed during runtime,
allowing for changes to how logic is processed within running applications. The chapter also covered the data model
used for getting data back and forth from a workflow and a means for communicating with the application hosting
the workflow through the WF runtime using a WF concept called bookmarks. Bookmarks will be covered in more
depth in later chapters, but this chapter supplied the code for building a custom bookmark activity that will also be
demonstrated in the next chapter on state-machine workflows. After establishing a good foundation for activities, the
focus changed to how activities can be unit tested and on patterns around debugging and implementing exception
management within activities. Finally, you discovered how to take advantage of the some of the activities provided
within WF, categorized as Primitive activities.

106

www.it-ebooks.info
Chapter 3 ■ Windows Workflow Activities

The many WF activities that are provided out-of-box will be used for modeling the majority of the business
processes you will encounter. The reason is because these activities closely mimic the constructs provided with
coding languages and written using syntax for writing logic. Instead, WF activities provide an alternative way for
developers to build code declaratively. In the next chapter, you will discover state-machine workflows within WF and
the advantages in using them to model event-driven and humanistic business processes that require interaction with
human behavior. The next chapters start focusing on the different types of workflows that can be built and why to use
one type of workflow over the other.

107

www.it-ebooks.info
Chapter 4

State Machine Workflows

In the previous two chapters I briefly skimmed over the basics of state machine workflows. This chapter will quickly
have you building state machine workflows to model some really cool scenarios, but before we get going, let’s review
the basics.
State machine workflows provide an alternative approach for modeling human behavior when the flow of events
cannot usually be predicted. An example is an approval process when events drive the flow of execution for the
process, usually as external events and guide transitions between other possible states. Basic characteristics of state
machine workflows include an initial and a final state. This means that a process must have a predefined state for
starting a process and a final state that represents that the process has completed. They also have a flexible flow of
logic that can cycle back and forth between states within a workflow. Because external events drive a state machine
workflow, they are reactive in nature (see Figure 4-1).

Figure 4-1.  Approval as state machine

109

www.it-ebooks.info
ws

State machine workflows were a part of the initial release of WF but they were not included with the release
of WF4. One reason was the thought that flowchart workflows would be a natural alternative for modeling state
machine processes. Also, state machine workflows were probably not included in WF4 because of the short window
for rewriting the entire WF framework and the lack of time to design a new state machine workflow. After WF4
was released, and in response to strong demand from developers, a new state machine workflow experience was
introduced within .Net 4.0 Platform Update 1, which is also included with WF4.5. Compared to the state machine
workflows released with WF 3.x, there is a much better modeling experience using the WF designer.
The best part about using state machine workflows is that they seamlessly integrate with sequential and flowchart
workflows, which was a complex task within WF 3.x. Because each type of workflow presents its own rich features for
modeling work, integrating multiple workflows combines the flexibility for modeling complex business processes.
A workflow can inherit the functionality and benefits gained from using different types of workflows to work together.
Most of the workflows I have built in the past have included state machines for long-running processes, so I think
re-introducing state machines into WF4.5 was a good thing. Primarily, long-running process flow usually includes
some human decision making.
Recently I was working with a client who asked me to make some enhancements to an existing online orders

First, each step of the ordering process relied on either a customer or employee to make a decision. This is a key

This chapter will demonstrate when state machine workflows should be used to model business processes

workflows within an application.

State Machine Components


Let’s take a look at the components that WF4.5 offers to developers for building state machines. The components
described in this section of the chapter are available for implementing state machine workflows in WF4.5.

State Machine Workflow


The state machine workflow activity resides within the namespace System.Activities.Statements.StateMachine
and provides the canvas for adding other activities. It is the first activity used for orchestrating state machine
workflows. One of the major features in WF4 was removing the boundary for the designer to care about the type of
workflow being built. Therefore, when creating a new workflow project, the default canvas for a workflow is blank. By
adding a state machine workflow to the designer canvas, additional state activities can be dragged to the designer to
model states within a process (see Figure 4-2).

110

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-2.  State machine workflow

When a state machine workflow is dragged to the designer, a design time exception is automatically indicated
within the initial default state, State1. Hovering over the exclamation mark reveals a pop-up tooltip that says,
"State1 must have at least 1 transition." This really means that another state needs to be manually added and
connected to State1 before the workflow will compile.

State
A state resides within the namespace System.Activities.Statements and is a sealed class, meaning that it is
not intended to be used as a base class for building custom objects. It also does not inherit from any of the base
activity objects, which means that is not an actual WF activity. States represent a logical position that a workflow
can have at any given time as the workflow executes. Each state on a workflow has an entry and exit action. These
are containers for adding additional child activities for modeling logic and executing units of work as the state
changes from one to another (see Figure 4-3). There is also a Transition(s) section that reflects the transitions are
directed to and away from a state.

111

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-3.  Entry and exit activity containers

State Entry
A state machine workflow by default provides a default state (see Figure 4-2), the first state that is transitioned to
automatically when a workflow is executed. When a transition is made to another state, the entry activity container
executes. Figure 4-4 shows the entry of a state that contains a Sequence activity that contains WriteLine activity that
says Entered State1. This activity will indicate to the console that the workflow is being transitioned to another state.
I added an If activity to demonstrate that the state entry itself executes as a workflow its self.

112

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-4.  State entry activities

113

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

After adding activities to a state’s entry action, the next time the complete state machine workflow is viewed
within the designer, there will be a circle with an arrow pointing into the left side that indicates the state has activities
contained within its entry action (see Figure 4-5).

  State1’s entry indicator arrow

A state’s exit action also allows activities to be added for performing business logic as the state is transitioned to
another state (see Figure 4-6).

114

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-6.  State exit point

Figure 4-7 indicates that after adding activities to the exit action of a state, a circle appears within State1 with an
arrow pointing out of the circle, indicating that activities exist while the state is about to transition to another state.

115

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

  State1’s exit indicator arrow

Transitions
State machines use transitions for flowing from one state to another. The logical flow of states can be transitioned
to and from one another within the same workflow; however, a transition can only flow in one direction. So if state
changes from one state to another and then back to the original state, there must be two transitions to model the flow
back and forth (see Figure 4-8).

Figure 4-8.  Transitions

116

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Each transition represents an event that is fired externally; transitions can only be executed from the current
state of the workflow. Each event is described using a bookmark that can be called externally from the workflow
(see Figure 4-9).

Figure 4-9.  Transition trigger, condition, and action

Trigger
A custom bookmark activity can be defined through code and is used for triggering the execution for a state machine
transition. When a bookmark is initiated from the hosted WorkflowRuntime, the corresponding bookmark activity is
executed initiating the execution for a transition. A transition’s trigger is used as a container for a bookmark activity.

Condition
Once the trigger has been fired from a bookmark activity, in order for the transition to succeed to another state,
business logic can be described using an expression determining if the transition should succeed or fail.

117

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Action
After the condition passes for a triggered transition, the action allows additional activities for modeling business
logic that should be performed as the transition completes from one state to another.

■■Note  The same bookmark can be defined for firing an external event for more than one transition. The condition
for the transition uses an expression that determines which transition will execute.

Final State
The FinalState models the last unit of work performed within a business process before it finally ends. It is identified
 4-10). The FinalState is used to finalize any

  FinalState

Once the transition is made to the FinalState of the workflow, any last bit of logic can be executed because it
 4-11).

Figure 4-11.  FinalState entry point

The main difference of the FinalState is that it does not have an exit action, because once the workflow has
transitioned to the FinalState, there are no other states to transition to and the workflow completes.

■■Tip  After opening state machine workflow projects that were created using .NET Platform Update 1, the project that
contains the state machine workflow needs to reference System.Activities.Statements.

118

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Auto-Connect
With the WF designer enhancements in WF4.5, state activities no longer have to be manually connected to other
states that do not already have a transition associated with them. Connecting states can now be automatic after a
state is selected from the toolbox and dragged within close proximity to an existing state on the designer canvas.

Auto-Insert
In WF4.5, states can also be automatically connected in between states that are already associated through a
transition, by positioning the mouse over a selected state from the toolbox and dragging it over an existing
transition. When the activity is released, it becomes the middle transitional state between the other existing two.

Debugging State Machine States


The WF4.5 designer also allows breakpoints to be added to a state itself, indicating that a state is about to be
executed during runtime (see Figure 4-12).

Figure 4-12.  Red circles with diagonal white lines indicate that a breakpoint has been added onto state activities

State Machine Behavior


There are some behaviors for state machine workflows in WF I want to cover so that you are aware of them while
building workflows. I will start off by demonstrating some of the behaviors by using functionality out of the box.
I’ll use a standard workflow console application and the state machine workflow activity that will be extended for
demonstration purposes.

119

www.it-ebooks.info
ws

Transition Requirement
Earlier I mentioned that after a new state machine workflow is added to the designer canvas, by default it will
contain a single state as depicted in Figure 4-2, but a transition must be set to another state. This is the case for each
state that is added to the workflow other than the FinalState, or an exception will be thrown for the latest state added
to the workflow (see Figure 4-13).

State transition requirement

Adding a FinalState to the workflow will satisfy the requirement; however, a transition can also be added back
to State1 from State2 and this will also satisfy the requirement (see Figure 4-14).

Figure 4-14. Circular transitions

120

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

As you can imagine, this could cause an infinite loop within the workflow depending on how the transition is
implemented. In this case, the transition has not been altered after making the connection from State1 to State2.
Therefore, there is no condition set up for the transition, which means that the transition will automatically
execute. This can be demonstrated through the console window by adding two WriteLine activities within the states.
Add one within the entry action of State1 saying Entered State1 and the other within the entry action of State2
saying Entered State2 (see Figure 4-15).

Figure 4-15.  Demonstrating circular transition

Transition Conditions
A state can make more than one transition to another state. Figure 4-16 shows that State1 has two transitions to
the FinalState of the workflow.

Figure 4-16.  More than one state transition

121

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

When this happens, the condition of the transition has to be set. If not, you will get an error like in Figure 4-17.

Figure 4-17.  Error message

To get rid of the exception, a condition using a C# or VB expression can be used. In this case, I used the C#
expression, which is a new feature in WF4.5. The syntax is 1==1 since the workflow project is a C# project (see Figure 4-18).

  Transition expression

The problem is that one or more transitions from the same state can actually use the same expression
 4-19).

Figure 4-19.  Multiple transitions with the same condition

122

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

If they do, the transitions will fire in the order that they were added to the workflow. This can be seen for
workflows built with XAML by looking through the XML for a workflow indicated as the first transition within the
transition’s XML element (see Listing 4-1). The good thing is that only one of the transitions will succeed, cancelling
out the other transitions.

Listing 4-1.  Possible Transitions with the Same Transition Condition


<State.Transitions>
<Transition DisplayName="T1" sap2010:WorkflowViewState.IdRef="Transition_3">
<Transition.Condition>
<mca:CSharpValue x:TypeArguments="x:Boolean">1==1</mca:CSharpValue>
</Transition.Condition>
<Transition.To>
<State x:Name="__ReferenceID0" DisplayName="FinalState"
sap2010:WorkflowViewState.IdRef="State_3" IsFinal="True" />
</Transition.To>
</Transition>
<Transition DisplayName="T2" sap2010:WorkflowViewState.IdRef="Transition_4"
To="{x:Reference __ReferenceID0}">
<Transition.Condition>
<mca:CSharpValue x:TypeArguments="x:Boolean">1==1</mca:CSharpValue>
</Transition.Condition>
</Transition>
<Transition DisplayName="T3" sap2010:WorkflowViewState.IdRef="Transition_5"
To="{x:Reference __ReferenceID0}">
<Transition.Condition>
<mca:CSharpValue x:TypeArguments="x:Boolean">1==1</mca:CSharpValue>
</Transition.Condition>
</Transition>
</State.Transitions>

■■Tip  A workflow created using the designer has a XAML file that can be viewed by right-clicking the workflow and
selecting View Code.

If a bookmark were to be used within trigger for transition T4, the next transition would fire within the transition’s
XML element because the T4 transition would then be waiting on an external event.

Using Bookmarks
A transition trigger is what really drives a transition into execution. The condition is merely used to determine if
the transition is successful for transitioning to the next state; therefore it does make sense for more than one transition
of a state to use the same condition. In fact, the same bookmark could be used for more than one transition within the
same state. This behavior is called a shared trigger because the bookmark is what drives the execution for more than one
transition. However, it’s not common for the same bookmark to be used more than once with the same condition. See
Listing 4-2.

123

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Listing 4-2.  WaitForResponse Bookmark Activity


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;

namespace FlowFocus.WF.Activities
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{

}

public string ResponseName { get; set; }

protected override bool CanInduceIdle
{

get
{
return true;
}
}

protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(this.ResponseName, new BookmarkCallback(this.ReceivedResponse));
}

void ReceivedResponse(NativeActivityContext context, Bookmark bookmark, object obj)
{
this.Result.Set(context, (TResult)obj);
}
}
} 

■■Caution  State transitions for the same state should not share the same bookmark trigger and the same condition.
This behavioral logic would be considered redundant.

124

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

USING BOOKMARKS FOR STATE MACHINES

I will use the code in Listing 4-1 to build a generic bookmark activity to demonstrate using bookmarks with state
machine transitions for different scenarios. To create a bookmark using the code in Listing 4-2 and walk through
the different scenarios, create a new Visual Studio 2011 solution by following these steps.
1. Open Visual Studio 2012 and create a new Project.
2. Select the Workflow template to see a list of installed workflow templates.
3. Select Workflow Console Application, and name it Chapter4.StateMachine.
4. Right-click on the project and select Add ➤ Class.
5. Name the new class file WaitForResponse.cs.
6. Delete the default code in WaitForResponse.cs and replace it with the code from Listing 4-1.
7. Compile the project. At this point there is now a WaitForResponse activity that will be
used for adding bookmarks to the state machine workflow.
8. Click on the default workflow file, Workflow.xaml, and add a new StateMachine activity
from the toolbox to the designer canvas.
9. Drag a FinalState over to the workflow and hover over the existing State1 state.
Arrows will appear on State1 and the bottom arrow will bold as the FinalState is
hovered over it. Drop the FinalState onto the bottom arrow as it bolds and it will
automatically provide a new transition called T1 to the FinalState (see Figure 4-20).

125

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-20.  Auto connecting FinalState

10. To see the entry and exit activity containers within a state, double-click on State1.
11. Drag a WriteLine activity from the toolbox and drop it within the entry container. Set the
Text property for the activity to Entered State1.

12. Double-click the FinalState activity and drag another WriteLine activity from the
toolbox and drop it within the entry container. Set the Text property for the activity to
Entered the FinalState.

Double-click transition T1 to view its trigger, condition, and action points. At the top of the toolbox, you
will notice the WaitForResponse bookmark activity that you added earlier. Drag it from the toolbox and add it to
the Trigger container. The Select Types box appears, requesting what type of data the bookmark is in charge of
126

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

passing from the WorkflowRuntime to the active bookmark. The default value type in this case is Int32, and for
simplicity Int32 will be used for executing the state-machine Transition. See Figure 4-21. If I needed to pass in
a certain entity, like an order object, then I could browse for that order type and use it to pass in an order that I
would like to appear in a workflow.

Figure 4-21.  Adding WaitForResponse bookmark

13. After selecting the bookmark type of Int32, the bookmark needs to be given a
ResponseName property so the workflow knows which bookmark the WorkflowRuntime
intends to resume. This is set within the Properties window while the new
WaitForResponse activity has been selected. Set the ResponseName property to
BookMarkResponse (see Figure 4-22).

127

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-22.  WaitForResponse bookmark properties

14. Usually there would be one last step to open up the Program.cs file and add
Console.Read(); right after WorkflowInvoker.Invoke(workflow1); This will allow the
console window to stay open after the workflow completes; however the workflow will
not complete now because of the bookmark you added. The bookmark tells the workflow
that it needs to wait for an event before it can complete, which is unlike the behavior
you saw earlier in the chapter where transitions without triggers were automatically
transitioning to other states (see Figure 4-23). The workflow can now be run; however
the WriteLine activity that you placed within the FinalState activity will never write to
the console because State1 never transitions.

Figure 4-23.  Workflow waiting on bookmark

■■Tip  The Result property of a bookmark can be set to a WF variable within the workflow so the value that is passed in through
the bookmark can assist in executing logic within the workflow.

128

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Hosting WF Bookmarks
Bookmarks will not work while using WorkflowInvoker.Invoke(). Bookmarks demand a closer intimacy with the
WorkflowRuntime that WorkflowInvoker does not provide. Instead WorkflowApplication must be used for hosting
workflows that request internal interaction with the WorkflowRuntime through bookmarks. The WorkflowApplication
hosts a workflow on a separate thread from the hosting application. Because of this, delegates are set up for handling
WorkflowRuntime events like when a workflow completes, aborts, goes idle, or encounters an unexpected exception.
The WorkflowApplication must be instantiated to host workflows. This is different than using the static
WorkflowInvoker. I will demonstrate this using the same code that was created in the exercise “Using Bookmarks
for State Machines.” Opening the Program.cs file will reveal the default code used for invoking Workflow.xaml
(see Listing 4-3).

Listing 4-3.  Default Program.cs


using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;

namespace Chapter4.StateMachine
{

class Program
{
static void Main(string[] args)
{
// Create and cache the workflow definition
Activity workflow1 = new Workflow1();
WorkflowInvoker.Invoke(workflow1);
}
}
}
Because there are two threads executing, one for the application and the other for the workflow, the using
statement, Using System.Threading must be added and WorkflowInvoker.Invoke(workflow1); can be removed.
The WorkflowApplication code can now be added to host the workflow built in the previous exercise. The following
code is added to instantiate the new host:
WorkflowApplication wfApp =
New WorkflowApplication(workflow1);
The next code to add handles the synchronization between the two threads between the host and the workflow:

AutoResetEvent autoEvent = new AutoResetEvent(false);

The next piece of code that is needed is notification for when the workflow completes, using the
WorkflowApplication host’s Completed action:
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Console.WriteLine("Workflow has completed!");
autoEvent.Set();
};

129

www.it-ebooks.info
ws

Finally, the WorkflowApplicaton is started, and the WorkflowRuntime is notified that there is a bookmark for the
workflow that is intended to resume workflow execution. The WorkflowApplication host uses the ResumeBookmark
method to pass in the bookmark’s name and value:
wfApp.Run();
wfApp.ResumeBookmark("BookmarkResponse",Convert.ToInt32(Console.ReadLine()));
Console.ReadKey();
The above code will accept an entered value typed in from the console, and since the bookmark within the
workflow in the exercise requires an integer, the value entered is converted to an integer. After the code mentioned
above is added to Program.cs, the workflow can be run. The first console window that appears is pretty much the
same as what you received in the exercise (see Figure 4-24)

Figure 4-24. Workflow waiting on a bookmark to resume execution

At this point, entering any number and pressing Enter will resume execution of the workflow to completion
(see Figure 4-25).

130

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-25.  The workflow completing

Congratulations, you have now created a bookmark and resumed a state machine workflow from the hosting
application.

Building State Machine Workflows


So far this chapter has helped you discover the components and basic behavior patterns used for building with
state machine workflows, and it briefly introduced a new way of communicating externally to hosted state machine
workflows using bookmarks. Now I want to show you how to tie this knowledge together and model a state
machine starting off with a simple familiar process for driving an automobile. You will learn the following concepts:

• Hosting application
• Implementing state
• Wiring up transitions
• Shared triggers

Driving a car is an activity that most of us either do every day or are at least have some familiarity with
performing, therefore modeling the process should be quite simple. The steps to modeling a process are another
important exercise that WF requires, because while requirements are being analyzed, WF can be used for modeling
the logical process. I have worked on many projects where I felt more comfortable using WF to model requirements
than Visio, and WF allows state machines to model work at a higher level, so additional requirements can be
discovered. But back to modeling driving an automobile: it may seem like an unrealistic business case, but to
make the concept more interesting you might like to know that there is software already doing what I am about to
demonstrate to you.
The first step in driving an automobile is getting in it. Well, since we are talking about states, before a driver
can get in to a car, it must be parked. But I think I was on to something when I mentioned “getting in.” This could be
modeled as a transition to another state. The next state could be to drive the car, or more specifically maybe the

131

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

next step should be to start the engine. There are a bunch of other obvious things that should happen before putting
an automobile in gear, such as the following:
• Fastening the seatbelt.
• Inserting the key.
• Locking the door.
This is why it is important to have requirements—because something that a stakeholder feels is very important
could be different than what developers think should be implemented. Modeling the flow allows developers to use
visual representations that can be used for driving additional requirements. To keep things simple, the states will
model the following:
• Parked
• EngineStarted
• Neutral
• Reverse
• Drive
• TurnedOff
Something to note about these states is the indented section for Drive and Reverse. Indentation could either
states or even transition. One way to differentiate between the two is to identify a transition as an action
state as a status. In this case they are states because they define a status of being in gear.
Out of the box, these bullets can be modeled into a logical flow without having to wire up anything within WF;
 4-26).

Figure 4-26.  Driving state machine model

132

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Immediately there is an exception indicating that there is more than one transition that does not have a trigger
associated with it. The other interesting thing is how the workflow modeled the bullets of
• Neutral
• Reverse
• Drive
A little bit of inferred logic indicates that Drive and Reverse must be set back to Neutral before the automobile
can be turned off; however this representation could also be used to prove the case that this should be the only
logical flow for turning off an automobile. Annotations can now be used in WF4.5 for indicating this logical decision.
Annotations can be added to the workflow or to individual activities. Figure 4-27 indicates an annotation was added to
the Neutral state to capture the requirement decision.

Figure 4-27.  Activity annotation

Transition descriptions can also be added to make the state machine more descriptive. By default WF adds a
“T,” followed by a sequential number that uniquely represents each transition. A transition has a DisplayName
property that can be customized to represent the action that the transition will take while the transition occurs
(see Figure 4-28). Selecting a transition, by clicking on it, allows the transition’s properties to be viewed within the
Properties window, where the DisplayName property can be changed.

Figure 4-28.  Transition’s DisplayName property

The goal for building workflows is to visually make a process’s logical flow of execution as easy as possible to
interpret. Changing the names for the transitions of a workflow makes it look just as good as any other modeling
tool used for producing process models (see Figure 4-29).

133

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

  Descriptive transitions

State Machine Host


Since the Driving state machine workflow provides some of the same basic functionality as a production state
machine workflow, the hosting application needs to provide additional functionality for hosting the workflow
and giving the user feedback on how the workflow is responding. In the last example, I demonstrated how to
build a simple console application used for hosting a state machine workflow through WF’s hosting provider
WorkflowApplication. This time I will demonstrate how the driving workflow can be hosted within a Windows
Presentation Foundation (WPF) application (see Figure 4-30), but first let’s walk through the Driving workflow.

134

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-30.  Driving workflow hosted in WPF

The transition descriptions added in Figure 4-29 really help to visually infer the modeled process for driving
an automobile, but just to make sure the workflow is clear, let’s walk through how it should flow while it is executed.
As the workflow is started, the first thing that happens is the workflow anticipates the automobile gear to be in Park,
so the workflow will automatically transition its state to In Park. While In Park, the driver has the ability to start the
engine. While In Park, the workflow indicates that the automobile has to be started and gear changed to neutral.
The automobile can then be driven by changing the gear to Drive or the engine can be turned off. If the engine is
turned off, you can see that the TurnedOff state uses a FinalState, so the workflow would then be completed, but
if automobile’s gear is put into drive, then later the automobile can be put back into neutral or even reverse. Let’s now
see how the workflow flows while it is hosted within the application, and how the hosting application functions and
response to the driving workflow as it is being executed.
The hosting application built to host the driving workflow will instruct the workflow how it needs to flow as the buttons
on the user interface (UI) are clicked. Commands are then communicated to the workflow through the WorkflowRuntime,
using WF bookmarks that are associated when a button is clicked. Figure 4-30 shows that there are five buttons:
• Start Engine
• Drive
• Neutral
• Reverse
• Turn Off
Each button commands how the workflow will flow and react based on the workflows feedback. As the
application is started, something interesting happens. The WorkflowRuntime is started and accepts the driving
workflow state machine as the model that will be executed. As a result, the workflow starts the workflow’s state goes to
In Park. Figure 4-31 illustrates how the UI reacts based on the workflow’s execution.

135

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

  Start Engine application command

This feedback is then returned to the hosting WPF application; the results seen in Figure 4-32 mirror the same

bookmark to change its state to In Neutral. The workflow then communicates back to the hosting
 4-32).

Figure 4-32.  Drive and Turn Off application commands

136

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

If the Drive button is selected, the workflow flows to the In Drive state, and the workflow can then either flow
back to the In Neutral state or the In Reverse state. The hosted application guides these choices by enabling the
corresponding buttons (see Figure 4-33).

Figure 4-33.  Neutral and Reverse application commands

The workflow can then go back to the In Neutral state or decide to go to the In Reverse state
(see Figure 4-34).

Figure 4-34.  Neutral application command

137

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Clicking the Neutral button will take the workflow back to the In Neutral state where the Drive and Turn
Off buttons are available. Clicking the Turn Off button will then complete the workflow. Once the workflow has
completed, all of the button commands are disabled, as you can see in Figure 4-35.

  Hosting application indicates workflow completion

Now that you see how the hosting application functions with the driving state machine workflow work, I want
to walk through the code. The first thing is the UI. If you are not familiar with WPF applications, it is similar to WF
workflows because WPF uses XAML as its markup for defining its UI. This is much different if you are used to building
Windows Forms applications.
I wanted it to be pretty simple to follow, since I want you to focus on the integrating interaction with the
application and the WorkflowRuntime using the WorkflowApplication hosting provider. WPF uses code the same way
as Windows Forms does for driving the UI, and building the UI is very similar to building a Windows Forms front end,
too (see Figure 4-36).

138

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-36.  WPF hosting application UI

While controls are dragged from the toolbox, XAML is also being automatically built, representing the form as
markup (see Listing 4-4).

Listing 4-4.  UI Markup (MainWindow.xaml)


<Window x:Class="Chapter4.Driving.Host.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="269" Width="159.5">
<Grid Margin="10,10,11,-6" RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-0.09"/>
<TranslateTransform/>
</TransformGroup>
</Grid.RenderTransform>
<Button Name="cmdStartEngine" Content="Start Engine" HorizontalAlignment="Left"
Margin="25,35,0,0" VerticalAlignment="Top" Width="75" Click="cmdStartEngine_Click"/>
<Button Name="cmdDrive" Content="Drive" HorizontalAlignment="Left"
Margin="25,71,0,0" VerticalAlignment="Top" Width="75" Click="cmdDrive_Click"/>
<Button Name="cmdNeutral" Content="Neutral" HorizontalAlignment="Left"
Margin="25,107,0,0" VerticalAlignment="Top" Width="75" Click="cmdNeutral_Click" />
<Button Name="cmdReverse" Content="Reverse" HorizontalAlignment="Left"
Margin="25,142,0,0" VerticalAlignment="Top" Width="75" Click="cmdReverse_Click"/>
<Button Name="cmdTurnOff" Content="Turn Off" HorizontalAlignment="Left"
Margin="25,180,0,0" VerticalAlignment="Top" Width="75" Click="cmdTurnOff_Click"/>

139

www.it-ebooks.info
ws

<Label Name="lblEvent" HorizontalAlignment="Left" Margin="8,5,0,0"


VerticalAlignment="Top"/>
</Grid>
</Window>
After getting the UI built, a representation for the transitions within the workflow are defined using enums. This
is shown in Listing 4-5.

Listing 4-5. Transition Enums (EnumTransition.cs)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

{
public enum DriveTransition
{
StartEngine,
TurnOff,
InGear,
PutInReverse,
PutInNeutral,
};

Other than the workflow, these are the only files that are external to the WPF hosting application. The actual code
that works with the WorkflowApplication is represented in Listing 4-6.

Listing 4-6. Hosting Application (MainWindow.xaml.cs)


using System;
using System.Activities;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Chapter4.Driving.Host
{
/// <summary>

140

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

/// Interaction logic for MainWindow.xaml


/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
StartWFRuntime();
}

private void cmdStartEngine_Click(object sender, RoutedEventArgs e)
{
StartEngine();
}
private void cmdDrive_Click(object sender, RoutedEventArgs e)
{
GoForward();
}

private void cmdNeutral_Click(object sender, RoutedEventArgs e)
{
PutInNeutral();
}
private void cmdReverse_Click(object sender, RoutedEventArgs e)
{
PutInReverse();
}
private void cmdTurnOff_Click(object sender, RoutedEventArgs e)
{
TurnOffEngine();
}
private static WorkflowApplication wfApp = null;
private void StartWFRuntime()
{
try
{
if (wfApp == null)
{
wfApp = new WorkflowApplication(new wfDriving.Activity1());
wfApp.SynchronizationContext = SynchronizationContext.Current;
wfApp.OnUnhandledException = OnUnhandledException;
wfApp.Completed = OnWorkflowCompleted;
wfApp.Idle = OnWorkflowIdle;
wfApp.Run();
}
}
catch (Exception ex)
{
throw ex;
}
}

141

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

public void PutInReverse()


{
try
{
ResumeBookmark("PutInReverse");
}
catch (Exception ex)
{
throw ex;
}
}
public void PutInNeutral()
{
try
{
ResumeBookmark("PutInNeutral");
}
catch (Exception ex)
{
throw ex;
}
}
public void GoForward()
{
try
{
ResumeBookmark("InGear");
}
catch (Exception ex)
{
throw ex;
}
}
public void TurnOffEngine()
{
try
{

ResumeBookmark("TurnOff");
}
catch (Exception ex)
{
throw ex;
}
}
public void StartEngine()
{
try
{
ResumeBookmark("StartEngine");
}

142

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

catch (Exception ex)


{
throw ex;
}
}

private void ResumeBookmark(string Bookmark)
{
try
{
wfApp.ResumeBookmark(Bookmark, string.Empty);
}
catch (Exception ex)
{
throw ex;
}
}

private UnhandledExceptionAction
OnUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs uh)
{
return UnhandledExceptionAction.Terminate;
}

/// <summary>
/// The on workflow completed.
/// </summary>
/// <param name="wc">
/// The event args
/// </param>
private void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs wc)
{

DisableButtons();
}

/// <summary>
/// Called when the workflow is idle
/// </summary>
/// <param name="args">
/// The event args.
/// </param>
private void OnWorkflowIdle(WorkflowApplicationIdleEventArgs args)
{
var bookmarkList = new StringBuilder();

DisableButtons();
foreach (var bk in args.Bookmarks)
{
DriveTransition ret;
Enum.TryParse(bk.BookmarkName, out ret);
switch (ret)

143

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

{
case DriveTransition.InGear:
cmdDrive.IsEnabled = true;
break;
case DriveTransition.PutInNeutral:
cmdNeutral.IsEnabled = true;
break;
case DriveTransition.PutInReverse:
cmdReverse.IsEnabled = true;
break;
case DriveTransition.StartEngine:
cmdStartEngine.IsEnabled = true;
break;
case DriveTransition.TurnOff:
cmdTurnOff.IsEnabled = true;
break;
}

bookmarkList.Append(bk.BookmarkName);
}
}
private void DisableButtons()
{
cmdDrive.IsEnabled = false;
cmdNeutral.IsEnabled = false;
cmdReverse.IsEnabled = false;
cmdStartEngine.IsEnabled = false;
cmdTurnOff.IsEnabled = false;
}
}
}

As the hosting application is started, the first thing that happens in the constructor is spinning up the
WorkflowRuntime so the application can send and receive feedback from the workflow, like so:
if (wfApp == null)
{
wfApp = new WorkflowApplication(new wfDriving.Activity1());
wfApp.SynchronizationContext = SynchronizationContext.Current;
wfApp.OnUnhandledException = OnUnhandledException;
wfApp.Completed = OnWorkflowCompleted;
wfApp.Idle = OnWorkflowIdle;
wfApp.Run();
}
As demonstrated in this code, the WorkflowRuntime receives the workflow definition for the driving workflow,
wfDriving.Activity1(). Because the WorkflowRuntime is run within its own thread and not the applications UI
thread, calling wfApp.SynchronizationContext = SynchronizationContext.Current; tells the WorkflowRuntime to
run within the same thread as the application. This makes debugging and processing events within the application
during the execution of the workflow much easier to manage. The next couple of lines wire up the events for when
the WorkflowRuntime completes, receives an unhandled exception, or goes idle. If you compare it to the example
earlier where you used this syntax

144

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Console.WriteLine("Workflow has completed!");
autoEvent.Set();
};
this example works off of the delegate used for defining the action for when a workflow completes. In this example,
the following function and methods handle each of the events being wired up to the WorkflowRuntime through the
WorkflowApplication:
private UnhandledExceptionAction OnUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs uh)
private void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs wc)
private void OnWorkflowIdle(WorkflowApplicationIdleEventArgs args)
So at this point you have started up the WorkflowRuntime within the hosting application and its events.
Now a workflow can be executed; however, the following code starts the initial communication point to the
workflow using a bookmark. This code is the method used for handling all of the bookmark calls from the host using
WorkflowApplication.ResumeBookmark. At this point the hosting application is not passing any information to the
workflow, so each time a bookmark is called, a string.empty is all that is passed to the workflow. In the next exercise,
I will demonstrate passing data to the workflow from the hosting application.
private void ResumeBookmark(string Bookmark)
{
try
{
wfApp.ResumeBookmark(Bookmark, string.Empty);
}
catch (Exception ex)
{
throw ex;
}
}
The first bookmark that can be called once the workflow is executed is StartEngine().
public void StartEngine()
{
try
{
ResumeBookmark("StartEngine");
}
catch (Exception ex)
{
throw ex;
}
}
The StartEngine() method is called when the corresponding click event is called from the Start Engine button.
private void cmdStartEngine_Click(object sender, RoutedEventArgs e)
{
StartEngine();
}

145

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Listing 4-6 shows that the other buttons are also wired the same way as just described to call ResumeBookmark,
passing in the name of the bookmark described in the bookmark:
• PutInReverse
• PutInNeutral
• InGear
• TurnOff
As a bookmark is called from the hosting application, and the event is registered within the workflow to transition
to another state, the transition is made and the workflow waits for another bookmark. Then it goes idle. The hosting
application is then notified that the workflow has gone idle through the WorkflowRuntime and via the method that
was used to handle the Idle event.

{
DisableButtons();
foreach (var bk in args.Bookmarks)
{
DriveTransition ret;
Enum.TryParse(bk.BookmarkName, out ret);
switch (ret)
{
case DriveTransition.InGear:
cmdDrive.IsEnabled = true;
break;
case DriveTransition.PutInNeutral:
cmdNeutral.IsEnabled = true;
break;
case DriveTransition.PutInReverse:
cmdReverse.IsEnabled = true;
break;
case DriveTransition.StartEngine:
cmdStartEngine.IsEnabled = true;
break;
case DriveTransition.TurnOff:
cmdTurnOff.IsEnabled = true;
break;
}
}
}
The OnWorkflowIdle method really handles the magic for knowing what the next interaction point(s) are with the
workflow by calling the eventargs, WorkflowApplicationIdleEventArgs, returned with the method. First, all of the
buttons are disabled until a determination can be made for what commands are allowed to be sent to the workflow. As
the next available bookmarks are collected, they are compared with the enum DriveTransition in Listing 4-5 because
each DriveTransition is matched with the workflow’s bookmarks. Once the available bookmarks are matched, logic
is used to identify corresponding buttons that are enabled so that the command(s) can be sent to the workflow.

146

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

State Machine Flexibility


How else could this example for driving a car be implemented if WF was not an option? One solution could be to write
code that represents the execution of logic that was modeled in Figure 4-29. Another solution could be to use database
table structures with one table called State that has all of the states required for driving and another table called
Transition that has two columns, StateTo and StateFrom, with foreign keys to the primary key of the State table.
Records could then be added to the Transition table to show which states could transition to other states.
Although these approaches are not ideal now that WF is available, if they were to be used, you automatically
lose transparency for the process being modeled, and the flexibility and functionality for how the workflow is to be
processed. Implementing this through code means that the changes have to be recompiled. Modeling the flow using
table structures might provide a better level of flexibility, but all of the functionality is lost for building the business
logic that has to be processed as the flow is executed.
Many of the software projects that developers work on will have some level of requirement changes. A good
deal of the changes could be the flow process that a state machine workflow was built to execute. Depending on the
development methodology that is being used for writing software, sometimes the changes are introduced during
the development phase. WF allows the separation of business process logic, and executed application logic was
mentioned in an earlier chapter, so solutions gain a heightened level of flexibility that was not available without using
a workflow technology like WF. Now I’ll demonstrate the real power of WF and using state machines!
I’ll introduce some changes that change up the flow and execution of how an automobile should be driven;
these changes need to be reflected within the workflow that was already built. This could be a significant task without
workflow, but I will show you how these changes can be made within a matter of seconds. Here are the changes that
need to be implemented:
• Neutral state is not important for driving.
• Put in Park to start the engine.
• Put in Park to turn off the engine.
• Shifting gears from Drive and Reverse should be done from Park.
The first thing I did was rename the hosting application’s button called Neutral to In Park, since the term
“Neutral” is no longer needed. Next, the state of In Neutral was renamed to Engine Running. The Stop Engine
transition was then selected and reconnected by dragging and dropping the origination and destination from the
In Park state to the TurnedOff state. Then the transition of Put In Neutral from the In Drive state was
reconnected to the In Park state and renamed to Put In Park. The other transition called Put In Neutral that
was connected from the In Reverse state to the In Neutral state was renamed to Put In Park and its destination
was reconnected to the In Park state. Thus the Driving workflow is transformed from the previously built workflow
in Figure 4-29 to the workflow represented in Figure 4-37.

147

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

  Modified workflow from requirement changes

If the description for one of the buttons on the hosting application was not changed from Neutral to Park,
the application would not need to be recompiled to handle the changes implemented because first, the workflow
is authored in XAML, and second, there is a clear separation between the workflow and the hosting application.
Restarting the hosting application after the changes will reveal the flow changes that model the new changes, and the
buttons will respond accordingly to the changes as the workflow is executed.

DRIVING STATE MACHINE

I will now walk you through the steps for building the Driving workflow in Figure 4-37. Some additional
functionality includes:
• While In Park before the car is started, bookmarking Start Engine is possible.
• Add another transition from In Park to In Reverse.
• Automatically go to Engine Started when the car is started.
This solution will consist of two projects: Chapter4.Driving.Host will strictly be used to host the workflow and
Chapter4.Driving.Workflow will contain files pertaining to the workflow and its activities.

1. Open Visual Studio 2012 and create a new Project.


2. Select the Windows template if it is not already selected and then select WPF Application.

148

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

3. Name the project Chapter4.Driving.Host and change the name of the solution to
Chapter4.Example.StateMachine.

4. Add another project to the solution, but this time select the Workflow template and select
Activity Library.
5. Name the workflow project Chapter4.Driving.Workflow.
6. Add a reference for the project Chapter4.Driving.Host to the workflow project
Chapter4.Example.StateMachine by right clicking References within the project
and select Add Reference, Visual Studio 2012 has a cool new interface for selecting
references. Selecting the Solution tab will display all of the projects represented within
the current solution that can be referenced.
7. Select Chapter4.Driving.Workflow to add it as a reference as the hosting 4 application
(see Figure 4-38). This will allow the workflow to be hosted within the WPF application.

Figure 4-38.  Adding a project reference

8. Locate the MainWindow.xaml file within the WPF application and add the same XAML
from Listing 4-4. Locate the button with the Name property and rename it to cmdPark,
Change the Content property for the same button to Park. Find the Click property
again for the same button and change it to cmdPark_Click.
9. Right-click MainWindow.xaml and click View Code, The changes made to the button’s
Name and Click property need to be reflected within the code, too.
149

www.it-ebooks.info
ws

10. Replace the code in MainWindow.xaml.cs by pasting in the code from listing 4-6 instead.
11. While MainWindow.xaml is open, press Ctrl-f to pull up the find and Replace dialog box.
Do a find for cmdneutral and replace it with cmdPark for the document (see figure 4-39).

Find and Replace in Visual Studio 2012

12. Press Ctrl-f and do another find and Replace for Putinneutral and replace it with
PutinPark.
13. Add a new class to the WPf project by right-clicking the project and selecting Class.
This file will hold the enum object for the possible transitions within the state machine
workflow. Add the code from listing 4-4 and rename the class file to EnumTransition.cs.
Change the last enum of Putinneutral to PutinPark.
14. Add a new class to the workflow project by right-clicking the project and selecting Class.
This file will hold the bookmark activity.
15. Rename the new class file as WaitForResponse.cs and copy in the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;

namespace Chapter4.StateMachine
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{

public string ResponseName { get; set; }

150

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

protected override bool CanInduceIdle


{ //override when the custom activity is allowed to make he workflow go idle
get
{
return true;
}
}

protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(this.ResponseName, new
BookmarkCallback(this.ReceivedResponse));
}

void ReceivedResponse(NativeActivityContext context, Bookmark bookmark,
object obj)
{
this.Result.Set(context, (TResult)obj);
}
}
}

16. Compile the workflow project so the WaitForResponse compiles and can later be used
from the activity toolbox.
17. Rename the default workflow file of Activity1.xaml to wfDriving.xaml. At this point
you are ready to start building the workflow.
18. Hover over the Toolbox tab if it is not already pinned to within the designer. Drag and drop
a new StateMachine activity onto the designer fabric for the workflow.
19. Drag and drop three new states and one FinalState onto the workflow, so the one that
was already included by default should make a total of five.
20. Rename the State1 that was included by default to In Park. Rename the other states
to Engine Running, In Reverse, and In Drive. Rename the FinalState as
Turned Off. At this point, it does not matter how the states are organized within the
designer fabric. After the transitions are added, they can be reorganized so that the
transitions are not spread so far apart.
21. Add a transition from the Engine Running state to the Turned Off state by hovering
over the Engine Running state until a transition node appears on the edge of the state.
Click on the node to drag a new transition from the state to the Turned Off state. Click on
the new transition and within the Properties window, change the DisplayName property to
Stop Engine. A transition can also be added automatically by dragging one state close to
another state and dropping it on one of the arrows that appears around the state’s edges.

22. Follow the same directions from the step above and add another transition from the
In Park state to the Engine Running state and change its DisplayName property to
Start Engine.

151

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

23. Add another transition from the In Drive state to the In Park state and change its
DisplayName property to Put In Park.

24. Add another transition from the In Drive state to the In Reverse state and change
its DisplayName property to Put In Reverse.
25. Add another transition from the In Reverse state to the In Park state and change its
DisplayName property to Put In Park.

26. Add another transition from the Engine Running state to the In Drive state and
change its DisplayName property to Put In Drive.
27. Add another transition from the Engine Running state to the In Drive state and
change its DisplayName property to Put In Reverse.
28. Add one last transition from the In Reverse state to the In Drive state and change
its DisplayName property to Put In Drive. At this point the workflow’s states and
transitions should follow the same flows as indicated in Figure 4-40. There are
probably a couple of design time errors but the next couple of steps will clear those up
as the bookmark activities are added for each transition.

Figure 4-40.  Completed states and transitions

152

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

29. Double-click the transition named Put In Drive from the state In Reverse to the
In Drive state. Drag the WaitForResponse activity from the Chapter4.StateMachine
section of the toolbox and drop it into the trigger of the transition. Select String as the
TResult value for the bookmark that will passed to the workflow; however values will not
be passed into the workflow in this lab.
30. While the WaitForResponse activity is selected, change the property of ResponseName
within the Properties window to InGear.
31. Double-click the transition named Put In Reverse from the state In Drive to the
In Reverse state, and add another WaitForResponse activity using String as the TResult
value. Change the ResponseName property to PutInReverse.
32. Double-click the transition named Put In Drive from the state Engine Running
to the In Drive state, and add another WaitForResponse activity using String as the
TResult value. Change the ResponseName property to InGear.
33. Double-click the transition named Put In Park from the state In Drive to the
In Park state, and add another WaitForResponse activity using String as the TResult
value. Change the ResponseName property to Put In Park.
34. Double-click the transition named Put In Park from the state In Reverse to the
In Park state, and add another WaitForResponse activity using String as the TResult
value. Change the ResponseName property to PutInPark.
35. Double-click the transition named Put In Reverse from the state Engine Running
to the In Reverse state, and add another WaitForResponse activity using String as the
TResult value. Change the ResponseName property to PutInReverse.
36. Double-click the transition named Stop Engine from the state Engine Running to the
Turned Off state, and add another WaitForResponse activity using String as the TResult
value. Change the ResponseName property to TurnOff.
37. Finally, double-click the transition named Start Engine from the state In Park to
the Engine Running state. This time add an If activity within the trigger, and then add a
WaitForResponse activity using String as the TResult value. Change the ResponseName
property to StartEngine.
38. Click on the Variables tab at the bottom of the workflow designer and add a new variable
named varEngineStarted with a variable type of Boolean and a default value of false.
Set the scope for the variable to StateMachine.
39. Add the variable name varEngineStarted within the condition of the If activity. If the
engine is already started, the bookmark will not be needed. If not, then a manual event
using the bookmark will indicate when the engine starts. See Figure 4-41.

153

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-41.  Engine started condition

40. Double-click the Engine Running state and add an Entry action. Drag and drop an
Assign activity and set the To property of the activity to varEngineStarted and the
Value property to true. Make sure that Chapter4.Driving.Host is set as the startup
project and then press F5 to run the solution.

After the solution compiles, the WPF application hosting the workflow should appear (see Figure 4-42).

Figure 4-42.  Starting the workflow

154

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Pressing the Start Engine button will allow the state to transition over to Engine Running, where the buttons for
Reverse, Turn Off, and Drive appear, as shown in Figure 4-43.

Figure 4-43.  Engine started

Selecting the Drive button cycles the buttons again, making Park and Reverse available, as shown in Figure 4-44.

Figure 4-44.  Automobile in Drive

155

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

This time, go into the workflow and add a breakpoint on the Engine Running state. This is a new feature in
WF4.5 for adding breakpoints on states, but you will notice that the buttons clearly show that the state can only
be changed to Park and Reverse while In Drive (see Figure 4-45).

Figure 4-45.  Auto-transitioning to Engine Running from In Park

Pressing the Park button transitions the workflow back to In Park but because you set the variable indicating
that the automobile is running, the breakpoint in Figure 4-44 catches the transition being made automatically
from the In Park state to the Engine Running state. While the automobile is running, it can finally be turned off,
which will gray out all the buttons, indicating that the workflow has completed (See Figure 4-46).

156

www.it-ebooks.info
CHAPTER 4 ■ State Machine Workflows

Figure 4-46.  Driving workflow completed

Summary
This chapter focused on the components for building state machine workflows within WF4.5 and provided steps
for how they can used to implement state machine workflows within applications. State machine workflows are an
important type of workflow for implementing workflows within WF and one that models human behavior. State
machine workflows model states and human events that are required for a business process to be executed. The
majority of the workflows built for modeling long-running business processes can take advantage of state machine
workflows, but I also demonstrated that applications can have their UI functionality driven by workflows as well.
State machines are made up of predefined states within a process that are connected through transitions and
defining states. Wiring up transitions has been simplified in the latest release in WF4.5, so state machine workflows
can be implemented very quickly. State machine workflows can be built without worrying about how to combine
them with flowchart and sequential workflows.
This chapter covered the important functionality for building and executing state machine workflows. Tracking
was not covered, however, so I want to mention two new classes that are included for tracking information on state
machine activities:
StateMachineStateQuery
StateMachineStateRecord
Although tracking state machine workflows was not covered in this chapter, Chapter 10 is dedicated to tracking
workflows; it will demonstrate how to track the different types of workflows in WF, including state machines. The next
chapter, Chapter 5, will show you how to build a different type of flow control called a Flowchart, which is used for
modeling decisions within workflows.

157

www.it-ebooks.info
Chapter 5

Flowchart Workflows

Before WF4 was released, WF workflows were composed as either sequential or state machine workflows, which left
many developers asking why flowchart workflows weren’t included with WF. Flowchart workflows have been around
for decades. They are one of the most natural ways of modeling complex logic; therefore sequential workflows were
used as the best way to model flowchart control flows.
Even with sequential workflows, there were limitations for modeling processes—precisely because sequential
workflows execute activities one after another and follow a top-down execution flow. With the release of WF4, the
flowchart control flow was introduced into WF for authoring workflows. It added the necessary flexibility for modeling
execution flow using decisions instead of a top-down execution flow or looping constructs through sequential
workflows. In fact, WF4 removed the idea of having workflow-type templates for building workflows and replaced
them with state machine and flowchart workflow activities, thus ending the limitations imposed by integrating state
machine, sequential, and flowchart control flows within the same workflow. Table 5-1 categorizes the different control
flows found in WF4 and 4.5 around flow, modeling type, and behavior.

Table 5-1.  WF4-4.5 Control Flows

State machine Sequential Flowchart


Flow Event driven Predetermined Flexible
Model Type Human interaction Ordered tasks Decision making
Behavior Reactive Systematic Options

The flexibility that flowcharting confers hinges on being able to flow back up to activities that have been executed
previously based on conditions used for making decisions within a business process. This flexibility provides a
modeling approach for building workflows using WF, which is parallel to how people usually visualize business logic.
Therefore, modeling a flowchart workflow in WF yields so detailed a picture that it can also serve as a medium for
communicating development requirements to non-technical people.
This chapter will walk you through different scenarios for building and implementing flowchart control flows
within workflows as well as communication between workflow and their hosting applications. While demonstrating
the flowchart control flows for building workflows, I will use the out-of-the-box activities within WF to show how
different patterns of logic can be modeled, taking advantage of the features provided by WF activities.

Flow Activities
WF4 provides three activities for building a flowchart control flow. At first glance it might seem that there is more involved,
but because a flowchart in WF is a type of control flow that leverages the same WF activities as any other workflow, it
doesn’t take much to model a business process as a flowchart. The three activities for building flowchart control flow are

159

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

• Flowchart: Provides the flowchart control flow itself when applied to the WF designer for
building a workflow.
• FlowDecision: Establishes the mechanics for making decisions throughout a workflow by
provisioning two distinctly opposite transitions where only one of the transitions can execute
based on a decision.
• FlowSwitch: Predefines transitions based on a matched value associated with a transition,
for coordinating work to other activities. Only one transition can be executed based on the
matched value.
Figure 5-1 shows the Flowchart section within the activity toolbox.

  Flowchart secion within the activity toolbox

Flowchart activity represents the foundation for building a flowchart control flow within the WF. Once it is added
 5-2).

Figure 5-2.  Flowchart activity

160

www.it-ebooks.info
CHAPTER 5 ■ FlowCHART woRkFlows

A Flowchart activity must be added first (Figure 5-2), before either a FlowDecision or FlowSwitch activity can
be added. A new feature that WF4.5 provides is a ValidateUnconnectedNodes property that can be viewed within the
Properties window in Visual Studio (see Figure 5-3).

Figure 5-3. ValidateUnconnectedNodes property

By default, the ValidateUnconnectedNodes property is set to false, so the workflow in Figure 5-4 will compile
when building a workflow project within which it is included.

Figure 5-4. Unconnected activity nodes

161

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

The ValidateUnconnectedNodes property was added because sometimes workflows that are built at
design time are incomplete but nevertheless will compile and run. Checking the property’s checkbox for
ValidateUnconnectedNodes tells WF to check and make sure that a flowchart workflow does not have any
unconnected activity nodes. If it does, WF will indicate at design time that there are one or more unconnected nodes
between activities.

FlowDecision
The FlowDecision activity can be added within a Flowchart activity (Figure 5-5) and represents the decision-making
logic that will be implemented within a workflow.

Figure 5-5.  FlowDecision activity

After a FlowDecision activity has been added to a workflow, there are three unique properties that need to be set:
• Condition
• FalseLabel
• TrueLabel
The Condition property uses an expression that returns a Boolean to determine a decision that needs to be
processed. FalseLabel and TrueLabel represent the two and only two transitions that can be executed based on the
returned Boolean value from the expression. The condition property for the FlowDecision activity in Figure 5-5
indicates that the expression 1==1 has been set and that there are two possible transitions that represent the possible
flow, which in this case will always be True. An interesting behavior in this case is that the activity can afford to only
use one transition, but only when ValidateUnconnectedNodes is unchecked, since the expression 1==1 will always
equal true. The FalseLabel and TrueLabel properties have the default text values of True and False, however they
can be changed for a more descriptive representation for the decision that is being made. WF4.5 also adds a new
property called DisplayName to the FlowDecision activity, so the FlowDecision activity can also be changed to be
more descriptive about the type of decision being made. See Figure 5-6.

162

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-6.  FlowDecision properties

To demonstrate this, a FlowDecision activity is used to verify if a WF variable, representing a person's age
and defined as an integer named varAge. The FalseLabel and TrueLabel properties representing the two transitions
have also been changed to “65 or older” and “Under 65” (see Figure 5-7).

Figure 5-7.  Using the Decision activity

163

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

FlowSwitch <T> Activity
The last activity within the flowchart palette is the FlowSwitch <T> activity. The logic it performs closely mimics the
coding construct of a switch statement represented within most programming languages. After a FlowSwitch activity
is added to the designer canvas of a flowchart workflow, the value type for the object that will be used to guide the
possible predefined flows of the workflow must be selected (see Figure 5-8).

  FlowSwitch type selection

After the value type for the FlowSwitch is set, the Expression property for the activity needs to be set; it will return
the value type. The expression’s return value must match the value type defined while adding the activity to the WF
designer. The Expression Editor in Figure 5-9 demonstrates using a custom WF variable, varCustomerRating, for a
return type of Int32 that returns the number representing a customer’s rating between 1 and 5.

Figure 5-9.  Expression used for returning the value type

Finally, when adding the first transition to the FlowSwitch, it is represented as the default transition, which
means that if there is no match for any of the other transitions for execution, the default transition is designated
to be executed. Figure 5-10 shows the results for setting up a simple workflow using the FlowSwitch activity and
transitioning the flow based on the returned value from the expression in Figure 5-9.

164

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-10.  Demonstrating the default and rating transitions

BUILDING A FLOWCHART WORKFLOW

This exercise will walk you through modeling the business process for retrieving a customer’s order from the
database, using a flowchart workflow, and totaling the amount owed based on the customer’s order. The data
plumbing for the workflow will take advantage of the Entity Framework’s Code First model, allowing you to
generate the database and wire the data plumbing through code.

■■Note  By the time this book is published, Entity Framework 5 will probably be released and can be used instead of
EF4.3 in this exercise.

1. Open Visual Studio 2012 and create a new Project


2. Select the Workflow template to see a list of installed workflow templates.
3. Select workflow console application and name it Chapter5.FlowChart.
4. Rename the workflow console application to Exercise1. Delete the workflow that is included
with the project and add a new activity named wfCustomerOrders.xaml.
5. Right-click on the solution and add new Visual C# class library. Name it Chapter5.DataModel.
This project will handle all of the database plumbing used within the solution.

165

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

6. Rename the file Class1.cs that was added by default to Customer.cs.


7. Paste the following code into the Customer.cs file:

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace Chapter5.DataModel
{
public class Customer
{
public Customer()
{
CustomerOrders = new Collection <Order> ();
}
[Key]
public int CustomerId { get; set; }
public string CCNumber { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public virtual ICollection <Order> CustomerOrders { get; set; }
}

public class Customers : Collection <Customer>

}
}

8. Add a new class to the Chapter5.DataModel and call it Order.cs. Paste the following code into
the file:

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace Chapter5.DataModel
{
public class Order

166

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

{
public Order()
{
LineItems = new Collection <OrderLineItem> ();
}
[Key]
public int OrderId { get; set; }
[Required]
public int CustomerId { get; set; }
[Required]
public DateTime DateOrdered { get; set; }
[Required]
public virtual ICollection <OrderLineItem> LineItems { get; set; }
}
}

9. Add new class to the Chapter5.DataModel and call it OrderLineItem.cs. Paste the following
code into the file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace Chapter5.DataModel
{
public class OrderLineItem
{

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int LineItemId { get; set; }
[Key]
public int OrderId { get; set; }
[Required]
public string LineDescription { get; set; }
[Required]
public string SKU { get; set; }
[Required]

public Decimal? Price { get; set; }


}

At this point you have built the Plain Old CLR Objects (POCO) that will be used to manage customers and their
orders. The code that was pasted in might look different to developers who are not used to using Microsoft’s
Entity Framework for object-relational mapping (ORM). However, later I will give a detailed description for what is
going on within the POCO classes that were just added.
Entity Framework (EF) has been around for a number of years now and is used for building and managing data
layers within an application and the tables and relationships for data within SQL Server. EF’s Code First approach
lets developers use code to handle the building of databases and application integration of an application and
167

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

a SQL Server database. EF’s Code First approach may not be appropriate to use as a standard for all software
projects, but huge benefits are gained within projects that are built from the ground up and when used within the
initial stages for building architecture. The next steps will set up the code that will be used to build the database
using EF’s Code First.
The version of EF used in this exercise it Entity Framework 4.3, and if it is not already installed with Visual Studio 11,
it can be downloaded via NuGet. NuGet is an extension to Visual Studio that assists in downloading companion
frameworks for writing software within Visual Studio. By default Entity Framework 4.3 will use SQL Server 2008 R2
Express, which can be downloaded and installed from www.microsoft.com/sqlserver/en/us/editions/
express.aspx to build the database required in this exercise.

10. Add new class to the Chapter5.DataModel and call it Ordering.cs. Paste in the following code
into the file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace Chapter5.DataModel
{
public sealed class Ordering : DbContext
{
public DbSet <Order> Orders { get; set; }
public DbSet <Customer> Customers { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
// Customer can have many Orders.
modelBuilder.Entity <Customer> ()
.HasMany <Order> (customer = > customer.CustomerOrders);

// Order can have many LineItems.

modelBuilder.Entity <Order> ()
.HasMany <OrderLineItem> (order = > order.LineItems);

// LineItems contains a composite key.

modelBuilder.Entity <OrderLineItem> ()
.HasKey(p = > new { p.OrderId, p.LineItemId });
}

}
}

11. Add a new Unit Test project to the solution by right-clicking the solution and selecting New
Project. Select the Test template that is represented under the Visual C# section (see Figure 5-11).

168

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-11.  New Test Project

12. Rename the new test project as TestOrders. This project will test the EF Code First
implementation and will also load customers and orders for the customers into the database.
13. By default the test project comes with a UnitTest1.cs file that has the stubs used for writing
unit tests. Replace the existing file with the following code:

using System;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chapter5.DataModel;
using System.Data.Entity;
using System.Linq;

namespace Chapter5.Testing
{

[TestClass]
public class UnitTest1
{

[TestMethod]
public void TestGetOrders()
{
using (var ordering = new Ordering())
{
var custs = from cust in ordering.Customers
select cust;

169

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

var customers = new Collection <Customer> ();


foreach (var c in custs)
{
customers.Add(c);
}
}
}

[TestMethod]
public void TestCodeFirst()
{

Database.SetInitializer(
new DropCreateDatabaseIfModelChanges <Ordering> ());

using (var ordering = new Ordering())


{
ordering.Database.Initialize(false);
var customer = new Customer
{
FirstName = "John"
,
LastName = "Smith"
};

var order =
new Order
{
DateOrdered = DateTime.Now
};

order.LineItems.Add(new OrderLineItem
{
LineDescription = "Widget 1",
Price = 5.50m,

SKU = "fff-321-gfsf"
});
order.LineItems.Add(new OrderLineItem
{
LineDescription = "Widget 2",
Price = 4.10m,

SKU = "AAA-234-asdf"
});
order.LineItems.Add(new OrderLineItem
{
LineDescription = "Widget 3",
Price = 10.10m,

170

www.it-ebooks.info
CHAPTER 5 ■ FlowCHART woRkFlows

SKU = "BBB-321-j7df"
}); customer.CustomerOrders.Add(order);
ordering.Customers.Add(customer);

ordering.SaveChanges();
}
}
}
}

14. some references within the projects need to be made, so we can start by adding the references
to the Chapter5.DataModel. once NuGet is installed, check the project to make sure that Entity
Framework 4.3 is installed. Figure 5-12 demonstrates how to right-click on the project and select
“Manage NuGet Packages.”

Figure 5-12. Managing NuGet packages

Check to see if Entity Framework 4.3 is installed by viewing the installed NuGet packages, illustrated in
Figure 5-13. If it is not installed, an Install button can be pressed to download Entity Framework 4.3.

171

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-13.  Checking if EntityFramework 4.3 is installed

15. Right-click References for the project and make sure to add references to System.
ComponentModel.DataAnnotations, System.Data.DataSetExtensions, and System.Data.
Entity. The reference list should look like Figure 5-14.

Figure 5-14.  Chapter5.DataModel references

16. After adding the references, build the project by right-clicking on the project and selecting Build.
Add a reference to the Entity Framework 4.3 within the test project TestOrders by either following the same
steps for adding it via NuGet or adding it to the test project by using the file path for the EntityFramework
referenced within the Chapter5.DataModel project, selecting References, and browsing to the same file path
to add the reference to Entity Framework 4.3. The TestOrders reference list should look like Figure 5-15.

172

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-15.  TestOrders references

17. Now that the references have been added, right-click the project and select Build.
18. After the project successfully builds, open up the UnitTest1.cs file within the TestOrders
project.
19. Put the cursor on the test method TestCodeFirst() and then go to the top menu for visual
studio and click on Test ➤ Run ➤ Tests in Current Context. This will run the test that will create
the customer order and line items. To test that the records have been successfully created, you
can either look at the database using SQL Server Management Studio or add a breakpoint by
clicking the grey boundary of the code editor on the same line as the foreach statement within
the test method TestGetOrders().
20. This time put the cursor on the test method TestGetOrders(), and then go to the top menu for
Visual Studio and click on Test ➤ Debug ➤ Tests in Current Context. This will run the test method
in debug mode, allowing the code to rest of the breakpoint. Press F10 to cycle through the
ForEach statement, indicating that a customer has been loaded into the database.

At this point, the data access layer is built and you can start building the workflow that is going
to retrieve the customer order and line items, and build the logic for tallying up the total cost for
the line items. To get the customer order, the workflow will rely on using a code activity that will
implement the same code used to test that the customer record had been added to the database.
21. Right-click the workflow project Exercise1 and add a new folder called Activities. Right-click
the folder and add a new class file. Name the file GetCustomeOrders.cs and replace the code
with the following code:

using System;
using System.Activities;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Chapter5.DataModel;
using System.Collections.ObjectModel;
namespace Exercise1.Activities
{
public class GetCustomerOrders:CodeActivity

173

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

{
[RequiredArgument]
public OutArgument < Customers > outCustomers { get; set; }

protected override void Execute(CodeActivityContext context)


{
using (var db = new Ordering())
{
db.Configuration.LazyLoadingEnabled = false;
var custs = db.Customers.Include("CustomerOrders.LineItems");

var customers = new Customers();


foreach (var c in custs)
{
customers.Add(c);
}
// Reassign the argument.
context.SetValue(outCustomers,customers);
}
}
}
}

22. There are two references that need to be made for the workflow project Exercise1. The first
reference needs to add EntityFramework and the second is Chapter5.DataModel. Right-click
on the Exercise1 project and select Build. After a successful build, the code activity
GetCustomerOrders should appear in the Exercise1.Activties section of the toolbox.

23. Now the flowchart workflow is ready to be modeled. Double-click the Workflow1.xaml file to
view the workflow designer. Next, drag a Flowchart activity from the toolbox onto the designer
canvas. This sets the foundation for the flowchart workflow. While the workflow has focus
and the Properites window is visible, check the ValidateUnconnectedNodes so all activity
connections are validated.
24. Drag and drop the custom code activity GetCustomerOrders onto the designer canvas and while
dragging the activity, lightly brush it close to the left side of the start activity on the workflow.
This behavior will cause the nodes to appear on the start activity and will automatically connect
the two activities once it is dropped. Figure 5-16 represents how the workflow should look at this
point.

Figure 5-16.  GetCustomerOrders

174

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

GetCustomersOrders activity has an OutArgument called OutCustomers and because it is not set, the
exception notification is visible on the activity. Viewing the Error List gives the details to the exception (that
it must be set when using the activity within a workflow). A WF variable needs to be created to receive the
collection of customers that the activity will return. See Figure 5-17.

Figure 5-17.  Out argument required exception

25. Add a new WF variable for the workflow by clicking the Variable tab on the bottom left of the
workflow. Set the name to varCustomers. The variable type is a custom type, Customers, and it
is referenced in the Chapter5.DataModel project (see Figure 5-18). Click the drop-down box for
the variable type and select Browse for Types.

Figure 5-18.  Customers type

26. Select Customers for the variable type and set the default value for the variable to new
Customers(). Make sure that the scope is set to Flowchart (see Figure 5-19).

Figure 5-19.  Creating the variable to hold customers

27. Add the new WF variable varCustomers to the GetCustomersOrders by selecting the activity
and viewing its properties within the Properties window. Select the button for outCustomers and
type varCustomers into the textbox. After clicking OK, the exception notification for the activity
will go away.
28. Drag a FlowDecision activity from the Flowchart section of the toolbox. And brush it close to
the GetCustomersOrders activity so it can auto-connect the activities. While the FlowDecision
activity has focus, make sure the Properties window is visible.

175

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

29. Set the Condition by pressing the button and adding varCustomers! = null&&varCustomers.
Count> 0. This Condition will return a Boolean that will determine the flow for activity. Click OK.

30. Change the FalseLabel property to No Orders and the TrueLabel to Orders Exist.
31. Drag a WriteLine activity from the toolbox and brush the right side of the FlowDecision activity
exposing the No orders node so it can auto-connect. Add There are no new customer orders
inside the textbox for the WriteLine activity.
32. Drag the ForEach <T> activity from the toolbox and brush it to the left side of the FlowDecision
activity to auto-connect the Orders Exist node with the ForEach <T> activity.
33. Click on the ForEach <t> activity to set configure its properties. Click the drop-down box for the
TypeArgument property and select Browse for Types. Expand Chapter5.DataModel as illustrated
in Figure 5-18 and select Customer as the argument type. At this point the workflow should
resemble Figure 5-20.

Figure 5-20.  Customer orders workflow

34. Double-click the ForEach activity, which will make the body for the activity accessible. There are
two textboxes at the top of the activity. Add cust as the value for the textbox on the left and the
LINQ expression from c in varCustomers select c for the value of the textbox on the right
(see Figure 5-21).

Figure 5-21.  Textbox value

176

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

35. Drag another ForEach <T> activity within the body of the existing ForEach <T> activity. Set the
TypeArgument property to Chapter5.DataModel.Order. Add order as the value for the textbox
on the left and cust.CustomerOrders for the value of the textbox on the right (see Figure 5-22).

Figure 5-22.  Textbox value

36. Drag another ForEach <T> activity within the body of the last ForEach <T> activity that was
added. Set the TypeArgument property to Chapter5.DataModel.OrderLineItem. Add lineItem
as the value for the textbox on the left and order.LineItems for the value of the textbox on
the right (see Figure 5-23).

Figure 5-23.  Textbox value

37. Drag an Assign activity within the body of the ForEach <T> activity that was just added.
38. Drag a WriteLine activity just underneath the last ForEach <T> activity. The designer will
automatically create a new Sequence activity so the WriteLine activity can be dropped.
39. Before the Assign activity can be configured, another WF variable needs to be added that has
scope within the auto-generated Sequence activity.
40. Click on the Variable tab and add a WF variable named varTotalPrice. Select Browse for Types
and set the VariableType to System.Decimal and set the scope to Sequence.
41. Click on the Assign activity and within the Properties window, set the To property to
varTotalPrice and the Value property to varTotalPrice + lineItem.Price.value.

42. Click on the WriteLine activity and set the Text property to

string.Format("Order for {0} has a total cost of {1}",


cust.FirstName + " "  + cust.LastName, string.Format("{0:C}",varTotalPrice))

At this point the composite activity built from steps 35–43 should look like Figure 5-24.

177

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-24.  Nested ForEach <T> activities

43. Open up the Program.cs file and replace the existing code with the following code:

using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;

namespace Exercise1

178

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

{
class Program
{
static void Main(string[] args)
{
// Create and cache the workflow definition
Activity workflow1 = new wfCustomerOrders();
WorkflowInvoker.Invoke(workflow1);
Console.ReadKey();
}
}
}

Remember that you already created the test data that the workflow will use. There should be one order for John
Smith with three line items within the order with the following prices:

Make sure that Exercise1 is set as the startup project and press F5 to run the solution. Once everything builds
successfully and the workflow runs to completion, the results are displayed within the console window (see
Figure 5-25).

Figure 5-25.  Order totaled for customer

179

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Using Entity Framework with WF


Entity Framework is a great data technology that can be used with WF for handling the data plumbing code required
to interact with SQL Server. In fact, the previous exercise demonstrated how EF4.3 can be used as a Code First
approach for writing code that generates a database within SQL Server Express 2008 R2 and the data access code
for inserting and retrieving order information for a customer. I want to quickly answer some of the most obvious
questions based on the code that was used in the exercise pertaining to EF.
Three POCO classes were built as the entities used for a customer order. The classes are nothing out of
the ordinary, other than they implement additional EF namespaces for added EF functionality. The first EF
implementation to point out is the namespace of System.ComponentModel.DataAnnotations. DataAnnotations
allowed the entity’s properties to be decorated indicating the following:
• Required: The property cannot be saved to the database with a value of null.
• Key: The property indicates that it is either part of a composite key or is the primary key.
• DatabaseGenerated (DatabaseGeneratedOption.Identity): Indicates the property is
seeded as an identity property, therefore the value will be generated within the database table.
If you have worked with POCO objects, some of the implementation for the Ordering.cs file probably is not

Code First describes all of the relationships through


DbContext. The difference is lies within protected override void
This override method is called first to mash up the relationships

 5-26 shows the tables that were generated.

180

www.it-ebooks.info
CHAPTER 5 ■ FlowCHART woRkFlows

Figure 5-26. Generate tables through EF Code First

ForEach <T> Implementation


The ForEach <T> activity was also used in Exercise 1, so I want to cover in more detail why it was used. The
ForEach <T> activity is used to loop through enumerable objects just like the coding construct of a ForEach loop.
The goal through WF, though, is to handle the same functionality declaratively. In Exercise 1, three different
ForEach <T> activities were used to loop through
• The number of customer records
• The number of orders per customer
• The number of line items per order
Most programmers might find that wiring up the ForEach <T> activity is not as intuitive when compared to the
writing ForEach loops in code, so here is what you need to know. There are two properties that need to be set:
• TypeArgument: The type returned from each iteration
• Values: Expression that will return a collection for the iteration type. This includes WF
variables, arguments, or LINQ statements for narrowing down collection values.

181

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

The first ForEach <T> activity that was added in Exercise1 was used to iterate through the customers objects that
were returned from the database, so the ForEach <T> properties set were as follows:
• TypeArgument: Chapter5.DataModel.Customer
• Values: from c in varCustomers select c or just varCustomers
Double-clicking the activity will show the two text boxes that resemble an actual ForEach coding construct in C#.
Figure 5-27 shows that the left textbox holds the name of the Customer object named cust, which is set to a Customer
object for each iteration of the loop. The textbox on the right is where the collection of customers is set using the
code from c in varCustomers select c, which grabs the customers from the WF variable that was set using the
OutArgument of the GetCustomerOrders activity.

  Intializing each customer object within a WF variable

The body of the ForEach <T> activity is where logic can be added to handle each iteration. A second
 <T> activity is added within the parent ForEach <T> activity for iterating further within a customer object, as
ForEach coding construct (see Figure 5-28).

Figure 5-28.  Nested ForEach <T> activity


182

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

The ForEach <T> properties set for iterating through the orders object are
• TypeArgument: Chapter5.DataModel.Order
• Values: cust.CustomerOrders
The TypeArgument property indicates that an order will be returned with each iteration for a customer’s order,
represented as cust.
A third ForEach <T> activity is added within the body of the existing nested ForEach <T> activity to iterate through
each of the line items of an order. The properties set for iterating through the OrderLineItems object are
• TypeArgument: Chapter5.DataModel.OrderLineItem
• Values: order.LineItems
The TypeArgument property indicates that a lineItem will be returned with each iteration for an order
represented as order. A Sequence activity is used as a container for the ForEach <T> activity because with each
iteration of an order’s line item, an Assign activity is required to calculate the total charge for the order. As a result, the
WF variable varTotalPrice is used to add the price for each line item (see Figure 5-28).
Of course the logic represented using the nested ForEach <T> activities could have been handled differently
either by using C# code within a code activity or by calling code outside of the workflow, but the goal for WF is to
declaratively handle logic for an order on the fly—also giving the power to others who may not be technical enough to
write custom code.

■■Tip  I mentioned that WF4.5 has a new Flowchart control flow property called ValidateUnconnectedNodes that
checks that unconnected nodes within the workflow are set. At first glance, it might make sense that this should be
set, but instead think of it as an option for added flexibility while building a workflow. For instance, you might want to
change up the workflow, so unchecking the property will allow activities to be disconnected from others but remain on
the designer canvas without validation, so they can be reconnected later if needed. It might be a good idea to check the
property in the beginning for authoring a flowchart, but later it can be unchecked while changing up the workflow.

Flowchart Composite Activities


One of the cool factors in WF is the ability to take declarative logic and use it more than once. As developers, we
already have this feature in code, where objects can be defined using classes and then compiled for reuse within
other frameworks or applications. The same applies to WF and composite activities, which are simply more than one
activity working together.
I know what you are thinking! “Then what is a workflow?” Well, a workflow is essentially a composite activity but
usually at a much grander scale. A good practice for writing code is to keep it simple and modular. The same applies
to composite activities. You want them to focus on a certain piece of logic. When they start becoming too large, that
might be a good time to look at refactoring.
A great example for building a composite activity is the logic used in Exercise 1 for calculating total cost for an
order, except let’s give the activity a little more functionality. Instead of just calculating cost, the activity will also do
the following:
• Calculate total cost for an order with tax.
• Return what the tax amount was for the order.

183

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

To make things even more interesting, I will demonstrate using the FlowSwitch activity to calculate tax for an
order based on certain states within the United States. Since the ordering system takes orders online orders, tax is
collected on orders that are shipped within the state where the company resides. So if your fictional company of
ACME is located within Florida and an order is to be shipped in Florida, the order will be taxed.
The first thing to do is to take the nested ForEach <T> activities used in Figure 5-24 and make them into their own
composite activity. A new activity can be added to a project by right-clicking on a project and selecting New
Item. Under the installed workflow templates there is an Activity template that can be selected, as illustrated in
Figure 5-29.

Figure 5-29.  Adding a new XAML activity

The new activity will be created from existing XAML. Note that the extension for the default activity file name is
XAML as well. After the new activity is added to the project, activities can be added to the WF designer, but instead of
adding activities from the toolbox, the ForEach <T> activities used earlier need to be added. Simply select the parent
ForEach <T> activity used for iterating though customers, right-click on the tab of the activity, and select Copy
(see Figure 5-30).

184

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-30.  Copying a composite activity

After copying the composite activity, it can be pasted into the new activities designer canvas. Once it is pasted
in, the nested ForEach <T> activities are exposed visually. Right away the expression used to get customers is invalid,
and the composite activity needs an argument so it can receive data. A new argument can be created by clicking on
the Arguments tab for the workflow. Figure 5-31 shows the new argument created for the composite activity. The
new argument’s direction is In and its argument type is Chapter5.DataModel.Customers. The Values property
of for the parent ForEach <T> activity can now be changed to argInCalcCustomers as a generic way for handling the
customers object within the activity.

Figure 5-31.  New composite activity in argument

Currently the activity calculates the total price for the order based on the order’s line items and then sends a
string to the console (see Figure 5-32).

185

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-32.  Calculating total price and writing to the console

To calculate the total price for the order and the tax amount, the activity needs to be changed around slightly,
as does the Order object. The following three properties were added to the Order object:

[NotMapped]
Public decimal Tax { get; set; ]

[NotMapped]
Public decimal TotalPrice { get; set; ]

[Required]
Public string ShippingState { get; set; ]

Instead of using the WF variable varTotalPrice, the properties TotalPrice and Tax will hold the appropriate
values. These properties are not important as far as the database is concerned, so they are marked with the attribute
[NotMapped] so Entity Framework is not aware of them. The ShippingState property will be used later for checking if
tax needs to be calculated for the order and at what percent.

186

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

To calculate the order’s total price, the workflow can utilize the new TotalPrice property for an order instead of
the WF variable, varTotalPrice. Also, the WriteLine activity is no longer needed. Instead, a Flowchart activity is added
to calculate the State tax. The Assign activity in Figure 5-33 can now calculate the total price of an order based on the
argument that was passed in, without having to build additional variables. Next, the composite Flowchart activity
represented in Figure 5-33 needs to be built to calculate tax if required.

Figure 5-33.  Calculating total price based on the argument passed in

So hypothetically, your fictitious company has stores in the specific States, with the corresponding tax rates,
shown in Table 5-2.

Table 5-2.  States and tax rates


State Tax Rate
Florida 7%
Georgia 3&
Alabama 5%

187

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Because there are more than two options for taxing and ordering, and because there could be more in the future,
a FlowSwitch activity can be used to model the flow for taxing an order. Figure 5-34 represents how this can be done.
Notice that annotations were added for the workflow and each of the activities in Figure 5-34, which is a feature of
WF4.5 that represents the logic for what needs to be performed.

Figure 5-34.  Flowchart activity for handing state tax logic

Annotations can be visible, as in Figure 5-34, or hidden where only a little icon, located in the top right of the
activity, can be seen indicating that an annotation exists. Stepping back out of the Flowchart activity, annotations are
visible for giving a description for what logic a composite activity performs (see Figure 5-35).

Figure 5-35.  External view of an activity’s annotation

188

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

To add activities within the Flowchart activity, double click on it, as Figure 5-35 indicates. This is just like starting
a new Flowchart activity except it is now a composite activity or a child activity. From the toolbox, a new FlowSwitch
activity is needed to coordinate the possible flows for taxing based on a state, and the value type that will be returned
from the expression property will be a String. The Expression property is set to order.ShippingState, therefore
the shipping state supplied with an order can be matched based on its literal value (see Figure 5-36).

Figure 5-36.  Setting the FlowSwitch activity Type

Only three states (Florida, Georgia and Alabama) need to collect taxes, so if a shipping state is blank or not equal
to one of these states, taxes will not be calculated. The Expression property for the FlowSwitch activity is set to
order.ShippingState; because the Flowchart activity is a composite activity, it also has scope to the order object
used in the parent ForEach <T> activity, shown in Figure 5-33.
Four Assign activities are also added. A shortcut for adding four of the same activities is to drag the first one from
the toolbox, copy the activity added using the shortcut keys Ctrl-C, and then pressing Ctrl-V four times to paste it
within the WF designer (see Figure 5-37). Three of the Assign activities are used to calculate taxes for each of the three
states and the fourth Assign activity adds the appropriate tax amount set within the order.Tax property to the
order.TotalPrice property.

Figure 5-37.  Cutting and pasting Assign activities

189

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

The three Assign activities that set the state taxes are similar (apart from the tax rate), so the To property is set to
order.Tax and the Value property for each state is as follows:
• Florida: Order.TotalPrice * .07m
• Alabama: Order.TotalPrice * .05m
• Georgia: Order.TotalPrice * .03m
The fourth Assign activity calculates the overall total price by adding any taxes to the total cost for the order based
on the line items. The To and Value properties values are as follows:
• To: order.TotalPrice
• Value: order.TotalPrice + order.Tax
And the other three Assign activities that calculate the state’s taxes can all be transitioned to it (see Figure 5-37).
After setting up the Assign activities, the FlowSwitch activities cases are wired up with the appropriate state tax

 5-2 represents the state names in relation to the tax rates that are set up
 5-34. Once the project is compiled, the CalculateOrder activity will show up within the toolbox for the

bookmarks to communicate with state machine


workflows and the hosting application using workflow application by adding the bookmarks within the trigger of
a transition. Flowchart and sequential workflows implement bookmarks differently. In most cases, state machine
workflows require human interaction before transitioning from one state to the other, but sequential and flowchart
workflows tend to always be executing and thus need the flexibility of resuming without worrying about a bookmark
being executed from the host. What activity is up for the task for hosting bookmarks within a flowchart activity? If you
guessed the Pick activity, you guessed correctly.

Pick Activity
After a Pick activity is dropped onto the designer canvas, it comes standard with two Branch activities; however,
additional Branch activities can be added from the toolbox. A Branch activity has two parts, similar to the state
machine in that it has its own Trigger and Action but within the Action there is no Condition component
(see Figure 5-38).

190

www.it-ebooks.info
CHAPTER 5 ■ FlowCHART woRkFlows

Figure 5-38. Pick activity with two Branch activities

Pick Branches execute in parallel. As one Branch completes, the other branch’s execution is canceled. Bookmarks
are added within the Trigger part of a Branch within the Pick activity, and because the Branches fire off in parallel,
additional logic can be set up within the other Branch for doing some type of logic. Most implementations take
advantage of a Delay activity within the other Branch for giving a time limit for how long a bookmark should wait
before the workflow continues execution.
Figure 5-39 represents a standard implementation for establishing resuming a workflow from a hosting
application. In this case, the Duration property is given to the Delay activity. A simple example would be 00:00:05,
which would represent 5 seconds, or TimeSpan.FromSeconds(5), which would represent the same.

191

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

  Pick activity with a bookmark and a delay activity

CALCULATING THE TAX ON AN ODER IN A TAXABLE STATE

The scenario will pick up from the previous exercise and will demonstrate how to calculate the tax on an order
from taxable states. The order will need to be approved if it is over $18.75; If the order is less than $18.75, it will
be approved. There will be a time limit of 7 seconds to approve an order; if the order is not approved by then, it
will not be approved. You might lose some sells here so hopefully most orders are approved or rejected within the
seven seconds.
This exercise will build a composite activity that will be used within a new flowchart workflow. After the logic has
been added to calculate state tax, the workflow will approve orders by implementing a bookmark within a Pick
activity. A Delay activity will be used to wait for the bookmark to be triggered from the hosting client application;
after the set time has expired, the workflow will continue its execution.
1. Open Visual Studio 12 and open up the solution containing the project from Exercise 1.
2. Right-click the solution, select Add and then New Project. Select the Workflow template.
3. Select a new Activity Library and name it Chapter5.Exercise2.
4. Rename the Activity1.xaml that is included with the project to wfCommunication.xaml.
5. Double-click the wfCommunication.xaml file to view the workflow designer. Next, drag a
Flowchart activity from the toolbox onto the designer canvas. While the workflow has focus
and the Properties window is visible, check the ValidateUnconnectedNodes so all activity
connections are validated.

192

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

6. Drag and drop the custom code activity GetCustomerOrders onto the designer canvas. The
GetCustomerorders activity was built within Exercise 1 and since it is a compiled activity, it
can be seen within the activity toolbox under Exercise 1.
7. Add a reference for the Exercise2 project to the Chapter5.DataModel.
8. This workflow will return a complex Customers object as a WF OutArgument, so create one by
clicking the Arguments tab. Set the Name to argOutCustomers, the Direction to Out and the
ArgumentType to Chapter5.DataModel.Customers.

Click the GetCustomersOrders activity so its Property window is viewable and set the
OutArgument called outCustomers to the workflow’s OutArgument, argOutCustomers.

9. Right-click the project and create a new folder called Activities.


10. Right-click the new folder and add a new activity from the Workflow template. Rename the
activity to CalculateOrder.xaml.
11. Click to expand the project Exercise1 and click on workflow wfCustomerOrders.xaml.
Right-click the ForEach <Customer> activity and select Copy.
12. Go back to the Exercise2 project and click on the activity CalculateOrder.xaml. Right-click
and paste in the ForEach <Customer> activity.
13. Create a new InArgument for the activity by selecting the Arguments tab. Set the Name property
to argInCustomers, and the Direction property to In. Set the Argument type property to
Chapter5.DataModel.Customers.

14. Click on the Variables tab and remove the variable varCustomers.
15. Open the Chapter5.DataModel project and open up the Order.cs file. Add the following lines of code:

[NotMapped]
Public decimal Tax { get; set; ]

[NotMapped]
Public decimal TotalPrice { get; set; ]

[Required]
Public string ShippingState { get; set; ]

16. Open up the Activities folder in Exercise2 and click the parent Foreach < T>. Click the
ellipses button to change the Values property from from c in varCustomers select c to
argInCalcCustomers.

17. Click on the Assign activity within the ForEach <OrderLineItem> activity and change the To
property to order.TotalPrice and the Value property to order.TotalPrice +
lineItem.Price.Value.

18. Click on the Variables tab and remove the variable varTotalPrice.
19. Remove the WriteLine activity and drag a Flowchart activity from the toolbox and add it where
the WriteLine was located.
20. Right-click the new Flowchart activity and select Annotations and Add Annotations. Set the annotation
to Calculates State tax based on the name of the State passed in (see Figure 5-40).
193

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-40.  Adding an annotation

21. Right-click again on the activity and select Annotations and then Show All Annotations.
22. Double-click the new Flowchart activity just added. Drag a FlowSwitch from the toolbox onto
the designer canvas and set the type to String.
23. Make sure the Property window is open while the FlowSwitch activity has focus and set the
Expression property to order.ShippingState.

24. Right-click the new FlowSwitch activity and select Annotations and Add Annotations. Set the
annotation to Tax is determined from an order's ShippingState property.
25. Drag a new Assign activity to the designer canvas. Set the To property to order.TotalPrice
and the Value property to order.TotalPrice + order.Tax.
26. Right-click the new Assign activity and select Annotations and Add Annotations. Set the
annotation to Calculates tax based on the State.
27. Drag a new Assign activity to the designer canvas. Set the To property to order.Tax and the
Value property to order.TotalPrice * .05m.

28. Select the Assign activity that was just added and press Ctrl-C to copy the activity. Then press
Ctrl-V twice to paste two new Assign activities.
29. Click one of the new Assign activities that were pasted and change the Value property of .05m
to .03m.
30. Right-click the Assign activity where its Value property was changed to reflect the .03m change
and select Annotations and Add Annotations. Set the annotation to Georgia is taxed at 3%.

194

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

31. Click the other Assign activity that was pasted and change the Value property of .05m to .07m.
32. Right-click on the on the Assign activity where its Value property was changed to reflect the
.07m change and select Annotations and Add Annotations. Set the annotation to Florida is
taxed at 7%.

33. Right-click on the on the Assign activity that does not have an annotation and select Annotations
and Add Annotations. Set the annotation to Alabama is taxed at 5%.
At this point the Flowchart workflow should look something like Figure 5-34, which will provide the functionality
for calculating State tax as a composite activity. The next couple of steps set up the approval process. If the
annotations are not visible, right click on the workflow and select Annotations and then Show All Annotations.
34. The WaitForResponse code activity that has been used in other chapters can be used as the
bookmark. If you don’t have the bookmark code activity handy, right click on the Activities
folder within Exercise2 and add a new class. Rename the class to WaitForResponse
35. Paste in the following code within the new class that was added:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;

namespace FlowFocus.WF.Activities
{
public sealed class WaitForResponse < TResult> : NativeActivity < TResult>
{
public WaitForResponse()
: base()
{

public string ResponseName { get; set; }

protected override bool CanInduceIdle


{ //override when the custom activity is allowed to make he workflow go idle
get
{
return true;
}
}

protected override void Execute(NativeActivityContext context)


{
context.CreateBookmark(this.ResponseName, new
BookmarkCallback(this.ReceivedResponse));
}

195

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

void ReceivedResponse(NativeActivityContext context, Bookmark bookmark,


object obj)
{
this.Result.Set(context, (TResult)obj);
}
}
}

36. Right click on the Exercise2 project and select Build. This will compile the new
WaitForResponse code activity so it can be selected from the toolbox and added to the workflow.

37. After the project compiles, drag an If activity from the toolbox and lightly brush the Assign
activity which has the annotation: Calculates tax based on the State, until the nodes
show and the two activities are connected.
38. Right-click the If activity and select Annotations and Add Annotations. Set the annotation to
Orders over $18.75 need approval.

39. Double-click the If activity to view its implementation.


40. Set the Condition for the If activity to order.TotalPrice > 18.75.
41. Drag a Pick activity onto the designer canvas and place it within the Then section of the If
activity. The Pick activity will be used to manually approve or reject an order.
42. By default, two Branch activities are provided for implementing the Pick activity. Drag the
WaitForResponse activity from the Exercise2 section of the toolbox and place it within the
Trigger for Branch1. Select its type to Boolean. See Figure 5-41.

Figure 5-41.  Adding a bookmark activity within the trigger

196

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

43. Drag a Delay activity from the toolbox and place it within the Trigger for Branch2. Click on the
Delay activity and set the Duration property to 00:00:07 or seven seconds. The duration of
seven seconds can also be set as TimeSpan.FromSeconds(7).
44. Drag an Assign activity from the toolbox and place it within the Action for Branch2. Set the To
property to order.OrderApproved. Set the Value property to false. The logic that Branch2 now
implements is waiting seven seconds for an order to be processed that is over $18.75. If the
order is not manually approved after seven seconds, the workflow automatically rejects the order.
45. Drag an Assign activity from the toolbox and place it within the Else section of the If activity.
Set the To property to order.OrderApproved. Set the Value property to true. The Else part of
the If activity will execute if an order is less than $18.75, therefore the order automatically is
approved. See Figure 5-42.

Figure 5-42.  Implementing the Else and Branch2

46. Click Branch1 and add a WF variable called varApproved. This variable will be used to hold
the approval or rejection Boolean response, sent from the hosting application through the
WaitForResponse activity. Set the variable type to Boolean and the scope to Branch1 along with
a default value of false. This will prevent any orders being automatically approved.
197

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

47. Drag an If activity into the Action for Branch1. Set the Condition to varApproved.
48. Drag an Assign activity from the toolbox and place it within the Then section for the If activity.
Set the To property to order.OrderApproved. Set the Value property to true.
49. Drag another Assign activity from the toolbox and place it within the Else section for the If
activity. Set the To property to order.OrderApproved. Set the Value property to false. See
Figures 5-43 and 5-44.

Figure 5-43.  Setting the approval flag

Figure 5-44.  Order approval workflow

198

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

50. Right-click the Exercise2 project and select Build. CalculateOrder activity should now appear
within the toolbox. Open the wfCommunication.xaml workflow so the designer is open. Drag
and drop the CalculateOrder activity on the designer canvas. Attach the GetCustomerOrders
activity to the new CalculateOrder activity; see Figure 5-45.

Figure 5-45.  wfCommunication parent workflow

At this point the workflow is complete. The next step is to set up the hosting application to start
off the workflow and provide functionality for approving or rejecting an order.
51. Right-click the Chapter5.FlowChart solution, select Add, and then New Project. Select the
Windows template and add a new WPF Application. Name the project WFHost.
52. Add two references for Exercise1 and Chapter5.DataModel to the WFHost project.
53. Click on the MainWindow.xaml file and paste in the following XAML:

<Window
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "d"
x:Class = "wpfHost.MainWindow"
Title = "MainWindow" Height = "350" Width = "300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "158*"/>
<RowDefinition Height = "8*"/>
<RowDefinition Height = "146*"/>
</Grid.RowDefinitions>
<Button Name = "cmdRuntime" Content = "Get Orders" HorizontalAlignment = "Left"
Margin = "108,83,0,0" VerticalAlignment = "Top" Width = "75" Click = "cmdRuntime_Click" Height = "22"/>
<Button Name = "cmdApprove" Content = "Approve/DisApprove Order"
HorizontalAlignment = "Left" Margin = "62,136,0,0" VerticalAlignment = "Top" Width = "166"
Click = "cmdApprove_Click" Height = "22"/>

199

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

<CheckBox Name = "chkApprove" IsChecked = "true" Content = "Approve"


HorizontalAlignment = "Left" Margin = "108,116,0,0" VerticalAlignment = "Top"/>

</Grid>
</Window>

This will add a simple interface with two buttons and a checkbox for approving or rejecting orders (see Figure 5-46).

Figure 5-46.  Simple approval user interface

54. Open up the MainWindow.xaml.cs file and paste in the following code:

using System;
using System.Activities;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

200

www.it-ebooks.info
CHAPTER 5 ■ FlowCHART woRkFlows

using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Chapter5.DataModel;
using Exercise2;

namespace wpfHost
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private WorkflowApplication wfApp;
public MainWindow()
{
InitializeComponent();
cmdApprove.IsEnabled = false;
}

private UnhandledExceptionAction
OnUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs uh)
{
return UnhandledExceptionAction.Terminate;
}

/// <summary>
/// The on workflow completed.
/// </summary>
/// <param name = "wc">
/// The event args
/// </param>
private void OnWorkflowIdle(WorkflowApplicationIdleEventArgs iw)
{
cmdApprove.IsEnabled = true;
}
/// <summary>
/// The on workflow completed.
/// </summary>
/// <param name = "wc">
/// The event args
/// </param>
private void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs wc)
{
foreach (var arg in wc.Outputs)
{
if (arg.Key.Equals("argOutCustomers"))
{
var customers = arg.Value as Customers;
foreach (var cust in customers)

201

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

{
foreach (var order in cust.CustomerOrders)
{
MessageBox.Show(string.Format(" Approved: {2}, Total
Order Price: {0} with Tax: {1}", string.Format("{0:C}", order.TotalPrice),
string.Format("{0:C}", order.Tax),order.OrderApproved.ToString()));
}
}
}
cmdRuntime.IsEnabled = true;

}
}

private void cmdRuntime_Click(object sender, RoutedEventArgs e)


{
try
{
Activity workflow = new wfCommunication();
wfApp = new WorkflowApplication(workflow);
wfApp.SynchronizationContext = SynchronizationContext.Current;
wfApp.OnUnhandledException = OnUnhandledException;
wfApp.Completed = OnWorkflowCompleted;
wfApp.Idle = OnWorkflowIdle;
wfApp.Run();

cmdRuntime.IsEnabled = false;

}
catch (Exception ex)
{
throw;
}
}

private void cmdApprove_Click(object sender, RoutedEventArgs e)


{
try
{
wfApp.ResumeBookmark("ApproveOrder",chkApprove.IsChecked);
cmdApprove.IsEnabled = false;
}
catch (Exception ex)
{

throw;
}
}
}
}

55. Right-click the WFHost project, choose “Set as StartUp Project,” and press F5 to run the solution.

202

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

Figure 5-46 represents the default screen used to approve orders through the workflow. Retrieving an order
without pressing Approve/Disapprove and waiting seven seconds causes the workflow to respond with the total cost
with tax for an order and indicates that the order was not approved through the message box represented in
Figure 5-47, as it pops up indicating the total for the order and that the order was not approved.

Figure 5-47.  Response from the workflow

Clicking “Get Orders” again and clicking on the Approve/Disapprove button once it becomes enabled pops up
another message box indicating the same information about the tax and total price, but shows this time that the order
has been approved (see Figure 5-48).

Figure 5-48.  Approved order

Summary
Flowchart workflows provide a natural way of modeling processes because they provide a high level of flexibility for
flowing logic within business processes. This chapter covered the components for building Flowchart workflows and
gave detailed examples for using the Flowchart activity for initiating a flowchart workflow. The FlowDecision activity
for controlling the flow for a workflow is based on a Boolean value returned by a condition. The FlowSwitch activity
was covered and used to model predetermined values that could be matched for directing the flow of a workflow
as well.
The chapter also demonstrated how to use the Microsoft Entity Framework Code First pattern to dynamically
change the database tables and data plumbing for accessing dynamic data on the fly. The idea of building composite
activities was introduced by using a flowchart control flow where parent activities contained child activities and were
then reused as an activity within other workflows. A composite activity was demonstrated for calculating total cost
for an order based on each of the order line item costs and state taxes. Finally, the chapter covered communication

203

www.it-ebooks.info
CHAPTER 5 ■ Flowchart Workflows

to flowchart workflows by demonstrating an order approval process. Bookmarks were used with a Pick activity
and branches within the Pick activity were used for triggering bookmarks and providing a time limit for how long a
bookmark should wait before a workflow should resume.
Now that I have introduced state machine and flowchart control flows, the next chapter will go into detail
about building the different types of custom activities and when they should be built and used within workflows for
encapsulating domain specific business logic.

204

www.it-ebooks.info
Chapter 6

Versioning and Updating Workflows

Up until the release of WF4.5, updating and versioning workflows had its challenges because of the lack of support for
managing changes within existing workflows. Even though WF provided a better programming paradigm than using
imperative code for modeling ever-changing business processes, support for updating and versioning workflows
was badly needed. One of the key contributors that drive the need for managing existing workflows that have been
implemented in production is business process maturity. As processes evolve within businesses, software that was
developed to model original business processes must be updated to provide new functionality. This can be a hard task
for software that models processes that are long-running and are actually in the middle of executing a long-running
task when changes need to be made to the software.
This chapter will cover WF4.5’s new features for updating and versioning workflows and will walk through
examples for when to use one over the other for managing workflows. Although Chapter 8 is dedicated to covering
persistence, some aspects of persisting workflows will be mentioned in this chapter in terms of how they apply to
managing versions of a workflow.

Persistence Maturity
Workflow persistence was introduced in Chapter 2 as the mechanism used for storing long-running workflows as they
are executed and become idle. Persisting an idle workflow frees up memory resources as the workflow waits. WF has
always supported persistence, but as it matured so did its model for how workflows are persisted.
In WF3.x, persisting a workflow included persisting both the instance of the workflow and the workflow definition
within a persistence store (see Figure 6-1).

205

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Start

Decision

Outcome Outcome

Workflow Definition
(XOML)

Data
Data Instance Store

Data

Instance Data

  Persistence in WF3.x

Persistence in WF4 was changed so that only instance data was stored within a persistence store but not the
workflow definition (see Figure 6-2).

Data
Data

Data

Instance Data Instance Store

Figure 6-2.  Storing only instance data within the instance store

This dramatically reduced the amount of data needed to persist workflows compared to WF3.x. It also provided
an increase in performance for persisting and rehydrating workflows. The drawback of persisting only instance data
in WF4 became noticeable when a workflow was versioned. In WF4, there was no way of knowing which workflow
should be used to reload an existing workflow instance. This could cause WF to throw exceptions when a workflow
instance was loaded into the wrong workflow definition.

206

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

With WF4.5, workflows can be versioned without having to deal with the uncertainty as to which workflow
definition a workflow instance should be associated with as it is loaded. A new concept within WF4.5 called
WorkflowIdentity handles the correlation between a persisted instance and a workflow definition. Table 6-1
illustrates the properties for a WorkflowIdentity.

Table 6-1.  System.Activities.WorkflowIdentity Properties

Property Description
Name Descriptive name for the WorkflowIdentity
Version Establishes the version for the WorkflowIdentity
Package Optional property providing clarity for a workflow definition. A package could be represented as a
unique service URI or assembly name.

The WorkflowIdentity is persisted as a part of the persistence store so the persistence model has been slightly
modified to implement this correlation within WF4.5. This means that version information can be queried via
the persistence store. When tracking is configured for a workflow, WorkflowIdentity data can also be tracked.
WorkflowIdentity allows the following new features for workflow execution in WF4.5:
• Side-by-side
• Version mismatch
• Dynamic updates

Side-by-Side Workflow Execution


As business processes evolve and are required to change, there are circumstances when work that has been initiated
must complete its execution within the original logic that started it. These are usually long-running processes that
were executed before one or more changes to a business process were identified, but the requirement states that any
new execution of the business process that the software models must incorporate any new business logic changes.
Any executing business logic that was executed before the change is said to be “grandfathered in” and does not follow
the updated logic. In the world of WF, changing the workflow model for workflow instances that have already been set
in motion or executed can cause problems. For instance, if an approval process has already been started or executed
within a workflow, exceptions or unanticipated logical results will occur if the approval process is changed and needs
to incorporate new business logic. Consider the approval workflow in Figure 6-3 for candidates applying for a teacher
position for the State.

207

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-3.  Simple application process for reviewing candidates for a teacher position

Running the workflow in Figure 6-3 causes a new candidate application to be submitted for approval. The WCF
Test Client is used to host the workflow and expose it as a service. Figure 6-4 shows how a candidate can submit an
application for the teacher position. In this case, the application process is kept simple and the only information that
is needed to submit an application is the candidate’s name.

208

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-4.  Hosting the workflow service within the WCF Test Client

After the candidate is submitted, the workflow generates an application number and returns a message
indicating that the application has been successfully submitted.
However, after reviewing the current process for approving teacher candidates, it has been determined that only
candidates that have more than 4 years of prior experience can have their applications approved (see Figure 6-5).

Figure 6-5.  Logic now checks that the candidate has more than 4 years of experience

209

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-4 shows that the application ID that was generated from the workflow the last time a candidate
submitted an application was 35. Figure 6-6 shows that running the workflow again for ApplicationId 35, but after
the workflow is modified, causes the application to be rejected. Even though the teacher application is manually
approved by setting the Approval flag to True, the new workflow logic insists that the candidate must have more than
4 years of experience. The workflow is now using the new logic for checking the number of years of experience, but
back when the candidate’s application was created, years of experience was not a factor; since the teacher application
was “grandfathered in” for simply approving or rejecting an application, years of experience should not be used to
determine if the application gets approved or rejected.

Figure 6-6.  Modified workflow fails during execution of a persisted instance

The behavior illustrated in Figure 6-6 shows that updating a workflow’s definition after workflow instances have
been executed using a previous workflow definition can have undesirable results. WF4.5 takes care of these types
of scenarios by allowing workflows to run side by side. This means that workflow instances can still be run using a
previous workflow definition rather than having to be run against an updated workflow definition. Let’s walk through
the project to get a better understanding of how this is set up.
Working with workflows that are hosted as WCF services is covered in detail in Chapter 12; however, I will
explain some of the basics for building workflow services in this chapter as well. The easiest way to host workflows
as WCF services is to create a new WCF Workflow Service Application project. The activities included within the
default workflow need to be removed. Figure 6-7 indicates that a new ReceiveAndSendReply messaging activity has
been added to the workflow and will allow the workflow to be called so candidate applications can be submitted.
The OperationName for the messaging activity is set to SubmitApplication. The only parameter that is passed with
the SubmitApplication service method is a custom object of type TeachingApplication, illustrated in Listing 6-1.
Although it only has two data members, it will be useful for demonstrating side-by-side workflow execution.

210

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating WorkfloWs

Figure 6-7. Implementing the application submission for a teacher position

Listing 6-1. TeachingApplication Class Passed as a Parameter into the Workflow


using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Web;

namespace Apress.Chapter7
{
[DataContract]
public class TeachingApplication
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
}
}

211

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Next, an Assign activity is used to generate a new application ID by assigning it to a randomly generated number
using the C# expression of new Random().Next(1, 100).ToString(). This value is then set to a WF variable called
holdApplicationId, which will be used to correlate a particular workflow instance when referring to a candidate’s
application ID. Although correlation is also covered in Chapter 9 as it associates to uniquely identifying persisting
workflows, I want to quickly mention how it is being used. The InitalizeCorrelation activity will take the value
stored in the holdApplicationId variable and use it to correlate workflow instances. An application ID will then be
used to call a workflow instance so it can be executed again after is has gone idle and persisted within the SQL Server
persistence store. Figure 6-7 illustrates how the SendReply activity is used to send a message back from the workflow
indicating that an application ID has been generated and that the application has been received.
Next is the approval process of the workflow. Figure 6-8 illustrates that another ReceiveAndSendReply messaging
activity is being used to indicate that a decision is being made to either approve or reject a candidate’s application.
The Receive activity has its OperationName property set to ApproveTeacher and it accepts two parameters,
ApplicationId and Approval. ApplicationId provides correlation, which has been configured for associating a
Approval parameter is Boolean type to indicate whether the teacher
SendResponse activity is used to pass the message that the candidate
If activity shows the original logic that does not account for a

212

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-8.  Logic for approving or rejecting a candidate’s application

213

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Listing 6-2 shows the contents of web.config that have been updated to allow persistence to be configured using
SQL Server as the workflows go idle.

Listing 6-2.  Configuring Persistence within the Project’s Web.config File


<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" strict="false" explicit="true" targetFramework="4.5"/>
<pages controlRenderingCompatibilityVersion="4.0"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<sqlWorkflowInstanceStore

<workflowIdle timeToPersist="00:00:05" timeToUnload="00:00:30"/>


<!-- To avoid disclosing metadata information, set the values below to false before

<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>


<!-- To receive exception details in faults for debugging purposes, set the value below to

<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>

Adding Definition Identities


At this point a workflow can be versioned so there is no confusion about which persisted workflow instance should be
applied to a particular workflow definition. Figure 6-9 illustrates two different versions of a workflow can be run side
by side for managing long-running workflows.

214

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Start
Start

Decision
Decision

Outcome Outcome
Outcome

Version 1 Version 2
Data
Data

Data

Instance Data

Instance Store

Figure 6-9.  Running workflow versions side by side

Setting up the versions of a workflow that are supported by a workflow host can be accomplished either through
code or configured using the WF designer.

Versioning Through Code


Listing 6-3 shows the code used to set the latest version of the workflow service that should be hosted using the
WorkflowServiceHost. By calling CurrentWorkflowService(), the version of WorkflowService is set to 2.0.0.0
using the DefinitionIdentity property, which is of type WorkflowIdentity described earlier in in Table 6-1. The
WorkflowServiceHost has a new SupportedVersions property, which is of type ICollection<WorkflowService>.
After the WorkflowServiceHost is initialized, each additional version of the workflow it supports is also added through
the WorkflowServiceHost SupportedVersions property.

Listing 6-3.  Supporting Multiple Workflow Versions Through Code


using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Activities;
using System.Web;

215

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

using System.Activities;
using System.Collections.ObjectModel;

namespace Apress.Chapter7
{
public static class HostWorkflowService
{
public static void StartServiceHost()
{
using (WorkflowServiceHost wfServiceHost
= new WorkflowServiceHost(CurrentWorkflowService(),
new Uri("http://localhost:8080/EquipmentRentalService")))
{
var supportedServices = SupportedWorkflowServices();
foreach(var wfService in supportedServices)
{//add each supported version
wfServiceHost.SupportedVersions.Add(wfService);
}

wfServiceHost.Open();
}
}

public static WorkflowService CurrentWorkflowService()
{
var v2Workflow = new WorkflowService
{
Body = new TeachingApplicationService(),
DefinitionIdentity = new WorkflowIdentity
{
Name = "SimpleApplication",
Version = new Version(2, 0, 0, 0) //set the current version of the workflow
}
};

return v2Workflow;
}

public static Collection<WorkflowService> SupportedWorkflowServices()
{
var services = new Collection<WorkflowService>();

var v1Workflow = new WorkflowService
{
Body = new TeachingApplicationService(),
DefinitionIdentity = new System.Activities.WorkflowIdentity

216

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

{
Name = "SimpleApplication",
Version = new Version(1, 0, 0, 0) //set the initial version of the workflow
}
};

services.Add(v1Workflow);

var v15Workflow = new WorkflowService
{
Body = new TeachingApplicationService(),
DefinitionIdentity = new System.Activities.WorkflowIdentity
{
Name = "SimpleApplication",
Version = new Version(1, 5, 0, 0) //set the updated minor version of the workflow
}
};

services.Add(v15Workflow);

return services;
}
}
} 

Versioning Workflow Applications


Workflows that are not intended to be delivered as WCF services can be versioned in a similar way. Listing 6-3
illustrates the WorkflowServiceHost versioning hosted workflow services, but the WorkflowApplication can also be
used for versioning workflows hosted within applications, as shown in Listing 6-4.

Listing 6-4.  Setting the Versions for a Workflow Hosted Through WorkflowApplication
WorkflowIdentity v1WorkflowIdentity = new WorkflowIdentity
{
Name = "SimpleApplication",
Version = new Version(1, 0, 0, 0) //set the current version of the workflow
};

WorkflowApplication wfApp = new WorkflowApplication(new TeachingApplication(),v1WorkflowIdentity);

// Setup the WorkflowApplication
WorkflowApplicationFactory(wfApp);

// Execute the workflow.
wfApp.Run();

217

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

After the workflow goes idle and is persisted, at a later time the workflow can be reloaded from its persisted store.
As the workflow is reloaded, the same WorkflowIdentity properties used when the workflow was persisted must be
used for loading the workflow. If the WorkflowIdentity is different, then a VersionMismatchException is thrown.
Listing 6-5 illustrates the contents for the message based on the WorkflowIdentity set in Figure 6-4.

Listing 6-5.  Error Message Thrown When a Version Is Loaded with the Wrong Version
The WorkflowIdentity ('SimpleApplication; Version=1.0.0.0') of the loaded instance does not match the
WorkflowIdentity ('SimpleApplication; Version=2.0.0.0') of the provided workflow definition. The
instance can be loaded using a different definition, or updated using Dynamic Update.

A new object called WorkflowApplicationInstance is returned while retrieving a persisted workflow instance
using WorkflowApplication.GetInstance. It has a DefinitionIdentity of type WorkflowIdentity that can be used
to check that that the workflow definition version is being used.

 6-10 illustrates how Visual Studio can be used to configure workflow versions through the WF designer. The
DefinitionIdentity is being set within Visual Studio to a WorkflowIdentity. The workflow version
1.0.0.0 and it has been given the name SimpleApplication. After setting these properties of the
, the workflow can be run.

Figure 6-10.  Persisting a workflow instance within SQL Server

To check that the workflow has been properly persisted, SQL Server Management Studio can be used to connect
to the database used for persisting workflow instances. The System.Activities.DurableInstancing.Instances view
can be run to view persisted workflow instances. Figure 6-11 indicates that the workflow instance has been persisted
and that the version of the workflow definition has been stored.

218

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-11.  Persisting the version for a particular workflow definition

The business now mandates that the workflow must be changed. The first thing to do is copy the workflow and
move it within the App_Code folder of the project. In this case, another folder called SimpleApproval is created and
used to hold older versions of the same type of workflow (older versions, in other words). Although Figure 6-12 only
shows the workflow file v1SimpleApproval.xamx, other versions of the same type of workflow can be added as well.

Figure 6-12.  Copying the older version of the workflow within the project

The original workflow, SimpleApproval, can have its version updated to 2.0.0.0 by changing the workflow’s
DefinitionIdentity property for the root of the workflow. The workflow can now be updated to implement the
logic within Figure 6-5, which mandates that a candidate must have more than 4 years of experience. The code in
Listing 6-1 must accommodate a new YearsOfExperience property:

[DataMember]
public int YearsOfExperience {get; set;}

219

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

This time, as the updated workflow is run, the new property, YearsOfExperience, can be set to indicate the years
of experience for the candidate. In this case, Linda Owen only has 3 years of experience, as shown in Figure 6-13.

Figure 6-13.  Passing into the workflow years of experience

Now two workflow instances have been persisted, but the second persisted instance indicates that it uses version
2.0.0.0 of the SimpleApplication workflow (see Figure 6-14).

Figure 6-14.  Different versions of the same workflow have been persisted

When the workflow is run again, the logic will check that the candidate has more than 4 years of experience
even if the candidate is approved, as illustrated in Figure 6-15. The workflow instance will then be removed from the
persistence store by the WF runtime because the workflow will have completed.

220

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating WorkfloWs

Figure 6-15. Candidate is rejected because of insufficient years of experience

 F Tip figure 6-14 shows that the persistence store in Wf4.5 now has an identityname and columns that correlate to
the version of the workflow definition that was used to execute a workflow instance. Chapter 8 includes a lab that shows
how the database view in figure 6-14 should be queried through code for making decisions based on the versions of
names of workflow instances that have been persisted.

Now the other workflow instance can be run using the previous version of the workflow, which does not factor
in years of experience for approval. Figure 6-16 illustrates that the candidate application has been approved without
taking into account the years of experience.

221

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

  Running a different version of a workflow at the same time

Caution Previous versions of a workflow must be copied within folders that have the same name as the original
App_Code folder for the project. Figure 6-11 illustrates how this should be done.

Updating Running Workflow Instances


Running workflows side by side is great when a long-running workflow instance has already been executed and
needs to finish executing an original version of the workflow definition, even after the workflow definition has been
updated and new workflow instances have been executed. However, sometimes workflow instances executed with an
earlier workflow definition need to be updated to directly reflect updates made to a new version of the workflow. This
scenario is different than running different workflow instances with different versions of a workflow. In this case, the
workflow definition needs to run with an updated workflow definition of the version of the workflow definition that
originally executed it.
A common example of updating existing workflow instances to run under an updated workflow definition arises
when an original workflow contains bugs or a business process mandates that a process must be changed even after
the workflow instance has been executed. Let’s take a look at what happens if a workflow is updated after a workflow
instance has been initiated through a previous version of a workflow. Consider the workflow orchestrated through
code in Listing 6-6.

Listing 6-6.  Simple Workflow Defined Through Code


var wf = new Sequence
{
Activities =
{
new WriteLine()

222

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

{
Text = "Started a new workflow..."
},
new WriteLine()
{
Text = "Time to persist the workflow..."
},
new Delay()
{
Duration = new TimeSpan(0, 0, 5)
},
new WriteLine()
{
Text = "Workflow is about to complete..."
}
}
};

Once a workflow instance is initiated through this workflow and becomes persisted, as it becomes idle, the
workflow definition cannot be updated. The following line of code

wf.Activities.Add(new WriteLine() { Text = "Ok workflow can finish!" });

adds a new WriteLine activity at the end of the workflow in Listing 6-5. If the workflow is rehydrated from the
persisted store so it can complete, the WF runtime will throw the error message illustrated in Figure 6-17. The error
indicates that the updated workflow cannot be used to run an existing workflow instance, therefore the workflow
instance must be dynamically updated to incorporate the new WriteLine activity that was added to the workflow.

Figure 6-17.  Running a workflow instance with an updated workflow

223

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

The next couple of sections will explain the steps required for dynamically updating workflow instances.
Therefore, when workflow instances have become idle and are persisted, they can be executed without throwing
exceptions.

Step 1: Preparing the Update Map


The first step that is required before dynamically updating a workflow is to map the changes from the original
workflow to a workflow with an updated implementation. Consider the update map to be the delta or difference
between the two workflows. Before a workflow can be updated in WF4.5, a delta must be prepared. WF4.5 has a new
namespace called System.Activities.DynamicUpdate, and it includes the class DynamicUpdateServices, which
provides functionality for dynamically updating a workflow definition. Before the delta can be created, the original
workflow must be prepared for the update or change. The method DynamicsUpdateServices.PrepareForUpdate must
be called on the workflow every time before it can be updated. The PrepareForUpdate method accepts a parameter of
Activity or ActivityBuilder type, and it will duplicate the original workflow from which it was created. The

  Preparing the Workflow to be Updated

{
Activities =
{
new WriteLine()
{
Text = "Started a new workflow..."
}
}
};

DynamicUpdateServices.PrepareForUpdate(wf);

Step 2: Apply the Update Map


After a workflow has been prepared to be updated, it can be modified to reflect new business logic. Figure 6-18
illustrates that the Version 1 workflow has initiated a new workflow instance that has been persisted. As a new version
of the workflow called Version 2 is updated, the changes made to Version 2 are mapped so the persisted workflow
instance is aware of the changes that have been made between the two versions of the workflow.

224

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Start
Start
Graphed Updated
changes
Decision
Decision

Outcome Outcome
Outcome

Version 1 Version 2
Data
Data

Data

Instance Data

Instance Store

Figure 6-18.  Mapping dynamic updates to a workflow

To map the changes made to a workflow, DynamicUpdateServices.CreateUpdateMap must be called. Imagine that
the workflow in Figure 6-6 needs to be modified. After the workflow has been prepared, as illustrated in Figure 6-6, the
workflow can be updated by adding a new WriteLine activity using the following code:

wf.Activities.Add(new WriteLine() { Text = "Ok workflow can finish!" });

Once the workflow definition is updated, a map of the changes made between the two different workflows can be
created using the following code:

DynamicUpdateMap wfMap = DynamicUpdateServices.CreateUpdateMap(wf);

Step 3: Updating the Workflow Instance


Now that the workflow has been prepared for changes, changes have been made to the workflow, and a
DynamicUpdateMap has been created to map the changes between the two workflows, the last step is to update
the workflow instance that has been loaded within the persistence store. The WorkflowApplicationInstance is
another new object provided with WF4.5 that provides a DefinitionIdentity property of type WorkflowIdentity.
This property assists in the versioning of workflow instances and the code in Listing 6-8 indicates how it is used
to get a particular workflow instance. The CurrentInstance parameter of type Guid indicates the workflow to
retrieve from the persistence store that is specified by the parameter CurrentPersistenceStore. In WF4.5, the
WorkflowApplication host has also been updated and now allows a DynamicUpdateMap object to be passed in when
calling its Load method. After loading the WorkflowApplicationInstance, the persisted workflow instance is updated
using the DynamicUpdateMap so it can be executed against the updated workflow definition.

225

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Listing 6-8.  Updating the Persisted Workflow Instance


WorkflowApplicationInstance wfApplicationInstance = WorkflowApplication.GetInstance(CurrentInstance,
CurrentPersistenceStore);
wfApplication.Load(wfApplicationInstance, wfMap);
wfApplication.Run();

Saving a DynamicUpdateMap to File


Before the code is called in Listing 6-8, the DynamicUpdateMap is created by calling
DynamicUpdateServices.CreateUpdateMap. The new DynamicUpdateMap is then immediately used to update a
persisted WorkflowApplicationInstance so it can use the updated workflow definition the next time the workflow
instance is reloaded from the persistence store. But what happens if one or more persisted instance cannot be

must be saved to the file system so it can be used later for updating persisted workflow instances

  Saving the DynamicUpdateMap to Disk

{
var path = System.IO.Path.ChangeExtension(fileName, "map");
DataContractSerializer serialize = new DataContractSerializer(typeof(DynamicUpdateMap));
using (FileStream fs = File.Open(path, FileMode.Create))
{
serialize.WriteObject(fs, map);
}
}

The code in Listing 6-7 can be updated so that the update map can be retrieved from disk at a later time to be
used for updating persisted workflow instances (see Listing 6-10).

Listing 6-10.  Retreiving the Update Map from Disk


//Retrieve the update map from disk
DataContractSerializer serializer = new DataContractSerializer(typeof(DynamicUpdateMap));
using (FileStream fs = File.Open(@"C:\MyWorkflowUpdateMap.xml", FileMode.Open))
{
updateMap = serializer.ReadObject(fs) as DynamicUpdateMap;
}
//use the new update map to
wfApplication.Load(wfApplicationInstance, updateMap); 

Decoupling Workflow Implementation from Workflow Updates


This section will demonstrate how a workflow application can be decoupled from a different application that prepares
a workflow to be updated. A separate application can be used to create an update map that is used to update a
workflow dynamically at a later point in time. In this scenario, a workflow that models a movie rental process will be
built. Later I will show how the rental process can be updated dynamically so that customers who have movies still
rented are able to experience added functionality within the rental process.

226

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Renting movies has changed quite a bit in the last few years, so the process will be based on those movie rental
machines that have become quite popular. I recently had my first experience renting a movie from a machine rather
than walking into a store, and I thought to myself that this scenario would be a fun exercise to model with WF. Here
are the steps I took to rent a movie.
1. Searching for one or more movies.
2. Confirming when I was done searching for movies.
3. Inserting my credit card to pay for selected rentals.
4. Confirming my rental order.
Once I had finished watching the rented movies, I brought them back to the rental machine and entered my credit
card so the rental machine knew that I had returned the movies; my credit card was charged the amount of the rentals.
Now that the steps for renting a movie have been identified, the next thing to do is to model the process. Since
this process is mainly human driven, a state machine control flow will be used.
Figure 6-19 illustrates the state machine that will be used to model the movie rental process, and the flow models
each of the steps mentioned for renting a movie. When the workflow is run, the first state that the workflow will
execute is the MovieSearch state.

Figure 6-19.  State machine control flow that models a movie rental process

227

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Setting Up the Workflow


While the current state of the workflow is MovieSearch, there are two transitions that can be made by the customer:
• DoneSearching
• SelectAMovie
Each of these transitions add a System.Activities.Statements.Transition to the MovieSearch
System.Activities.Statement.State, and each transition uses the custom activity of WaitForResponse to set up a
bookmark for each transition (see Listing 6-11).

Listing 6-11.  WaitForResponse Custom Activity


using System;


7.Activities
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{

}

public string ResponseName { get; set; }

protected override bool CanInduceIdle
{ //override when the custom activity is allowed to make he workflow go idle
get
{
return true;
}
}

protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(this.ResponseName, new BookmarkCallback(this.ReceivedResponse));
}

void ReceivedResponse(NativeActivityContext context, Bookmark bookmark, object obj)
{
this.Result.Set(context, (TResult)obj);
}
}
}

228

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-20 illustrates that the WaitForResponse activity has been added within the Trigger section of the
SelectAMovie transition and the object type Movie will be passed through the bookmark from the workflow host to
make the transition occur. This event will indicate that the customer has made their first movie selection. Listing 6-12
indicates the properties associated with the Movie object:
• MovieName
• Rating
• Price

Figure 6-20.  Setting up the SelectAMovie transition

Listing 6-12.  Movie Class That Identifies Properties for a Movie


using System;
using System.Collections.Generic;
using System.Linq;

using System.Text;
using System.Threading.Tasks;

namespace MovieRental.DataModel
{
[Serializable]
public class Movie

229

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

{
public string MovieName { get; set; }
public string Rating { get; set; }
public Decimal Price { get; set; }
}
}

Since the SelectAMovie transition points to the same State MovieSearch, the event SelectAMovie can be fired
multiple times indicating that a customer can rent as many movies as they would like for a rental order.
The bookmark’s ResponseName property indicates the name of the bookmark that needs to be called from the
workflow host and the Result property that is used to set an existing property or argument within the workflow.
The workflow has a property called holdSelectedMovie of type Movie, and this property accepts the value that is
passed into the workflow so it can be used to process logic. The Condition section of the transition checks that the
workflow property holdSelectedMovie is not null based on the Movie object that was passed in from the workflow
 6-20).
Finally, an AddToCollection activity is added to create a collection of movies that the customer intends to rent.
 6-21 indicates the properties that are set within the activity.

Figure 6-21.  Setting up the SelectAMovie transition

There is a TypeArgument property that is set to type Movie. The Item property is set to holdSelectedMovie for the
movie that is passed in through the bookmark, and the Collection property is used to hold a collection of movies. This
property is set to holdNewRental.Movies, and holdNewRental is another workflow property of type CustomerRental
that is indicated in Figure 6-20. Listing 6-13 shows the CustomerRental class.

Listing 6-13.  CustomerRental Class


using System;
using System.Collections.Generic;
using System.Linq;

using System.Text;
using System.Threading.Tasks;

230

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating WorkfloWs

namespace MovieRental.DataModel
{
[Serializable]
public class CustomerRental
{
public CustomerRental()
{
Movies = new List<Movie>();
}

public Guid RentalId { get; set; }


public List<Movie> Movies { get; set; }
public CreditCard PaymentCard { get; set; }
}
}

The other transition, DoneSearching, also uses a WaitForResponse activity; however, it is not intended to do
much other than indicate when a customer is done searching and selecting movies to rent. The WaitForResponse
accepts a Boolean type that will be passed in from the workflow host to the workflow (see Figure 6-22).

Figure 6-22. Setting up the DoneSearching transition

231

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Once the customer is done selecting movies to be rented, the next state that is activated is
CompletedMovieSearch. Once CompletedMovieSearch is set as the current state for the workflow, a customer can then
insert their credit card to process and confirm the order. The InsertCard transition is initiated from the workflow host
once the customer is ready to process the order. The WaitForResponse activity for this transition accepts a CreditCard
object and sets it to holdNewRental.PaymentCard. There is also a custom activity that inherits from CodeActivity<T>,
which simulates processing a credit card. If this was real, a third-party reference could be made and the code that uses
the third party code could be added here. Instead, Listing 6-14 shows the custom activity code for RunCreditCard that
simulates a credit card being successfully run by returning a transaction number and assigning the number to the
CreditCard object that was passed in. The RunCreditCard activity accepts a CreditCard object and then returns the
object with a hard-coded transaction number.

Listing 6-14.  RunCreditCard Custom Activity


using System;


7.Activities
{
[Serializable]
public class RunCreditCard : CodeActivity<CreditCard>
{
[RequiredArgument]
public InArgument<CreditCard> inCreditCard { get; set; }
protected override CreditCard Execute(CodeActivityContext context)
{
var ccWithTransNo = inCreditCard.Get(context);
ccWithTransNo.TransactionNumber = 1542514612;
return ccWithTransNo;
}
}
}

The CreditCard object used is indicated in Listing 6-15, which contains the property TransactionNumber. It also
implements IEquatable<T> so a CreditCard object can be compared to another CreditCard object based on certain
its properties. The Equals function purposely omits checking the TransactionNumber because later when a customer
comes back to return the movies, the same credit card that was entered to process the order will be used to check that
it was the same credit card charged for the rental.

Listing 6-15.  CreditCard Class Used to Pass Credit Card Data to the Workflow
using System;
using System.Collections.Generic;
using System.Linq;

using System.Text;
using System.Threading.Tasks;

232

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

namespace MovieRental.DataModel
{
[Serializable]
public class CreditCard:IEquatable<CreditCard>
{

public string CCNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int ExpireMonth { get; set; }
public int ExpireYear { get; set; }
public int TransactionNumber { get; set; }
public bool Equals(CreditCard other)
{
if (this.CCNumber == other.CCNumber
&& this.ExpireMonth == other.ExpireMonth
&& this.ExpireYear == other.ExpireYear
&& this.FirstName == other.FirstName
&& this.LastName == other.LastName)
return true;
else
return false;
}
}

After the customer’s credit card is charged, a Persist activity is used to persist the workflow instance within the
persistence store in SQL Server; this way memory is released for customer movie rental until the customer returns the
movies, allowing other customer rental orders to be processed. The Condition section for the transition checks that a
transaction number has been added and that it has been charged (see Figure 6-23).

233

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-23.  Setting up the DoneSearching transition

Once the customer decides to return the movies, they will enter the same credit card they used to place the
movie rental. The transition ReturnMovie is used to indicate that a customer has entered their credit card by using
another WaitForResponse activity. The activity accepts the CreditCard object and sets it to another workflow property
called holdReturnMovieCard. Although the ResponseName is also set to ReturnMovie, which is the same name as the
transition, it does not have to be. The condition for the transition uses the CreditCard.Equals function to check that
the CreditCard object that was passed in is the same CreditCard object that was used to process the rental, but of
course the Equals function does not care about the TransactionNumber property since the CreditCard object that is
passed in to return the movie will not have a transaction number set (Figure 6-24).

234

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-24.  Returning rented movies and passing in the credit card to the transition

The last state of the workflow uses a FinalState state. This indicates the last state of the workflow and also sets
the OutArgument OutMovieRental associated to the workflow. Here the holdNewRental CustomerRental type is set
to OutMovieRental argument, which is also a CustomerRental type. The OutMovieRental argument will be returned
back to the workflow host to indicate that the workflow has completed and specific properties were set for the
argument inside the workflow (see Figure 6-25).

235

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

  Returning movies rented and passing in the credit card to the transition

Note To understand how to set up a persistence store within SQL Server, please review Chapter 8.

Now that the workflow has been built to create movie rentals, a workflow host needs to be built to make sure
the workflow works as intended. This workflow relies on using the WF runtime to manage workflow execution
like persistence and versioning. Since the workflow will execute solely within the rental machine’s software, the
WorkflowApplicationHost will be used to manage the workflow as a hosted application. Figure 6-26 shows the steps
that were defined within the workflow, and now code needs to be written to interact with the workflow and test its
execution before the solution can be loaded onto the rental machines hardware.

236

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-26.  Simple workflow host used to test the workflow

When the application to simulate how a customer will rent one or more movies is started using the workflow
illustrated in Figure 6-26, a new rental is initiated when the “New Rental” button is selected. The next step is to add a
movie that a customer would like to rent. After the movie is entered then the “Select Movie” button is pressed to store
the movie selection. Remember that the workflow in Figure 6-19 is built so that multiple movies are allowed to be
rented at one time; however, after the customer has decided that they are done adding movies to rent, the “Selection
Complete” button is pressed.
At this point payment is required, so the customer needs to select the “Insert Card” button, which will take
the credit card information and process the card. Finally, the workflow is unloaded from the WF runtime when the
customer selects the “Finish” button. At this point the application can stop its execution to simulate a later time when
the customer will bring back the movies. This workflow is considered to be long-running because a customer may
take hours or even days to return the one or more of the rented movies.
To simulate a customer returning one or more movies, the Guid that was generated through the persistence store
is used to track the original rental. The Guid is then added to the RentalTransactionId textbox and the button called
“Insert Card to Return Movie” is then pressed to complete the workflow. When the workflow has completed, a pop-up
window indicates that the movie has been returned. Figure 6-27 illustrates how workflow instances can be monitored
using the Server Explorer within VS2012 to see the Instances database view that is created within the SQL Server
persistence store.

237

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

  Using the Instances view to monitor workflow instances

Tip The persistence store used in this example does not require a full-blown instance of SQL Server to be installed
Of course, a licensed production version of SQL Server would normally be required for a
Figure 6-27 illustrates that SQL2012’s LocalDB is being used. This version of SQL Server is extremely

The next few pages will walk through the code used to host the movie rental workflow. The custom workflow
host application in Figure 6-26 is a simple WPF application that is its own project within the solution. The application
uses the namespaces illustrated in Listing 6-16. The using statements after System.Activities indicate some of the
references that are required to be made to the project.

Listing 6-16.  Initiating the Workflow Host Application


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

238

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

using System.Activities;
using System.Runtime.DurableInstancing;
using System.Activities.DurableInstancing;
using System.Threading;
using MovieRental.DataModel;
namespace Apress.Chapter7.Host
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private WorkflowApplication _wfApp;
private SqlWorkflowInstanceStore _instanceStore;

public MainWindow()
{
InitializeComponent();
CreatePersistenceStore();
}

The SqlWorkflowInstanceStore is initiated first as a private variable to be used throughout the


code; as the class loads, its constructor creates and configures the persistence store defined as the private
variable. To initialize the SqlWorkflowInstanceStore, its ConnectionString property needs to be set.
Optionally its InstanceCompletionAction property can be set, and for the application it is set to
InstanceCompletionAction.DeleteNothing. This setting indicates that after a workflow instance persists and
later completes, it will not be removed from the persistence store (see Listing 6-17).

Listing 6-17.  Configuring the Persistence Store


private void CreatePersistenceStore()
{
try
{
_instanceStore = new SqlWorkflowInstanceStore();
_instanceStore.ConnectionString =
@"Data Source=(LocalDB)\v11.0;Initial Catalog=WFPersist;Integrated Security=True";
_instanceStore.InstanceCompletionAction = InstanceCompletionAction.DeleteNothing;
}
catch (Exception)
{
throw;
}
}

After the persistence store is configured, the WorkflowApplication is then instantiated. Listing 6-18
illustrates that after instantiating the MovieRentalProcess workflow as an Activity object, it is passed into a new
WorkflowApplication as the workflow that will be managed by the WF runtime. In WF4.5, a WorkflowApplication
object now accepts a WorkflowIdentity object. Here the Name property is set to a meaningful name and a new
Version object indicating the current version of the workflow.

239

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Since the WorkflowApplication allows workflow instances to be run asynchronously, this application will
only run workflow instances using the same thread that the WPF application uses. This is enforced by setting the
WorkflowApplication object’s SynchronizationContext property to SynchronizationContext.Current. The
WorkflowApplication object’s InstanceStore property is also set to use the persistence store that was set up
in Listing 6-18.

Listing 6-18.  Configuring the WorkflowApplication Variable


private void InitiateWorkflowRuntime()
{
try
{
Activity rentalWorkflow = new MovieRentalProcess();
_wfApp = new WorkflowApplication(rentalWorkflow,
new WorkflowIdentity
{
Name = "v1MovieRentalProcess",
Version = new System.Version(1, 0, 0, 0)
});
_wfApp.SynchronizationContext = SynchronizationContext.Current;
_wfApp.OnUnhandledException = OnUnhandledException;
_wfApp.Completed = OnWorkflowCompleted;
_wfApp.Idle = OnWorkflowIdle;
_wfApp.InstanceStore = _instanceStore;
_wfApp.Unloaded = OnWorkflowUnloaded;
}
catch (Exception ex)
{
throw ex;
}
}

Next, a couple of WF runtime events are also wired up using the WorkflowApplication events. For instance,
OnUnhandledExeption is an important delegate to wire up because it is important to know when a workflow instance
has thrown an exception. I cannot stress the amount I have wasted while building workflow applications in the past
without wiring up this delegate and not knowing when a workflow instance was failing. The OnWorkflowCompleted
event fires when a workflow instance finishes, of course. An OnWorkflowIdle event fires while the workflow waits, and
OnWorkflowUnloaded fires once the workflow is unloaded from the WF runtime. In some cases, like in Listing 6-19, it is
not important to add code; however, it is nice to have the event fire when these different events occur.

Listing 6-19.  Declaring How the WF Runtime Events Will Be Handled


private void OnWorkflowIdle(WorkflowApplicationEventArgs args)
{
}

private void OnWorkflowUnloaded(WorkflowApplicationEventArgs args)
{
}

240

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating WorkfloWs

private void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs wc)


{
var createdRental =
wc.Outputs["OutMovieRental"] as CustomerRental;
MessageBox.Show(
string.Format("New rental for {0} {1} has been
returned!",createdRental.PaymentCard.FirstName,createdRental.PaymentCard.LastName));
}

private UnhandledExceptionAction OnUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs uh)


{
return UnhandledExceptionAction.Terminate;
}

Now the application starts getting into the button events that will drive the workflow. Listing 6-20 indicates
that when the cmdStartRentalProcess button is pressed, the WorkflowApplication is initialized by calling
InitiateWorkflowRuntime() and running the code in Listing 6-18.

Listing 6-20. Starting the Workflow


private void cmdStartRentalProcess_Click(object sender, RoutedEventArgs e)
{
InitiateWorkflowRuntime();
_wfApp.Run();
}

After a new workflow instance is started, the customer can start adding movie titles that they want to rent.
As each new movie title is added, the movie’s price and rating is hard-coded, as illustrated with the code in Listing 6-21.
Once a Movie object is created, the workflow host indicates to the workflow that it is ready to pass it some information.
The SelectMovie bookmark is called using the WorkflowApplication method of ResumeBookmark and passing
to the workflow the Movie object that was created. Once the customer is done adding movie titles to rent, the
FinishSearching bookmark is called. The bookmark accepts a Boolean, which is not really significant; however, if
additional logic needed to be built off of the bookmark or another bookmark with the same name needed to be added
to define another transition, it could be applied based on the Boolean value that is passed. Here the code simply
passes in true, indicating that the customer has finished entering movies.

Listing 6-21. Adding Movies To Be Rented Using the SelectMovie Bookmark


private void cmdSelectMovie_Click(object sender, RoutedEventArgs e)
{
var movie = new Movie
{
MovieName = txtMovieName.Text,
Rating = "PG",
Price = Convert.ToDecimal(4.50)
};
_wfApp.ResumeBookmark("SelectMovie", movie);
}

241

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

private void cmdSelectionComplete_Click(object sender, RoutedEventArgs e)


{
_wfApp.ResumeBookmark("FinishedSearching", true);
}

After one or more movies have been selected to be rented, the workflow host needs to initiate when a payment
card is entered in order to process a movie rental. Figure 6-21 shows the cmdInsertCard button click event that
builds a new CreditCard object and passes it to the workflow by calling the ScanPaymentCard bookmark. Listing 6-22
indicates that the values have been hard-coded.

Listing 6-22.  Passing a CreditCard Object to be Processed for the Rental Order
private void cmdInsertCard_Click(object sender, RoutedEventArgs e)
{
var creditCard = new CreditCard()
{
CCNumber = "1235626427465",
FirstName = "Bayer",
LastName = "White",
ExpireMonth = 10,
ExpireYear = 14
};

_wfApp.ResumeBookmark("ScanPaymentCard", creditCard);
}

The last button required to unload the workflow instance from memory is the cmdUnload button. The button is
labeled as Finished; however, its click event, which is defined in Listing 6-23, illustrates that the WorkflowApplication
object’s Unload method is called. Since the workflow instance is persisted, it no longer needs to run in memory while
waiting for the customer to return rented movies.

Listing 6-23.  Unloading the Workflow Instance from Memory


private void cmdUnload_Click(object sender, RoutedEventArgs e)
{
_wfApp.Unload();
}

After running the application illustrated in Figure 6-26 to create a new movie rental, the Instances database view
should contain a new record. Figure 6-28 illustrates some of the fields that indicate that the workflow instance has
become idle and is waiting on the ReturnMovie bookmark to be resumed. The last couple of fields on the record also
indicate the values for the fields IdentityName, IdentityPackage, Build, Major, Minor, and Revision that were given
to the workflow when it was created using the code in Listing 6-18.

Figure 6-28.  Record within the Instances database view indicating persistence

242

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

■■Caution It is important to make sure that sensitive or Personal Identification Information (PII) are not added as
property values of a WorkflowIdentity object. These properties are persisted to the persistence store in plain text, so be
sure to only add property values that are strictly relevant to the workflow version.

Preparing a Workflow for Update


At this point the workflow is being used to process movie rentals, but as the business’s goals change, the workflow
needs to be updated in order to reflect updates to the business process. In some cases, it might not be practical to
update workflows within the same solution that contains the implementation of the workflow application. In this
section, a new WPF application is built to demonstrate how an application can be decoupled into a separate solution
for updating a workflow, rather than updating a workflow from the same application that hosts the workflow.
The scenario changes just a bit for the process of renting movies. Now, when a customer returns one or more
movies, the workflow needs to provide the customer with a list of new movies that have just become available to rent.
Hopefully this will entice the customer to rent one of the new movies. This not only includes new movie rentals that
occur after the change is rolled into production, but the change should also apply to existing customers who have
rented movies but have not yet returned them. Figure 6-29 shows the custom tool that will be used for updating the
movie rental workflow.

Figure 6-29.  Custom tool used to update the movie rental workflow

This application will follow the three steps introduced earlier for dynamically updating a workflow. These
steps are
• Prepare the workflow for update.
• Create an identity map for workflow changes.
• Update existing workflow instances to use this version of the workflow.
The last step of updating an existing workflow instance will use a workflow instance that was created and
persisted similar to the one in Figure 6-28. To get started, the application in Figure 6-29 will search for the workflow

243

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

represented as a XAML file to be updated. After a workflow is located that needs to be updated, it will be prepared for
update. In this case, a new workflow XAML file will be created, so after a workflow file path is identified by clicking the
“Set Workflow to Update” button, the “Save Original Snapshot” button will be clicked next to create the snapshot. The
new workflow XAML file path will then be added to the next textbox (see Figure 6-30).

  Preparing a workflow for update

The first file path indicates the workflow used to compile the Apress.Chapter7.Workflow project. The second
file path, which points to the file location for the wfReadytoUpdate.xaml, represents the snapshot file that was created
when the “Save Original Snapshot” button is clicked. Listing 6-24 shows the code used to create a workflow snapshot.
The magic that creates the workflow snapshot happens in cmdWorkflowUpdate_Click where a workflow is loaded, set
as a snapshot, and then saved back to file.

Listing 6-24.  Creating and Saving a Workflow Snapshot from an Original Workflow
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

244

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

using System.Activities;
using Microsoft.Win32;
using System.Activities.DynamicUpdate;
using System.IO;
using System.Xaml;
using System.Activities.XamlIntegration;
using System.Runtime.Serialization;
using System.Xml;
using System.Activities.DurableInstancing;
using System.Threading;
using System.Activities.Statements;

namespace Apress.Chapter7.DynamicUpdate
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ActivityBuilder OriginalWF = null;
ActivityBuilder DeltaWF = null;
DynamicUpdateMap updateMap = null;


public MainWindow()
{
InitializeComponent();
}

private void cmdSearchWorkflow_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Filter = @"Workflow files (*.xaml, *.xamlx)|*.xaml;*.xamlx";
fileDialog.Multiselect = false;
bool? close = fileDialog.ShowDialog(this);
if (close.HasValue && close.Value)
{
txtWorkflowFile.Text = fileDialog.FileName;
}
}

private void cmdWorkflowUpdate_Click(object sender, RoutedEventArgs e)
{
OriginalWF = LoadActivityBuilder(txtWorkflowFile.Text); //Load original workflow to update
DynamicUpdateServices.PrepareForUpdate(OriginalWF); //Prepare the workflow for update
SaveActivityBuilder(OriginalWF, txtWorkflowFile.Text); //Save the prepared workflow
}

245

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

public void SaveActivityBuilder(ActivityBuilder builder, string path)


{
var actualpath = System.IO.Path.GetDirectoryName(path) + "\\wfReadytoUpdate.xaml";
txtWorkflowSnapshot.Text = actualpath;
using (var writer = File.CreateText(actualpath))
{
var xmlWriter = new XamlXmlWriter(writer, new XamlSchemaContext());
using (var xamlWriter = ActivityXamlServices.CreateBuilderWriter(xmlWriter))
{
XamlServices.Save(xamlWriter, builder);
}
}
}

Next, view the other solution that contains the project used to build the original workflow, MovieRentalProcess.xaml .
wfReadytoUpdate.xaml file that was created can be
wfReadytoUpdate.xaml file, click on the “Show All Files” button at the
 6-31).

Figure 6-31.  Showing all files to include and exclude workflow files

Now that the snapshot workflow is included within the project, it can be updated so that customers are aware
of the latest movies that have been released. To simulate getting the new movies, a new custom activity is created
so it can be added to the workflow. The code in Listing 6-25 illustrates the code used to define the new activity of
MoviesJustReleased. This accepts a mandatory InArgument and will return the latest movies released, simulated
by three hard-coded movies that are available. The activity first checks the rating type for the movie passed in and
returns the latest movies that have the same rating. The return object is a collection of movies of type List<Movie>.
Once the MoviesJustReleased activity is compiled successfully, it can then be added to the workflow snapshot that
was created earlier.

246

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Listing 6-25.  Unloading the Workflow Instance from Memory


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Activities;
using MovieRental.DataModel;

namespace Apress.Chapter7.Activities
{
public class MoviesJustReleased : CodeActivity<List<Movie>>
{
[RequiredArgument]
public InArgument<Movie> inBasedOnMovie { get; set; }
protected override List<Movie> Execute(CodeActivityContext context)
{
List<Movie> retMovies = null;
var basedOnMovie = inBasedOnMovie.Get(context);
if (basedOnMovie.Rating == "PG")
{//could do some logic here to return only movies based on a movie's rating
retMovies = new List<Movie>
{
new Movie
{
Rating="PG",
MovieName="The Walking Dead (Seasons 1 and 2)",
Price=Convert.ToDecimal(4.50)
},
new Movie
{
Rating="PG",
MovieName="Promethius",
Price=Convert.ToDecimal(5.50)
},
new Movie
{
Rating="PG",
MovieName="Hotel Transylvania",
Price=Convert.ToDecimal(4.50)
}
};
}
return retMovies;
}
}
}

247

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

■■Caution  While updating a workflow snapshot, it is important not to remove a bookmark that a persisted workflow is
expecting to be available. Instead, focus on the execution of a workflow after an existing bookmark is resumed.

Updating the Workflow Snapshot


After the workflow snapshot is created, the workflow may look a little rough through the WF designer. Figure 6-32
illustrates the workflow snapshot when compared to the workflow in Figure 6-19. While interrogating the XML used to
build the workflow snapshot generated within the wfReadyToUpdate.xaml file, I don’t believe the original locations for
the states and transitions is being accounted for as they were laid out in Figure 6-19.

Figure 6-32.  Updating the workflow snapshot

248

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

By clicking on the CompletedMovieRental FinalState of the workflow, the MoviesJustReleased activity can
be added. The inBasedOnMovie argument needs to be set to the movie that the customer is returning using the
holdNewRental.Movies(0). This value represents the first movie that was rented and set earlier when the movie rental
was created. The Result argument will be set to holdNewRental.Movies (see Figure 6-33).

Figure 6-33.  Updating the workflow snapshot

■■Caution The original workflow used in Figure 6-19 is a C# workflow, meaning it uses C# expressions. You may
have noticed that Figure 6-33 uses VB expressions instead when making updates to the generated snapshot. Another
interesting observation is that the existing C# expressions used show up as “Value was set in XAML.” Hopefully these
characteristics will be taken care of in the next update of WF4.5.

Now that the workflow has been updated, it is important to recompile the solution and update the references that
the workflow-updating application will use. In this case, the references that are indicated as being stale in Figure 6-34
will be updated.

249

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

  Updating references for the workflow application to be used in the workflow-updating application

Now that the changes to the workflow have been updated, a DynamicUpdateMap can be created so it can later be
 6-35 illustrates that that selecting the “Save Update Map” button
wfReadytoUpdate.map file.

Figure 6-35.  Creating the update map and using it to update a workflow instance

The code used to generate the DynamicUpdateMap is shown in Listing 6-26. Specifically, the code in
cmdSaveUpdateMap_Click is used to load the updated workflow snapshot from disk, create a DynamicUpdateMap file by
calling DynamicUpdateServices.CreateUpdateMap, and then save the map to disk.

250

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating WorkfloWs

Listing 6-26. Creating the Update Map


private void cmdSaveUpdateMap_Click(object sender, RoutedEventArgs e)
{

DeltaWF = LoadActivityBuilder(txtWorkflowSnapshot.Text);
updateMap = DynamicUpdateServices.CreateUpdateMap(DeltaWF);

SaveUpdateMap(updateMap, txtWorkflowSnapshot.Text);
}

public ActivityBuilder LoadActivityBuilder(string fileName)


{
ActivityBuilder builder;
using (var xamlReader = new XamlXmlReader(fileName))
{
var builderReader = ActivityXamlServices.CreateBuilderReader(xamlReader);
builder = (ActivityBuilder)XamlServices.Load(builderReader);
xamlReader.Close();
}
return builder;
}

public void SaveUpdateMap(DynamicUpdateMap map, string fileName)


{
var path = System.IO.Path.ChangeExtension(fileName, "map");
DataContractSerializer serialize = new DataContractSerializer(typeof(DynamicUpdateMap));
using (FileStream fs = File.Open(path, FileMode.Create))
{
serialize.WriteObject(fs, map);
}

txtUpdateMapFile.Text = path;
}

After the map file is created, it can be used to update any workflow instances that have been persisted using the
original workflow for renting movies. The code in Listing 6-26 is used to update a workflow instance associated by a
Guid that is added in the textbox, as illustrated in Figure 6-35. Listing 6-27 indicates that the persistence store must be
configured first using a connection string.
Next, a WorkflowApplicationInstance is created using the SqlWorkflowInstanceStore object that was
configured and the Guid identifying which workflow instance needs to be updated. The DynamicUpdateMap is
then read from file and created into memory. A WorkflowApplication object is then instantiated using a new
MovieRentalProcess, representing the workflow definition that will be used. It is important to set this since the
MovieRentalProcess workflow is reflected based on the referenced DLLs from the workflow project and when the
workflow project was compiled, it was using an updated workflow snapshot during compilation.
The WorkflowApplication also takes a WorkflowIdentity object. You may have noticed that the workflow
application host was creating workflow instances using v1MovieRentalProcess as the name of the workflow and
version 1.0.0.0. This information was saved to the persistence store each time a workflow was persisted. Listing 6-27
shows that the workflow instance should now be updated to the workflow named v2MovieRentalProcess, and its
version is now 2.0.0.0. This will now be reflected in the persistence store after the workflow is unloaded from memory.

251

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Once the workflow is loaded using the WF runtime and updated within the persisted store, it is simply unloaded again
so it can later be loaded when a customer returns their movies.

Listing 6-27.  Updating a Persisted Workflow Instance to the New Version


private void cmdUpdateInstance_Click(object sender, RoutedEventArgs e)
{

SqlWorkflowInstanceStore instanceStore = new SqlWorkflowInstanceStore();

instanceStore.ConnectionString =
@"Data Source=(LocalDB)\v11.0;Initial Catalog=WFPersist;Integrated Security=True";

WorkflowApplicationInstance wfInstance =
WorkflowApplication.GetInstance(new Guid(txtUpdateInstance.Text), instanceStore);

DataContractSerializer s = new DataContractSerializer(typeof(DynamicUpdateMap));
using (FileStream fs = File.Open(txtUpdateMapFile.Text, FileMode.Open))
{
updateMap = s.ReadObject(fs) as DynamicUpdateMap;
}

var wfApp =
new WorkflowApplication(new MovieRentalProcess(),
new WorkflowIdentity
{
Name = "v2MovieRentalProcess",
Version = new Version(2, 0, 0, 0)
});

IList<ActivityBlockingUpdate> act;
if(wfInstance.CanApplyUpdate(updateMap, out act))
{
wfApp.Load(wfInstance, updateMap);
wfApp.Unload();
}
}

Knowing What Can be Updated


Even though a workflow has been updated, it does not mean that persisted workflow instances that were
run using a previous workflow definition can be updated. When a workflow is persisted, it might have gone
idle while waiting on some external event, like a bookmark. When this happens, as far as the workflow knows,
a bookmark existed the last time it was persisted; therefore the same bookmark should not be removed or
changed. The WorkflowApplicationInstance also has a CanApplyUpdate(DynamicUpdateMap updateMap,
out IList<ActivityBlockingUpdate> activitiesBlockingUpdate() function that returns a Boolean,
indicating if a persisted workflow instance can be updated (see Listing 6-27). The first parameter, updateMap, is
of course the update map used to update a persisted workflow instance, and IList<ActivityBlockingUpdate>
activitiesBlockingUpdate passes back a collection of ActivityBlockUpdate objects that indicate why a persisted
workflow instance cannot be updated. The Reason property on the ActivityBlockUpdate returns a message
explaining why an update for an instance cannot occur.

252

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

■■Tip Do not make changes to the arguments while updating a workflow definition. This will cause the CanApplyUpdate
function to return false. This includes adding or removing arguments.

Now that the workflow instance is updated, when a customer returns they should get the latest release of movies
based on the rating for the first movie they return. The focus is now back on the workflow application that is hosting
the movie rental workflow. All that needs to happen to simulate a customer returning a movie is to enter the Guid,
represented as the persisted workflow ID, and press the “Insert Card to Return Movie” button (see Figure 6-36).

Figure 6-36.  Returning movies that were rented

Running the Correct Workflow Version


Earlier I mentioned that the workflow host was creating workflow instances under a different workflow name and
version, as illustrated in Listing 6-18. If you simulated a customer returning movie and received the error in Figure 6-37,
that means that the WF runtime is smart enough to check and make sure that the right version of the workflow is
being run.

253

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-37.  Resuming an incorrect version of a workflow

What’s happening here is that the workflow instance version has been updated as illustrated by looking at the
values pertaining to versioning within the persistence store. To resume workflow instances that have been updated,
the WF runtime in WF4.5 now has a safety mechanism that checks to see if you want to run an existing workflow
instance against an updated workflow version or run it using the same workflow that was used to initiate the workflow.
In the scenario for the customer returning a movie, Figure 6-17 needs to be updated so that version 2.0.0.0 is used for
completing the workflow.
Once the update is made, Figure 6-38 illustrates that the workflow is now calling out the MoviesJustReleased
activity as the workflow is debugged during execution. The workflow instance still has the record within the
persistence store because we indicated to the WF runtime to keep completed workflow instance records in the
persistence store.

254

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Figure 6-38.  Executing new functionality by calling the MoviesJustReleased activity

Updating Activities
Activities can also be used for updating workflows, but only activities that inherit from NativeActivity can be
dynamically updated. Activities that inherit from NativeActivity have direct interaction to the WF runtime, therefore
the WF runtime needs to be aware of any updates made to an activity, and any new child activities must be manually
scheduled. After a custom activity inherits from NativeActivity, there are two methods that need to be overridden
from NativeActivity. The first method is OnCreateDynamicUpdateMap, which is an event that gets raised when a map
gets created for a dynamic update. The other is UpdateInstance, which updates the instance of the activity with any
new activities, assuring that they are added as child activities and scheduled for execution (see Listing 6-28).

255

www.it-ebooks.info
Chapter 6 ■ Versioning and Updating Workflows

Listing 6-28.  Overriding Methods for an Activity That Inherits from NativeActivity
protected override void OnCreateDynamicUpdateMap(NativeActivityUpdateMapMetadata metadata, Activity
originalActivity)
{
metadata.AllowUpdateInsideThisActivity();
}
protected override void UpdateInstance(NativeActivityUpdateContext updateContext)
{
if (updateContext.IsNewlyAdded(childActivity))
{
updateContext.ScheduleActivity(childActivity);
}
}

The second type of versioning demonstrated was dynamic updating workflows; this works by ensuring a running

the next version of the workflow.


Chapter 7 will switch gears a bit and focus on explaining how different types of custom activities can be created.

256

www.it-ebooks.info
Chapter 7

Building Custom Workflow Activities

In Chapter 3, you built a solid foundation for workflow activities and learned how they can be used for authoring
workflows. WF provides many workflow activities that can handle most of the functionality required by workflows, but
occasionally there are business requirements that require custom activities. Most of the time, custom activities are
required to define workflow logic that simplifies the modeling experience so the workflow is easier to manage.
This chapter will show you how to build custom workflow activities that model business logic for domain-specific
business processes. There are different types of workflow activities, so I will suggest patterns and practices for building
the right type of custom activity based on different workflow scenarios. The chapter will also show you how to define
the physical layout for a custom activity so it can be used declaratively within workflows.

Activity Base Classes


The authoring experience has not changed much between WF4 and WF4.5, but, regardless of WF version, it is
important to understand how to build custom workflow activities since they are the basic unit of functionality that
drive a workflow.
Before you start building custom workflow activities, let’s first talk about workflow activities on a coding level,
since this is how custom activities will built within this chapter. I mentioned earlier in the book that workflow
activities are the basic building blocks for authoring workflows. Classes defined as code could also be considered the
same for defining software. For instance, the .NET Framework is built from defined classes that work together.
There are base classes that represent foundational characteristics that other classes can inherit from. This is the
same principle that applies to the activity base classes. For example, a Person class can be defined as a base class so
that other classes can inherit from it, and the Person class might contain the following properties:
• DOB
• Age
• SSN
Another class called, for instance, Student can now inherit from the Person class and is defined by additional
properties, such as
• StudentNumber
• HomeRoom
• CurrentGrade
A Student class can inherit from the Person class, and in doing so, it too will have the additional properties
defined within the Person class.

257

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Polymorphism is an object-oriented programming (OOP) technique that builds on the concept of inheritance,
in this case by implying that the Student class has all the characteristics of a person, together with those special
characteristics that distinguish a student. The same concept applies to the activity base classes because even though a
custom activity can be built, it still uses the activity base class it inherits from to represent the base type of the activity.
Encapsulation is another important OOP technique; it abstracts or hides the implementation of an interface so
the caller is not aware of exactly how the functionality is defined because the caller only cares about the end result.
An example of encapsulation could be registering a new student at a new school. One way of handling this through
code might be passing a new Student class and supplying values for the DOB, Age, and SSN properties of the Student
object. The detailed logic for registering the student is hidden, but the results returned show that the student has in
fact been registered. The Student object returned now has values associated with the StudentNumber, HomeRoom, and
CurrentGrade properties. Since encapsulation drives code reuse, this example of functionality could be built into a
custom activity, which can be used to author more than one workflow.
WF defines the base classes within the System.Activities namespace, which can be extended for building
custom workflow activities. System.Activities.Activity is used as the base class for defining all activities, including

 7-1 shows the different activity types that derive from System.Activities.Activity and the
column indicates whether the activity type can be extended for custom activities.

  Activity Types That Derive from System.Activities.Activity


Table Head Extended
Gets the value or type for an activity out True
argument.
Handles asynchronous code execution from True
beginning to end.
System.Activities.CodeActivity Executes code within an activity using the True
Execute method and gives access to arguments
and variables. Some activities that come with
WF inherit from CodeActivity.
System.Activities.DynamicActivity Allows activities to assemble dynamically that False
interface to the WF designer and WF runtime.
System.Activities.NativeActivity Similar to the CodeActivity except has full access True
to the WF runtime features. Some activities that
come with WF inherit from NativeActivity.
System.ServiceModel.Activities.Receive Used to receive messages within a workflow. False
System.ServiceModel.Activities. Used to receive messages and then send False
ReceiveReply a response message to the sender.
System.ServiceModel.Activities.Send Used to send a message out from a workflow. False
System.ServiceModel.Activities.SendReply Used to send a message out and wait for a False
response message.

The activity types listed in Table 7-1 that can be used for extending custom workflow activities are illustrated in a
class diagram in Figure 7-1. The class diagram shows the relationships for the classes that can be extended to create
custom workflow activities.

258

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-1.  Class diagram showing the relationships for the activity base classes

■■Note  Custom workflow activities do not have to be built using code. Composite activities can be built declaratively
using the WF designer. A composite activity is composed of more than one activity with the expectation of being reused
within multiple workflows. Chapter 5 covers how a custom composite activity is built and used within a workflow.

Getting Started
When building custom activities, it is important to make sure the activities being built can be executed and tested.
Adding custom activities to a workflow and executing the workflow can all be done through code; however, being
able to declaratively add your own custom activities to a workflow and seeing them execute is more exciting. Opening
VS2012 and adding a new Workflow Console Application project to a solution provides the WF plumbing needed to
test activities derived from CodeActivity, as illustrated in Figure 7-2.

259

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

  Boilerplate code for hosting a workflow within a console application

The project contains boilerplate code that uses WorkflowInvoker to host the default workflow that comes with
Workflow1.xaml. Workflow.xaml will be used to test new custom activities.
The next step is to add another project to the solution to contain the custom activities that will be built. Custom

 7-3 shows that a new Class Library project has been added to the

Class1.cs will be used to define a custom activity, and

CodeActivity has been added to hold just activities that inherit from CodeActivity.

Figure 7-3.  Custom activities are contained within their own project
260

www.it-ebooks.info
Chapter 7 ■ Building Custom WorkfloW aCtivities

Code Activity
Most of the examples used in this book derive from CodeActivity when a custom activity is used to demonstrate
functionality. Activities that derive from CodeActivity behave much like the behavior around a C# function. A C#
function might have parameters passed into it and return something back. Table 7-2 identifies some of the out-of-the-
box activities that derive from System.Activities.CodeActivity; however, in this section I will demonstrate how
additional custom code activities can be created to provide additional logic when using these out-of-the-box activities
does not provide the functionality needed.

Table 7-2. Out-of-the-box Activities That Derive from System.Activities.CodeActivity


Activity Name Table Head
System.Activities.Statements.AddToCollection<T> Adds an object to a collection.
System.Activities.Statements.Assign Assigns a value to another object.
System.Activities.Statements.Assign<T> Assigns a value to another object based on a
specified type.
System.Activities.Statements.ClearCollection<T> Clears out items within a collection.
System.Activities.Statement.Throw Throws an exception within a workflow.
System.Activities.Statements.WriteLine Writes string information from the workflow.

Figure 7-3 also shows that a folder called CodeActivity has been added to the Apress.Chapter7.Activities.Custom
project, which will contain workflow activities that derive from CodeActivity. The default class called Class1.cs
has been renamed to FirstCodeActivity and will be used to implement the first custom code activity. Therefore,
System.Activities needs to be referenced within project Apress.Chapter7.Activities.Custom, so the
FirstCodeActivity class can derive from CodeActivity (see Figure 7-4).

Figure 7-4. Referencing System.Activities


261

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

After the reference to System Activities is made, the code within FirstCodeActivity.cs can now inherit
from CodeActivity, as illustrated in Figure 7-5, and the using statement using System.Activities has been
added. Right-clicking on CodeActivity within the class shows an option called Go To Definition, which allows the
CodeActivity’s metadata to be explored within Visual Studio to learn more about the base class (see Figure 7-5).

Figure 7-5.  Implementing the base class CodeActivity

Listing 7-1 illustrates the metadata for CodeActivity that appears after selecting Go To Definition.

Listing 7-1.  CodeActivity Metadata Shown in Visual Studio


#region Assembly System.Activities.dll, v4.0.0.0
// C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Activities.dll
#endregion

using System;
using System.Runtime;

namespace System.Activities
{
// Summary:
// An abstract class for creating a custom activity with imperative behavior
// defined with
the System.Activities.CodeActivity.Execute(System.Activities.CodeActivityContext)

262

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

// method, which gives access to variable and argument resolution and extensions.
public abstract class CodeActivity : Activity
{
// Summary:
// When implemented in a derived class, creates an instance of the derived class.
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen
image boundaries")]
protected CodeActivity();
// Summary:
// Not supported.
//
// Returns:
// Always returns null.
protected override sealed Func<Activity> Implementation { get; set; }
// Summary:
// Not implemented. Use
System.Activities.CodeActivity.CacheMetadata(System.Activities.CodeActivityMetadata)
// instead.
//
// Parameters:
// metadata:
// Not implemented.
protected override sealed void CacheMetadata(ActivityMetadata metadata);
//
// Summary:
// Creates and validates a description of the activity's arguments, variables,
// child activities, and activity delegates.
//
// Parameters:
// metadata:
// The activity's metadata that encapsulates the activity's arguments, variables,
// child activities, and activity delegates.
protected virtual void CacheMetadata(CodeActivityMetadata metadata);
//
// Summary:
// When implemented in a derived class, performs the execution of the activity.
//
// Parameters:
// context:
// The execution context under which the activity executes.
protected abstract void Execute(CodeActivityContext context);
}
}

Figure 7-5 also shows an option called Implement Abstract Class, another feature that implements a base class
so specific parts of the class can be overridden. Listing 7-2 shows how the class FirstCodeActivity now overrides
the base method Execute from the CodeActivity class, after Implement Abstract Class is selected within the
FirstCodeActivity class.

263

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Listing 7-2.  Implementing the CodeActivity Class


using System;
using System.Activities;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.Chapter7.Activities.Custom
{
public class FirstCodeActivity:CodeActivity
{

protected override void Execute(CodeActivityContext context)
{
throw new NotImplementedException();
}
}

Now that the FirstCodeActivity’s Execute method is overridden, the new custom activity is ready to implement

Execute method will execute any code that is written inside of its method. The line of code

Console.WriteLine("FirstCodeActivity has executed!");

Right-clicking on activity project Apress.Chapter7.Activities.Custom and selecting Build will compile


the activity so it can be added within the Apress.Chapter7.WF project’s workflow. After the Apress.Chapter7.
Activities.Custom compiles successfully, a reference to it needs to be added to the Apress.Chapter7.WF project. The
other project, Apress.Chapter7.WF, now needs to be compiled after making the reference. The FirstCodeActivity
activity now shows up within its own section within the activity WF toolbox called Apress.Chapter7.Activities.
Custom and is ready to be used within Workflow1.xaml (see Figure 7-6).

264

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-6.  Adding the FirstCodeActivity activity to a workflow

In order to see what happens as the workflow is hosted, Console.Read() needs to be added to the Program.cs file
just after the workflow is invoked through WorkflowInvoker.

WorkflowInvoker.Invoke(workflow1);
Console.WriteLine("Press any key to end...");
Console.Read();

Now that the workflow is ready to run, what happens as the console application hosts the workflow is that the
message “FirstCodeActivity has executed!” will be written to the console from the workflow. Although the workflow
has finished executing, the syntax Console.Read() will allow the console window to stay open so the message can be
observed. Pressing any key within the console window will end the execution of the console application (see Figure 7-7).

Figure 7-7.  FirstCodeActivity activity has been executed


265

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Activity Arguments
Workflows can accept and return data to a workflow host through the WF runtime. Predefined arguments provide the
vehicle for getting data to and from a workflow. Since a workflow is defined from one or more activities, data passed
into the workflow also needs to be passed to its activities because an activity may need to execute code based on
the data that was passed into the workflow. So far, a custom workflow activity that derives from CodeActivity has
been built. The activity has been added to a workflow declaratively and the Execute method executed custom code,
showing the results to the console. Now the custom activity needs to use arguments so data can be passed in and out
of the activity.
The next custom activity that will be demonstrated takes two arguments that are passed into the activity to
calculate sales commission based on net sales. Arguments can be passed into an activity through two different
types of arguments. One type of WF argument to use is an InArgument, which specifies that data will be passed into
an activity. An InOutArgument can also be used, which would specify that an object can be passed in first and then
passed out. This is similar to passing objects ByRef through C#.
The new activity will be a SalesCommission activity. Listing 7-3 shows that a new class called SalesCommission
CodeActivity. Two variables of type InArgument have been created so
SalesCommission activity. Code has been added within the Execute method so the two

NetSales and Percentage, these values are retrieved from the CodeActivityContext parameter
, which is passed into the Execute method during execution. The code context.GetValue(NetSales) and
retrieves the WF argument values passed into the SalesCommission activity. The

  SalesCommission Activity Calculates Commission Based on Sales

using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.Chapter7.Activities.Custom
{
public class SalesCommission:CodeActivity
{
public InArgument<decimal> NetSales { get; set; }
public InArgument<decimal> Percentage { get; set; }

protected override void Execute(CodeActivityContext context)
{
var sales = context.GetValue(NetSales);
var salesPercentage = context.GetValue(Percentage);
var commission = sales*(salesPercentage/100);
Console.WriteLine("For sales of {0:C} and sales percentage at {1}%, the sales commission
is {2:C}", sales, salesPercentage, commission);
}
}
}

After successfully compiling the Apress.Chapter7.Activities.Custom project first and then the
Apress.Chapter7.WF project, the SalesCommission activity will show up in the WF toolbox under the
FirstCodeActivity activity (see Figure 7-8).

266

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-8.  An addition activity shows up within the toolbox

The SalesCommission activity can now be added to the workflow directly under the FirstCodeActivity activity.
After adding the Addition activity, the Properties window will show the two arguments, NetSales and Percentage.
Let’s say that net sales are $20,000 and the salesperson is at a sales percentage of 15%. Figure 7-9 shows the results
written to the console window as the workflow is executed.

Figure 7-9.  Executing the FirstCodeActivity and SalesCommission activities within the workflow

267

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Writing information out to the console is a great way to test activities within a workflow; however, it might
not be practical when data needs to be returned from the workflow to the host. In this case, an OutParameter can
be used and set within the Execute method. Listing 7-4 shows the code used to pass an OutParameter from the
SalesCommission activity. A new OutArgument named CalculatedCommission has been added to the activity and is
set within the Execute method.

Listing 7-4.  Using an OutParameter to Pass Data from the CalculatedCommission Activity
using System;
using System.Activities;
using System.Collections.Generic;
using System.Linq;
using System.Text;

{
public class SalesCommission:CodeActivity
{
public InArgument<decimal> NetSales { get; set; }
public InArgument<decimal> Percentage { get; set; }

public OutArgument<decimal> CalculatedCommission { get; set; }
protected override void Execute(CodeActivityContext context)
{
var sales = context.GetValue(NetSales);
var salesPercentage = context.GetValue(Percentage);
var commission = sales*(salesPercentage/100);
context.SetValue(CalculatedCommission, commission);
}
}
}

This time, after rebuilding the activity and viewing the workflow, the Properties window now shows that
CalculatedCommission can be set for the activity within the workflow. Figure 7-10 shows that the WF variable
varCommission has been defined for the workflow and set to the CalculatedCommission OutArgument for the
SalesCommission activity. A WriteLine activity has been added and its Text property has been set to String.
Format("Calculated commission is {0:C}",varCommission) to demonstrate that the SalesCommission activity has
passed sales commission to the workflow.

268

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-10.  Setting the CalculatedValue so it can be passed to the workflow

Running the workflow shows that the CalculatedCommission WF argument has been passed to the WF variable
varCommission and used within the WriteLine activity by writing the calculated value to the console window
(see Figure 7-11).

Figure 7-11.  The calculated commission has been written to the console

269

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

CodeActivity<TResult>
An easier way of returning the value for the SalesCommission activity is to inherit from CodeActivity<TResult>
instead of CodeActivity. CodeActivity<TResult> has the additional properties of Result for returning an argument
and ResultType for defining the type of OutArgument returned. Listing 7-5 shows the code that has been changed
so that the SalesCommission activity returns a decimal, which will be the commission after it is calculated. The first
noticeable change to the SalesCommission activity is that it now inherits from CodeActivity<decimal>. This means
that the activity is expecting to return a decimal. The second change is the new Execute method, protected override
decimal Execute(CodeActivityContext context), which by default now returns a decimal represented as the
commission value returned from the activity.

Listing 7-5.  Changing the SalesCommission Activity to Inherit from CodeActivity<decimal>


using System;

{
public class SalesCommission:CodeActivity<decimal>
{
public InArgument<decimal> NetSales { get; set; }
public InArgument<decimal> Percentage { get; set; }

protected override decimal Execute(CodeActivityContext context)
{
var sales = context.GetValue(NetSales);
var salesPercentage = context.GetValue(Percentage);
var commission = sales*(salesPercentage/100);
return commission;
} 
}
} 
Recompiling the project Apress.Chapter7.Activities.Custom and then viewing the workflow in
Apress.Chapter7.WF now shows a Result property within the Properties window (see Figure 7-12).

Figure 7-12.  Result property for the Addition activity

270

www.it-ebooks.info
Chapter 7 ■ Building Custom WorkfloW aCtivities

Design Time Validation


So what happens if either the Percentage or NetSales arguments for the SalesCommission activity are not wired
within the workflow? The workflow will still compile, but when it tries to calculate the commission, it can’t; you don’t
get an error, except the calculated commission is $0.00. Validation could be coded within the activity to check that
data has been entered; however, a better approach is to make sure that as the activity is added to the workflow, it is
wired up correctly. After adding a SalesCommission activity in Figure 7-13, the WF designer displays a little blue stop
sign with an exclamation mark indicating that something is wrong. Visual Studio’s Error List also explains that there
are issues with the workflow. By allowing the workflow to validate how activities are configured during design time,
there is less to triage and overlook when running into errors during runtime.

Figure 7-13. Design-time validation for activity arguments

The only change that is required to make sure activity arguments are set during design time is to add the
[RequiredArgument] attribute to the argument(s) expected to be passed into the activity. Once values to the
arguments are added so that sales and percentage information is passed into the activity, the WF designer no longer
throws the errors.

[RequiredArgument]
public InArgument<decimal> NetSales { get; set; }
[RequiredArgument]
public InArgument<decimal> Percentage { get; set; }

271

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Let’s say the company’s model for paying commission changes, so now the company will not pay commissions
over 20%. Again, there are a couple of ways to handle this business process change using WF. The workflow can be
modified declaratively to validate that the sales percentage is less than 20%, but what really needs to happen is the
sales percentage should be checked before the workflow starts executing.
There is a method called CacheMetadata, and it is available via the CodeActivity base class. It can be used for
handling custom design-time validation for data input for an activity. But there is a catch, and actually it makes logical
sense! Data passed into an activity of type InArgument cannot be validated at design time. Arguments are intended to
be set through resources within the workflow, although their values can be set to values, as illustrated in Figure 7-12.
Logically speaking, there is no need to check data passed into an activity during design time, except when data is set
to a value directly within activity. Figure 7-12 shows that data is being set to the arguments for the SalesCommission
activity. This data should be validated if there are certain values that should not be allowed. To validate the data in
Figure 7-12, the SalesCommission activity will use standard properties instead of workflow arguments. Listing 7-6
shows that NetSales and Percentage have been traded from using arguments to using standard properties. The
CacheMetadata method can now be used to validate the properties as their values are set during design time. The
NetSales and Percentage are not less than 0 and also that Percentage is not

  Validating Property Data During Design Time

{
public class SalesCommission : CodeActivity<decimal>
{
public decimal NetSales { get; set; }
public decimal Percentage { get; set; }

protected override decimal Execute(CodeActivityContext context)
{
var commission = NetSales*(Percentage/100);
return commission;
}

protected override void CacheMetadata(CodeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
if (NetSales <= 0)
metadata.AddValidationError("Sales cannot be less than 0!");
else if (Percentage <= 0)
metadata.AddValidationError("Sales percentgage cannot be less than 0!");
else
{

272

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

if (Percentage > 20)


metadata.AddValidationError(string.Format("Sales percentgage {0} cannot be
greater than 20%", Percentage));
}
}
}
}

Now after the SalesCommission activity is compiled, it is immediately validated if it is still part of the workflow. If
not, reading the activity will also immediately show that it is being validated, as illustrated in Figure 7-14. Hovering the
mouse over the exception indicator on the activity will also pop up the exception messages.

Figure 7-14.  Validating SalesCommission activity properties during design time

After entering a value of 1000 for the NetSales property and a value of 21 for the Percentage property, Figure 7-15
shows the exception that the sales percentage must be less than 20%.

273

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

  Validates that sales percentage cannot be greater than 21

Passing Data In and Out of a Workflow


In Figure 7-15, the net sales and a sales person’s percentage for earning commission on sales are not defined during
runtime. Net sales should really be an argument into the workflow because sales will surely change over time.
The percentage used for sales could change, too. Earlier I demonstrated that limits on the max allowable
percentage that can be paid based on sales had changed, so there could be a case for making sales percentage
dynamic, too. Figure 7-16 shows that three workflow arguments have been created. The arguments EnterNetSales
and EnterSalesPercentage are InArgument types, while returnSalesCommission is of type OutArgument. The
SalesCommission activity can now use the arguments for having net sales and sales percentage passed into the
SalesCommission activity and returning the commission to the workflow so the data can be used by the workflow
host. The SalesCommission activity needs to be reverted back to the code in Listing 7-5 so it can use arguments
instead of the properties.

274

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-16.  Adding workflow arguments for passing data in and out of the workflow

The code within the workflow host needs to be added so it can handle getting user input from the console
application, validating the data, and then passing it to the workflow. After the workflow completes, the sales
commission will be written to the console window from the workflow host. Listing 7-7 shows the updated Program.cs
file that hosts the workflow.

Listing 7-7.  Updated Program.cs File for Passing User Input to the Workflow
using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;

namespace Apress.Chapter7.WF
{

class Program
{
static void Main(string[] args)
{
try
{
Activity workflow1 = new Workflow1();
var netSales = new Decimal();
var salesPercentage = new Decimal();

Console.WriteLine("Enter net sales: ");
try

275

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

{
netSales = Convert.ToDecimal(Console.ReadLine());
}
catch (Exception)
{
throw new ApplicationException("Net sales was not entered as a number!");
}

Console.WriteLine("Enter sales percentage: ");
try
{
salesPercentage = Convert.ToDecimal(Console.ReadLine());
}
catch (Exception)
{
throw new ApplicationException("Sales percentage was not entered as a number!");
}

var inArgs = new Dictionary<string, object>();
inArgs.Add("EnterNetSales", netSales);
inArgs.Add("EnterSalesPercentage", salesPercentage);
var arg = WorkflowInvoker.Invoke(workflow1, inArgs);
Console.WriteLine("Calculated commission is {0:C}", arg["returnSalesCommission"]);
Console.WriteLine("Press any key to end...");
Console.Read();
}
catch(ApplicationException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Press any key to end...");
Console.Read();30
}
catch (Exception ex)
{
Console.WriteLine("An error has occured!");
Console.WriteLine("Press any key to end...");
Console.Read();
}
}
}
}

Figure 7-17 shows how sales and percentage data can be entered through the console application and sent to
the workflow. The workflow then returns the calculated sales commission, and the console application displays how
much to pay the salesperson based on the data that was entered.

276

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-17.  Entering sales data and percentage outside of the workflow

The ActivityDesigner Experience


Designing activities has become much easier than it was in WF3.x. Earlier I mentioned that WF4 harnesses the power
of Windows Presentation Foundation (WPF) for not only rehosting the WF Designer but for building custom activity
designers as well. The base class ActivityDesigner is part of the System.Activities.Presentation namespace
and provides the components necessary for building custom UIs for workflow activities; however, there are some
characteristics to consider when constructing a custom activity. The first characteristic to think about is how the
activity will be configured so data can be passed in and out of the activity.
The custom activities built so far in this chapter have relied on the standard user interface (UI) WF provides for
activities. Depending on the business requirements for building custom activities, the default UI experience may be
all that is needed, especially when the primary goal for building an activity is based on the custom functionality it
provides. There are times, though, when a custom activity’s UI needs to be customized, sometimes to provide custom
interaction that compliments the activity or circumstances where the properties window within the WF designer is
not available for configuring the activity. To get started in defining an activity designer, take a look at Figure 7-18.

277

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-18.  Adding the Activity Designer component in Visual Studio

ActivityDesigner.Icon
The Activity Designer template provides the XAML file required to customize how a custom activity will look. After
adding the file, a default activity designer provides the basic layout for the activity. The default icon that comes with
the activity designer is the first thing to change so the workflow can be branded as an activity authored by you. The
ActivityDesigner class contains the property icon that controls an activity’s current icon, so since the activity that
was built on earlier calculated commissions, the activity icon needs to be represented by a money icon. After finding
a good image file and adding it to the project, its Build Action property needs to be changed to Resource so Visual
Studio will embed the file. This allows all files within an assembly to reference it. The XAML between the start and
closing tags for <sap:ActivityDesigner.Icon> in Listing 7-8 changes the icon (see Figure 7-19).

278

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-19.  Branding the activity designer with a default icon

Listing 7-8.  Changing the Default Icon for the Activity Designer
<sap:ActivityDesigner x:Class="Apress.Chapter7.Activities.Custom.ActivityDesigner1"
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">
<sap:ActivityDesigner.Icon>
<DrawingBrush>
<DrawingBrush.Drawing>
<ImageDrawing>
<ImageDrawing.Rect>
<Rect Location="0,0" Size="16,16"></Rect>
</ImageDrawing.Rect>
<ImageDrawing.ImageSource>
<BitmapImage UriSource="Images\money.png"></BitmapImage>
</ImageDrawing.ImageSource>
</ImageDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</sap:ActivityDesigner.Icon>
<Grid>

</Grid>
</sap:ActivityDesigner>

279

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Now that the icon has been changed, the SalesCommission activity can be associated with the new activity
designer. The only step that needs to happen in order to associate the designer is to add the type of designer as an
attribute to the custom activity. The designer has been renamed to SalesCommissionDesigner, so the attribute for the
class SalesCommission looks like this:

[Designer(typeof(SalesCommissionDesigner))]
public class SalesCommission : CodeActivity<decimal>

After rebuilding the solution and adding a SalesCommission activity to the workflow, the activity will now be
represented with a new icon.

ExpressionTextBox UserControl
System.Activities.Presentation.View contains a class called ExpressionTextBox that inherits
UserControl. The class ExpressionTextBox provides the implementation for customized editing of expressions
SalesCommission activity contains two in arguments and returns an argument of type
since it inherits from CodeActivity<decimal>. Figure 7-16 shows how the default activity is rendered within

 7-20).

Figure 7-20.  Using an activity designer for configuring arguments

280

www.it-ebooks.info
Chapter 7 ■ Building Custom WorkfloW aCtivities

Listing 7-9 illustrates how to add three textboxes to the activity designer’s grid. An ExpressionTextBox has been
added for each of the workflow arguments and bound to each of the arguments within the SalesCommission activity.
An Expression is required when using an ExpressionTextBox to declaratively bind arguments with an activity. The
ConvertParameter is set to In for in arguments and Out for out arguments. Also, for the ExpressionTextBox used for
the out argument, the UseLocationExpression is set to True and the Binding Path is set to ModelItem.Result to
indicate the data returned from the activity.

Listing 7-9. Using ExpressionTextBox to Edit Expressions


<sap:ActivityDesigner x:Class="Apress.Chapter7.Activities.Custom.SalesCommissionDesigner"
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>
<ResourceDictionary>
<sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<sap:ActivityDesigner.Icon>
<DrawingBrush>
<DrawingBrush.Drawing>
<ImageDrawing>
<ImageDrawing.Rect>
<Rect Location="0,0" Size="16,16"></Rect>
</ImageDrawing.Rect>
<ImageDrawing.ImageSource>
<BitmapImage UriSource="Images\money.png"></BitmapImage>
</ImageDrawing.ImageSource>
</ImageDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</sap:ActivityDesigner.Icon>
<Grid>
<StackPanel>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Net Sales:</TextBlock>
<sapv:ExpressionTextBox Expression="{Binding Path=ModelItem.NetSales,Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}"
OwnerActivity="{Binding Path=ModelItem}" MinLines="1" MaxLines="1" MinWidth="50" HintText="Enter the
total net sales"/>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Percentage:</TextBlock>
<sapv:ExpressionTextBox Expression="{Binding Path=ModelItem.Percentage,Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}"
OwnerActivity="{Binding Path=ModelItem}" MinLines="1" MaxLines="1" MinWidth="50" HintText="Enter the
commission percentage"/>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Commission:</TextBlock>

281

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

<sapv:ExpressionTextBox UseLocationExpression="True" Expression="{Binding


Path=ModelItem.Result,Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}" MinLines="1" MaxLines="1"
MinWidth="50" HintText="Commission returned to"/>
</StackPanel>
</Grid>
</sap:ActivityDesigner>

After applying the XAML in Listing 7-9, editing the textboxes causes a drop-down to appear so the appropriate
value can be selected, as you can see in Figure 7-21. After setting the value, the bound argument value for the activity
is set as well.

Figure 7-21.  Selecting an existing argument from the drop-down menu

WorkflowItemPresenter ContentControl
Another characteristic to consider is if an activity will be standalone building block within a workflow, or if a custom
activity will act as a parent that will contain child activities. The namespace System.Activities.Presentation
has two classes for allowing one or more activities to be placed within a custom activity. In order to drag and
drop one activity, the WorkflowItemPresenter class is used as a predefined control that WF provides. Since
WorkflowItemPresenter inherits from ContentControl, it can serve as a parent control so a child activity can be
added (see Figure 7-22).

282

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-22.  Adding a child activity through the designer to a custom activity

Listing 7-10 shows how easy it is to add a WorkflowItemPresenter to an activity designer but first another
assembly should be added that references System.Activities.Statements:

xmlns:sa="clr-namespace:System.Activities.Statements;assembly=System.Activities">

This will allow the WorkflowItemPresenter to limit what activities get added, as illustrated in Listing 7-10, which
limits the activities added to only WriteLine activities. The Binding path is set to ModelItem.ChildWriteLine which
refers to an activity property, public Activity ChildWriteLine { get; set; }, which is required to be added
within a custom activity. After adding the XAML in Listing 7-10, a WriteLine activity will be the only activity that can
be added to the custom activity. If you attempt to add another activity, the WF designer will visually indicate that
another activity cannot be added.

■■Caution Although I am demonstrating how to use an activity designer to add child activities and in this case limit
the child activity to a WriteLine activity, once the WriteLine activity is added, it will never execute. This is because the
custom activity SalesCommission inherits from CodeActivity. Child activities that are added to custom activities must
be scheduled using the WF runtime. Later in the chapter, I will show how this is done when I cover custom activities that
inherit from NativeActivity.

283

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Listing 7-10.  Adding a WorkflowItemPresenter to Limit the Kinds of Activities that can be Added
<Grid>
<StackPanel>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Net Sales:</TextBlock>
<sapv:ExpressionTextBox Expression="{Binding Path=ModelItem.NetSales,Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}"
OwnerActivity="{Binding Path=ModelItem}" MinLines="1" MaxLines="1" MinWidth="50" HintText="Enter the
total net sales"/>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Percentage:</TextBlock>
<sapv:ExpressionTextBox Expression="{Binding Path=ModelItem.Percentage,Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}"
OwnerActivity="{Binding Path=ModelItem}" MinLines="1" MaxLines="1" MinWidth="50" HintText="Enter the
commission percentage"/>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Commission:</TextBlock>
<sapv:ExpressionTextBox UseLocationExpression="True" Expression="{Binding

<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" Width="200" BorderThickness="2" CornerRadius="5">
<sap:WorkflowItemPresenter HintText="Drop an activity" Item="{Binding

</Border>
</StackPanel>
</StackPanel>
</Grid>

■■Note There is also another class, WorkflowItemsPresenter, that allows more than one child activity to be added to
a custom activity. This is beneficial for adding activities like a Parallel activity, which can contain other activities.

Asynchronous Activities
A major feature released with .NET Framework 4.0 was a simplified level of abstraction for applying asynchronous
patterns through code. Although modeling asynchronous processes was possible before .NET 4.0, it was not as
straightforward to implement.
What does asynchronous really mean and how is it different than multi-threaded applications? Many IT
professionals think of asynchronous as just another multi-threaded pattern. Although this is not entirely wrong, it is
not entirely correct either. Technically, more than one thread can execute, but not necessarily within the workflow.
When I think of building a multi-threaded application, I think of all of the complexities, from memory overhead for
starting up new threads and managing each additional thread’s execution. This is not the case when implementing
asynchronous execution. An asynchronous execution simply allows a thread to continue to execute, even after making
I/O calls like reading a file or making web service calls. Instead of blocking additional execution as a synchronous
invocation does, an asynchronous invocation allows the thread to continue execution. Once the I/O process finishes,
the initiating thread is notified so it can process the results from the I/O call. Although both multi-threading and
asynchronous executions can lead to better performing code, an asynchronous execution pattern is much easier to
implement and should be used instead.

284

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

WF supports asynchronous work to be performed, which does not block the workflow scheduler thread so
scheduled activities can still be executed at the same time. The base class System.Activities.AsyncCodeActivity
allows asynchronous work to be processed within an activity while other activities are executing in parallel; however,
the workflow cannot be persisted during execution of asynchronous work because AsyncCodeActivity creates a
no-persist block. The WF runtime also prevents the workflow from unloading while the workflow is technically still
executing.
Execution logic is implemented through code for an activity that derives from AsyncCodeActivity by overriding
the BeginExecute and EndExecute methods. These methods share state by passing an AsyncCodeActivityContext
object using the UserState property. There is also a Cancel override that is optional when the activity is
cancelled. When an AsyncCodeActivity activity is cancelled, the MarkCanceled method should be called from the
AsyncCodeActivityContext object after its property IsCancellationRequested is checked.
Asynchronous activities should be used for handling long running processes or I/O operations, as mentioned
earlier. Placing asynchronous activities within a parallel activity allows multiple activities to execute concurrently,
which provides task parallelism.
Consider a scenario where two files need to be read so the contents of each can be read to the console. An
advantage of using WF to do this type of operation instead of using straight C# code is the simplicity WF provides for
implementing asynchronous patterns. The code in Listing 7-11 demonstrates how a file is opened within the Execute
method based on the path and file name provided by the WF argument PathAndFile. After the file’s content is read,
the thread sleeps for two seconds to simulate that the activity is busy working to read the content of the file. As the
thread wakes up, the content of the file is returned from the activity.

Listing 7-11.  Code Activity That Reads From a File and Returns its Content as a String
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.IO;
using System.Threading;

namespace Apress.Chapter7.Activities.Custom
{
public sealed class ReadFile : CodeActivity<string>
{
// Define an activity input argument of type string
public InArgument<string> PathAndFile { get; set; }

// If your activity returns a value, derive from CodeActivity<TResult>
// and return the value from the Execute method.
protected override string Execute(CodeActivityContext context)
{
// Obtain the runtime value of the Text input argument
string pathFile = context.GetValue(this.PathAndFile);
var fileText = File.ReadAllText(pathFile);
Thread.Sleep(2000);

return fileText;
}
}
}

285

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

The ReadFile activity can now be used to read the content of other files from a workflow. Based on the scenario,
the workflow will need to use two ReadFile activities so the content of both files can be read. The two files that are
read are other workflows represented as XAML. Figure 7-23 shows that two files are being read and the content of each
of the files is being stored within a WF variable. The path and file name are passed in through the ReadFile activity’s
PathAndFile InArgument<string>. The workflow also has an argument called ReturnFileContent. The Assign
activity in Figure 7-23 is used to set its value to the files’ combined content so it can be returned from the workflow
and displayed within the console window. Listing 7-12 shows the code that will be used to host the workflow that uses
the code activities to read and combine the file.

Figure 7-23.  Reading content from two files

Listing 7-12.  Code Activity That Reads from a File and Returns its Content as a String
var CodeTest = new TestCode();
var arg = WorkflowInvoker.Invoke(CodeTest);

Console.WriteLine("File content : {0}", arg["ReturnFileContent"]);
Console.Read();

After running the code in Listing 7-12, for a brief two seconds nothing is written to the console window. Then
the console window displays “Code activity read Workflow1”. This is because the thread sleeps for two seconds within
the ReadFile activities. Finally, after the thread wakes up, the other file is read and both files’ contents are read to the
console window (see Figure 7-24).

286

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

Figure 7-24.  Reading the contents from two files to the console window

This is where the AsyncCodeActivity comes into play and allows the Parallel activity to read each of the files
asynchronously instead of waiting for each file to finish, as simulated by forcing the thread to sleep for two seconds.
Listing 7-13 show the code for building a custom activity that can read a file asynchronously.

Listing 7-13.  Reading a File Asynchronously


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.IO;
using System.Threading;

namespace Apress.Chapter7.WF
{

public sealed class ReadFileAsync : AsyncCodeActivity<string>
{
// Define an activity input argument of type string
public InArgument<string> PathAndFile { get; set; }

protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback
callback, object state)
{
var filePath = context.GetValue(PathAndFile);
Func<string,string> ReadFileDelegate = new Func<string,string>(ReadFile);
context.UserState = ReadFileDelegate;
Thread.Sleep(2000);
return ReadFileDelegate.BeginInvoke(filePath,callback,state);
}

287

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

protected override string EndExecute(AsyncCodeActivityContext context, IAsyncResult result)


{
Func<string, string> ReadFileDelegate = (Func<string, string>)context.UserState;
return Convert.ToString(ReadFileDelegate.EndInvoke(result));
}

string ReadFile(string FilePath)
{
var fileText = File.ReadAllText(FilePath);

return fileText;
}

}


In Listing 7-13, a new custom activity is created that inherits from AsyncCodeActivity<string>. It has the same
, PathToFile, defined and overrides both the BeginExecute and EndExecute method and function. The
code is similar to the code in Figure 7-11, except that a delegate initialized as Func<string,string>
ReadFile can be delegated and called. ReadFile simply reads a file based on the file and
PathAndFile. The results are then stored within context.UserState and the
is invoked. After the file’s contents are gathered, the callback function EndExecute then executes the file’s

delegate is invoked. Changing the code in Listing 7-12 from

to

var asyncTest = new TestAsync();


var arg = WorkflowInvoker.Invoke(asyncTest);

allows the new TestAsync workflow that uses the ReadFileAsync activities to execute. The TestAsync workflow is
exactly the same as the workflow in Figure 7-23, except the TestAsync workflow uses the ReadFileAsync activities
defined in Listing 7-13 instead of using the ReadFile code activities in Listing 7-11.
Running the new workflow immediately demonstrates how the ReadFileAsync activities are being fired
asynchronously, instead of waiting for the first scheduled ReadFileAsync activity to finish executing. Take a closer
look at how the workflows run by modifying the code in the Program.cs. Comment out

Console.WriteLine("File content : {0}", arg["ReturnFileContent"]);

so the combined file contents are not read to the console. Then let both workflows execute and use

var CodeTest = new TestCode();


var arg = WorkflowInvoker.Invoke(CodeTest);

var asyncTest = new TestAsync();


var arg = WorkflowInvoker.Invoke(asyncTest);

288

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

so each workflow is fired at the same time. What you will notice is that the first message, “Code activity read FirstFile”,
appears after two seconds. Then “Code activity read SecondFile” appears after two more seconds. Then, as the
asyncTest workflow is then invoked and both of the threads wake up, messages about both files being read appear
at the same time. Therefore, there is no delay in reading the two files. This proves that both files are being read
asynchronous from each other instead of having to wait for each activity to read the file before executing the next
file read.
Although this example simply combines two files, any number of files could be used in the workflow and read
using the custom ReadFileAsync activity asynchronously.

■■Note  While watching Microsoft’s launch for VS2012 and .NET 4.5, one of the takeaways I remember was Microsoft’s
goal of making it easier for implementing asynchronous patterns through code syntax, and how it should be just as
simple as writing regular synchronous applications. There are more improvements in .NET 4.5 for asynchronous code;
however, it does not change asynchronous activities in WF4.5.

Native Activities
Sometimes activities need WF runtime support to
• Communicate from outside of a workflow.
• Schedule and cancel child activity execution.
• Abort activities.
These are examples of functionality that the other custom activities, CodeActivity and AsyncCodeActivity (which were
introduced earlier), cannot provide. This is because these activities are not considered native activities. Even though
they are citizens in WF and can run within a workflow, non-native activities simply run within workflows without any
real interaction to the WF runtime. This is where native activities are different because they have direct interaction
to the WF runtime as they are executed within a workflow. They have full access to features that the WF runtime
provides, such as communication through bookmarks, scheduling of child activities, and persistence. As a workflow is
being executed through the WF runtime, there are times when an activity needs this interaction.
There are quite a few out-of-the-box activities that inherit from System.Activities.NativeActivity. This is
not hard to believe since the WF runtime provides a majority of the functionality that the workflows provide. For
instance, the code in Listing 7-14 shows that the custom WaitForResponse activity inherits from the abstract class
NativeActivity. Bookmarks, which were first introduced in Chapter 3, establish communication through the WF
runtime and can provide external interaction with a workflow. An approval process is a good example because a
Bookmark can be created from an activity that inherits from NativeActivity so a workflow can wait for an approval
response. Once a response is received by the WF runtime from outside of the workflow, the bookmark is used to
resume the workflows execution. Listing 7-14 illustrates the code that derives from NativeActivity and can be used
within a workflow to receive events from outside the workflow. Chapters 4 and 5 demonstrate how bookmarks can be
used in both state machine and flowchart workflows.

Listing 7-14.  WaitForResponse Activity That Allows Communication to a Workflow Through Bookmarks
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;

289

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

namespace FlowFocus.WF.Activities
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{

}

public string ResponseName { get; set; }
protected override bool CanInduceIdle
{ //override when the custom activity is allowed to make he workflow go idle
get
{
return true;
}
}

protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(this.ResponseName, new BookmarkCallback(this.ReceivedResponse));
}

void ReceivedResponse(NativeActivityContext context, Bookmark bookmark, object obj)
{
this.Result.Set(context, (TResult)obj);
}
}
}

The WaitForResponse activity overrides the CanInduceIdle property, so the workflow knows that the workflow
needs to go idle. Then the WF runtime knows that a Bookmark has been created and that it needs to wait for the
Bookmark or some other condition before continuing the execution of the workflow. The overridden Execute method
uses NativeActivityContext and its CreateBookmark method for creating a Bookmark.

Scheduling Activities
In addition to creating bookmarks, activities that derive from NativeActivity also allow custom control flow to be
implemented within an activity. This requires that the parent activity has control over scheduling of its own child
activities. Once an activity schedules a child activity, it can execute.
Once a class inherits from NativeActivity, a new property can be added to the class that represents a child
activity. As the custom activity executes, the Activity type property can then be scheduled. The Execute method for
a custom activity that inherits from NativeActivity uses a parameter of type NativeActivityContext, which has a
ScheduleActivity method that is used to schedule child activities. After the child activity executes, a callback method
can be used to notify once the child activity has completed it execution (see Listing 7-15).

Listing 7-15.  Scheduling Child Activities


using System;
using System.Activities;
using System.Collections.Generic;
290

www.it-ebooks.info
Chapter 7 ■ Building Custom WorkfloW aCtivities

using System.ComponentModel;
using System.Linq;
using System.Text;
using Apress.Chapter7.Activities.Custom;

namespace Apress.Chapter7.WF
{
[Designer(typeof(IamNativeDesigner))]
public sealed class IamNative:NativeActivity
{
public Activity childActivity { get; set; }
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(childActivity, ChildActivityCompleted);
}

void ChildActivityCompleted(NativeActivityContext context, ActivityInstance instance)


{
Console.WriteLine("WriteLine activity completed its execution!");
}
}

To declaratively add a child activity to the custom native activity, XAML similar to Listing 7-10 can be used for
using the WorkflowItemPresenter control within an ActivityDesigner. The only change is setting the Binding Path
property to Model.childActivity, which is the name used for the Activity type property. By keeping everything
else the same, the only allowed type of child activity that can be added is the WriteLine activity. This will allow child
activities to be added to the custom activity (see Figure 7-25).

Figure 7-25. Adding an activity designer so child activities can be declaratively added

291

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

After compiling and adding a new IamNative activity to a workflow, the only child activity that can be added is a
WriteLine activity (see Figure 7-26).

  Executing a WriteLine child activity

After running the workflow, the console window will demonstrate that the WriteLine activity is executing and the
 7-27).

Figure 7-27.  Executing a child WriteLine activity

Multiple child activities can also be added instead of adding only one child activity as in the previous example.
A collection of type activities can be added as a property by specifying Collection<Activity>. Each member of the
activity collection can then be scheduled using an index for the collection. The WorkflowItemsPresenter control is
used to declaratively add multiple controls to the activity.

■■Tip There is no guarantee of the execution order of scheduled activities, so if you want to execute an activity only
after another one finishes, the activity should be scheduled within the first activity’s completed execution callback.

292

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

CacheMetadata
In Listing 7-6, I showed how validation can be added to an overridden CacheMetadata method so design-time
validation could be implemented for a custom activity. The CacheMetadata method is also used to let the WF runtime
know about specific parts of a custom activity. For instance, when public properties are used for declaring activities,
variables, or arguments, the default implementation of CacheMetadata reaches out to these properties through
reflection and declares them. Since reflection is not the best for performance, it can be avoided by commenting out
the standard implementation and implementing custom code for adding these properties as metadata. Instead,
activities, variables, and arguments can be added through custom code, as illustrated in Listing 7-16.

Listing 7-16.  Using Custom Code to Add Non-public Properties as Metadata


protected override void CacheMetadata(NativeActivityMetadata metadata)
{
//base.CacheMetadata(metadata);
// activities
foreach (Activity activity in this.Activities)
{
metadata.AddChild(activity);
}

// variables
foreach (Variable variable in this.Variables)
{
metadata.AddVariable(variable);
}
metadata.AddImplementationVariable(this.lastIndexHint);
// add arguments
metadata.AddArgument(new RuntimeArgument(<ArgName>, typeof(<datatype>),
<ArgumentDirection>, <Required>));
}

Distributing Custom Activities


Distributing custom activities so they can be hosted within activities is as simple as referencing the assembly that is
compiled. Chapters 10 and 12 will talk more about using custom activities and even using them within workflows
where the WF designer is rehosted outside of Visual Studio. Figure 7-28 illustrates that the SalesCommission activity
that was created earlier has been referenced within a rehosted WF designer.

293

www.it-ebooks.info
Chapter 7 ■ Building Custom Workflow Activities

  Rehosting a custom activity within a rehosted WF designer

Summary
This chapter built on Chapter 3 by showing how custom workflow activities can be built and used to author
workflows. Different types of activities can be built based on the requirement for the workflow, and there are different
abstract classes that WF provides for building activities. The chapter went through these base Activity classes and
demonstrated how each of the base classes were used to benefit different functionality within workflows and applied
patterns, and showed practices for when the different types of activities should be built and used for authoring a
workflow. As a reminder, if a custom activity is required for performing a short burst of code, then the activity should
implement from CodeActivity. If functionality is needed for standard I/O, like reading and writing a file or calling
web services, then AsyncCodeActivity should be inherited. Finally, if an activity needs to enlist scheduling of
child activities or if functionality is required of the WF runtime, like scheduling activities and using bookmarks for
long-running workflows, then NativeActivity should be inherited.

294

www.it-ebooks.info
Chapter 8

Persisting Workflows

What would happen if a server hosting one or more applications was accidentally unplugged or shut down? Maybe
there is a hardware failure or a server is hacked because it was not protected appropriately. These are just a few of the
many scenarios that can bring a server down. Depending on what applications are hosted on a given server, situations
like this can be devastating. So developers should always plan for the worst case, ideally going for a high availability
(HA) infrastructure.
How bad is it really when a server goes down? If it is hosting an e-commerce web solution, there might be a few
sales lost, but once the server is restarted customers can continue placing orders because the order process only
lasts for a couple of minutes or even a few seconds. It looks like a fire fight until the servers are back up because sales
cannot be completed and new customers cannot create orders; but because the process is so short for creating the
order, business quickly gets back to normal after the restart.
What happens when an order process takes longer than a couple of minutes and instead lasts for days, months,
or even years? These are referred to as long-running processes, and because WF supports implementation for long-
running business processes, persisting workflows is the solution for managing a long-running process successfully.
This chapter focuses on showing how workflows can be persisted for long periods of time using the
WorkflowApplication and WorkflowServiceHost hosting options. Persisting workflows with Windows Server
AppFabric and hosting workflows within the cloud with Windows Azure will be covered in other chapters. After
reading this chapter you will have a better understanding of how persistence can be achieved for hosting
long-running workflows within WF4.5.

Persistence Behavior
Workflow persistence is not set by default because it is not configured to be used with the WF runtime. In fact,
all workflow instances that are created run within memory, so if a server that is hosting WF goes down, all of the
workflow instances are lost, and there is no way of knowing where each of the business processes left off during
execution. All of the data schema, plumbing, and implementation for persisting workflows are available out of the
box with WF, so the only thing left to do is to configure workflow persistence. Once it is configured, workflows can
be persisted within SQL Server and it is less of a worry if a server goes down, because each instance of a running
workflow can quickly recover once the server is back up.
Workflow persistence is what allows long-running workflows to process efficiently. As the number of workflows
being processed at the same time grows, so does the memory allocation required to process each workflow instance.
Persisting workflows allows the WF runtime to take snapshots of an executing workflow and save them to a data
store at key points of its execution. Most of the time, it is when the workflow becomes idle; and this makes logical
sense because when a workflow instance is waiting for an external event or checking for logical conditions, it can be
removed from memory, freeing up RAM.
At this point, the workflow, so to speak, is cryogenically stored and available for future execution. A workflow
initiated and persisted through one hosting application can later be resumed through another workflow hosting
application. To illustrate multiple workflow hosts used to host a workflow, imagine if a workflow is initiated based on

295

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

customers placing orders from an e-commerce web site. Then, later the employees in the warehouse fill the order
using mobile devices or another internal Windows application.
A workflow instance is an execution of a workflow. An example of a workflow instance could be a workflow
executing a single order from a customer. Therefore, each new order created by a customer would also initiate another
workflow instance. So think of a workflow instance as the train on the tracks of a workflow, but with a slight twist. There
can be many trains on the track, and each train can be at a different location on the track. Some trains are stationary
at certain locations on a track, while others are barreling down the track. The good part is that the workflow host
coordinates all of the workflow’s execution. Therefore, many orders can be created by customers and will follow the
same workflow, and all will be communicated, monitored, and managed during execution through the WF runtime.
The WF runtime manages persistence for workflow instances. After building a custom hosting application
for hosting workflows, persistence can only be configured to use with the WorkflowApplication and
WorkflowServiceHost hosts because of their strong interaction with the WF runtime (see Figure 8-1). Therefore,
WorkflowInvoker cannot be configured to use out-of-box persistence. Persistence for a workflow can be initiated in
several ways:

Figure 8-1.  Workflow persistence

• Idle workflows that are configured to “persist on idle.”


• When the WF runtime is instructed to persist or unload a workflow.
• Completion or termination of a workflow.
The following activities trigger persistence as well:
• After completion of
• TransactedReceiveScope
• TransactionScope

• During execution of
• Persist activity
Workflows can become idle when they are in a state of waiting on instructions to resume execution. While a
workflow waits, it is said to be idle, and while a workflow instance is idle, the WF runtime can inform a workflow
hosting application that the workflow instance has become idle. While the workflow instance is idle, it’s a great
opportunity to take a picture or snapshot of the workflow instance to record the latest progress that has been
performed at that point in time.

296

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

A workflow instance can be persisted when instructed to do so from the WF runtime. The WorkflowApplication
host has a Persist method that can be called from the hosting application, which also causes it to persist the
workflow instance even without the workflow going idle. Therefore, a workflow can be persisted even before it is
started. After the workflow runs and becomes idle it can be persisted again. The WorkflowApplication can also
dictate when a workflow should be unloaded by using the Unload method. When a workflow instance is unloaded, it is
first persisted; then it is unloaded from memory.

■■Note If the operation for the methods Unload and Persist takes longer than thirty seconds, a TimeoutException
will be thrown.

Non-Persisted State
Before a workflow instance has been persisted, it is said to be in a non-persisted state. When a workflow instance is
in such a state, it cannot be retrieved if there is a failure with the workflow hosting application or once it is removed
from memory. When a workflow is in a non-persisted state and experiences an exception before it has had a chance
to be persisted, an UnhandledExceptionAction catches the exception. The UnhandledExceptionAction can be set
to Abort, Cancel, and Terminate. Setting the UnhandledExceptionAction to Abort writes information pertaining to
why the workflow instance was aborted to the instance store, but the workflow instance cannot be reloaded. If the
UnhandledExceptionAction is set to Cancel or Terminate, information about why the workflow was aborted is also
written to the instance store and the instance state is set to Closed.
As non-persisted workflow instances are written to the instance store, there is no feature for managing clean
up for the instances. One way to clean up the instance store is to check for workflow instances that have not been
persisted. This can be done by checking the database table called System.Activities.DurableInstancing. The
following SQL command that hits a view can be used to find all non-persisted instances:

SELECT
Instance,
CreationTime
FROM
[System.Activities.DurableInstancing].[Instances]
WHERE
IsInitialized = 0

The same view can be used to see if non-persisted instances are currently not loaded by checking the
CurrentMachine field to see if it is null.

SELECT
Instance,
CreationTime
FROM
[System.Activities.DurableInstancing].[Instances]
WHERE
IsInitialized = 0
AND
CurrentMachine is NULL

297

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Non-persisted instances can be removed from the data store; however, it is important to verify that the record can
be removed before running the following delete script:

DELETE
[System.Activities.DurableInstancing].[Instances]
WHERE
InstanceId = '0234jh54-fdg4-3jde-5j4c-f8ds-fd9s8d7g7d9s'

Persistence Patterns
In order to maintain performance and scalability, it is recommended that workflow instances be persisted as early as
possible within their lifecycle. This can easily be done by initiating the persistence either by the WF runtime or within
the workflow, and by taking advantage of the Persist activity. However, to manage scalability and performance,

Persist
Persist command can be used to make sure that the workflow instance is persisted at key points in case

When short durations are used with the Delay activity, it is better to persist the workflow but not unload the

SQL Server persistence has been available since the first release of WF, and SQL scripts are provided with each release.
Running the appropriate SQL script provided through the .NET Framework is required for setting up the instance
store. Because each of the SQL scripts is coordinated with a release of the .NET Framework, they can be found in the
Windows path at C:\Windows\Microsoft.NET (see Figure 8-2). If a 64-bit runtime is installed, the scripts can be found
in the Framework64 folder, but if not, they are located in the Framework folder.

Figure 8-2.  .NET Framework versions

298

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Expand the folder displays each of the versions of .NET that is installed, and since WF was released with .NET 3.0,
there are SQL scripts for each version of .NET for 3.0 through 4.0 (see Figure 8-3). However, the scripts for persisting
all versions of WF, including WF4.5, are located within the file path v4.0.30319\SQL\en.

Figure 8-3.  SQL scripts for persisting all versions of WF

There are a couple of SQL scripts for each version of WF persistence and each comes with a two SQL script files.
• Schema
• Logic
The files ending in “Schema” set up the database schema, which includes the tables and views used storing
information on persisted workflow instances. The other files ending in “Logic” include the SQL stored procedures
and functions used for automating the persistence database process. For certain versions of persistence, there are
also script files that start with “Drop,” indicating that the scripts remove persistence data stores from SQL Server that
were previously installed (see Figure 8-3). The drop scripts can be used for starting from scratch and reloading a new
persistence data store.
Table 8-1 gives details for each of the scripts that are provided with WF4.5; however, the only scripts that are
important to use for implementing persistence are the following:
• SqlWorkflowInstanceStoreSchema.sql
• SqlWorkflowInstanceStoreLogic.sql

299

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Table 8-1.  Persistence SQL Scripts


Script Description
SqlPersistenceService_Schema Sets up the persistence database for WF3
SqlPersistenceService_Logic Sets up the persistence stored procedures and functions for WF3
SqlPersistenceProviderSchema Sets up the persistence database for WF3.5
SqlPersistenceProviderLogic Sets up the persistence stored procedures and functions for WF3.5
DropSqlPersistenceProviderSchema Drops the persistence store for WF3.5
DropSqlPersistenceProviderLogic Drops the persistence stored procedures and functions for WF3.5
SqlWorkflowInstanceStoreSchema Sets up the persistence database for WF4.x
Sets up the persistence stored procedures and functions for WF4.x

Drops the persistence stored procedures and functions for WF4.x

SqlWorkflowInstanceStoreSchema.sql

script. In order to run the scripts, a new database needs to be created.

be created.
I usually give the database the name of WFPersist; however, if you are already using persistence with other
versions of WF, it might be better if the persistence database is named WF4Persist. After the database has been
created, the SqlWorkflowInstanceStoreSchema.sql file can be opened by browsing to its location on the file system
mentioned earlier (see Figure 8-4). After the script file opens, it is important to make sure that the right database will
be used to run the script against. If not, it can be selected from the drop-down box beside the Execute command
button within the toolbar for Management Studio.

300

www.it-ebooks.info
Chapter 8 ■ persisting WorkfloWs

Figure 8-4. Opening script file within Management Studio

After the script has run, the database can be refreshed, revealing that the tables and views were
created successfully (see Figure 8-5). After checking that the tables and views have been created,
SqlWorkflowInstanceStoreLogic.sql can be opened the same way within Management Studio to build the
stored procedures and SQL functions.

Figure 8-5. Schema tables and views

301

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

After the SqlWorkflowInstanceStoreLogic.sql script file has been run against the same database, refreshing
the database again should reveal the same as Figure 8-6 and show the stored procedures and SQL functions that
were created.

Figure 8-6.  Stored procedures and functions

■■Note If a persistence data store was created before the beta or release of WF4.5, then
SQLWorkflowInstanceStoreSchemaUpgrade.sql is all that is required to upgrade.

SQL Server Profiler


One advantage in having the persistence data store kept in SQL Server is monitoring activity between the WF runtime
and the tables within the persistence store. The SQL Server Profiler can be started up within the Microsoft SQL Server
Management Studio by clicking on Tools from the toolbar and selecting SQL Server Profiler. After launching the profiler,
a login (which is the same login used to log into Management Studio) requires authentication (see Figure 8-7).

302

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-7.  Profiler login

SQL Server Profiler uses profiles for what events are traced. The Standard (default) template can be used, and the
trace can be started by clicking Run (see Figure 8-8).

Figure 8-8.  Standard (Default) trace template

303

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

After the trace starts, depending on your SQL Server environment, there can be a ton of events being traced. To
focus on the events that are tied to the persistence database, the template can be filtered. To learn more about how to
filter events using the profiler, visit http://msdn.microsoft.com/en-us/library/ms175520.aspx.
Once the trace is running, new events will load into the profiler, as demonstrated in Figure 8-9.

  Profiler trace

SqlWorkflowInstanceStore
After the persistence data store has been created by running the scripts mentioned earlier, it needs to be wired up
to the WF runtime using the SqlWorkflowInstanceStore. The SqlWorkflowInstanceStore object inherits from an
abstract class called InstanceStore for out-of-the-box persistence configuration with the WF runtime, and there are
two ways to configure it within solutions. One way is through code, and the other is through XML configuration using
files like the app.config or Web.config. The SqlWorkflowInstanceStore requires configuration with a database;
therefore, as changes happen within server environment, there may be more cases when it makes sense to configure
persistence through XML configuration.

ConnectionString Property
After the database has been created using the SQL scripts, the SqlWorkflowInstanceStore needs to be set up to use
the database through its ConnectionString property so it can serve as a middle man between the instance store and
the WF runtime.

SqlWorkflowInstanceStore instanceStore = new SqlWorkflowInstanceStore;


instanceStore.ConnectionString =
"Server=HYPERVWINSEVEN2;Database=WFPersist;Trusted_Connection=yes";

304

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

After the ConnectionString property is set, SqlWorkflowInstanceStore can be added to a WF runtime host like
the WorkflowApplication:

Activity rentalWorkflow = new Exercise1.wfRentalEquipment();



var wfApp = new WorkflowApplication(rentalWorkflow);
wfApp.InstanceStore = instanceStore;

PersistableIdleAction Property
Technically, persistence is wired and ready to go; however, there an event called PersistableIdle that needs to be
configured. It is a delegate, so it can be wired like this:

wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
{
Return PersistableIdleAction.Unload;
}

The PersistableIdle event gets fired when a workflow instance has become idle and is set to become persisted,
but it will only fire after persistence has been set up properly to the WF runtime. Setting the return value for the
PersistableIdle provides a level of granularity when configuring the behavior for how persisting workflows should
function. The return type PersistableIdleAction has three different members that can be set, determined by how
the application will be used with persisting workflow instances.
• Persist: Tells the WF runtime that after a workflow instance goes idle, it can simply be
persisted.
• Unload: Indicates to the WF runtime that after a workflow instance goes idle, it should unload
from the memory; however, remember that the workflow is persisted first before it is unloaded
from the WF runtime.
• None: No persistence or unloading needs to take place when a workflow instance goes idle.

■■Note After persistence is configured, the Idle event will fire before the PersistableIdle event.

PERSISTING WITH WORKLFLOWAPPLICATION

The exercise will demonstrate how to use the WorkflowApplication host with the SqlWorkflowInstanceStore
for persisting workflow instances. After workflow instances are created, the PersistableIdleAction will be
changed between Unload and Persist and instance ownership will be demonstrated with how it reacts to the
PersistableIdleAction being modified.

The scenario anticipates that persistence has already been created using the scripts discussed earlier; therefore this
exercise will use the new persistence data store for persisting workflow instances with a simple equipment rental
workflow that tracks when rentals should be returned. The goal is to keep track of the rentals and when equipment
needs to be returned, even when the application goes down. Because the exercise uses the WorkflowApplication,
each persisted workflow instance will need to be loaded manually, so I will show you how to use Entity Framework’s
code first pattern to pull persisted records from the persistence store. Finally, I will demonstrate how to use
extensions to communicate from the workflow back to the hosting application. Let’s get started.

305

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

1. Open Visual Studio 2012, and create a new Project.


2. Select the Workflow template to see a list of installed workflow templates.
3. Select a new Activity Library, and name it ApressChapter8.
4. Rename the Activity1.xaml that is included with the project to
wfRentalEquipment.xaml.

5. Rename the project to Exercise1.


6. Right-click on the solution and add new Visual C# class library. Name it
Rental.DataModel. This project will maintain the EquipmentRental entities that will be
passed from the workflow hosting application to the workflow.
7. Rename the file Class1.cs that was added by default to Equipment.cs.
8. Paste the following code into the Equipment.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace Rental.DataModel
{
public class Equipment
{
public int EquipmentId { get; set; }
public string EquipmentName { get; set; }
public decimal Price { get; set; }
public DateTime DateRented { get; set; }
public int RentalMinutes { get; set; }
public DateTime ReturnedOn { get; set; }
}
}

9. Add another class to the Rental.DataModel project and name it EquipmentRental.cs.


10. Paste the following code into the Equipment.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rental.DataModel
{
public class EquipmentRental
{
public int EquipmentId { get; set; }

306

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

public int RentalId { get; set; }


public Equipment RentedEquipment { get; set; }

}
}

11. Right-click on the solution and add new Visual C# class library. Name it
WFPersistence.DataModel. This project will retrieve the workflow instances that have
been persisted using the [System.Activities.DurableInstancing].Instances view
that is standard with the persistence database that was generated.
12. Rename the file Class1.cs that was added by default to PersistedInstance.cs.
13. Paste the following code into the PersistedInstance.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Collections.ObjectModel;

namespace WFPersistence.DataModel
{
public class Instance
{
public Guid InstanceId { get; set; }
public DateTime? PendingTimer { get; set; }
public DateTime? CreationTime { get; set; }
public DateTime? LastUpdatedTime { get; set; }
public int? ServiceDeploymentId { get; set; }
public string SuspensionExceptionName { get; set; }
public string SuspensionReason { get; set; }
public string ActiveBookmarks { get; set; }
public string CurrentMachine { get; set; }
public string LastMachine { get; set; }
public string ExecutionStatus { get; set; }
public bool? IsInitialized { get; set; }
public bool? IsSuspended { get; set; }
public bool? IsCompleted { get; set; }
public byte? EncodingOption { get; set; }
public byte[] ReadWritePrimitiveDataProperties { get; set; }
public byte[] WriteOnlyPrimitiveDataProperties { get; set; }
public byte[] ReadWriteComplexDataProperties { get; set; }
public byte[] WriteOnlyComplexDataProperties { get; set; }
public string IdentityName { get; set; }
public string IdentityPackage { get; set; }
public long? Build { get; set; }
public long? Major { get; set; }
public long? Minor { get; set; }
public long? Revision { get; set; }
}

307

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

public class Instances : Collection<Instance>


{

}
}

14. Add another class to the WFPersistence.DataModel project and name it


WFPersistence.cs.

15. Paste the following code into the WFPersistence.cs file:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WFPersistence.DataModel
{
public class WFPersistenceStore : DbContext
{
public WFPersistenceStore()
: base("WFPersist")
{
}

public DbSet<Instance> PersistedInstances { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Instance>().ToTable("System.Activities.
DurableInstancing.Instances");
}
}
}

This snippet of code implements the code-first pattern for EF4.3 a little differently. Code-first indicates that code
is written before the database, but in this case the persistence data store has already been created. Therefore,
you can write code modeling the data structure you want to retrieve data from and use the class above as the
DbContext object for getting the records; in this case, the structure is a database view System.Activities.
DurableInstancing.Instances. Notice also that the constructor points to a base description of WFPersist. This
tells EF code first that a database already exists so there is no need to modify it, and to use a connectionstring
within a config file for connecting to the database.
Creating a POCO object called Instances will not work alone for retrieving records from the view because it
does not match the view name, and surely creating an object called System.Activities.DurableInstancing.
Instances will not work, so instead the POCO class Instance is mapped to the System.Activities.
DurableInstancing.Instances view.

308

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

16. Now that the classes are built, a reference to the EF4.3 needs to be added. Right-click on
References for the WFPersistence.DataModel and select Manage NuGet Packages, as
shown in Figure 8-10.

Figure 8-10.  Manage NuGet Packages

17. Check to see if Entity Framework 4.3 is installed by checking to see if it has a green
check, which is located in the upper right corner as indicated in Figure 8-11. If it is not
installed, an Install button can be pressed to download Entity Framework 4.3.

Figure 8-11.  Checking if EntityFramework 4.3 is installed

309

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

As workflow instances are persisted, this project will be responsible for retrieving the persisted instances from
the application hosting the workflows.
18. Open wfRentalEquipment.xaml within the Exercise1 project so it can be viewed within
the workflow designer. This workflow is going to be a pretty simple one so there is no
need for it to be anything other than a sequential workflow. It is a sequential workflow
because it does not rely on a flowchart or state machine style.
19. Right-click on the Exercise1 project and select Add Reference to the Rental.DataModel
project by clicking the checkbox for the project.
20. Click the Arguments tab and add a new argument called argInRental. Set the Direction
for the argument to In and select “Browse for Types.” Select Rental.DataModel located
under <Referenced assemblies> for the Argument type EquipmentRental. This
argument will accept a new equipment rental that will be used within the workflow.
21. Click the Variables tab and create a new variable called varDelayDuration. This variable
will be used to calculate the duration for how long the workflow should wait until the
equipment rental becomes overdue. Change the Variable type to TimeSpan and set the
Default value for the variable to TimeSpan.FromMinutes(argInRentedEquipment.
RentalMinutes). The variable will be set based on the number of minutes passed in
through the argInRental argument.
22. Drag a Delay activity from the toolbox and place it on the designer canvas and set its
Duration property to varDelayDuration.

23. Right-click the Exercise1 project and create a new folder called Activities.
24. Right-click the folder and add a new class file. Name the file NotifyHost.cs and replace the
current code by pasting in the code that follows. This code will communicate with the hosting
application when rental equipment has become overdue using a workflow extension.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Activities;
using Rental.DataModel;

namespace ApressChapter8.Activities
{
public class NotifyHost : CodeActivity
{
[RequiredArgument]
public InArgument<EquipmentRental> inRental { get; set; }
protected override void Execute(CodeActivityContext context)
{
var rental = new EquipmentRental();
rental = context.GetValue(inRental);
INotifyHost host = context.GetExtension<INotifyHost>();
if (rental.RentedEquipment.DateRented.AddMinutes(rental.
RentedEquipment.RentalMinutes)<DateTime.Now)

310

www.it-ebooks.info
Chapter 8 ■ persisting WorkfloWs

host.OverDueRental(string.Format("{0} rental is Overdue!",


rental.RentedEquipment.EquipmentName),
context.WorkflowInstanceId);
}
}
}

25. a workflow extension in the form of a simple interface needs to be created so the
NotifyHost activity knows how to communicate with the hosting application.
right-click the folder and add a new class file. name the file INotifyHost.cs and replace
the current code with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ApressChapter8.Activities
{
public interface INotifyHost
{
void OverDueRental(string RentalStatus, Guid InstanceId);
}
}

26. right-click on Exercise1 and select Build to make sure the workflow and NotifyHost
activity builds correctly. once the Exercise1 project builds successfully, drag the new
NotifyHost activity located at the top of the toolbox, under the AppressChapter8.
Activities section, and add it beneath the Delay activity added earlier. a sequence
activity will be created automatically and will serve as the container for both the Delay
and NotifyHost activities (see figure 8-12).

Figure 8-12. Delay and Notify activities

311

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

27. Set the inRental argument for the NotifyHost activity to the Inargument for the
workflow, argInRental.
At this point the workflow and the data models that will be used with the workflow have been created. The last
step is setting up the workflow hosting application. Instead of using a WPF app, a simple WinForms app will be
created.
28. Right-click on the ApressChapter8 solution and select Add and then New Project. Select
the Windows template and add a new Windows Forms Application. Name the project
RentalHost.

29. Add the solution references in Figure 8-13 to the new RentalHost project.

Figure 8-13.  WFPersistence.DataModel references

30. Make sure to reference Entity Framework 4.3, and if not, use Nuget to install it.
31. Right-click on References and add the Framework references checked in Figure 8-14.

312

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-14.  .NET Framework references

32. Open the default Form1.cs file so the form can be created. Figure 8-15 shows the basic
user interface that needs to be created.

Figure 8-15.  RentalHost form

313

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

33. Drag a ListView from the toolbox and place it at the top of the form. After it is added,
click the Columns property and add following three columns (also shown in Figure 8-16):

Figure 8-16.  Adding ListView columns

Name:“RentalId” Text:“Rental Number”


Name:“WorkflowState” Text:“ WorkflowState”
Name:“Status” Text:“Status”
34. Drag a Label to the form and set the Text property to Equipment Rental. Use a font
size of 12.
35. Drag another Label to the form and set the Text property to Equipment just below the
first label.
36. Drag a Combobox and place it directly below the label above. Set the Id property to
cboEquipment.

37. Click on the arrow located on the top right of the cboEquipment to add the default items
(see Figure 8-17):
-select-
Backhoe
Stump Grinder
Compactor

314

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-17.  Adding Equipment values

38. Drag another Label to the form and set the Text property to Rental Minutes just below
the cboEquipment.
39. Drag another Combobox and place it directly below the previous label. Set the Id property
to cboRentalMinutes and do the same thing as Step 35 for adding the items but instead
use the following:

-select-
1
2
3

40. Drag a new Button from the toolbox and place it just below cboRentalMinutes. Set the
Name property to cmdCreateRental and the Text value to Create Rental.

41. Drag another Button from the toolbox and place it in the far right corner of the form. Set
its Name property to cmdUnloadInstances and the Text value to Release.
Drag another Button from the toolbox and place it within far right corner but on the left-
hand side from cmdUnloadInstances. Set its Name property to cmdCreateRental and the
Text value to Create Rental.

42. Double-click each of the buttons to create their click events.


43. Right-click the Windows form, select View Code, and replace the code with the following:

using System;
using System.Collections.Generic;
using System.ComponentModel;

315

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Activities;
using System.Threading;
using System.Runtime.DurableInstancing;
using System.Activities.DurableInstancing;
using System.Xml.Linq;
using WFPersistence.DataModel;
using Rental.DataModel;
using System.ServiceModel.Activities;
using ApressChapter8.Activities;


namespace RentalHost
{
public partial class Form1 : Form,INotifyHost
{
private WorkflowApplication _wfApp;
private SqlWorkflowInstanceStore _instanceStore;
public Form1()
{
InitializeComponent();
listView1.View = View.Details;
CreatePersistenceStore();
CreateInstanceStoreOwner();
LoadInstancesIntoListView();
cmdSetOwner.Enabled = false;
}

private void LoadInstancesIntoListView()
{
try
{
var instances =
GetPersistedInstances();
listView1.Items.Clear();

if (instances.Count > 0)
{

foreach (var instance in instances)
{
InitiateWorkflowRuntime();
_wfApp.Load(instance.InstanceId);
_wfApp.Run();
var item = new ListViewItem(

316

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

new string[3]
{
_wfApp.Id.ToString(),
"Loaded",
"Not Overdue"

});
listView1.Items.Add(item);
}
}
}
catch (Exception ex)
{
throw;
}
}

private UnhandledExceptionAction
OnUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs uh)
{
return UnhandledExceptionAction.Terminate;
}

private Instances GetPersistedInstances()
{
var PersistedWFInstances = new Instances();
try
{
using (var PersistStore = new WFPersistenceStore())
{
var result = from t in PersistStore.PersistedInstances
select t;

foreach (var instance in result)
{
PersistedWFInstances.Add(
new Instance
{
InstanceId = instance.InstanceId,
PendingTimer = instance.PendingTimer,
CreationTime = instance.CreationTime,
LastUpdatedTime = instance.LastUpdatedTime,
ServiceDeploymentId = instance.ServiceDeploymentId,
SuspensionExceptionName = instance.SuspensionExceptionName,
SuspensionReason = instance.SuspensionReason,
ActiveBookmarks = instance.ActiveBookmarks,
CurrentMachine = instance.CurrentMachine,
LastMachine = instance.LastMachine,
ExecutionStatus = instance.ExecutionStatus,
IsInitialized = instance.IsInitialized,
IsSuspended = instance.IsSuspended,
IsCompleted = instance.IsCompleted,

317

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

EncodingOption = instance.EncodingOption,
ReadWritePrimitiveDataProperties = instance.
ReadWritePrimitiveDataProperties,
WriteOnlyPrimitiveDataProperties = instance.
WriteOnlyComplexDataProperties,
ReadWriteComplexDataProperties = instance.
ReadWriteComplexDataProperties,
WriteOnlyComplexDataProperties = instance.
WriteOnlyComplexDataProperties, IdentityName =
instance.IdentityName,
IdentityPackage = instance.IdentityPackage,
Build = instance.Build,
Major = instance.Major,
Minor = instance.Minor,
Revision = instance.Revision
});

}
}
}
catch (Exception ex)
{
throw ex;
}
return PersistedWFInstances;
}

/// <summary>
/// The on workflow completed.
/// </summary>
/// <param name="wc">
/// The event args
/// </param>
private void OnWorkflowIdle(WorkflowApplicationIdleEventArgs iw)
{

}
/// <summary>
/// The on workflow completed.
/// </summary>
/// <param name="wc">
/// The event args
/// </param>
private void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs wc)
{
foreach (ListViewItem item in listView1.Items)
{
if (item.Text == wc.InstanceId.ToString())
listView1.Items[item.Index].SubItems[1].Text = "Completed";

}
}

318

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

private PersistableIdleAction OnWorkflowPersitableIdle


(WorkflowApplicationIdleEventArgs ia)
{
return PersistableIdleAction.Unload;
}

private void InitiateWorkflowRuntime(Dictionary<string,object> WFArg=null)
{
try
{
Activity rentalWorkflow = new Exercise1.wfRentalEquipment();

if(WFArg!=null)
_wfApp = new WorkflowApplication(rentalWorkflow, WFArg);
else
_wfApp = new WorkflowApplication(rentalWorkflow);

_wfApp.SynchronizationContext = SynchronizationContext.Current;
_wfApp.OnUnhandledException = OnUnhandledException;
_wfApp.Completed = OnWorkflowCompleted;
_wfApp.Idle = OnWorkflowIdle;
_wfApp.PersistableIdle = OnWorkflowPersitableIdle;
_wfApp.InstanceStore = _instanceStore;
_wfApp.Extensions.Add(this);
}
catch (Exception ex)
{
throw ex;
}
}

private EquipmentRental BuildWorkflowArg()
{
EquipmentRental rental = null;
try
{
if (cboEquipment.SelectedIndex > 0)
{
rental = new EquipmentRental();
rental.RentedEquipment =
new Equipment
{
DateRented = DateTime.Now,
EquipmentName = cboEquipment.SelectedItem.ToString(),
RentalMinutes = Convert.ToInt32
(cboRentalMinutes.SelectedItem)
};
}
}

319

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

catch (Exception)
{

throw;
}
return rental;
}

private void CreatePersistenceStore()
{
try
{
_instanceStore = new SqlWorkflowInstanceStore();
_instanceStore.ConnectionString =
"Server=ServerName;Database=WFPersist;Trusted_Connection=yes";
}
catch (Exception)
{

throw;
}
}
private void CreateInstanceStoreOwner()
{
try
{

InstanceHandle handle = _instanceStore.CreateInstanceHandle();
InstanceView view = _instanceStore.Execute(handle,
new CreateWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));

handle.Free();
_instanceStore.DefaultInstanceOwner = view.InstanceOwner;
}
catch (Exception ex)
{
throw;
}
}
private void LoadNewRental()
{
try
{
var DateAndTimeRented = DateTime.Now.ToShortDateString()+" "
+DateTime.Now.ToShortTimeString();
var DateAndTimeDue = DateTime.Now.AddMinutes(Convert.
ToInt32(cboRentalMinutes.SelectedItem));
var Due = DateAndTimeDue.ToShortDateString()+" "+DateAndTimeDue.
ToShortTimeString();

320

www.it-ebooks.info
Chapter 8 ■ persisting WorkfloWs

var item = new ListViewItem(


new string[3]
{
_wfApp.Id.ToString(),
"Started",
string.Format("Rented on {0} and due back by {1}"
,DateAndTimeRented
,Due)

});
listView1.Items.Add(item);
}
catch (Exception ex)
{
throw ex;
}
}
private void cmdCreateRental_Click(object sender, EventArgs e)
{
var rental = BuildWorkflowArg();
if (rental != null)
{
Dictionary<string, object> wfArg = new Dictionary<string, object>(){
{
"argInRental",rental
}
};

InitiateWorkflowRuntime(wfArg);
_wfApp.Run();
LoadNewRental();
}

}
private void cmdUnloadInstances_Click(object sender, EventArgs e)
{
try
{
InstanceHandle handle = _instanceStore.CreateInstanceHandle();

InstanceView view = _instanceStore.Execute(handle,


new DeleteWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));
handle.Free();

listView1.Items.Clear();
cmdUnloadInstances.Enabled = false;
cmdSetOwner.Enabled = true;

321
n
www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

catch (Exception)
{

throw;
}
}

public void OverDueRental(string RentalStatus,Guid InstanceId)
{
foreach (ListViewItem item in listView1.Items)
{
if (item.Text == InstanceId.ToString())
{
listView1.Items[item.Index].SubItems[2].Text = RentalStatus;
}
}
}

private void cmdSetOwner_Click(object sender, EventArgs e)
{
try
{
CreatePersistenceStore();
CreateInstanceStoreOwner();
LoadInstancesIntoListView();
cmdSetOwner.Enabled = false;
cmdUnloadInstances.Enabled = true;
}
catch (Exception ex)
{
throw ex;
}
}

}
}

44. Make sure the Connectionstring property is correct and matches the server and
database name used to build the persistence store.

Server=ServerName;Database=WFPersist;Trusted_Connection=yes;

45. Press F5 to run the application. Once the application starts, equipment can be selected
from the drop-down box as well as a value for the minutes for how long it can be rented.
Click Create Rental to add a new equipment rental.
As equipment is rented and the workflow goes idle, each workflow instance will be persisted and unloaded. To
simulate a system failure, the application can be stopped and then restarted and each of the persisted instances will
be loaded into memory. When the workflow is idle, it will then persist and unload from memory again. The Set Owner
and Release buttons can be used to reload persisted workflow instances. Once the workflow instance completes the
workflow, it will be removed from the persistence data store and will no longer be viewed within the application.

322

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

DefaultInstanceOwner
A cool feature that persistence provides is the ability to rehost persisted workflow instances that were created using
other hosts. This means that a workflow created on one computer can be rehosted on another. An example of this is
an application that is used to create a customer’s order on one computer and another computer is used to view and
manage the orders. In order for this magic to take place, the DefaultInstanceOwner needs to be set to indicate which
WF runtime is hosting the workflow instance.
Exercise1 used the WorkflowApplication for hosting workflow instances. It does not provide as much
functionality as the WorlkflowServiceHost, but it does provide a way to build custom applications for hosting
workflows and taking advantage of persisting workflows. It is good practice to indicate to the instance store
ownership for a workflow instance while it has been persisted but is yet still running within the WF runtime using
WorkflowApplication. Exercise1 included the code to do this within the Set Owner and Release button click events;
however, Exercise1 had the PersistableIdleAction set to Unload, which unloaded the workflow instances from
the WF runtime every time they went idle. Changing the code within the PersistableIdle delegate in Exercise1 to
Persist will not allow any of the workflow instances to be unloaded from memory but instead will just be persisted
within the instance store. Even though a workflow instance has been persisted, it is still running in memory, and the
persistence store takes this precaution until it is otherwise indicated. An owner lock remains on the workflow instance
after the host that initiated the workflow persistence is shut down, as the following error message indicates:

The execution of an InstancePersistenceCommand was interrupted because the instance 'db5dd1e2-498d-


4a84-972b-13d83e1b0988' is locked by a different instance owner. This error usually occurs because
a different host has the instance loaded. The instance owner ID of the owner or host with a lock on
the instance is '713955de-99ed-414b-a099-0b9a7d0f5c59'.

Now the owner lock will eventually expire, releasing the persisted workflow instance to other potential hosts, so
to stay safe, the first suggestion is to just set the PersistableIdleAction to Unload, which is what Exercise1 initially
demonstrated; it indicates that the instance within the instance store is not locked and has been removed from the
hosting application. This mechanism is practical if the workflow host ever fails for reasons that cannot be controlled.
However, the correct way for managing ownership of a persisted workflow instance is wiring up ownership for a
workflow instance, as shown in Listing 8-1.

Listing 8-1.  Setting a Default Workflow Instance Owner

InstanceHandle handle = _instanceStore.CreateInstanceHandle();


InstanceView view = _instanceStore.Execute(handle,
new CreateWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));
Handle.Free();
_instanceStore.DefaultInstanceOwner = view.InstanceOwner

Listing 8-1 demonstrates setting a globally defined SqlWorkflowInstanceStore, _instanceStore and lets the
instance store know that there is a workflow instance owner. As workflows are persisted, they need be locked until
they are either unloaded from the WF runtime or the host indicates that other hosts can host a workflow instance
by executing the DeleteWorkflowOwnerCommand(). PersistableIdleAction can now be set to Persist so workflow
instances will not be unloaded from the WF runtime; however, it is important to call DeleteWorkflowOwnerCommand()
when the application host is ready to shut down. The code in Listing 8-2 demonstrates how to let the instance
store know that even though the workflow instances are not unloaded but just persisted, it is ok for other hosting
applications to load them from the persistence store.

323

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Listing 8-2.  Removing the Workflow Owner Lock

InstanceHandle handle = _instanceStore.CreateInstanceHandle();


InstanceView view = _instanceStore.Execute(handle,
new DeleteWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));
Handle.Free();

PERSISTENCE WITHOUT UNLOADING

This exercise makes a change to Exercise1 and demonstrates how to persist workflow instances without
unloading them from memory and the behavior that takes place when the hosting application fails.
1. Open Exercise1 within Visual Studio 2012.
2. Open Microsoft SQL Server Management Studio and connect the persistence store
database. Open up a query window that is connected to the persistence store database
as well.
3. Add the following queries to the query window. The first query uses the view Instances to
get all of the persisted instances. The second gets all of the owner locks for the persisted
instances.

SELECT TOP 1000 *


FROM [WFPersist].[System.Activities.DurableInstancing].[Instances]

SELECT TOP 1000 *
FROM [WFPersist].[System.Activities.DurableInstancing].[LockOwnersTable]

4. Open Form1.cs to view the code. Find the OnWorkflowPersistedIdle function and
replace

return PersistableIdleAction.Unload;

with

return PersistableIdleAction.Persist;

5. Run the application. This will change the behavior of the workflow instances to NOT
unload from memory after the workflow instance goes Idle.
Select an equipment rental and 1 minute for the duration and then click Create Rental. You will notice
that the WorkflowState column says Started.
6. Wait one minute and notice how the workflow instance information changes in the
ListView (see Figure 8-18).

324
c
www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-18.  Persisted but not unloaded

After a minute has passed and without interacting with the application, the equipment rental becomes overdue.
Notice that the WorkflowState has changed from started to completed, which indicates that the workflow instance
has completed the workflow. The status has also changed to indicate that the rental is overdue (see Figure 8-19).

Figure 8-19.  Equipment rental is overdue


325

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Multiple rentals can now be added and given different rental durations. As each duration expires, the application
updates the rental status (see Figure 8-20).

Figure 8-20.  Multiple rentals

7. Click the Release button after adding a rental. This tells the WF runtime that the persisted
workflow instance can be picked up by another application. If the rentals have become
overdue, then the workflow has completed and it is removed from the persistence data
store by default.
Now let’s demonstrate how persisted workflow instances are locked from other hosts.
8. Select another equipment rental and 2 minutes for the duration, and then click Create
Rental.
9. Click the Release button so the owner lock is removed and then shut down the
application. Make sure the application is no longer within Visual Studio, too.
10. Start up the application to simulate a new hosing application. The workflow instance will
be reloaded and this time simulated as a new hosting application. After 2 minutes the
workflow will complete.
11. Select another equipment rental and 2 minutes for the duration, and then click Create
Rental.
12. Now shut down the application, and make sure the application is no longer within Visual
Studio, too.

326

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

13. Start up the application to simulate a new hosing application. The application will break
with an error similar to that in Figure 8-21.

Figure 8-21.  InstanceLockException

Exercise2 has demonstrated on top of Exercise1 how to persist workflows instead of unloading them and how
to release ownership of workflow instances so other workflow hosts can access them. I also demonstrated what
can happen if a persisted workflow instance is not released from a host while another host tries to access it.

HostLockRenewalPeriod
Exercise2 covered what can happen with owner locks for persisted workflow instances, but let’s dig deeper into
what is really happening behind the scene. In Exercise2, an equipment rental was started, but when the application
was shut down and then restarted, the WF runtime saw another application trying to access the persisted workflow
instance. There was an owner lock issued, so the application that was started could not obtain the workflow instance.
When a new workflow instance is started, a new record associated with the workflow instance creates an owner lock
within the LockOwnersTable. While additional workflow instances are created, each new workflow instance that
is persisted uses the same lock. By default, an owner lock has a time out period of 30 seconds plus the Host Lock
Renewal Period and will do so as long as the originating workflow host does not renew the lock. This is a good thing
because it provides a way to access persisted instances safely after a given time period.
HostLockRenewalPeriod is a property provided with the SqlWorkflowInstanceStore that can be configured.
Exercise1 set up the SqlWorkflowInstanceStore within the CreatePersistenceStore method, so when _
instanceStore.HostLockRenewalPeriod = TimeSpan.FromMinutes(5); is added, the owner lock renewal period
changes to 5 minutes.

InstanceCompletionAction
The SqlWorkflowInstanceStore has a property called InstanceCompletionAction, and if it is not set, the default
behavior for persisted workflow instances is that they are removed from the persisted data store once they have
completed. There may be times where this is not the desired behavior and instead purging the instances from the
persistence data store needs to be done manually. There are two settings that control this behavior:
• DeleteNothing: Persisted workflow instances are stored even after they are completed and all
data and metadata is retained.
• DeleteAll: Default behavior for persisted workflow instances, which deletes them from the
persisted data store once the workflow has been completed.
Changing the setting within an application is simple. Exercise1 does not set the InstanceCompletionAction so,
and each persistence record is automatically removed upon completion. To change the setting within Exercise1, _
instanceStore.InstanceCompletionAction = InstanceCompletionAction.DeleteNothing(); can be added within

327

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

the CreatePersistenceStore method. However, other considerations need to be made within Exercise1 for how it
reloads persisted workflows, because currently it anticipates all of the records returned to be active persisted workflow
instances. If Exercise1 is run after making the change, you’ll get the error in Figure 8-22.

Figure 8-22.  Loading completed workflow instance

One way to verify if a persisted instance will be removed after completion or not is the
field within the LockOwnersTable table of the persistence database. The
is a bit datatype, so if it has a value of 0, then persisted instances related to the locking
 8-23).

  DeletesInstanceOnCompletion field

provides a custom Windows Communication Service (WCF) hosting application that


WorkflowServiceHost also provides
more functionality for working with persisting workflows. There are two ways to setup persistence with the
WorkflowServiceHost. One way is to use the DurableInstanceingOptions.InstanceStore, which can be set to
a configured SqlWorkflowInstanceStore object, because SqlWorkflowInstanceStore inherits from InstanceStore
(see Listing 8-3).

Listing 8-3.  Building the InstanceStore


var instanceStore = new SqlWorkflowInstanceStore();
instanceStore.ConnectionString =
"Server=ServerName;Database=DatabaseName;Trusted_Connection=yes";

wfServiceHost.DurableInstancingOptions.InstanceStore = instanceStore;

Another way is by taking advantage of the System.ServiceModel.Activities.Description.
SqlWorkflowInstanceStoreBehavior which is tailored more for working with workflows that are exposed as WCF
services, as shown in Listing 8-4.

Listing 8-4.  Building the SqlWorkflowInstanceBehavior

SqlWorkflowInstanceStoreBehavior instanceStoreBehavior
= new SqlWorkflowInstanceStoreBehavior("Server=ServerName;Database=DatabaseName;
Trusted_Connection=yes");
instanceStoreBehavior.HostLockRenewalPeriod = new TimeSpan(0, 0, 5);
instanceStoreBehavior.RunnableInstancesDetectionPeriod = new TimeSpan(0, 0, 2);
instanceStoreBehavior.InstanceCompletionAction = InstanceCompletionAction.DeleteAll;

328

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

instanceStoreBehavior.InstanceLockedExceptionAction = InstanceLockedExceptionAction.AggressiveRetry;
instanceStoreBehavior.InstanceEncodingOption = InstanceEncodingOption.GZip;

wfServiceHost.Description.Behaviors.Add(instanceStoreBehavior);

ServiceBehavior Element
Configuring persistence with the WF runtime can be done in two ways. One way is to use code for the settings as
mentioned in the previous section; however these settings can be added through configuration file, either through
the Web.config or app.config file. There is a serviceBehavior element that can be used in conjunction with
the SqlWorkflowInstanceStoreBehavior object. When the configuration is used, DurableInstancingOptions.
InstanceStore is set based on the settings within the ServiceBehavior element during runtime therefore there is no
reason to use both (see Listing 8-5).

Listing 8-5.  Configuring persistence through XML

<serviceBehaviors>
<behavior name="">
<sqlWorkflowInstanceStore
connectionString="Data Source=(local);Initial
Catalog=DefaultPersistenceProviderDb;Integrated Security=True;Async=true"
instanceEncodingOption="GZip | None"
instanceCompletionAction="DeleteAll | DeleteNothing"
instanceLockedExceptionAction="NoRetry | BasicRetry | AggressiveRetry"
hostLockRenewalPeriod="00:00:30"
runnableInstancesDetectionPeriod="00:00:05">
<sqlWorkflowInstanceStore/>
</behavior>
</serviceBehaviors>

OnIdle
Other behaviors can be added that facilitate how the runtime manages persistence, such as OnIdle (see Listing 8-6),
and the behavior a workflow instance performs while it becomes idle, such as TimeToPersist and TimeToUnload.
• TimeToPersist: The duration of time the WF runtime needs to wait to persist after a workflow
instance becomes idle and while the workflow instance is still loaded within memory.
• TimeToUnload: The duration of time the WF runtime needs to wait to unload a workflow
instance after it has become idle and when the workflow instance should be unloaded from
the WF runtime.
TimeToUnload should never be less than TimeToPersist because the workflow instance has to be loaded into memory
before it can be persisted. If TimeToUnload is less, it is ignored; however, TimeToPersist must finish before unloading
of a workflow instance can occur, so in some cases it could occur after the TimeToUnload is originally set
to occur.

Listing 8-6.  Configuration for OnIdle

<behavior name="">
<workflowIdle timeToPersist="00:00:05" timeToUnload="00:00:30"/>
</behavior>

329

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Persistence Participant
There are two classes, PersistenceParticipant and PersistenceIOParticipant, that provide the mechanics for
providing additional data to a workflow instance that can be persisted along with the workflow instance. A persistence
participant is useful for adding data to workflow not only through the host but also through the workflow itself, and
the data can come from sources other than the workflow. So if a workflow is associated with a line-of-business (LOB)
solution, data can be associated between the workflow and the solution. Using persistence participants becomes ideal
when there is additional information that needs to be added after the workflow has been created, along with all of the
arguments and variables. WF provides persistence participants as extensions so they can be added later, extending the
data that needs to be stored per workflow instance.
PersistenceParticipant is an abstract class that can be extended to create a custom persistence, and
PersistenceIOParticipant extends PersistenceParticipant to provide additional functionality for providing
I/O under a persistence transaction while the host persists an instance and while loading a persistence instance.
In this scenario, equipment is rented; if it is not returned, it is flagged as late. The workflow is already built;

To build a custom persistence participant, a new class needs to inherit from PersistenceParticipant, so
System.Activities.Persistence. A new class is added to a new project, or in this

Extensions and a new class. There is already


System.Activities.Persistence namespace, so the next step is to inherit from the
as shown in Listing 8-7.

Listing 8-7.  Inherit from PersistenceParticipant

using System;
using System.Activities.Persistence;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace wfEquipmentRentalService.Extensions
{
public class DateOverdueExtension : PersistenceParticipant
{

To make sure that the persistence participant is unique, a custom namespace can be added to introduce the
participant’s unique name. To add these properties, the System.Xml.Linq namespace must be added, too, as
indicated in Listing 8-8.

Listing 8-8.  Configuring Unique Participant Namepace and Name

static XNamespace DateOverDueNamespace = XNamespace.Get("urn:schemas-Apress:Chapter8/Persistence");


static XName ParticipantName = DateOverDueNamespace.GetName("DateOverdue");

330

www.it-ebooks.info
Chapter 8 ■ persisting WorkfloWs

Next, a property needs to be added that will be set internally through the participant. Here is where the real magic
can take place because custom logic can be added to the extension for grabbing data from other systems or it can just
retain the characteristics about the workflow environment. In this case, a simple DateOverdue property is added, so it
can be set internally within the participant. Then a simple internal method of SetDateOverDue is created to set the
DateOverdue property. The internal declaration just indicates that the call cannot be made externally from the DLL
that is compiled for the project (see Listing 8-9).

Listing 8-9. Property and Method Used for Holding and Setting the DateOverdue Value

public DateTime DateOverdue { get; set; }

internal void SetDateOverDue()


{
DateOverdue = DateTime.Now;
}

One of the overrides that needs to take place is the Collection CollectValues(out IDictionary<XName, object>
readWriteValues, out IDictionary<XName, object> writeOnlyValues). This method gets the value that was
set for the participant and creates a Dictionary<XName, object> object signature so the value can be automatically
persisted within the SQL Server data store (see Listing 8-10).

Listing 8-10. Overriding CollectValues for Setting Persistence Value

protected override void CollectValues(out IDictionary<XName, object> readWriteValues, out


IDictionary<XName, object> writeOnlyValues)
{
readWriteValues = new Dictionary<XName, object>(1) { { ParticipantName, DateOverdue } };
writeOnlyValues = null;
}

The last override is the PublishValues(IDictionary<XName, object> readWriteValues), which allows the value to
be returned and set back to the DateOverdue property provided within the participant (see Listing 8-11).

Listing 8-11. Overriding PublishValues for Getting Persisted Value

protected override void PublishValues(IDictionary<XName, object> readWriteValues)


{
object loadedData;
if (readWriteValues.TryGetValue(ParticipantName, out loadedData))
{
if (loadedData != null)
DateOverdue = Convert.ToDateTime(loadedData);
}
}

331

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

PERSISTING WITH WORKFLOWSERVICEHOST

This exercise builds on the concept for equipment rentals used in the earlier exercises. The workflow that is built
in this exercise will provide a workflow client a way to return equipment that was rented, and the workflow itself
will indicate to the service host when an equipment rental becomes overdue. The WorkflowServiceHost will be
used to host the workflow as a WCF service; however, the focus will be around setting up the workflow instance
persistence and using the XML to define the configuration settings, and the persistence participant that was
discussed earlier will be implemented. You will also learn how a workflow can use it as an extension for persisting
internal data.
1. Open Exercise2 within Visual Studio 2012.
2. Open Microsoft SQL Server Management Studio and connect the persistence store
database. Open a query window that is connected to the persistence store database.
3. Add the following queries below to the query window. The first query uses the view
Instances to get all of the persisted instances. The second gets all of the owner locks
for the persisted instances. The third query grabs all of the workflows that are deemed
runnable because they have unloaded from the WF runtime and gone idle.

SELECT TOP 1000 *


FROM [WFPersist].[System.Activities.DurableInstancing].[Instances]
SELECT TOP 1000 *
FROM [WFPersist].[System.Activities.DurableInstancing].[LockOwnersTable]
SELECT TOP 1000 *
FROM [WFPersist].[System.Activities.DurableInstancing].[RunnableInstancesTable]

4. A console application will be used for hosting the workflow service, so right-click on the
solution and add a new Console application and name it ServiceHost.
5. Add the references shown in Figure 8-24 to the project.

Figure 8-24.  ServiceHost Framework references

332

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

6. Open the Program.cs file and replace the existing code with the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Activities;
using System.Runtime.DurableInstancing;
using System.Activities.DurableInstancing;

using System.ServiceModel.Activities;

using Rental.DataModel;
using System.ServiceModel.Activities.Description;
using System.ServiceModel;


namespace ServiceHost
{
class Program
{
[ServiceContract]
public interface IEquipmentRental
{
[OperationContract( IsOneWay=false)]
string CreateNewRental(EquipmentRental NewRental);
[OperationContract(IsOneWay = false)]
string RentalReturned(EquipmentRental CurrentRental);
[OperationContract(IsOneWay = false)]
string RentalReturnedLate(EquipmentRental LateRental);
}

const string hostAddress = "http://localhost:8080/EquipmentRentalService";
static void Main(string[] args)
{
try
{
using (WorkflowServiceHost wfServiceHost
= new WorkflowServiceHost(new wfEquipmentRentalService.
Activity1(), new Uri(hostAddress)))
{
wfServiceHost.AddServiceEndpoint("IEquipmentRental",
new BasicHttpBinding(), hostAddress);
Console.WriteLine("Added http Service endpoint...");
Console.WriteLine("Adding persistence and metadata behaviors...");
// Open service host.
wfServiceHost.Open();
Console.WriteLine("Started Equipment rental service...");
Console.WriteLine("Press [ENTER] to exit");

333

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

// Create a client that sends a message to create an instance


of the workflow.
//IEquipmentRental client = ChannelFactory<IEquipmentRental>.
CreateChannel(new BasicHttpBinding(), new EndpointAddress(hostAddress));
//var ret = client.CreateNewRental(new EquipmentRental
//{
// RentalId = 1,
// RentedEquipment = new Equipment{
// EquipmentName="Backhoe",
// RentalMinutes = 2,
// DateRented=DateTime.Now
// }
//});

Console.ReadLine();
wfServiceHost.Close();
}
}
catch (Exception ex)
{
throw;
}

}
}
}

7. Right-click the ServiceHost project and add a new app.config. The template is found
under Visual C# Items and is called Applications Configuration File Visual C#
Items.

8. Replace the content for the new app.config file with the following:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service name="EquipmentRentalService" behaviorConfiguration="">
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<sqlWorkflowInstanceStore
connectionString="Server=HYPERVWINSEVEN2;Database=WFPersist;
Trusted_Connection=yes"
hostLockRenewalPeriod="00:00:30"
runnableInstancesDetectionPeriod="00:02:00"
instanceCompletionAction="DeleteAll"

334

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

instanceLockedExceptionAction="AggressiveRetry"
instanceEncodingOption="GZip"
/>
<workflowIdle timeToPersist="00:00:15" timeToUnload="00:00:30"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

9. Right-click the solution and add a new Activity Library called


wfEquipmentRentalService.

10. Rename the Activity1.xaml that is included with the project to


wfEquipmentRentalService.xaml.

11. Right-click the wfEquipmentRentalService project and select Build so it can be


referenced within the ServiceHost project.
12. Add the solution references in Figure 8-25.

Figure 8-25.  Solution references

13. Expand the Rental.DataModel and add the serialization attribute to the the top of the
Equipment.cs and EquipmentRental.cs class as follows:

[Serializable]
public class Equipment
And
[Serializable]
public class EquipmentRental

14. Expand the wfEquipmentRentalService project and open up the


wfEquipmentRentalService.xaml workflow activity. Drag a new Flowchart activity
onto the designer canvas.
15. Drag a Sequence activity and auto-connect it to the Start activity. Then double-click the
Sequence activity to view its container.

16. Drag a new ReceiveAndSendReply activity within the Sequence activity. Then drag
a WriteLine activity and place it between the Receive and SendReplyToReceive
activities.

335

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

17. Drag a new Pick activity and auto-connect it with the Sequence activity, as shown in
Figure 8-26.

Figure 8-26.  Equipment rental workflow

18. Double-click the Pick activity. Within the Trigger activity of the left Branch activity, add
a new Sequence activity.
19. Drag a new ReceiveAndSendReply activity within the Sequence activity. Then drag
a WriteLine activity and place it between the Receive and SendReplyToReceive
activities.
20. Add a Delay activity within the Trigger activity for the right Branch activity and set
the Duration property to TimeSpan.FromMinutes(varRental.RentedEquipment.
RentalMinutes).

21. Add a new Sequence activity within the Action activity of the right Branch activity.
22. Add a new WriteLine activity and set the Text property to string.Format("Rental Id
{0} is overdue!",varRental.RentalId).

23. Within the right Branch activity, drag a new ReceiveAndSendReply activity within the
Sequence activity. Then drag a WriteLine activity and place it between the Receive and
SendReplyToReceive activities.

24. Navigate back to the Flowchart activity by selecting Flowchart at the top of the workflow
designer.
25. Click on the Variables tab and add a new variable named varRental. Browse for the
variable type and set it to Rental.DataModel.EquipmentRental. Because Receive

336

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

activities have been added, a default CorrelationHandle has been added


(see Figure 8-27). Change its name to _RentalId and make sure it has Scope for the
Flowchart activity. Correlation will be discussed in detail within the chapter discussing
workflows hosted as services, but just remember that correlation is used to uniquely
identify existing workflow instances that are running. This is different than Exercise1
where the GUID was used to uniquely identify a running workflow instance.

Figure 8-27.  Workflow variables

26. Double-click the Sequence activity connected to the Start activity to view its container.
Click the existing Receive activity and add CreateNewRental to the OperationName
property.
27. Change the ServiceContractName to IEquipmentRental and check the checkbox
representing CanCreateInstance, since this will be the activity that starts the workflow
(see Figure 8-28).

Figure 8-28.  ServiceContract and CanCreateInstance properties

CreateNewRental is the call that the workflow service will use to create a new
equipment rental, so a new parameter for the Receive activity needs to be added. Click
the Content property and click the Parameters radio button. Add a new parameter
named NewRental and set its type to Rental.DataModel.EquipmentRental. Assign the
parameter to the workflow variable varRental. This will allow the variable to be used
within the workflow with other activities (see Figure 8-29).

337

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-29.  Creating a Receive parameter

28. Set up the correlation for the parameter that is passed in and how the workflow instance
will be uniquely identified. Click the CorrelatesOn property and add a new Key. Clicking
the drop-down box will analyze the parameter type EquipmentRental to select what
value will be used to uniquely identify the workflow instance. Select the RentalId. A new
key will be created, as indicated in Figure 8-30.

Figure 8-30.  CorrelatesWith Property assigned to RentalId

338

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

29. Set the correlatesWith property to _RentalId. Click CorrelationInitializers and


make sure that _RentalId has been added as an initializer and that the Request-reply
correlation initializer has been selected within the drop-down box (see Figure 8-31).

Figure 8-31.  Adding the Correlation Initializer

30. Set the Text property for the WriteLine activity to


string.Format("A rental request for a {0} has been received!",
varRental.RentedEquipment.EquipmentName).

Click the SendReplyToReceive activity Content property and click the Parameters radio
button. Add a new parameter named SendReplyMessage and set its type to String.
Assign the parameter the value "Your rental request has been received!"
31. Navigate back to the Flowchart activity by selecting Flowchart at the top of the workflow
designer. Double-click the Pick activity, and within the left Branch activity, change the
Receive activities OperationName property to RentalReturned.

32. Change the ServiceContractName to IEquipmentRental but make sure to leave the
CanCreateInstance checkbox unchecked.

33. RentalReturned is the call that the workflow service will use to indicate that the
equipment rental has been returned on time, so a new parameter for the Receive activity
needs to be added. Click the Content property and click the Parameters radio button.
Add a new parameter named CurrentRental and set its type to Rental.DataModel.
EquipmentRental.

34. Click the CorrelatesOn property and use the RentalId associated with the
CurrentRental parameter created in the previous step (see Figure 8-32).

339

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-32.  CorrelatesOn property for parameter

35. Set the correlatesWith property to _RentalId.


36. Click the CorrelationInitializers and make sure that _RentalId has been added as
an initializer and that the Request-reply correlation initializer has been selected
within the drop-down box.
37. Set the Text property for the WriteLine activity to "Rental has been returned!"
38. Click the SendReplyToReceive activity Content property and click the Parameters radio
button. Add a new parameter named retMessage and set its type to String. Assign the
parameter the value "Rental has been returned on time!"
39. Click the WriteLine activity in the same Action activity and its Text property to string.
Format("Rental has been returned late on {0}!", DateTime.Now).

40. Click the Receive activity within the Action activity of the right Branch activity. Change
the Receive activities OperationName property to RentalReturnedLate.
41. Change the ServiceContractName to IEquipmentRental but make sure to leave the
CanCreateInstance checkbox unchecked.

42. RentalReturnedLate is the call that the workflow service will use to indicate the
equipment rental has been returned after it has become overdue, so a new parameter
for the Receive activity needs to be added. Click the Content property and click the
Parameters radio button. Add a new parameter named LateRental and set its type to
Rental.DataModel.EquipmentRental.

340

www.it-ebooks.info
Chapter 8 ■ persisting WorkfloWs

43. Click the CorrelatesOn property and use the RentalId associated with the LateRental
parameter created in the previous step (see figure 8-33).

Figure 8-33. CorrelatesOn LateRental parameter

44. set the correlatesWith property to _RentalId.


45. Click the CorrelationInitializers and make sure that _RentalId has been added as
an initializer and that the Request-reply correlation initializer has been selected
within the drop-down box.
46. Click the SendReplyToReceive activity Content property and click the Parameters radio
button. add a new parameter named retMessage and set its type to String. assign the
parameter the value of "Rental has been returned late!"

at this point the workflow is complete for creating a new equipment rental and indicating when the rental has
been returned on time or if it has become overdue. figure 8-34 shows the Receive activity, which is used to
initialize the workflow instance and create an equipment rental.

341

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-34.  CreateNewRental Receive activity

Figure 8-35 shows the left Branch for the Pick activity that was built.

342

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-35.  RentalReturned Receive activity

There is still some work to do on the right Branch activity of the Pick activity but at this point the workflow host
can be started. As it starts, a console window will open displaying the information shown in Figure 8-36.

343

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-36.  Starting the workflow service host

There is some code that was added but commented out within the workflow service that shows how an
equipment rental can be added via a host. The final part of the exercise is to build a simple client that can call the
workflow service, but first the persistence participant needs to be built to grab the rental overdue date so it can
be presented within the console window in Figure 8-36. Two custom activities also need to be created to add the
persistence participant extension to the workflow to set and retrieve the overdue date if a rental is not returned
on time.
47. Open the wfEquipmentRentalService project and add two new folders to the project,
Activities and Extensions.

48. Add two new class files to the Activities folder and one new class file to the
Extensions folder.

49. Rename the class file within the Extensions folder to DateOverdueExtension.cs and
replace the existing code with the following code:
using System;
using System.Activities.Persistence;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace wfEquipmentRentalService.Extensions
{
public class DateOverdueExtension : PersistenceParticipant
{
static XNamespace DateOverdueNamespace = XNamespace.Get
("urn:schemas-Apress:Chapter8/Persistence");
static XName ParticipantName = DateOverdueNamespace.GetName("DateOverdue");

public DateTime DateOverdue { get; set; }

344

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

internal void SetDateOverDue()


{
DateOverdue = DateTime.Now;
}
protected override void CollectValues(out IDictionary<XName,
object> readWriteValues, out IDictionary<XName, object> writeOnlyValues)
{
readWriteValues = new Dictionary<XName, object>(1) { { ParticipantName,
DateOverdue } };
writeOnlyValues = null;
}

protected override void PublishValues(IDictionary<XName,
object> readWriteValues)
{
object loadedData;
if (readWriteValues.TryGetValue(ParticipantName, out loadedData))
{
if (loadedData != null)
DateOverdue = Convert.ToDateTime(loadedData);
}
}
}
}

50. Rename one of the class files within the Activities folder to GetDateOverdue.cs and
replace the existing code with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Activities;
using wfEquipmentRentalService.Extensions;

namespace wfEquipmentRentalService.Activities
{
public sealed class GetDateOverdue : CodeActivity
{
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
metadata.AddDefaultExtensionProvider(() => new DateOverdueExtension());
}

protected override void Execute(CodeActivityContext context)
{
DateOverdueExtension dateOverdueExtension =
context.GetExtension<DateOverdueExtension>();

345
z
www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Console.WriteLine(string.Format("Equipment became overdue on


{0}",dateOverdueExtension.DateOverdue));
}
}
}

51. Rename the other class file within the Activities folder to SetDateOverdue.cs and
replace the existing code with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Activities;

namespace wfEquipmentRentalService.Extensions
{
public sealed class SetOverdueDate : CodeActivity
{
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
metadata.AddDefaultExtensionProvider(() => new DateOverdueExtension());
}

protected override void Execute(CodeActivityContext context)
{
DateOverdueExtension dateOverdueExtension =
context.GetExtension<DateOverdueExtension>();
dateOverdueExtension.SetDateOverDue();
}
}
}

52. Right-click wfEquipmentRentalService and select Build. This will compile the two new
activities that will take advantage of using the custom persistence participant that was
created and will add them to the toolbox.
53. Grab the SetOverdueDate and drag it onto the designer canvas within the right Branch
activity’s Action activity and just above the WriteLine activity. As a rental becomes
overdue, the SetOverdueDate activity will add the date that the rental became late as a
persistence participant.
54. Grab the GetDateOverdue activity and drag it onto the designer canvas just below
the other WriteLine activity that is between the Receive and SendReplyToReceive
activities. The right Branch of the Pick activity should now look like Figure 8-37.

346

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-37.  Overdue rental using a persistence participant

347

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Finally, create a simple client that will use the new equipment rental service that was just built.
55. Create a new solution outside of the Apress.Chapter8 solution and create a new
Windows Forms Application. Name the solution ExternalClient.
56. Open the RentalHost project that was created in Exercise1 and hold Ctrl while at the
same time using the mouse to click on the controls indicated in Figure 8-38.

Figure 8-38.  Selecting controls for new host

57. Copy the controls by pressing Ctrl-C at the same time. Double-click the Form1.cs within
the ExternalClient project and press Ctrl-V to paste the controls onto the new form.
58. Drag a new checkbox control and button to the new form. Name the new button
cmdReturnedRental and set the Text property to Return Rental.

59. Set the Text property of checkbox1 to Returned Late. Position the new controls similar
to Figure 8-39 and then double-click the cmdReturnedRental button so the click event
code is created.

Figure 8-39.  Referencing Rental.DataModel.dll

348

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

60. Add a new reference for the project by searching the file location for Exercise1 and
setting a reference to the Rental.DataModel.dll (see Figure 8-40).

Figure 8-40.  Equipment rental client UI

61. Right-click Form1 and select View Code. Replace the existing code with the following:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Rental.DataModel;

using System.ServiceModel;

namespace ExternalClient
{
[ServiceContract]
public interface IEquipmentRental
{
[OperationContract(IsOneWay = false)]
string CreateNewRental(EquipmentRental NewRental);
[OperationContract(IsOneWay = false)]
string RentalReturned(EquipmentRental CurrentRental);
[OperationContract(IsOneWay = false)]
string RentalReturnedLate(EquipmentRental LateRental);
}

349

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

public partial class Form1 : Form


{
public Form1()
{
InitializeComponent();
}
private EquipmentRental BuildWorkflowArg()
{
EquipmentRental rental = null;
try
{
if (cboEquipment.SelectedIndex > 0)
{
rental = new EquipmentRental();
rental.RentedEquipment =
new Equipment
{
DateRented = DateTime.Now,
EquipmentName = cboEquipment.SelectedItem.ToString(),
RentalMinutes = Convert.ToInt32(cboRentalMinutes.SelectedItem)
};
}
}
catch (Exception)
{

throw;
}
return rental;
}

private void cmdCreateRental_Click(object sender, EventArgs e)
{
try
{
var rental = BuildWorkflowArg();

// Create a client that sends a message to create an instance of the
workflow.
IEquipmentRental client = ChannelFactory<IEquipmentRental>.CreateChannel(new
BasicHttpBinding(), new EndpointAddress("http://localhost:8080/EquipmentRentalService"));

rental.RentalId = 1;
var ret = client.CreateNewRental(rental);
}
catch (Exception ex)
{
throw ex;
}
}

350

www.it-ebooks.info
Chapter 8 ■ persisting WorkfloWs

private void cmdReturnedRental_Click(object sender, EventArgs e)


{
try
{
// Create a client that sends a message to create an instance of the
workflow.
IEquipmentRental client = ChannelFactory<IEquipmentRental>.CreateChannel(new
BasicHttpBinding(), new EndpointAddress("http://localhost:8080/EquipmentRentalService"));

var ret = string.Empty;


if(checkBox1.Checked)
ret = client.RentalReturnedLate(new EquipmentRental { RentalId = 1 });
else
ret = client.RentalReturned(new EquipmentRental { RentalId = 1 });
}
catch (Exception ex)
{
throw ex;
}
}
}
}

at this point, all the code has been added to run the workflow host and use a custom client for managing
equipment rentals. note that the rentalid within the custom client application has a hard coded value of 1 for
demonstration purposes, so be sure that an equipment rental has been returned before trying to add another
rental while one already exists. right-click on the ServiceHost project, select “set as startUp project,” and press
F5 to run the solution.

62. after the ServiceHost project starts running, press F5 to start the ExternalClient
project.
63. select one of the equipment rentals and select 2 for the rental minutes. notice what
happens (see figure 8-41).

351

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-41.  Renting equipment

After waiting two minutes the workflow service host shows that the rental has become late (see Figure 8-42).

Figure 8-42.  Rental is overdue

352

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

64. Now that the rental is overdue, click the checkbox acknowledging that the rental is late
and press the Return Rental button (see Figure 8-43).

Figure 8-43.  Returning late equipment

Figure 8-43 indicates that the persistence participant was added as an extension to the workflow and returned
the overdue date value by showing it within the console, using both the SetOverdueDate and GetDateOverdue
activities. Next, let’s create another rental and check to see what gets logged to SQL Server.
65. Create another rental just like before, but this time set the value for the rental minutes to
3. After creating the new rental, Figure 8-44 shows that the workflow instance has been
persisted and that an owner lock has been created; however, there are no records within
the RunnableInstancesTable.

353

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Figure 8-44.  Workflow instance persisted and locked

The workflow has been configured within the app.config to unload the workflow instance after 30 seconds. Because
the workflow uses a Delay activity, the workflow comes idle while waiting a 3 minute rental duration. Running the
query again results in the workflow instance being added to the RunnableInstancesTable (see Figure 8-45).

Figure 8-45.  RunnableInstancesTable contains idled workflow instance

354

www.it-ebooks.info
Chapter 8 ■ Persisting Workflows

Also configured for persistence within the app.config is the check for runnable instances every 2 minutes, which
means that the record will be removed from the RunnableInstancesTable indicating it has been reloaded into
memory and ready to finish processing.
66. Now that another rental has become overdue, click the checkbox acknowledging that the
rental is late and press the Return Rental button.
67. Create another equipment rental. Once the service acknowledges that the rental has
been created, make sure the Returned Late checkbox is not checked and click the Return
Rental button. The service then acknowledges that the rental has been returned and
without being late.
This exercise showed how WorkflowServiceHost can be used to persist workflow instances with a custom
persistence participant and how to configure persistence using the app.config. Behavior of the SQL Server
persistence data store was also demonstrated based on the persistence configuration.

Summary
This chapter focused on giving you a solid understanding of why workflow persistence is important and how it works.
Workflow persistence is provided out of the box and implements a WF instance data store within either SQL
Server 2005 or 2008 and later. Persistence is built within SQL Server by running the SQL scripts that come out of the
box with the .NET frameworks; this includes the database, stored procedures, and database functions within SQL
Server.
The chapter also offered detailed insight into the different WF objects used to configure persistence with the WF
runtime. Once the persistence data store was created, examples of how to use persistence and the behavior for how
persistence works were demonstrated within custom applications.
There are a couple of things about WF persistence that were not covered in the chapter, so I want to mention
them now. A custom persistence data store can be built, so persistence does not have to use the SQL Server
persistence provided with the .NET runtime SQL scripts. The WF runtime provides libraries that can be implemented
to create a custom data store like XML or system files, and this can be a viable solution for smaller workflow
applications. However, to effectively handle WF persistence within enterprise solutions, I recommend using the out-
of-the-box persistence through SQL Server. Most of the time, Microsoft server technologies that utilize WF will provide
their own persistence.
The last thing I want to talk about is querying the SQL Server persistence data store. Although the records are
contained in SQL Server and it is possible to write SQL commands or LINQ statements against the persistence
database, I recommend using the provided views instead of querying directly from the database tables.
There is also a WF concept called promoted properties that allows custom data to be provided with workflow
instances so they can be tied to other LOB systems; however, there are other methods of connecting WF data with
LOB applications, either through WF tracking or managing workflow instances solely through correlations of LOB
data or GUIDs generated through the WF runtime. Persisting workflows allows them to run for long periods of time.
Understanding a workflow’s execution is important for making sure the workflow is running correctly by tracking
its execution events. The next chapter will explain how tracking workflows is implemented to understand what is
happening underneath the covers while a workflow executes.

355

www.it-ebooks.info
Chapter 9

Tracking Workflows

One trade-off when working with a declarative modeling framework (compared to writing traditional code) is
the ability to understand each step of execution as the corresponding line of code executes at runtime. Without
this insight into how things are being processed, it becomes extremely difficult to manage software and make
enhancements for existing business processes, especially when they become large and complex. While working with
WF, insight into a workflow’s execution quickly becomes a necessity. Some common concerns that come up while
working with WF are
• How do I know when an activity executes?
• What decisions were made?
• How long did a process take?
Windows Workflow tracking is the solution WF provides for answering questions like these. Tracking workflows
provides a way for configuring standard workflow events and data that needs to be captured using the WF runtime.
Just as workflows provide transparency into logic that is applied to business processes, WF tracking provides
the foundation for tracking a workflow instance as it executes through a workflow. All the way from when a workflow
starts to when it finishes or is aborted, there are additional events within each activity within a workflow.
WF tracking comes ready to use out of the box, so there is nothing extra required for implementing it. It just needs
to be configured based on the data that is required to be tracked. Each version of WF since its inception has come
with a version of tracking, as the original authors of WF saw early on the importance of being able to track and gather
important data on how a workflow executes. However, since the release of WF4 significant changes have been made in
making the tracking service more efficient and the implementation easier to understand and set up.
Essentially, data pertaining to a workflow’s execution is always being generated from the WF runtime. Tracked
data can be subscribed to and filtered based on the necessary data that is required to be collected. After obtaining the
workflow instance’s tracked data, it can be stored within a database or data file.

Tracking Overview
When WF was originally released, tracking workflow instance data was automatically stored within SQL Server; this
changed when WF4 was released and storing tracked data became more generic. Instead of WF handling where the
data is stored, WF4 requires the developer to decide the best option for where tracked workflow instance data should
be stored based on a given workflow solution. WF4 does, however, provide tracing of a workflow instance by logging
tracked data to the Event Tracing for Windows (ETW), as illustrated within Figure 9-1. ETW was first provided with
Windows 2000. Through the years, ETW has been enlisted to instrument internal system event tracing tied to the
operating system because ETW is an efficient way for high-velocity tracing. ETW also provides support for tracing
events for running applications. ETW can be configured dynamically so that applications being traced are not aware,
nor are affected, while making changes about specific data that is required to be traced. The traced data that is sent to
the ETW is logged in chronological order.

357

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

  Windows Workflow tracking overview

tracking record. There are different


 9-1.

  Different Types of Tracking Records

Workflow life cycle Emitted as a workflow instance reaches events for a workflow’s lifecycle.
Activity life cycle Emitted as a workflow instance reaches events for an activity’s life cycle.
Bookmark resumption Emitted when a bookmark is resumed for a workflow instance.
Custom tracking Issued when custom data needs to be tracked within a custom activity.

Each tracking record that is emitted from the WF runtime inherits from the abstract class TrackingRecord.
Table 9-2 presents the properties provided with the TrackingRecord object that can be used to define metadata about
the event being tracked.

Table 9-2.  TrackingRecord Properties


Property Description
Annotations Name/value pairs collection of type IDictionary<string,string> that are added when
additional information needs to be supplied.
EventTime Defines a date and time for when a tracking record occurred.
InstanceId Defines the ID that is a System.Guid type for a workflow instance.
Level Defines a System.Diagnostics.TraceLevel type for a tracked event. The level represents
the tracking record’s purpose. TraceLevel members include Off, Error, Warning,
Info, and Verbose.
RecordNumber Sequential number that represents the order for a generated tracking record.

358

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Workflow Lifecycle
Tracking records are used to give transparency into a workflow instance’s execution path, but it is also important to
understand the flow of the workflow’s lifecycle. A workflow’s tracked events include when a workflow instance
• Is aborted
• Updates a workflow definition (New in WF4.5)
• Suspends
• Terminates
• Encounters an unhandled exception
Therefore there are tracking records that are emitted to indicate when these events occur.

WorkflowInstanceRecord
When the WF runtime is tracking events within a workflow’s lifecycle, the WF runtime uses the tracking record
WorkflowInstanceRecord, which inherit from the TrackingRecord object. Along with properties mentioned in
Table 9-2 which are already inherited through TrackingRecord, WorkflowInstanceRecord also includes some
additional properties (see Table 9-3).

Table 9-3.  Unique WorkflowInstanceRecord Properties

Property Description
ActivityDefinitionId Represents the root activity represented as the workflow that produced the
tracking record.
State The current stage of the workflow’s life cycle when the tracking record was created.
WorkflowDefinitionIdentity A new property within WF4.5 of type System.Activities.WorkflowIdentity that
represents the Name, Package, and Version for a workflow.

As the state changes for a workflow instance, WorkflowInstanceRecord is emitted from the WF runtime;
however, other events within the workflow life cycle trigger additional tracking records to be emitted that inherit from
WorkflowInstanceRecord. Each of the tracking records indicated below inherits from WorkflowInstanceRecord, and
therefore inherits the properties from Tables 9-2 and 9-3:
• WorkflowInstanceAbortedRecord is emitted when a workflow instance is aborted. Another
property WorkflowInstanceAbortedRecord has is Reason, which is a string type that
indicates through the WF runtime why a workflow instance was aborted.
• WorkflowInstanceSuspendedRecord is emitted when a workflow instance is suspended and
also has a Reason property indicating why a workflow instance has been suspended.
• WorkflowInstanceTerminatedRecord is emitted when a workflow instance is terminated and
also has a Reason property indicating why a workflow instance was terminated.
• WorkflowInstanceUnhandledExceptionRecord is emitted when a workflow instance has
encountered an unhandled exception. Instead of having a Reason property, it has an
UnhandledException property of type System.Exception indicating the actual exception that
was not managed.

359

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

• WorkflowInstanceUpdatedRecord is a new tracking record emitted within WF4.5 and


occurs when workflow has been versioned or changed. Three new properties exist on the
WorkflowInstanceUpdatedRecord:
• IsSuccessful is a Boolean value indicating if the updated record for a workflow instance
is successful.
• OriginalDefinitionIdentity is a System.Activities.WorkflowIdentity type
indicating the original definition for the workflow identity.
• BlockingActivities is a collection of blocking activities for the workflow instance
being updated.

• Activity state
• Scheduled
• Cancelled
• Fault Propagation
• Bookmark Resumption
• Custom data tracking

Therefore these events have a tracking record that is emitted to indicate when an event occurs. The tracking
records mentioned in the next section also inherit from TrackingRecord, so they also inherit the same properties
indicated in Table 9-4.

Table 9-4.  Unique ActivityStateRecord Properties

Property Description
Activity Represents characteristics about the activity that produced the tracking record. The property type
is System.Activites.Tracking.ActivityInfo and it provides the following properties:
• Id: Unique value representing the activity
• InstanceId: Runtime ID for the activity instance.
• Name: Represented with the activity.
• TypeName: Gets the type name of the activity
Arguments Represents the type IDictionary<string,Object> as the collection of arguments associated with
an activity when the tracking record is emitted.
Variables Represents the type IDictionary<string,Object> as the collection of variables associated with
an activity when the tracking record is emitted.
State Represents the current stage of the activity when the tracking record is emitted.

360

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

ActivityStateRecord
As a workflow instance runs, it is equally important to gain transparency into each activity that is executed within a
workflow. The ActivityStateRecord is emitted when an activity is executed. The ActivityStateRecord provides detailed
data about an activity to easily understand the flow of a workflow; it also includes the properties described in Table 9-4.

ActivityScheduledRecord
When a workflow starts its execution, the WF runtime schedules the root activity of the workflow. After a workflow
starts, activities that contain other child activities can also schedule their children activities. The WF runtime
maintains scheduled activities within a queue/stack of activities. ActivityScheduledRecord is emitted when an
activity is scheduled. Table 9-5 shows the properties that distinguish the tracking record.

Table 9-5.  Unique ActivityScheduledRecord Properties

Property Description
Activity Represents characteristics about the scheduling activity that produced the tracking record.
The property type is System.Activites.Tracking.ActivityInfo and it provides the
following properties:
• Id: Unique value representing the activity.
• InstanceId: Runtime ID for the activity instance.
• Name: Represented with the activity.
• TypeName: Gets the type name of the activity.
Child Represents characteristics about the child activity that is scheduled that produced the tracking
record. The property is also a type of System.Activites.Tracking.ActivityInfo.

FaultPropagatedRecord
When code has an exception, it bubbles up the exception until it is finally handled or it simply fails. However, there is
a software trail that is built during this process called the StackTrace, and the same concept is applied when the WF
runtime emits a FaultPropagateRecord. A FaultPropagateRecord contains data about a fault that occurred within a
workflow activity. Table 9-6 identifies the key properties of the tracking record.

Table 9-6.  Unique FaultPropagatedRecord Properties

Property Description
Fault Represents the System.Exception that produced the tracking record.
FaultHandler Gets the fault handler. The property type is System.Activites.Tracking.ActivityInfo
and it provides the following properties:
• Id: Unique value representing the activity.
• InstanceId: Runtime ID for the activity instance.
• Name: Represented with the activity.
• TypeName: Gets the type name of the activity.

(continued)

361

www.it-ebooks.info
Chapter 9 ■ traCking WorkfloWs

Table 9-6. (continued)

Property Description
FaultSource Represents the activity that generated the fault. The property type is also
System.Activites.Tracking.ActivityInfo.
IsFaultSource A Boolean type value that represents if the handler was the first handler for the fault.

BookmarkResumptionRecord
Bookmarks are used within a workflow when a workflow instance requires an event, sometimes an event that provides
data that a workflow instance requires to continue processing. BookmarkResumptionRecord is emitted when a
bookmark is resumed within a workflow instance. Table 9-7 identifies the key properties of the tracking record.

Unique BookmarkResumptionRecord Properties

Description
Name of the bookmark that is resumed when producing the tracking record.
Gets the scope ID, which is a System.Guid type tied to the bookmark.
Represents the activity that was waiting on the bookmark to resume. The property type is
System.Activites.Tracking.ActivityInfo and it provides the following properties:
• Id: Unique value representing the activity.
• InstanceId: Runtime ID for the activity instance.
• Name: Represented with the activity.
• TypeName: Gets the type name of the activity.
Payload A System.Object type representing the value that was passed with the bookmark as it is resumed.

CustomTrackingRecord
There are times when custom information needs to be returned within a tracking record. A CustomTrackingRecord
is a tracking record used within custom activities for tracking custom data deemed important to a workflow author.
Table 9-8 describes the properties used for tracking custom data.

Table 9-8. Unique CustomTrackingRecord Properties


Property Description
Data Represents the collection of data that is defined as type IDictionary<string,Object>.
Name The unique name that identifies the custom tracking record.
Activity Represents the custom activity that custom data is gathered. It’s type is
System.Activites.Tracking.ActivityInfo and it provides the following properties:
• Id: Unique value representing the activity.
• InstanceId: Runtime ID for the activity instance.
• Name: Represented with the activity.
• TypeName: Gets the type name of the activity.

362

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

State machine workflows also have their own tracking record called StateMachineStateRecord. It was
introduced with the release of the Microsoft .NET Framework 4 Platform Update 1; however, it also is provided with
WF4.5. It inherits from CustomTrackingRecord but provides its own unique properties to track specific state machine
information (see Table 9-9).

Table 9-9.  Unique StateMachineStateRecord Properties


Property Description
StateMachineName Represents the name of the state machine activity that contains the state.
StateName Gets the state name for the executing state when the tracking record is created.

■■Note The ReceiveMessageRecord and SendMessageRecord also inherit from CustomTrackingRecord and are used
for tracking when messages are received and sent within a workflow service instance.

Tracking Profile
All of the tracking records mentioned above are published through the WF runtime, but in order to subscribe to
tracking orders, a tracking profile has to be built. A tracking profile indicates which tracking records need to be
published based on workflow instances. Tracking profiles contain the queries used to select which tracking records
are needed as well as filtering for specific information within a particular tracking record.
There are two approaches for building a tracking profile. One is a standard approach that requires a tracking
profile that subscribes to a generic set of tracking records, and the other is tailored around a subset of data that is
specific for understanding the exact flow of workflow instances.
Tracking profiles can be built using XML elements, placed within a standard .NET configuration like a
Web.config or App.config file when using workflows hosted as WCF services, using the WorkflowServiceHost.
However, tracking profiles are built through code when the workflows are hosted using WorkflowInvoker or
WorkflowApplication. The main advantage of building tracking profiles within a configuration file is that the
profile can be configured or modified during runtime without affecting the running workflow service’s execution.
Listing 9-1 indicates a tracking profile used to track all of a workflow instance’s life cycle stages while being hosted as a
WF service or using the WorkflowServiceHost for hosting a workflow.

Listing 9-1.  Tracking Profile Tracking Workflow Lifecycle


<tracking>
<profiles>
<trackingProfile name="Custom Tracking Profile">
<workflow>
<workflowInstanceQueries>
<workflowInstanceQuery>
<states>
<state name="*"/>
</states>
</workflowInstanceQuery>
</workflowInstanceQueries>
</workflow>
</trackingProfile>
</profiles>
</tracking>

363

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

As a workflow instance processes, each state of the workflow instance lifecycle is tracked. The reason why each
state is tracked is because of the syntax <state name="*"/> and the wildcard syntax it uses, which indicates to get all
states. The same tracking profile can be built using the C# syntax in Listing 9-2.

Listing 9-2.  CustomTracking Profile


static TrackingProfile BuildTrackingProfile()
{
try
{
var profile = new TrackingProfile
{
Name = "Custom Tracking Profile",
Queries =
{
new WorkflowInstanceQuery
{
States = {"*"}
}
}
};
return profile;
}
catch (Exception ex)
{
throw ex;
}
}

ImplementationVisibility
Tracking profiles have a way of subscribing to child activity tracking records that are emitted for children activities
contained within composite activities; however, by default it is turned off. ImplementationVisibility is the property
that can be found of the following tracking profiles:

• ActivityScheduledRecord
• ActivityStateRecord
• FaultPropagationRecord
• CancelRequestedRecord

The ImplementationVisibility property can be set to either


• RootScope: The root activity of a composite activity emits tracking records.
• All: All activities within a composite activity emit tracking records.
Listing 9-3 shows a TrackingProfile object supplied with an ImplementationVisibility property changed to
System.Activities.Tracking.ImplementationVisibility.All.

364

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Listing 9-3.  Changing the Default ImplementationVisibility Property to All


var profile = new TrackingProfile
{
Name = "Custom Tracking Profile",
ImplementationVisibility =
System.Activities.Tracking.ImplementationVisibility.All
}

The same can be done through the configuration for a tracking profile, as indicated in Table 9-4.

<tracking>
<trackingProfile name="Custom Tracking Profile" implementationVisibility="All">

TrackingQuery
An earlier section covered the different types of tracking records that are emitted through the WF runtime; however,
to subscribe to certain types of tracking records, a TrackingQuery is required to be enlisted for identifying the type of
tracking record that should be subscribed to as well as what characteristics of the tracking record should be filtered.
Listing 9-2 illustrates that all WorkflowInstanceRecord types should be subscribed to by using the following syntax:

Queries =
{
new WorkflowInstanceQuery
{
States = {"*"}
}
}

Each type of tracking query that is used for subscribing to tracking records inherits from
System.Activities.Tracking.TrackingQuery:

• ActivityScheduledQuery
• ActivityStateQuery
• BookmarkResumtionQuery
• CancelRequestedQuery
• CustomTrackingQuery
• FaultPropagationQuery
• WorkflowInstanceQuery

In order to track multiple tracking records, the following C# syntax can be used:

Queries =
{
new WorkflowInstanceQuery
{
States = {"*"}
},

365

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

new ActivityStateQuery
{
States={"*"}
}
}

And when the tracking profile is configured using XML, the queries can be stacked, like so:

<workflowInstanceQueries>
<workflowInstanceQuery>
<states>
<state name="*"/>
</states>
</workflowInstanceQuery>

<activityStateQuery>
<states>
<state name="*"/>
</states>
</activityStateQuery>

Note The default value for the ImplementationVisibility property for a tracking record is RootScope.

Tracking Participant
Tracking participants are the vehicle for delivering tracking records. In WF3.x, tracking was delivered out of the box
to SQL Server, but in WF4.x this method is no longer supplied out of the box. Instead, a tracking participant can be
created for pushing tracking records to SQL Server or any other database or file system. Tracking participants are
added to the WF runtime as an extension, so in order to add a tracking participant to a workflow application hosted
either by WorkflowInvoker or WorkflowApplication, the workflow extension needs to be added through code. For
workflows that are either hosted as WCF services or hosted using WorkflowServiceHost, extensions can be added
through the configuration. Tracking participants execute within the same process as the workflow instance, so it is
good practice to make sure code within a tracking participant does not interfere with objects within the workflow or
contain processes which may run for long periods of time.

■■Note  System.Activities.Tracking.EtwTrackingParticipant is the one tracking participant that is provided out


of the box with WF4.x. It loads tracking records that are subscribed to using a tracking profile and emits an Event Tracing
for Windows (ETW) event that consumes the tracking record data.

366

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

BASIC TRACKING

This exercise demonstrates how to track a workflow using the following:


• Tracking Records
• Tracking Profile
• Tracking Participant
This example demonstrates tracking a workflow application so the tracking profile and tracking participant will be
wired up using C# code.
1. Open Visual Studio 2012, and create a new project.
2. Select the Workflow template to see a list of installed workflow templates.
3. Select Workflow Console Application, and name it Apress.Chapter9.
4. Rename the workflow console application to Exercise1. Drag a Flowchart activity onto the
designer canvas.
5. Drag a Writeline activity into the designer canvas and auto-connect it with the Start
activity. Set the Text property to Workflow started.
6. Drag a Delay activity into the designer canvas and auto-connect it with the Writeline
activity that was added within the previous step. Set the Duration property to 00:00:05.
7. Drag another Writeline activity onto the designer canvas and auto-connect it with the
Delay activity. Set the Text property to Workflow finished. See Figure 9-2.

Figure 9-2.  Writeline, Delay, Writeline workflow

367

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

8. Press F5 to run the workflow to make sure everything compiles and the workflow runs
correctly. Figure 9-3 illustrates the expected results. If everything runs correctly, the console
windows will display “Workflow started!” Five seconds later, after the delay activity’s duration
expires, the console should display “Workflow finished!”

Figure 9-3.  Running the workflow

9. Right-click on the solution and add new Visual C# class library. Name it
ConsoleTrackingParticipant.cs. This class will be used to build the custom tracking
participant. Replace the existing code with the following code:

using System;
using System.Activities.Tracking;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WorkflowConsoleApplication1
{
public sealed class ConsoleTrackingParticipant:TrackingParticipant
{
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
try
{
if (record != null)
{
if(record is WorkflowInstanceRecord)
{
WorkflowInstanceRecord InstanceRecord = record as
WorkflowInstanceRecord;

Console.WriteLine(string.Format("{0} InstanceId: {1} Workflow
instance state {2}",

368

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

InstanceRecord.ActivityDefinitionId,
record.InstanceId,
InstanceRecord.State));
}
else if(record is ActivityStateRecord)
{
ActivityStateRecord ActivityRecord = record as
ActivityStateRecord;

Console.WriteLine(string.Format("Activity: {0}
ActivityInstanceId: {1} State : {2}",
ActivityRecord.Activity.Name,
record.InstanceId,
ActivityRecord.State));
}
}
}
catch (Exception ex)
{
throw ex;
}
}
}
}

The ConsoleTrackingParticipant class inherits from TrackingParticipant and overrides the Track
method. Within the Track method, the first parameter, TrackingRecord, is passed in and this is where the
tracking participant serves as a vehicle for exposing and saving tracking record data to external sources like
the file system or data store like SQL Server. The ConsoleTrackingParticipant takes the TrackingRecord
parameter and uses it to check for the type of tracking record being passed. When a WorkflowInstanceRecord
or ActivityStateRecord is passed, the record is interregated and key properties for the tracking records are
displayed to the console.
10. Open the Program.cs file and replace the existing code with the following code:

using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Activities.Tracking;
namespace WorkflowConsoleApplication1
{

class Program
{
static void Main(string[] args)
{
// Create and cache the workflow definition
Activity workflow1 = new Workflow1();
ConsoleTrackingParticipant tracker = new ConsoleTrackingParticipant();
tracker.TrackingProfile = BuildTrackingProfile();

369

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

WorkflowInvoker invoker = new WorkflowInvoker(workflow1);


invoker.Extensions.Add(tracker);
invoker.Invoke();
Console.ReadLine();
}

static TrackingProfile BuildTrackingProfile()
{
try
{
var profile = new TrackingProfile
{
ImplementationVisibility=
System.Activities.Tracking.ImplementationVisibility.All,
Name = "Custom Tracking Profile",
Queries =
{
new WorkflowInstanceQuery
{
States = {"*"}
},
new ActivityStateQuery
{
States={"*"}
}
}
};
return profile;
}
catch (Exception ex)
{
throw ex;
}
}
}
}

The new code in program.cs adds the new ConsoleTrackingParticipant as an extension to the
WorkflowInvoker host. WorkflowInvoker ranks the lowest for interaction with the WF runtime. For instance,
WorkflowApplication provides workflow lifecycle events that are fired during execution where WorkflowInvoker
does not. This reinforces why WF tracking is so important even for workflows hosted through WorkflowInvoker
for tracking a workflow’s execution and gaining insight on how a workflow instance flows.
After adding the tracking participant, the tracking profile is built; it subscribes to two types of tracking records,
WorkflowInstanceRecord and ActivityStateRecord. A WorkflowInstanceQuery and ActivityStateQuery
are used to query the two types of tracking records so that all states within both tracking records are subscribed
to and returned through the tracking participant ConsoleTrackingParticipant.
11. Press F5 and run the workflow to see the tracking data presented within the console.
Figure 9-4 illustrates the results.

370

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Figure 9-4.  Console tracking data

The two types of tracking records WorkflowInstanceRecord and ActivityStateRecord track each of the states
entered for the workflow instance and each activity within the workflow. As the workflow instance is loaded into
memory, tracking information indicates that the workflow has started, but when the tracking data indicates that
the Delay activity starts execution, the WorkflowInstanceRecord emits a record that indicates that the workflow
instance has gone idle. For 5 seconds, no more records are emitted to the console; once the Delay duration
expires, tracking continues until the workflow finally completes.
Another concept to discuss is the ImplementedVisibility property. Remember that the code that was added for
the tracking profile set the property to ImplementedVisibility.All and the default value is actually RootScope,
which means that only the root activity emits a tracking record and not the child activities.

But this is not the reason why the workflow records and activity records are being emitted. The next steps will
demonstrate how tracking records are emitted based on the setting of the ImplementedVisibility property.
12. To test the point, comment out this line of code
//ImplementationVisibility = System.Activities.Tracking.ImplementationVisibility.All,

and press F5 to re-run the workflow instance. The same tracking records are emitted as in Figure 9-4.
Drag a Sequence activity and add it to the designer canvas (see Figure 9-5). Click and hold the left
mouse button and drag a square around the two WriteLine and Delay activities. The three activities
should be highlighted in blue. Another way to select the activities is to press Ctrl-A at the same time,
releasing the A key while still pressing the Ctrl key and with the mouse clicking on the Sequence activity.

371

www.it-ebooks.info
Chapter 9 ■ traCking WorkfloWs

Figure 9-5. Selecting multiple activities

13. press Ctrl-x at the same time to cut the selected activities. Double-click the Sequence activity
and press Ctrl-v at the same time to paste the activities within the Sequence activity
(see figure 9-6).

Figure 9-6. Child activities within a Sequence activity

372

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

14. After the activities are contained within the Sequence activity, go back to the Flowchart
activity and auto-connect the Sequence activity to the Start activity (see Figure 9-7).

Figure 9-7.  Auto-connecting the Sequence activity

15. Press F5 to re-run the workflow instance. Still the same tracking records are emitted as in
Figure 9-4.
The last part of this exercise will create a composite activity to demonstrate the ImplementationVisibility
settings.
16. Right-click the WorkflowConsoleApplication1 and add a new Item. Select the Workflow
template and select a new activity library. Change the name of the new activity to
compositeActivity.xaml.

17. Open the Workflow1.xaml file and select the Sequence activity that has the container
of children activities. Press Ctrl-x to cut the Sequence activity and add it within the
compositeActivity.xaml file. Double-click the Sequence activity to make sure that it still
contains its child activities.
18. Right-click the WorkflowConsoleApplication1 project and select Build. This will add the
new composite activity to the activity toolbox.
19. Open the Workflow1.xaml workflow and drag the compositeActivity activity from the tool
box and auto-connect it with the Start activity (see Figure 9-8).

Figure 9-8.  Flowchart containing a composite activity

373

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

20. With the following code still commented out within the Program.cs file

//ImplementationVisibility = System.Activities.Tracking.ImplementationVisibility.All,

press F5 to run the workflow. Notice the difference in results between Figure 9-9 and Figure 9-4.

Figure 9-9.  Tracking of composite activity

21. Uncomment the commented code within the Program1.cs file:

ImplementationVisibility = System.Activities.Tracking.ImplementationVisibility.All,

22. Press F5 again to run the workflow instance and this time notice that the results are the
same as Figure 9-4 as far as the same tracking records that are emitted.
This exercise demonstrated how to implement tracking for a workflow by using a custom tracking profile and
tracking participant for tracking a workflow instance that was hosted using WorkflowInvoker. The tracking
record information was sent to the console to demonstrate the flow of the workflow. This exercise also
demonstrated how to use the ImplementationVisibility property of a TrackingRecord to control whether
the root activity of a composite activity is the only activity subscribed to for getting tracking records or if children
tracking records also should be subscribed.

WorkflowServiceHost Tracking
The Basic Tracking exercise demonstrated building the tracking profile and adding a tracking participant as a
workflow extension through code because WorkflowInvoker was used to host the workflow as an application.
But when a workflow is either hosted as a WCF service or the workflow is hosted using the WCF service host
WorkflowServiceHost, tracking participants and tracking profiles can be configured using XML, as mentioned
earlier. There is one caveat: a tracking participant still requires custom code for adding it as a workflow extension.
Therefore a custom service behavior is required, which can be built within a custom class that implements the
interface System.ServiceModel.Description.IServiceBehavior. In order to use the custom service behavior,
a custom behavior extension element is required, too, that inherits from System.ServiceModel.Configuration.
BehaviorExtensionElement.

374

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

IServiceBehavior
Before a custom tracking participant can be configured through a configuration file for gathering data on subscribed
tracking records from a workflow hosted through WorkflowServiceHost, a tracking participant must be added as
an extension using a custom service behavior. The IService interface provides a way to insert and edit custom
extensions for a WCF service. The interface exposes three methods:
• AddBindingParameters: Provides custom data for binding elements to support the service.
• ApplyDispatchBehavior: Exposes the serviceHostBase so custom extension objects can be
added.
• Validate: Provides a way to check that the service can run without any issues.
In the next exercise, a generic tracking behavior will be built that can be used for configuring custom tracking
participants. The goal for doing this is so different tracking participants can be used and changed out during runtime
for a workflow instance.

BehaviorExtensionElement
System.ServiceModel.Configuration.BehaviorExtensionElement enables developers to add their own custom
configuration within the System.ServiceModel section for a configuration file. It works with the .NET runtime, so
assemblies can use dynamic parameters through configuration. BehaviorExtensionElement provides a way to define
custom configuration elements that can be used to customize service or endpoint behaviors. To build a custom
behavior extension, a custom class must inherit from BehaviorExtensionElement. There are two members of the
object that need to overridden:
• BehaviorType: Gets the behavior type.
• CreateBehavior: Creates a behavior extension based on custom configuration properties
created and provided to the behavior extension.

■■Tip  EtwTrackingParticipant does not require any custom service behaviors or extension elements when
configured as a tracking participant for either a workflow service or workflow hosted using WorkflowServiceHost.

TRACKING CONFIGURATION

This exercise will demonstrate how to configure tracking for workflows hosted through the
WorkflowServiceHost. A custom tracking extension element and tracking behavior will be created to show how
they are used to configure a custom tracking participant.
1. Open Visual Studio 2012 and create a new project within the previous exercise’s solution
Apress.Chapter9.

2. Select the Workflow template to see a list of installed workflow templates


and select workflow console application. Make sure to name of the project is
WorkflowConsoleApplication2.

3. Drag a Flowchart activity onto the designer canvas.


4. Drag a Receive activity onto the designer canvas and auto-connect it to the Start activity.

375

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

5. Set the Operation Name property for the Receive activity to StartWorkflow (see Figure 9-10).

Figure 9-10.  Setting the OperationName property

6. Click on the Receive activity to view the Receive activity’s properties within the property
window. Check the CanCreateInstance checkbox and set the ServiceContractName property
to ItrackingDemoService (see Figure 9-11).

Figure 9-11.  Setting CanCreateInstance and ServiceContractName

376

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

This exercise will not use correlation nor pass parameters, so these properties will not change.
7. Right-click on the WorkflowConsoleApplication2 and add a new Item. Select the
Workflow template and select a new activity library. Change the name for the new activity to
ProcessWork.xaml.

8. Drag a Writeline activity into the designer canvas and auto-connect it with the Start
activity. Set the Text property to Started processing work.
9. Drag a Delay activity into the designer canvas and auto-connect it with the Writeline
activity that was added within the previous step. Set the Duration property to 00:00:05.
10. Drag another Writeline activity onto the designer canvas and auto-connect it with the
Delay activity. Set the Text property to Finished processing work (see Figure 9-12).

Figure 9-12.  ProcessWork Workflow

11. Right-click the project created for this exercise and select “Set as StartUp Project” so the
project starts first the next time the solution runs, and then right-click the new project and
select Build to make sure the project builds successfully and the ProcessWork activity shows
up within the activity toolbox.
12. Drag the new ProcessWork activity onto the designer canvas and auto-connect it to the
existing Receive activity (see Figure 9-13).

377

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Figure 9-13.  Adding a composite activity to a Receive activity

13. Right-click the new project and select Build to make sure the project builds successfully.
The workflow that will be used for this exercise has been built. The next steps for the exercise will show how
to configure workflow tracking through configuration. The next file that will be created will allow the tracking
participant to be configured based on custom properties that can be set within a configuration file.
14. Right-click the WorkflowConsoleApplication2 project and add a new class. Rename the
class as GenericTrackingExtensionElement.cs and replace the existing code with the
following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ServiceModel.Configuration;
using System.Configuration;
using System.Activities.Tracking;

namespace WorkflowConsoleApplication2
{
public class GenericTrackingExtensionElement:BehaviorExtensionElement
{
[ConfigurationProperty("profileName", DefaultValue= null,IsKey=false, IsRequired =
false)]
public string ProfileName
{
get { return (string)this["profileName"]; }
set { this["profileName"] = value; }
}

378

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

[ConfigurationProperty("participantTypeName", DefaultValue = null, IsKey = false,


IsRequired = false)]
public string ParticipantTypeName
{
get { return (string)this["participantTypeName"]; }
set { this["participantTypeName"] = value; }
}

public override Type BehaviorType
{
get
{
return typeof(GenericTrackingBehavior);
}
}
protected override object CreateBehavior()
{
return new GenericTrackingBehavior(ProfileName, ParticipantTypeName);
}
}
}

This code creates two properties, profileName and participantTypeName, that can be used for configuring
a custom service behavior. The profileName is used for choosing the correct tracking profile and
participantTypeName is used to identify the type of tracking participant that should be configured. There are
two other members within the BehaviorExtensionElement abstract class that are overridden, BehaviorType
property and the function CreateBehavior. BehaviorType is used to return the type of the service
behavior and CreateBehavior returns a new instance of the service behavior, passing the two new properties
profileName and participantTypeName.
15. Right-click the WorkflowConsoleApplication2 project and add a new class. Rename the
class as GenericTrackingBehavior.cs and replace the existing code with the following
code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ServiceModel.Description;
using System.ServiceModel.Activities;
using System.ServiceModel;
using System.Activities.Tracking;
using System.ServiceModel.Activities.Tracking.Configuration;
using System.Web.Configuration;
using System.Configuration;

namespace WorkflowConsoleApplication2
{
public class GenericTrackingBehavior:IServiceBehavior

379

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

{
private string trackingProfileName { get; set; }
public string ParticipantTypeName { get; set; }
//Constructor which passes in the tracking profile name
public GenericTrackingBehavior(string profileName,string participantTypeName)
{
try
{
ParticipantTypeName = participantTypeName;
trackingProfileName = profileName;
}
catch (Exception ex)
{
throw ex;
}
}

//method that is implemented through the IServiceBehavior contract
//Handles adding the tracking participant to the WorkflowServiceHost
public virtual void ApplyDispatchBehavior(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
WorkflowServiceHost workflowServiceHost = serviceHostBase as
WorkflowServiceHost;

try
{
if (null != workflowServiceHost)
{
string workflowDisplayName = workflowServiceHost.Activity.
DisplayName;
TrackingProfile trackingProfile = GetTrackingProfileFromConfig
(trackingProfileName, workflowDisplayName);

var participant = Activator.CreateInstance(Type.
GetType(ParticipantTypeName));
TrackingParticipant tp = participant as TrackingParticipant;
tp.TrackingProfile = trackingProfile;
workflowServiceHost.WorkflowExtensions.Add(tp);
}
}
catch (Exception ex)
{
throw ex;
}
}

TrackingProfile GetTrackingProfileFromConfig(string profileName, string
displayName)
{
TrackingProfile trackingProfile = null;

380

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

TrackingSection trackingSection = (TrackingSection)WebConfigurationManager.


GetSection("system.serviceModel/tracking");
if (trackingSection == null)
{
return null;
}

//Find the profile with the specified profile name in the list of profile
found in config
var match = from p in new List<TrackingProfile>(trackingSection.
TrackingProfiles)
where p.Name == profileName
select p;

if (match.Count() == 0)
{
throw new ConfigurationErrorsException(string.Format("Could not find a
profile with name '{0}'", profileName));
}
else
{
trackingProfile = match.First();
}

//No matching profile with the specified name was found
if (trackingProfile == null)
{
//return an empty profile
trackingProfile = new TrackingProfile()
{
ActivityDefinitionId = displayName
};
}

return trackingProfile;
}

public virtual void AddBindingParameters(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.
ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.
BindingParameterCollection bindingParameters)
{
}
public virtual void Validate(ServiceDescription serviceDescription, System.
ServiceModel.ServiceHostBase serviceHostBase)
{
}
}
}

381

www.it-ebooks.info
Chapter 9 ■ traCking WorkfloWs

GenericTrackingBehavior implements the interface IServiceBehavior, so it can be used to add new service
extensions. in this case, it will use the method ApplyDispatchBehavior to add a new WorkflowExtension
to the WorkflowServiceHost, which is very similar to the code that was used in the Basic tracking exercise.
the only difference is that GenericTrackingBehavior accepts the configuration properties of profileName
and participateTypeName as they are added to its constructor. GenericTrackingBehavior also checks
the tracking configuration to try to find the tracking profile that was designated within the configuration.
even if there is only one tracking record added within the configuration, it still needs to load the profile.
GetTrackingProfileFromConfig first finds the tracking section through code of the configuration file. if there is
a tracking section added to the configuration, then linQ is used to find the tracking profile based on the name of
the tracking profile that was passed into the constructor. AddBindingParameters and Validate are not used but
still have to be implemented for the interface IServiceBehavior. the next step is to set up the host for hosting
the workflow service.
16. open the Program1.cs file and replace the existing code with the following:

using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.ServiceModel.Activities;

using System.ServiceModel;

namespace WorkflowConsoleApplication2
{
[ServiceContract]
public interface ITrackingDemoService
{
[OperationContract(IsOneWay = true)]
void StartWorkflow();
}
class Program
{
static void Main(string[] args)
{
// Create and cache the workflow definition
Activity workflow1 = new Workflow1();
using (var host = new WorkflowServiceHost(workflow1))
{
host.Open();
// Create a client that sends a message to create an instance of the workflow.
ITrackingDemoService client = ChannelFactory<ITrackingDemoService>.
CreateChannel(new BasicHttpBinding(), new EndpointAddress("http://localhost:8080/
TrackingDemoService"));
client.StartWorkflow();
Console.ReadLine();
}
}
}
}

382

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

There is not much code used for hosting the workflow as a service other than instantiating
WorkflowServiceHost. This is because all of the settings are configured within the App.config, which will be
added later. Other than starting the workflow service host, most of the code within the Program1.cs file simply
calls the workflow service once it has been hosted. The ITrackingDemoService contract is used by the service
and is used to build a client that calls the service over BasicHttpBinding using the EndPointAddress URL
http://localhost:8080/TrackingDemoService. The next steps set up the app.config file for hosting the
workflow service and configuring a custom tracking participant.
17. Make sure that the project has the Framework references shown in Figure 9-14.

Figure 9-14.  Framework references

18. Open the App.config file and replace the existing XML with the following:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service name="Workflow1" behaviorConfiguration="">
<endpoint address="" binding="basicHttpBinding" contract="ITrackingDemoService" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/TrackingDemoService"/>
</baseAddresses>
</host>
</service>
</services>
<extensions>

383

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

<behaviorExtensions>
<add name="ConsoleTracking" type="WorkflowConsoleApplication2.
GenericTrackingExtensionElement, WorkflowConsoleApplication2, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior>
<ConsoleTracking profileName="CustomTrackingProfile" participantTypeName="Workf
lowConsoleApplication2.ConsoleTrackingParticipant" />
</behavior>
</serviceBehaviors>
</behaviors>
<tracking>
<profiles>
<trackingProfile name="CustomTrackingProfile">
<workflow>
<workflowInstanceQueries>
<workflowInstanceQuery>
<states>
<state name="*"/>
</states>
</workflowInstanceQuery>
</workflowInstanceQueries>
</workflow>
</trackingProfile>
</profiles>
</tracking>
</system.serviceModel>
</configuration>

The first part of the configuration to address is the settings for the workflow exposed as a service. Within the
services section, the service with the name “Workflow1” is defined, which must match the workflow name. The
service is also set to use an endpoint binding of basicHttpBinding and the contract ITrackingDemoService.
The base address is set to http://localhost:8080/TrackingDemoService.
The next section of the configuration is the extensions section; it adds a behavior extension. The extension is used
to customize the configuration for the custom behavior extension that is in the behaviors section. The behavior
extension sets the name of the behavior to be used, plus the type, namespace, version, and PublicToken. After
setting the behavior extension, the custom service behavior is ready to be configured. The service behavior is
configured by using the name of the behavior extension as the XML element. The ProfileName property is set
to the name CustomTrackingProfile for the tracking profile configured within the profiles section and the
participantTypeName is set to the type WorkflowConsoleApplication2.ConsoleTrackingParticipant for the
tracking participant that will be used for tracking the workflow service. The profiles section contains the tracking
profile that will be used to track the workflow service.
19. Select and copy the ConsoleTrackingParticipant.cs file used in Exercise1 within the
other project. Add it to the current project WorkflowConsoleApplication2.

384

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

20. Make sure that WorkflowConsoleApplication2 is still set as the startup project and press
F5 to run the solution. The results in Figure 9-15 indicate that tracking for the workflow
has been configured.

Figure 9-15.  Tracking data sent to the console

This exercise showed how to implement tracking for a workflow hosted by a WorkflowServiceHost through
configuration.

Filtering Tracking Records


The Basic Tracking exercise demonstrated subscribing to the published tracking records, WorkflowInstanceRecord
and ActivityStateRecord. Filtering tracking records is important for subscribing to specific information about
a workflow instance’s execution. This section will look at each of the tracking records to understand how specific
workflow instance data can be tracked. The Tracking Configuration exercise demonstrated how to track workflows
through configuration, which makes it easier for customizing tracking data by changing tracking profiles on the fly.
In order to demonstrate filtering of tracking records, the workflow that will be used will build on the tracking
infrastructure that was implemented within the Tracking Configuration exercise; however, I did change out the
tracking participant, ConsoleTrackingParticipant, and created another one so the entire tracking record could be
serialized to XML and written to the console window. The only thing that needed to change in order to do this was the
code within the override Track method, which was replaced with the code in Listing 9-4.

Listing 9-4.  Serializing the Tracking Records


protected override void Track(TrackingRecord record, TimeSpan timeout)
{
string x = string.Empty;
try
{
var serialize = new DataContractSerializer(record.GetType());
XmlTextWriter writer = new XmlTextWriter(Console.Out)

385

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

{
Formatting= Formatting.Indented
};
serialize.WriteObject(writer, record);
writer.WriteRaw(Environment.NewLine);
writer.Flush();
}
catch (Exception ex)
{
throw ex;
}
}

The examples for filtering tracking will build on the simple workflow service illustrated in Figure 9-16.

  Simple tracking workflow

The WorkflowInstanceRecord uses the WorkflowInstanceQuery for filtering tracking records. However, let’s first
demo all of the records that will be returned when “*” is used as a wildcard, demonstrated with the tracking profile
illustrated in Listing 9-5 for pulling all of the workflow instance tracking records with the new tracking participant.

Listing 9-5.  Serializing the Tracking Records


<trackingProfile name="CustomTrackingProfile">
<workflow>
<workflowInstanceQueries>
<workflowInstanceQuery>
<states>
<state name="*"/>
</states>
</workflowInstanceQuery>
</workflowInstanceQueries>
</workflow>
</trackingProfile>

Figure 9-17 shows that the workflow instance tracking records have been serialized, showing the properties that
are provided on the tracking record.

386

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Figure 9-17.  Serialized workflow instance tracking record

To filter the tracking records for when the workflow instance starts, the tracking profile can be changed to

<states>
<state name="Started"/>
</states>

The result is

<WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
<EventTime>2012-03-24T00:32:56.4870156Z</EventTime>
<InstanceId>3d4cc291-864c-40c3-9772-e5769ec7db60</InstanceId>
<Level>Info</Level>
<RecordNumber>0</RecordNumber>
<ActivityDefinitionId>Workflow1</ActivityDefinitionId>
<State>Started</State>
<WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.
Activities" i:nil="true" />
</WorkflowInstanceRecord>

387

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Tracking when the workflow also ends requires the following change:

<states>
<state name="Started"/>
<state name="Completed"/>
</states>

The result is

<WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
<EventTime>2012-03-24T00:45:00.3395546Z</EventTime>
<InstanceId>026aa5d3-5457-4b1e-8d65-7d37904e2077</InstanceId>
<Level>Info</Level>
<RecordNumber>0</RecordNumber>
<ActivityDefinitionId>Workflow1</ActivityDefinitionId>
<State>Started</State>
<WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.
" i:nil="true" />
</WorkflowInstanceRecord>
<WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
">
<EventTime>2012-03-24T00:45:00.4176796Z</EventTime>
<InstanceId>026aa5d3-5457-4b1e-8d65-7d37904e2077</InstanceId>
<Level>Info</Level>
<RecordNumber>5</RecordNumber>
<ActivityDefinitionId>Workflow1</ActivityDefinitionId>
<State>Completed</State>
<WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.
Activities" i:nil="true" />
</WorkflowInstanceRecord>

This type of tracking filter is more practical for tracking starting and completing of a workflow instance. Note
that the EventTime property is set and can be used to determine the length of time it took for one workflow instance
over others, but what happens when unanticipated issues happen to a workflow instance? Without either tracking all
workflow instance records or specifying aborted workflow instances within the tracking record, they won’t be seen.
In fact, if the workflow throws an exception, the only indication that something might be wrong is that the Completed
state tracking record is never received, therefore the tracking profile needs to add Aborted as a filtered state as well.

<states>
<state name="Started"/>
<state name="Aborted"/>
<state name="Completed"/>
</states>

To test this, add a Throw activity that will simulate an exception within the workflow (see Figure 9-18).

388

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Figure 9-18.  Adding a Throw activity

Now the WorkflowInstanceAbortedRecord will be tracked, as illustrated in the following results:

<WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="


http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
<EventTime>2012-03-24T01:54:38.0475625Z</EventTime>
<InstanceId>285ff549-67e2-4074-a56d-2c3ed596f014</InstanceId>
<Level>Info</Level>
<RecordNumber>0</RecordNumber>
<ActivityDefinitionId>Workflow1</ActivityDefinitionId>
<State>Started</State>
<WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Activities"
i:nil="true" />
</WorkflowInstanceRecord>
<WorkflowInstanceAbortedRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
<EventTime>2012-03-24T01:54:38.1881875Z</EventTime>
<InstanceId>285ff549-67e2-4074-a56d-2c3ed596f014</InstanceId>
<Level>Info</Level>
<RecordNumber>6</RecordNumber>
<ActivityDefinitionId>Workflow1</ActivityDefinitionId>
<State>Aborted</State>
<WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Activities"
i:nil="true" />
<Reason>Here is an exception</Reason>
</WorkflowInstanceAbortedRecord>

389

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Notice that there is not a Completed state record, because the workflow aborts rather than completes. The
WorkflowInstanceAbortedRecord contains the reason, which maps back to the exception message. If an exception
caused a workflow to abort, than the WorkflowInstanceUnhandledExceptionRecord can be tracked too by querying
its state

<states>
<state name="UnhandledExeption"/>
</states>

so additional information can be provided for debugging.

Activity State
DoSomeWork has been


public class DoSomeWork:CodeActivity
{
[RequiredArgument]
public InArgument<int> MillaSecondsToWait{ get; set; }

protected override void Execute(CodeActivityContext context)
{
Thread.Sleep(context.GetValue(SecondsToWait));
}
}

Adding the new activity, the milliseconds are set to 5000, simulating 5 seconds for work being processed
(see Figure 9-19).

Figure 9-19.  Adding a custom code activity DoSomeWork

390

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

A new tracking profile is added to the configuration for just tracking activity records:

<trackingProfile name="ActivityTrackingProfile">
<workflow>
<activityStateQueries>
<activityStateQuery>
<states>
<state name="*"/>
</states>
</activityStateQuery>
</activityStateQueries>
</workflow>
</trackingProfile>

The service behaviors custom property profileName is changed to ActivityTrackingProfile so the activity
records can be tracked.

■■Note Tracking profiles can contain different tracking queries. For example, a tracking profile can contain a workflow
instance and activity state queries together.

After running the workflow using the changed profile, all of the activity states for each activity in the workflow
get tracked, but you’re interested in tracking just the custom DoSomeWork activity. So instead of getting all of the other
records, you can just see the custom activity’s execution. The tracking profile needs to be changed to

<activityStateQuery activityName="DoSomeWork">
<states>
<state name="*"/>
</states>
</activityStateQuery>

The results within the console show the DoSomeWork activity's execution.

<ActivityStateRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
<EventTime>2012-03-24T16:29:04.2771797Z</EventTime>
<InstanceId>5562743c-be60-428c-9751-d933636ee2de</InstanceId>
<Level>Info</Level>
<RecordNumber>4</RecordNumber>
<Activity>
<Id>1.3</Id>
<InstanceId>7</InstanceId>
<Name>DoSomeWork</Name>
<TypeName>CustomTrackingQueries.DoSomeWork</TypeName>
</Activity>
<State>Executing</State>
<arguments xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
<variables xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</ActivityStateRecord>
<ActivityStateRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="

391

www.it-ebooks.info
Chapter 9 ■ traCking WorkfloWs

http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
<EventTime>2012-03-24T16:29:09.3240547Z</EventTime>
<InstanceId>5562743c-be60-428c-9751-d933636ee2de</InstanceId>
<Level>Info</Level>
<RecordNumber>5</RecordNumber>
<Activity>
<Id>1.3</Id>
<InstanceId>7</InstanceId>
<Name>DoSomeWork</Name>
<TypeName>CustomTrackingQueries.DoSomeWork</TypeName>
</Activity>
<State>Closed</State>
<arguments xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
<variables xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />

Notice that the EventTime property is exactly 5 seconds, which represents the duration used to sleep the thread
MilliSecondsToWait is not included, so the tracking profile can be
arguments value, like so:

<argument name="MilliSecondsToWait"/>

The results are

<arguments xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:KeyValueOfstringanyType>
<d2p1:Key>MilliSecondsToWait</d2p1:Key>
<d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:int"
>5000</d2p1:Value>
</d2p1:KeyValueOfstringanyType>
</arguments>

including the value of 5000 for the argument. Variables can also be tracked if required using the <variables>
collection query.

  Caution Make sure to get the Name properties spelling and case right when querying tracking records. the
value MillisecondsToWait for the argument to track would not have returned the argument because the argument is
MilliSecondsToWait.

Custom Data Tracking


Custom activities can also have custom data tracked that is neither a variable nor an argument. These are scenarios
where data needs to be tracked because it either is retrieved from other sources outside of the workflow or it is data
that is important to track so it can be referenced later—like transaction numbers or date stamps. A custom activity can
create a CustomTrackingRecord and its data property can be used to hold data that needs to be published and later
subscribed to using a CustomTrackingQuery.

392

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

The DoSomeWork activity has been changed and now includes a new using statement using System.Activities.
Tracking; and the following code was added within the Execute method:

var record = new CustomTrackingRecord("WorkingLevel")
{
Data =
{
{"Scale", 10},
{"ScaleDescription", "Pretty dawg on hard!"}
}
};

context.Track(record);

This code builds a new CustomTrackingRecord and sets the Data property to two IDictionary<string,
Object> values, Scale and ScaleDescription. The tracking profile is changed to query all custom tracking records,
like so:

<customTrackingQueries>
<customTrackingQuery activityName="*"/>
</customTrackingQueries>

The results are

<CustomTrackingRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns=
"http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
<EventTime>2012-03-24T17:25:26.4978828Z</EventTime>
<InstanceId>5b64b301-201b-49c4-9ee1-4f5d10cc4234</InstanceId>
<Level>Info</Level>
<RecordNumber>4</RecordNumber>
<Activity>
<Id>1.5</Id>
<InstanceId>7</InstanceId>
<Name>DoSomeWork</Name>
<TypeName>CustomTrackingQueries.DoSomeWork</TypeName>
</Activity>
<Name>WorkingLevel</Name>
<data xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:KeyValueOfstringanyType>
<d2p1:Key>Scale</d2p1:Key>
<d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:int">
10</d2p1:Value>
</d2p1:KeyValueOfstringanyType>
<d2p1:KeyValueOfstringanyType>
<d2p1:Key>ScaleDescription</d2p1:Key>
<d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:string">Pretty
dawg on hard!</d2p1:Value>
</d2p1:KeyValueOfstringanyType>
</data>
</CustomTrackingRecord>

393

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

This shows the data values that were set within the custom activity DoSomeWork. If there were other custom activities
but only custom data from the DoSomeWork activity is required, the tracking profile’s activtyName can be changed, like so:

<customTrackingQuery activityName="DoSomeWork"/>

Or, if other custom activities within the workflow have a tracking record called WorkingLevel that needs to be
tracked, the profile can be changed to

<customTrackingQuery activityName="*" name="WorkingLevel"/>

which will return back tracked data for WorkingLevel for each activity.

<annotation name="LevelOfWork" value="These records indicate how hard the author


is working!"/>
</annotations>

Annotations can even be used to categorize records, like so:

<annotations>
<annotation name="Publisher" value="Apress"/>
<annotation name="BookName" value="Pro WF4.5"/>
<annotation name="LevelOfWork" value="These records indicate how hard the author is
working!"/>
</annotations>

This way other tracking records can share the same categories like Publisher and BookName for consolidating how
hard other authors are working.

ETW Tracking Participant


Tracking workflows within Event Tracing for Windows (ETW) is provided out of the box with WF4.x and provides a
natural way for viewing tracked workflows within the Event Viewer, a centralized place that provides visualization into
the health for the system and custom applications. Many internal processes are logged to the Event Viewer, so it can
be used to monitor the health of internal and external processes.

394

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

There are two ways of setting up the ETW tracking participant while using it to track workflow services or
workflows hosted using WorkflowServiceHost. One way is to use code for workflow applications, hosted by
WorkflowApplication or WorkflowInvoker, as demonstrated here:

Activity workflow1 = new Workflow1();
WorkflowInvoker invoker = new WorkflowInvoker(workflow1);
var tracker =
new EtwTrackingParticipant
{
TrackingProfile = BuildTrackingProfile()
};
Invoker.Extensions.Add(tracker);
Invoker.Invoke();

Another way is through a configuration file when using WorkflowServiceHost:

<behaviors>
<serviceBehaviors>
<behavior>
<etwTracking profileName="Sample Tracking Profile" />
</behavior>
</serviceBehaviors>
<behaviors>

Setting up the configuration for the ETW tracking participant is different than setting up other custom tracking
participants because there is no need to add it as a custom extension to the WF runtime. Therefore, custom service
behavior and element extensions do not need to be built.
The Event Viewer is provided within the operating system and can be found by clicking Start on Windows and
typing “Event Viewer” (see Figure 9-20).

Figure 9-20.  Searching for the Event Viewer


395

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

After opening the Event Viewer, tracking records can be found by expanding the Applications and Services Logs
➤ Microsoft ➤ Windows ➤ Application Server-Applications ➤ Microsoft-Windows-Application Server Applications/
Analytic node (see Figure 9-21).

  Event Viewer location for WF tracking

If the logs are disabled, they can be enabled by right-clicking on the node and selecting Enable Log (see Figure 9-22).

Figure 9-22.  Enabling the logs

396

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Using the same project as before to demonstrate tracking queries, the custom service behavior is commented
out and the etwTracking is included as well as the same tracking profile for tracking custom tracking data
(see Figure 9-23).

Figure 9-23.  Configuring ETW tracking

Running the project again will query the same tracking records for custom data like before, but this time the
records will be viewable through the Event Viewer (see Figure 9-24).

Figure 9-24.  Viewing the tracked records within the log

397

www.it-ebooks.info
Chapter 9 ■ Tracking Workflows

Summary
Workflow instances run within a black box called the WF runtime, which provides limited information based on
the current state within a workflow instance’s life cycle. Workflow tracking publishes different types of tracking
records based on most of the characteristics that are important to track, and this is why it is important to have a good
understanding of what data is available and how to track workflow instance execution.
Three core concepts for tracking workflow instances were covered in this chapter. The different tracking records
provide the detailed data that can be tracked. Tracking participants provide a way to touch, save, and view tracked
data. And tracking profiles allow customization of what tracking information is subscribed to from tracking records.
Tracking workflows is also a good way to see how WF4.5 works under the hood, so things like scheduling
workflows and asynchronous activity execution can be monitored. Debugging is also greatly improved by tracking
workflows because issues can be pinpointed to better understand different flow patterns. Tracked information in
WF4.x can be sent to ETW, which provides a natural way for tracking workflow instances; however, using a custom

A tracking store is different from using a line-of-business application for storing data, so there are many factors

The next chapter will change direction and dive into rehosting the WF designer so that workflows can be

398

www.it-ebooks.info
Chapter 10

Rehosting the Workflow Designer

Each chapter thus far has focused on building workflows within VS2012, which is primarily the integrated
development environment (IDE) for building .NET software. There are several development tools within Visual
Studio, including WF, that also provide their own visual software designers. For instance, ASP.NET/MVC has its own
web designer for visually designing and changing a web page’s HTML. Windows Presentation Framework (WPF) also
has a designer for adding user controls and changing the XAML used to render rich client user interfaces (UI). Finally,
Entity Framework (EF), which abstracts the data plumbing code between applications and databases, also has a
designer for modeling relationships among table structures within a database.
These Microsoft technologies rely on Visual Studio or other third party software development tools for
developing software applications. However, authoring workflows should not be limited to only using Visual Studio
like some of the technologies just mentioned. WF allows rehosting of its WF designer outside of Visual Studio and
hosted within a custom application. This is how WF stands out from other development technologies mentioned
earlier, because these technologies embed their visual designer within Visual Studio for building custom solutions by
developers and are not geared to the basic end user for customizing or adding additional functionality to software.
A higher level of development skills than most end users possess is required for customizing software and this is
where WF crosses over the boundary between technical users and basic end users—by letting non-developers modify
business logic within applications.
Many savvy business people who are non-technical are well versed in the day-to-day business processes they
follow. End users do not have to be technical to understand business processes, so why not provide the end user with
a solution for modeling business processes while at the same time providing more flexibility to software because it
can be easily extended by end users? WF focuses on specifying the business process and separating it out from an
application by proposing a new architecture where business processes are instead hosted within applications rather
than developed internally within applications.
In order for non-developers to design workflows, the WF designer can be rehosted within a custom application
to take advantage of not requiring a Visual Studio license for building workflows. Although workflows provide a
natural way to model business processes, end users should not be provided the same technical functionality that WF
provides within Visual Studio. The goal should be to abstract most of the inner workings of WF, such as management
of workflows within the WF runtime, tracking, and persistence. Providing too much technical flexibility and letting
end users make mistakes while building custom workflows could be a recipe for disaster, so even though WF provides
a way for rehosting the WF designer, developers that implement solutions that rehost the WF designer should
strategically focus on the level of functionality that is needed; thus end users will have parameters around what
functionality that can be performed while rehosting the WF designer.
One important thought for rehosting workflows is aligning the technical level required for authoring workflows
with the technical level anticipated for the end users who will be building workflows. In most cases, rehosting the
WF designer will be built within custom software for empowering non-technical users to perform tasks that are
extremely technical without requiring that they understanding the technical abstraction of what is being performed
as a workflow runs. Custom workflow activities can be built and rehosted that pertain to a particular business domain,
one that the end user should be familiar with. A business domain refers to a specific type of business (for example,
healthcare or banking) and WF activities can be written to help manage a particular business domain.

399

www.it-ebooks.info
Chapter 10 ■ Rehosting the Workflow Designer

Particular WF activities that only provide a level of anticipated flow for the logic of workflows should also be
included. Some of these WF activities are out-of-the-box activities that provide conditions that a majority of end
users can understand and feel comfortable using. It is probably not a good idea to provide out-of-the-box activities
that pertain to development functionality like interacting with services, looping through collections, error handling,
etc. This functionality can be provided, but it should be abstracted in such a way that all the end user has to do is
drag and drop the activities that provide this functionality without having to think about how configure the workflow
activities. When thinking about what workflow activities should be rehosted, make sure that it is as simple as possible
to configure the activities without being too technical.
This chapter demonstrates the components that can be rehosted within custom software. A major goal of the WF
team was to make the steps for rehosting parts of the WF designer simple. Because WF4 was rewritten from scratch,
WPF was used for all of the visualization provided. Rehosting the WF designer carries over into WF4.5, so this chapter
will walk you through the following:
• WF components that are rehostable
• Rehosting the WF designer
• Managing workflows built within a rehosted WF designer
• Running the workflows on the fly

• WF designer
• WF toolbox
• WF activities
• WF properties

WF Designer
The WF designer is the main component required to be rehosted for authoring workflows within custom software
instead of Visual Studio. Figure 10-1 shows the WF designer being hosted within a WPF application, which looks
similar to the WF designer that is provided within Visual Studio. The rehosted designer shows that a Sequence
activity has been used as the default activity within the WF designer, so it will be used as the parent activity for all
other activities that are added to the workflow. The WF designer also includes the Expand All and Collapse All
commands for viewing the details for parent activities that have additional child activities. Also included are a
Variables tab for managing WF variables within workflows and an Import tab for importing additional referenced
libraries that can be used within workflows.

400

www.it-ebooks.info
Chapter 10 ■ Rehosting the Workflow Designer

Figure 10-1.  Rehosting the designer

Designer Metadata
In order to get the drag-and-drop capabilities for building workflows, metadata for the designer must be included as
well. Without adding the designer metadata, the Sequence activity in Figure 10-1 would resemble the same Sequence
activity in Figure 10-2 except without any drag-and-drop capabilities.

Figure 10-2.  Designer metadata required for drag-and-drop capabilities

DesignerMetadata is provided from the System.Activities.Core.Presentation.DesignerMetadata


namespace. Calling the Register() method registers metadata for the runtime, which will be demonstrated in the
first exercise in this chapter, Rehosting WF Components.

401

www.it-ebooks.info
Chapter 10 ■ rehosting the WorkfloW Designer

WF Toolbox
After rehosting the WF designer, the WF toolbox can be rehosted so that activities can be used for building workflows,
just as workflows are built using WF activities within Visual Studio.
Figure 10-3 shows the WF toolbox on the left-hand side of the WF designer. Activity Category illustrates a category
of workflow activities that can be included for building workflows. Even the Search textbox is included with the WF
toolbox for searching for workflow a