Pro WF 4.5
Pro WF 4.5
com
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.itbookshub.com
Contents at a Glance
Index���������������������������������������������������������������������������������������������������������������������������������617
www.itbookshub.com
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.itbookshub.com
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.itbookshub.com
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.itbookshub.com
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).
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
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.itbookshub.com
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
www.itbookshub.com
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
Yes
Transfer to Checking
www.itbookshub.com
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.
www.itbookshub.com
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.
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.
1
Model-driven architecture (MDA) is an industry standard maintained by the Object Management Group (OMG).
7
ws
8
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
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.
9
Chapter 1 ■ Why Workflows
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
Chapter 1 ■ Why Workflows
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
Chapter 1 ■ Why Workflows
12
Chapter 1 ■ Why Workflows
13
Chapter 1 ■ Why Workflows
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
Chapter 1 ■ Why Workflows
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
Chapter 1 ■ Why Workflows
Check Store
Inventory
Check Warehouse
Inventory
Check External
Supplier
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.itbookshub.com
Chapter 1 ■ Why Workflows
Check Store
Inventory
Check Warehouse
Inventory
Check External
Supplier
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
ws
Check Store
Inventory
[Inventory Exists]
[Not In Inventory]
Check Warehouse
Inventory
[Inventory Exists]
Process 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
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
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
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
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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).
24
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.
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).
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
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.
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.itbookshub.com
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.
28
Chapter 2 ■ IntroduCIng WIndoWs WorkfloW foundatIon
29
Chapter 2 ■ Introducing Windows Workflow Foundation
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).
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
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.
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.
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.
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
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.
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
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.
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
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.
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”.
35
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).
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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.
37
www.itbookshub.com
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.
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
Chapter 2 ■ IntroduCIng WIndoWs WorkfloW foundatIon
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).
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
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
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-25 shows the logic within the transition Voted, which can be seen by double-clicking on the transition.
42
Chapter 2 ■ Introducing Windows Workflow Foundation
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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#.
44
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.
45
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 >= 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("Too young")]"
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
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.
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.
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.itbookshub.com
Chapter 2 ■ Introducing Windows Workflow Foundation
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
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.
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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.
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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).
51
Chapter 2 ■ Introducing Windows Workflow Foundation
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.
52
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.
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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)
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.
54
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.
■■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
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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.
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.itbookshub.com
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.
58
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).
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
Chapter 2 ■ Introducing Windows Workflow Foundation
Figure 2-39 shows that a new Sequence activity is automatically added, which now serves as a parent activity to
WriteLine activities.
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
Chapter 2 ■ Introducing Windows Workflow Foundation
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
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
Chapter 3
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
63
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).
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
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.
65
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.
66
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.
Executing
Faulted Canceled
Closed
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.itbookshub.com
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.
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).
namespace Apress.Example.Chapter3
{
public partial class ImperativeCodeWorkflow
{
public void AdditionActivity()
{
Variable<int> Add1 = new Variable<int>
68
Chapter 3 ■ WindoWs WorkfloW aCtivities
{
Name = "Add1",
Default = 5
};
WorkflowInvoker.Invoke(wfSequence);
}
69
Chapter 3 ■ Windows Workflow Activities
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).
System.Activities.Activity.
activity.
Name of the activity that the WF designer displays.
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.
namespace Apress.Example.Chapter3
{
public partial class DynamicCodeActivity
{
public Activity AdditionActivity()
70
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
};
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.
71
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
72
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.
class Program
{
static void Main(string[] args)
{
CallDynamicAdditionActivity();
}
73
Chapter 3 ■ Windows Workflow Activities
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
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
Chapter 3 ■ Windows Workflow Activities
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).
75
Chapter 3 ■ Windows Workflow Activities
The completed workflow defined in code is completely demonstrated within the designer in Figure 3-7.
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.
76
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>
77
Chapter 3 ■ Windows Workflow Activities
78
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:
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).
79
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.
80
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.
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).
81
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.
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
Chapter 3 ■ Windows Workflow Activities
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.
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
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).
3
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{
84
Chapter 3 ■ Windows Workflow Activities
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.
85
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.
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
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.
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).
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
Chapter 3 ■ Windows Workflow Activities
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).
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.
89
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.
14. Remove the breakpoint set on the Assign activity by right clicking on the activity
Breakpoint > Delete Breakpoint (see Figure 3-19).
90
Chapter 3 ■ Windows Workflow Activities
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
Chapter 3 ■ Windows Workflow 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.
}
92
Chapter 3 ■ Windows Workflow Activities
}
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
93
Chapter 3 ■ Windows Workflow Activities
and
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
Chapter 3 ■ Windows Workflow Activities
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
Chapter 3 ■ Windows Workflow Activities
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
Chapter 3 ■ Windows Workflow Activities
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
Chapter 3 ■ Windows Workflow Activities
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
Chapter 3 ■ WindoWs WorkfloW aCtivities
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
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.
23. The Exception dropdown also allows .Net types to be browsed. Type
System.ApplicationException. See Figure 3-26.
100
Chapter 3 ■ Windows Workflow Activities
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
Chapter 3 ■ Windows Workflow Activities
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
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
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
Chapter 3 ■ Windows Workflow Activities
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
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).
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
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
Chapter 4
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).
109
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
110
CHAPTER 4 ■ State Machine Workflows
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
CHAPTER 4 ■ State Machine Workflows
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
CHAPTER 4 ■ State Machine Workflows
113
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).
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
CHAPTER 4 ■ State Machine Workflows
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
CHAPTER 4 ■ State Machine Workflows
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).
116
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).
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
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).
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
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.
Figure 4-12. Red circles with diagonal white lines indicate that a breakpoint has been added onto state activities
119
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).
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).
120
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).
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.
121
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.
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).
122
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.
■■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
CHAPTER 4 ■ State Machine Workflows
namespace FlowFocus.WF.Activities
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{
get
{
return true;
}
}
■■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
CHAPTER 4 ■ State Machine Workflows
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
CHAPTER 4 ■ State Machine Workflows
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
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.
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
CHAPTER 4 ■ State Machine Workflows
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.
■■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
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).
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:
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
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)
At this point, entering any number and pressing Enter will resume execution of the workflow to completion
(see Figure 4-25).
130
CHAPTER 4 ■ State Machine Workflows
Congratulations, you have now created a bookmark and resumed a state machine workflow from the hosting
application.
• 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
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).
132
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.
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.
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
CHAPTER 4 ■ State Machine Workflows
Descriptive transitions
134
CHAPTER 4 ■ State Machine Workflows
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
CHAPTER 4 ■ State Machine Workflows
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).
136
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).
The workflow can then go back to the In Neutral state or decide to go to the In Reverse state
(see Figure 4-34).
137
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.
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
CHAPTER 4 ■ State Machine Workflows
While controls are dragged from the toolbox, XAML is also being automatically built, representing the form as
markup (see Listing 4-4).
139
ws
{
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.
namespace Chapter4.Driving.Host
{
/// <summary>
140
CHAPTER 4 ■ State Machine Workflows
141
CHAPTER 4 ■ State Machine Workflows
ResumeBookmark("TurnOff");
}
catch (Exception ex)
{
throw ex;
}
}
public void StartEngine()
{
try
{
ResumeBookmark("StartEngine");
}
142
CHAPTER 4 ■ State Machine Workflows
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
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
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
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
CHAPTER 4 ■ State Machine Workflows
147
CHAPTER 4 ■ State Machine Workflows
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.
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.
148
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.
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
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).
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()
{
150
CHAPTER 4 ■ State Machine Workflows
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
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.
152
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
CHAPTER 4 ■ State Machine Workflows
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).
154
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.
Selecting the Drive button cycles the buttons again, making Park and Reverse available, as shown in Figure 4-44.
155
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).
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
CHAPTER 4 ■ State Machine Workflows
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
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.
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
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 activity represents the foundation for building a flowchart control flow within the WF. Once it is added
5-2).
160
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).
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.
161
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.
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
CHAPTER 5 ■ Flowchart Workflows
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).
163
CHAPTER 5 ■ Flowchart Workflows
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.
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
CHAPTER 5 ■ Flowchart Workflows
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.
165
CHAPTER 5 ■ Flowchart Workflows
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; }
}
}
}
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
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]
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
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; }
modelBuilder.Entity <Order> ()
.HasMany <OrderLineItem> (order = > order.LineItems);
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
CHAPTER 5 ■ Flowchart Workflows
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
CHAPTER 5 ■ Flowchart Workflows
[TestMethod]
public void TestCodeFirst()
{
Database.SetInitializer(
new DropCreateDatabaseIfModelChanges <Ordering> ());
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
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.”
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
CHAPTER 5 ■ Flowchart Workflows
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.
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
CHAPTER 5 ■ Flowchart Workflows
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
CHAPTER 5 ■ Flowchart Workflows
{
[RequiredArgument]
public OutArgument < Customers > outCustomers { get; set; }
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.
174
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.
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.
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).
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
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.
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).
176
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).
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).
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
At this point the composite activity built from steps 35–43 should look like Figure 5-24.
177
CHAPTER 5 ■ Flowchart Workflows
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
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).
179
CHAPTER 5 ■ Flowchart Workflows
180
CHAPTER 5 ■ FlowCHART woRkFlows
181
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.
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).
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.
183
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.
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
CHAPTER 5 ■ Flowchart Workflows
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.
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
CHAPTER 5 ■ Flowchart Workflows
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
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.
So hypothetically, your fictitious company has stores in the specific States, with the corresponding tax rates,
shown in Table 5-2.
187
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.
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).
188
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).
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.
189
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
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
CHAPTER 5 ■ FlowCHART woRkFlows
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
CHAPTER 5 ■ Flowchart Workflows
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
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.
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
CHAPTER 5 ■ Flowchart Workflows
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
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()
{
195
CHAPTER 5 ■ Flowchart Workflows
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.
196
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.
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
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.
198
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.
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
CHAPTER 5 ■ Flowchart Workflows
</Grid>
</Window>
This will add a simple interface with two buttons and a checkbox for approving or rejecting orders (see Figure 5-46).
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
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
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;
}
}
cmdRuntime.IsEnabled = false;
}
catch (Exception ex)
{
throw;
}
}
throw;
}
}
}
}
55. Right-click the WFHost project, choose “Set as StartUp Project,” and press F5 to run the solution.
202
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.
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).
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
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
Chapter 6
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
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
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
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.
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
207
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
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
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.
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
Chapter 6 ■ Versioning and Updating WorkfloWs
namespace Apress.Chapter7
{
[DataContract]
public class TeachingApplication
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
}
}
211
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
Chapter 6 ■ Versioning and Updating Workflows
213
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.
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
214
Chapter 6 ■ Versioning and Updating Workflows
Start
Start
Decision
Decision
Outcome Outcome
Outcome
Version 1 Version 2
Data
Data
Data
Instance Data
Instance Store
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.
215
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();
}
}
return v2Workflow;
}
216
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);
services.Add(v15Workflow);
return services;
}
}
}
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
};
217
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.
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
Chapter 6 ■ Versioning and Updating Workflows
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
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.
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
Chapter 6 ■ Versioning and Updating WorkfloWs
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
Chapter 6 ■ Versioning and Updating Workflows
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.
222
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
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.
223
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.
{
Activities =
{
new WriteLine()
{
Text = "Started a new workflow..."
}
}
};
DynamicUpdateServices.PrepareForUpdate(wf);
224
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
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:
Once the workflow definition is updated, a map of the changes made between the two different workflows can be
created using the following code:
225
Chapter 6 ■ Versioning and Updating Workflows
must be saved to the file system so it can be used later for updating persisted workflow instances
{
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).
226
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
Chapter 6 ■ Versioning and Updating Workflows
7.Activities
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{
228
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
using System.Text;
using System.Threading.Tasks;
namespace MovieRental.DataModel
{
[Serializable]
public class Movie
229
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.
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.
using System.Text;
using System.Threading.Tasks;
230
Chapter 6 ■ Versioning and Updating WorkfloWs
namespace MovieRental.DataModel
{
[Serializable]
public class CustomerRental
{
public CustomerRental()
{
Movies = new List<Movie>();
}
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).
231
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.
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
Chapter 6 ■ Versioning and Updating Workflows
namespace MovieRental.DataModel
{
[Serializable]
public class CreditCard:IEquatable<CreditCard>
{
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
Chapter 6 ■ Versioning and Updating Workflows
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
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
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
Chapter 6 ■ Versioning and Updating Workflows
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
Chapter 6 ■ Versioning and Updating Workflows
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.
238
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();
}
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
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.
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.
240
Chapter 6 ■ Versioning and Updating WorkfloWs
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.
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.
241
Chapter 6 ■ Versioning and Updating Workflows
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.
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
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.
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
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).
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
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();
}
245
Chapter 6 ■ Versioning and Updating Workflows
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
Chapter 6 ■ Versioning and Updating Workflows
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
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.
248
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).
■■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
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
Chapter 6 ■ Versioning and Updating WorkfloWs
DeltaWF = LoadActivityBuilder(txtWorkflowSnapshot.Text);
updateMap = DynamicUpdateServices.CreateUpdateMap(DeltaWF);
SaveUpdateMap(updateMap, txtWorkflowSnapshot.Text);
}
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
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.
instanceStore.ConnectionString =
@"Data Source=(LocalDB)\v11.0;Initial Catalog=WFPersist;Integrated Security=True";
WorkflowApplicationInstance wfInstance =
WorkflowApplication.GetInstance(new Guid(txtUpdateInstance.Text), instanceStore);
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();
}
}
252
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).
253
Chapter 6 ■ Versioning and Updating Workflows
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
Chapter 6 ■ Versioning and Updating Workflows
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
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
256
Chapter 7
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.
257
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.
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
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
Chapter 7 ■ Building Custom Workflow Activities
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
CodeActivity has been added to hold just activities that inherit from CodeActivity.
Figure 7-3. Custom activities are contained within their own project
260
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.
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).
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).
Listing 7-1 illustrates the metadata for CodeActivity that appears after selecting Go To Definition.
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
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
Chapter 7 ■ Building Custom Workflow Activities
namespace Apress.Chapter7.Activities.Custom
{
public class FirstCodeActivity:CodeActivity
{
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
264
Chapter 7 ■ Building Custom Workflow Activities
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).
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
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; }
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
Chapter 7 ■ Building Custom Workflow Activities
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
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; }
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
Chapter 7 ■ Building Custom Workflow Activities
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
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.
{
public class SalesCommission:CodeActivity<decimal>
{
public InArgument<decimal> NetSales { get; set; }
public InArgument<decimal> Percentage { get; set; }
270
Chapter 7 ■ Building Custom WorkfloW aCtivities
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
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
{
public class SalesCommission : CodeActivity<decimal>
{
public decimal NetSales { get; set; }
public decimal Percentage { get; set; }
272
Chapter 7 ■ Building Custom Workflow Activities
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.
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
Chapter 7 ■ Building Custom Workflow Activities
274
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();
275
Chapter 7 ■ Building Custom Workflow Activities
{
netSales = Convert.ToDecimal(Console.ReadLine());
}
catch (Exception)
{
throw new ApplicationException("Net sales was not entered as a number!");
}
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
Chapter 7 ■ Building Custom Workflow Activities
Figure 7-17. Entering sales data and percentage outside of the workflow
277
Chapter 7 ■ Building Custom Workflow Activities
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
Chapter 7 ■ Building Custom Workflow Activities
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
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).
280
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.
281
Chapter 7 ■ Building Custom Workflow Activities
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.
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
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
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
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; }
return fileText;
}
}
}
285
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.
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);
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
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.
namespace Apress.Chapter7.WF
{
287
Chapter 7 ■ Building Custom Workflow Activities
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
to
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
so the combined file contents are not read to the console. Then let both workflows execute and use
288
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
Chapter 7 ■ Building Custom Workflow Activities
namespace FlowFocus.WF.Activities
{
public sealed class WaitForResponse<TResult> : NativeActivity<TResult>
{
public WaitForResponse()
: base()
{
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).
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);
}
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
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).
After running the workflow, the console window will demonstrate that the WriteLine activity is executing and the
7-27).
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
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.
// 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>));
}
293
Chapter 7 ■ Building Custom Workflow Activities
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
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
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:
• 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
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
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.
298
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.
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
Chapter 8 ■ Persisting Workflows
SqlWorkflowInstanceStoreSchema.sql
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
Chapter 8 ■ persisting WorkfloWs
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.
301
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.
■■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.
302
Chapter 8 ■ Persisting Workflows
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).
303
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.
304
Chapter 8 ■ Persisting Workflows
After the ConnectionString property is set, SqlWorkflowInstanceStore can be added to a WF runtime host like
the WorkflowApplication:
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.
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
Chapter 8 ■ Persisting Workflows
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; }
}
}
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
Chapter 8 ■ Persisting Workflows
}
}
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
Chapter 8 ■ Persisting Workflows
}
}
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")
{
}
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
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.
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.
309
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
Chapter 8 ■ persisting WorkfloWs
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).
311
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.
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
Chapter 8 ■ Persisting Workflows
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.
313
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):
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
Chapter 8 ■ Persisting Workflows
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.
using System;
using System.Collections.Generic;
using System.ComponentModel;
315
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;
}
if (instances.Count > 0)
{
316
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;
}
317
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
Chapter 8 ■ Persisting Workflows
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;
}
}
319
Chapter 8 ■ Persisting Workflows
catch (Exception)
{
throw;
}
return rental;
}
throw;
}
}
private void CreateInstanceStoreOwner()
{
try
{
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
Chapter 8 ■ persisting WorkfloWs
});
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();
listView1.Items.Clear();
cmdUnloadInstances.Enabled = false;
cmdSetOwner.Enabled = true;
321
n
Chapter 8 ■ Persisting Workflows
catch (Exception)
{
throw;
}
}
}
}
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
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:
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 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
Chapter 8 ■ Persisting Workflows
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.
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
Chapter 8 ■ Persisting Workflows
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).
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).
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
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.
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
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.
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
wfServiceHost.DurableInstancingOptions.InstanceStore = instanceStore;
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
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).
<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.
<behavior name="">
<workflowIdle timeToPersist="00:00:05" timeToUnload="00:00:30"/>
</behavior>
329
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
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.
330
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
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).
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).
331
Chapter 8 ■ Persisting Workflows
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.
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.
332
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);
}
333
Chapter 8 ■ Persisting Workflows
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:
334
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>
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
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
Chapter 8 ■ Persisting Workflows
17. Drag a new Pick activity and auto-connect it with the Sequence activity, as shown in
Figure 8-26.
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
Chapter 8 ■ Persisting Workflows
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).
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
Chapter 8 ■ Persisting Workflows
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.
338
Chapter 8 ■ Persisting Workflows
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
Chapter 8 ■ Persisting Workflows
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
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).
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
Chapter 8 ■ Persisting Workflows
Figure 8-35 shows the left Branch for the Pick activity that was built.
342
Chapter 8 ■ Persisting Workflows
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
Chapter 8 ■ Persisting Workflows
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");
344
Chapter 8 ■ Persisting Workflows
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());
}
345
z
Chapter 8 ■ Persisting Workflows
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());
}
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
Chapter 8 ■ Persisting Workflows
347
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.
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.
348
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).
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
Chapter 8 ■ Persisting Workflows
throw;
}
return rental;
}
rental.RentalId = 1;
var ret = client.CreateNewRental(rental);
}
catch (Exception ex)
{
throw ex;
}
}
350
Chapter 8 ■ persisting WorkfloWs
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
Chapter 8 ■ Persisting Workflows
After waiting two minutes the workflow service host shows that the rental has become late (see Figure 8-42).
352
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 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
Chapter 8 ■ Persisting Workflows
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).
354
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
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
Chapter 9 ■ Tracking Workflows
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.
358
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).
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
Chapter 9 ■ Tracking Workflows
• 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.
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
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.
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.
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
Chapter 9 ■ traCking WorkfloWs
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.
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.
362
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).
■■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.
363
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.
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
364
Chapter 9 ■ Tracking Workflows
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
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.
366
Chapter 9 ■ Tracking Workflows
BASIC TRACKING
367
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!”
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;
368
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
Chapter 9 ■ Tracking Workflows
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
Chapter 9 ■ Tracking Workflows
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
Chapter 9 ■ traCking WorkfloWs
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).
372
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).
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).
373
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.
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
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.
375
Chapter 9 ■ Tracking Workflows
5. Set the Operation Name property for the Receive activity to StartWorkflow (see Figure 9-10).
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).
376
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).
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
Chapter 9 ■ Tracking Workflows
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
Chapter 9 ■ Tracking Workflows
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
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;
}
}
try
{
if (null != workflowServiceHost)
{
string workflowDisplayName = workflowServiceHost.Activity.
DisplayName;
TrackingProfile trackingProfile = GetTrackingProfileFromConfig
(trackingProfileName, workflowDisplayName);
380
Chapter 9 ■ Tracking Workflows
//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();
}
return trackingProfile;
}
381
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
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.
18. Open the App.config file and replace the existing XML with the following:
383
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
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.
This exercise showed how to implement tracking for a workflow hosted by a WorkflowServiceHost through
configuration.
385
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.
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.
Figure 9-17 shows that the workflow instance tracking records have been serialized, showing the properties that
are provided on the tracking record.
386
Chapter 9 ■ Tracking Workflows
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
387
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
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
Chapter 9 ■ Tracking Workflows
389
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>
Activity State
DoSomeWork has been
Adding the new activity, the milliseconds are set to 5000, simulating 5 seconds for work being processed
(see Figure 9-19).
390
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.
391
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"/>
<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.
392
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:
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>
393
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
which will return back tracked data for WorkingLevel for each activity.
<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.
394
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:
<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).
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).
If the logs are disabled, they can be enabled by right-clicking on the node and selecting Enable Log (see Figure 9-22).
396
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).
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).
397
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
Chapter 10
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
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
Chapter 10 ■ Rehosting the Workflow 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.
401
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 activities within itself. The next step is to provide out-of-the-box workflow activities
that can be added to the rehosted WF toolbox (see Figure 10-4).
402
Chapter 10 ■ Rehosting the Workflow Designer
Figure 10-4 illustrates rehosting the WF toolbox, which includes the If and WriteLine activities. The workflow
being built includes the WriteLine activity, which resides as a child activity within the default Sequence activity. By
including the WF toolbox, workflow activities can now be dragged and dropped within a workflow.
WF Properties
There are times when the WF property component also needs to be rehosted so that properties for the workflow can
be configured. Rehosting the WF property window dynamically represents the properties for the workflow itself or
for selected workflow activities so they can be configured. Figure 10-5 illustrates the rehosting of the WF toolbox; by
selecting the WriteLine activity, the Text and TextWriter properties that are provided with the WriteLine activity are
dynamically displayed so values can be assigned to them.
403
Chapter 10 ■ Rehosting the Workflow Designer
■■Note A new feature in WF4.5 is using C# expressions for workflows designed using VS2012. Because the C#
compiler is not rehostable like the VB compiler, rehosting the WF designer still only allows VB expressions to be added to
a workflow rather than using C# expressions. Be on the lookout for additional patches of VS2012 that will probably use
Microsoft’s “Roslyn” project to bring C# syntax to a rehosted WF designer
404
Chapter 10 ■ Rehosting the Workflow Designer
REHOSTING WF COMPONENTS
Now that you have had a chance to get familiar with each of the WF components that can be rehosted within a
custom application, this exercise will walk you through the steps and demonstrate the C# code required to build a
custom WPF application so that workflows can be authored outside of Visual Studio.
1. Open VS2012 and click on the File menu. Select New and then Project.
2. Within the installed project templates, select Windows and then select the WPF
Application project template.
3. Set the solution name to Apress.Chapter10 and the name of the project to Exercise1.
4. Rename the existing WPF window to RehostedWF designer.xaml.
Replace the existing XAML contents with the XAML in Listing 10-1. This XAML markup
10sets the window height to 350, width to 650, and title to WF Designer. More
importantly, it sets up three columns where the different rehosted WF components will
reside once they are added to the WPF window. The XAML markup in Listing 10-1 is all
that is needed for setting up the layout of the WPF window.
5. Now that the layout of the window is complete, C# code can be used to programmatically
rehost the WF components into the window. A couple of references need to be made first.
Add the following references, as illustrated in Figure 10-6:
System.Activities
System.Activities.Core.Presentation
System.Activities.Presentation
405
Chapter 10 ■ Rehosting the Workflow Designer
6. Expand out the RehostedWF designer.xaml file within the Solution Explorer and open
the RehostedWF designer.xaml.cs file. Make sure the using statements in Listing 10-2
exist at the top of code file.
//WF Namespaces
using System.Activities.Core.Presentation;
using System.Activities.Presentation;
using System.Activities.Presentation.Toolbox;
using System.Activities.Statements;
406
Chapter 10 ■ Rehosting the Workflow Designer
7. Add private WorkflowDesigner WF designer; just after the class declaration but
before the RehostedWF designer() constructor.
8. Use the DesignerMetaData object for adding metadata about the WF designer. Change
the constructor of the RehostedWF designer class, as shown in Listing 10-3.
9. Since the WF designer variable for representing the WF designer was created in
step 7, it can be used for rehosting the WF designer within the second ColumnDefinition
for the MainGrid1 grid. Add the code in Listing 10-4 for the method that takes a
WorkflowDesigner object and adds it within the WPF window.
10. Pressing F5 will compile the project. After the project compiles correctly, the WF designer
will appear hosted within a new window, as illustrated within Figure 10-7.
407
Chapter 10 ■ Rehosting the Workflow Designer
11. Add a method for building a WF toolbox so it can also be rehosted. The toolbox will be placed
to the left of the WF designer, as illustrated in Figure 10-7. After the code in Listing 10-5
is added, modify the constructor once more and add the call BuildWFToolbox();.
//Add an if activity
var act1 =
new ToolboxItemWrapper("System.Activities.Statements.If",
typeof(If).Assembly.FullName, null,
"If");
//Add a WriteLine activity
var act2 =
new ToolboxItemWrapper("System.Activities.Statements.WriteLine",
typeof(WriteLine).Assembly.FullName, null, "WriteLine");
//Add a DoWhile activity
var act3 =
new ToolboxItemWrapper("System.Activities.Statements.DoWhile",
typeof(DoWhile).Assembly.FullName, null, "DoWhile");
//Add an Assign activity
408
Chapter 10 ■ Rehosting the Workflow Designer
var act4 =
new ToolboxItemWrapper("System.Activities.Statements.Assign",
typeof(Assign).Assembly.FullName, null, "Assign");
12. Pressing F5 will compile the project and show the WF toolbox and four workflow activities
included within it. At this point, any of the four workflow activities can be dragged and
dropped into the WF designer to build a workflow (see Figure 10-8).
409
Chapter 10 ■ Rehosting the Workflow Designer
13. The last WF component to rehost is the WF Property window, which is used for
managing the workflow and its activities. Add the code in Listing 10-6. Then 10include
AddWFPropertyWindow(); as another call within the constructor.
14. Pressing F5 again will compile and run the code, which will now show that the WF
Property window is now also rehosted. Figure 10-9 illustrates that the WF toolbox,
designer, and Property window have all been rehosted within the application.
This exercise walked through the code and XAML markup to build a simple application that can be used to build
workflows outside of Visual Studio. As you saw, it’s quite easy to actually rehost the WF components.
410
Chapter 10 ■ Rehosting the Workflow Designer
xmlns:WFC="clr-namespace:System.Activities.Presentation.Toolbox;assembly=System.Activities.
Presentation"
It adds the namespace for listed WF assemblies. WFC serves as the prefix for the WF controls that will be used to
design the rehosted designer. A Window.Resources tag is added to specify information about the System.Activities
namespace so it can be used to add out-of-the-box WF activities to the WF Toolbox, like so:
<Window.Resources>
<sys:String x:Key="WFAssembly">System.Activities, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35</sys:String>
Listing 10-7 shows the XAML markup that is required for building the same application as in the Rehosting WF
Components exercise, except specifying the WF toolbox layout with XAML.
411
Chapter 10 ■ rehosting the WorkfloW Designer
</WFC:ToolboxItemWrapper>
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.If
</WFC:ToolboxItemWrapper.ToolName>
</WFC:ToolboxItemWrapper>
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.While
</WFC:ToolboxItemWrapper.ToolName>
</WFC:ToolboxItemWrapper>
</WFC:ToolboxCategory>
</WFC:ToolboxControl>
</Border>
<Border Grid.Column="1" Name="DesignerBorder"/>
<Border Grid.Column="2" Name="PropertyBorder"/>
</Grid>
The necessary C# code is reduced to only specifying the WF designer and WF Properties window.
namespaces are required:
Then the DesignerMetadata can be specified along with the WorkflowDesigner, which specifies views to the
exiting grid columns DesignerBorder and PropertyBorder identified in Listing 10-8
using System.Activities.Core.Presentation;
using System.Activities.Presentation;
using System.Activities.Statements;
412
Chapter 10 ■ Rehosting the Workflow Designer
namespace Exercise1
{
/// <summary>
/// Interaction logic for RehostedWFThroughMarkup.xaml
/// </summary>
public partial class RehostedWFThroughMarkup : Window
{
public RehostedWFThroughMarkup()
{
InitializeComponent();
}
}
}
}
413
Chapter 10 ■ Rehosting the Workflow Designer
Instead of needing to right-click on a workflow file, maybe a better experience would be to provide a way for
clients to tab back and forth from a declarative model for the workflow and to its XAML representation. Figure 10-11
shows how tabs can be added to handle this functionality tastefully.
414
Chapter 10 ■ Rehosting the Workflow Designer
Clicking on the XAML tab will show the XAML that the WF designer builds behind the scene while authoring a
workflow declaratively through activities (see Figure 10-12).
Figure 10-12. XAML representing the workflow being built outside of Visual Studio
415
Chapter 10 ■ Rehosting the Workflow Designer
The three new lines of code represent setting the AnnotationEnabled property to true and indicating to the WF
designer that target .NET Framework is set to the .NET 4.5 version. Running the application built in the Rehosting WF
Components exercise after adding the lines of code in Listing 10-9 enables the following features:
• Panning for workflows
• Multi-select of activities
• Auto-surround with the Sequence activity
• Toolbox search (available without the code in Listing 10-9)
• Designer annotations
• Workflow outline view
These features provide functionality for visually managing workflows through the WF designer. Since WF invests
heavily in declaratively building workflows, features released for building workflows in WF4.5 provide essential tools
that allow workflow authors to manage the visibility of large workflows.
Panning Workflows
Panning the workflow is a new feature within the WF4.5 designer where a workflow can be grabbed by clicking on a
part of the workflow, holding down the left mouse button, and moving the mouse around to view different parts of
the workflow. As the left mouse button is selected and released, you can actually see the mouse icon used to indicate
that the workflow is being manipulated. To begin panning a workflow, select the hand icon from the toolbar below the
workflow, as illustrated in Figure 10-14.
416
Chapter 10 ■ Rehosting the Workflow Designer
Multi-Selecting Activities
WF 4.5 offers another much needed feature: multi-selecting activities. Selecting multiple activities applies the same
functionality that UI designers like ASP.NET and WPF have for selecting multiple UI controls within their respective
designers. A user can select more than one UI control and then drag them to another location while keeping the
same format intact. After the controls are pasted, they keep the same properties and layout as they did before they
were selected. A workflow activity can be selected by clicking it using the left mouse button. Another activity can be
selected by pressing the Ctrl button on the keyboard and then using the left mouse button to click it. Figure 10-15
illustrates selecting a Flowchart activity and two WriteLine activities. After selecting workflow activities, they become
highlighted, indicating that they have been selected. Right-clicking on a selected workflow activity pops up a dialog
menu that will allow all of the selected activities to be cut, copied, pasted, or deleted.
Once the workflow activities are selected, they can be pasted within a new workflow or existing activity within the
same workflow. Figure 10-16 indicates that the workflow activities selected in Figure 10-15 have been placed within
the Then branch of the If activity, which is also contained within the same workflow.
417
Chapter 10 ■ Rehosting the Workflow Designer
Auto-Surround Sequencing
A Sequence activity is used for holding more than one child activity, but in WF4 adding a Sequence activity for holding
child activities is a manual task when a Sequence activity is not currently available within a workflow and more than
one activity is needed for building business logic. WF4.5 provides functionality called auto-surround that allows
activities to be dropped onto a workflow and indicates if the new activity should be placed above or below an existing
activity, which is not already within a Sequence activity. To understand auto-surround with a Sequence activity better,
think about how the If activity is used. There is a Then and Else branch for the If activity; however, if a WriteLine
activity is added to the either branch, WF4 does not allow for another activity to be placed within the same branch
(see Figure 10-17).
418
Chapter 10 ■ Rehosting the Workflow Designer
Figure 10-17. Adding more than one activity within the branches of an If activity
WF4.5 allows for another workflow activity to be placed within the Then branch even when an existing WriteLine
activity exists within the same branch. In WF4, the existing WriteLine activity would have to be cut from the Then
branch so a Sequence activity could be manually added for allowing multiple activities. Instead, WF designer in WF4.5
behaves just as if a Sequence activity already existed and prompts for the new workflow activity as it is dragged within
the Then branch in Figure 10-17, indicating if the new workflow activity should be placed above or below the current
WriteLine activity. Once a new workflow activity is dropped within the Then branch, both workflow activities are
automatically placed within a Sequence activity. Figure 10-18 indicates that an existing WriteLine activity already
existed within the Then branch of the If activity. The WF4.5 WF designer allowed another WriteLine activity to
be dragged and dropped within the same Then branch and gave the choice of adding the new WriteLine activity
either before or after the existing WriteLine activity. Once the new WriteLine activity was added to the workflow, a
Sequence activity was automatically added by the WF designer so both WriteLine activities could exist within the
Then branch.
419
Chapter 10 ■ Rehosting the Workflow Designer
Figure 10-18. Sequence is automatically added while adding a new WriteLine activity
Textbox Search
Textbox search allows properties of a selected workflow activity to be searched, and this functionality is
automatically provided while using the WF toolbox in WF4.5. Therefore, the code in Listing 10-8 is not necessary
to gain search functionality. Instead it is provided by using the PropertyInspectorView or property grid within the
WorkflowDesigner object in WF4.5.
Figure 10-19 illustrates that the If activity has been selected and its two properties are displayed within the
property grid. Using the Search textbox located at the top right-hand corner of the WF designer, properties can be
searched for the selected workflow activity. This can be an important feature if there are many properties provided for
a workflow activity.
420
Chapter 10 ■ Rehosting the Workflow Designer
Figure 10-19. Sequence is automatically added while adding a new WriteLine activity
Designer Annotations
Being able to add annotations to workflow activities is a feature that has been missing since the initial release of
WF. Annotations allow documenting parts of a workflow by adding descriptions to child activities that make up a
workflow. Once annotations are added, they can be shown, hidden, or deleted by right-clicking on a workflow activity
and selecting Annotations. Figure 10-20 illustrates these options for annotations.
421
Chapter 10 ■ rehosting the WorkfloW Designer
Hiding annotations provide more designer real estate for displaying a workflow; however, when the annotations
are hidden, activities that contain annotations are indicated by an annotation icon. When the mouse is hovers over
the icon, the annotation pops up, much like a tooltip (see Figure 10-21). When focus is lost from the annotation icon,
the annotation goes away.
While hovering over an annotation, you also get 10the ability to pin the annotation to the workflow activity by
10-22.
422
Chapter 10 ■ Rehosting the Workflow Designer
423
Chapter 10 ■ Rehosting the Workflow Designer
The outline view for workflows provides an easier way to see and understand the relationship of the activities that
make up very large workflows. As activities within the workflow are selected, the corresponding activities within the
outline view are highlighted as well. The same behavior occurs when activities within the outline view are selected
first instead of selecting activities within the WF designer. The Property window is also correlated, so as activities are
selected, the properties for the selected activity also appear within the workflow Property window. Figure 10-25 shows
how the Assign activity is selected within the Outline view, which highlights the workflow in the WF designer and
displays the properties for the Assign activity within the WF Property window.
Figure 10-25. Selecting the Assign activity within the Outline view
Rehosting Arguments
At this point, thanks to the custom WPF application that was built in the Rehosting WF Components exercise plus the
new features for WF 4.5 and the WF designer that were added in Figure 10-25, the custom application for building
workflows is starting to look similar and provide some of the same features for authoring workflows within Visual
Studio. The main difference is that now the developer is in control of what features are provided to the end user for
building workflows. Before going any further, Listing 10-10 shows the complete XAML for building the application
illustrated in Figure 10-25. The added markup for the XAML includes
• Tabs for toggling the XAML that represents the workflow and the workflow itself.
• New ColumnDefinitions and RowDefinitions for laying out the different WF components
within the application.
424
Chapter 10 ■ Rehosting the Workflow Designer
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="WF designer" Height="350" Width="650">
<Window.Resources>
<sys:String x:Key="WFAssembly">System.Activities, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35</sys:String>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.6*"/>
<ColumnDefinition Width="1.6"/>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.Row="0">
<WFC:ToolboxControl>
<WFC:ToolboxCategory CategoryName="Basic">
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}" >
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Sequence
</WFC:ToolboxItemWrapper.ToolName>
</WFC:ToolboxItemWrapper>
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.WriteLine
</WFC:ToolboxItemWrapper.ToolName>
</WFC:ToolboxItemWrapper>
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.If
</WFC:ToolboxItemWrapper.ToolName>
</WFC:ToolboxItemWrapper>
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Flowchart
</WFC:ToolboxItemWrapper.ToolName>
</WFC:ToolboxItemWrapper>
</WFC:ToolboxCategory>
</WFC:ToolboxControl>
</Border>
<Border Grid.Column="0" Grid.Row="1" Name="WFOutline" />
<Grid Grid.Column="2" Grid.RowSpan="2" Margin="2" Width="Auto">
<TabControl x:Name="DesignerXamlTabDeck" SelectionChanged="DesignerXamlTabDeck_
SelectionChanged">
<TabItem Name="DesignerTab" Header="Designer">
425
Chapter 10 ■ Rehosting the Workflow Designer
Listing 10-11 contains the code used to write the XAML representation of workflows and to add the XAML and
workflow within tabs. It also shows the new code for hosting the new features provided by WF4.5 within the WF
{
WorkflowDesigner _wd = new WorkflowDesigner();
public RehostedWFThroughMarkup()
{
InitializeComponent();
}
//Adding annotations
_wd.Context.Services.GetService<DesignerConfigurationService>().AnnotationEnabled = true;
//Targeting the .NET 4.5 Framework for the configuration service for the WF Designer control
_wd.Context.Services.GetService<DesignerConfigurationService>().TargetFrameworkName = new
System.Runtime.Versioning.FrameworkName(".NET Framework", new Version(4, 5));
_wd.Load(wf);
PropertyBorder.Child = _wd.PropertyInspectorView;
DesignerTab.Content = _wd.View;
//Loads the outline view for the workflow
//Adds the Outline View
WFOutline.Child = _wd.OutlineView;
}
}
426
Chapter 10 ■ Rehosting the Workflow Designer
One thing that you may have noticed through all of the illustrations up to this point is that WF arguments have
not been available in the rehosted designer. In some cases, business users might need the ability to customize or
create WF arguments, so I want to describe how to add functionality of providing arguments and give a technical
explanation as to why WF arguments have not been available within the rehosted WF designer application thus far.
The code in Listing 10-11 shows the WF designer control loading an Activity object that contains a Sequence activity
that contains an Assign activity:
By passing in an Activity object to the WF designer, the designer thinks that the workflow is nothing more than
an activity, even though additional activities can be added for creating a workflow that looks no different than any
other workflow. The difference is in the XAML that is created. Without making any changes to the default activities
that the designer loads up, the activities within the designer represent a basic workflow (see Figure 10-26).
Figure 10-26. Default Sequence and Assign activities added within the WF designer
Clicking the XAML tab to look at the XAML that was created reveals that the root element is <Sequence>. This
indicates to the WF designer that this is not a workflow but a logical connection of activities (see Figure 10-27).
427
Chapter 10 ■ Rehosting the Workflow Designer
XAML representing the Sequence and Assign activities within the WF designer
Now let’s check out the XAML for a new workflow within Visual Studio and add a Sequence activity with a child
activity. To view the XAML for a workflow in Visual Studio, right-click on the XAML file within the Solution
10-28 shows different XAML for the
same activities added within the WF designer as represented in Figure 10-25.
Figure 10-28. XAML representing the Sequence and Assign activities within the WF Designer
428
Chapter 10 ■ Rehosting the Workflow Designer
The XAML in Figure 10-28 has a starting element of <Activity>, which means that the WF designer interprets
the added activities as generic workflow activities rather than a specific activity, depending if the activity is out of the
box or custom. Even though a particular activity can have arguments, the WF designer uses the Arguments tab for
adding arguments that are meant for adding or returning data from a workflow rather than a workflow activity. Instead
of loading an Activity object within a rehosted WF designer, an ActivityBuilder object can be loaded instead.
Loading an ActivityBuilder object into the WF designer will generate the same XAML as in Figure 10-29 and will use
the <Activity> root for the XAML instead of using a particular activity as the root, as illustrated in Figure 10-27.
Figure 10-29. XAML produced by loading an ActivityBuilder object instead of an Activity object
Now the when you tab back to the Designer tab, the Arguments tab appears, indicating that WF arguments can
be added to the workflow for trafficking data in and out of the workflow. Figure 10-30 shows how the Arguments tab
is now viewable and how a new WF argument can be added to the workflow. An In argument called inArgEmployee is
being created and the Argument Type is being searched on within the solution.
429
Chapter 10 ■ Rehosting the Workflow Designer
The code for creating the ActivityBuilder object is not too difficult. Listing 10-12 illustrates how to add a new
Sequence activity and a child Assign activity to the workflow.
Listing 10-12. OnIntialized Event for Rehosting the Designer and WF Components
private ActivityBuilder BuildBaseActivity()
{
try
{
ActivityBuilder builder = new ActivityBuilder
{
Name = "CustomWorkflow",
Implementation = new Sequence()
{
Activities =
{
new Assign<Int32>()
}
}
};
return builder;
}
catch (Exception)
{
throw;
}
}
The code in Listing 10-12 is a function that returns an ActivityBuilder object. Once the ActivityBuilder
object is instantiated, the first things to set are the Name and Implementation properties. The Name property will
identify the workflow, as illustrated in Figure 10-30 at the top of the workflow and just underneath the Designer tab.
The Implementation property sets the activities that will be used within the workflow. First, a Sequence activity is
instantiated and then its Activities property is used to add child activities to the Sequence activity. In this case, an
instantiated Assign<Int32> activity is added. Finally, to get the WorkflowDesigner to load the ActivityBuilder, the
line of code in Figure 10-9,
_wd.Load(wf)
can be changed to load the ActivityBuilder object that is returned in Listing 10-10 by using
_wd.Load(BuildBaseActivity())
instead. Most importantly, in order to use the ActivityBuilder object, you must add System Activities as a using
statement.
430
Chapter 10 ■ Rehosting the Workflow Designer
using System.Activities.Presentation.View;
_wd.Context.Services.GetService<DesignerView>().WorkflowShellBarItemVisibility =
ShellBarItemVisibility.None;
The line of code should be added after calling the Load method for loading default activities for the
WorkflowDesigner object. Running the application after these lines of code are added removes the entire toolbar so
end users are not made aware of arguments, variables, and imports for the workflow (see Figure 10-31).
Figure 10-31. Removing the toolbar for the Variables, Arguments, and Imports tabs
Next, WF arguments can be programmatically created and automatically applied to a workflow. The
ActivityBuilder object also provides a Properties property that can be set to WF arguments that are intended to be
used within the workflow. Listing 10-13 illustrates how WF arguments can be loaded into a workflow through code.
First, a new DynamicActivityProperty must be added to the Properties property for the ActivityBuilder. The
DynamicActivityProperty has four properties that need to be set:
• Name: Name of the argument
• Type: The typeof argument (In, Out, both)
431
Chapter 10 ■ rehosting the WorkfloW Designer
Name = "CustomWorkflow",
Implementation = new Sequence()
{
Activities =
{
new Assign<Int32>()
}
},
Properties =
{
new DynamicActivityProperty
{
Name = "inArgFullName",
Type = typeof(InArgument<string>),
Attributes =
{
new RequiredArgumentAttribute(),
},
Value
= new InArgument<string>
{
Expression =
new InArgument<string>("Bayer White")
}
}
}
};
The InArgument object that is set to the Value property has an Expression property. Since the
InArgument<string> argument accepts a string, it is natural to think that the Expression property can simply be set
to a string; however this is not the case. The Expression property should be set to new InArgument<string>
("Bayer White") instead.
Managing Workflows
Visual Studio provides a developers experience in building and managing workflows that are either created using C#
code or XAML, and each workflow is associated to a file that is created and added to a .NET project. When rehosting
the WF designer, managing workflows are built, but it’s different because the purpose is to either run workflows within
the same custom application that is used to build workflows or to build workflows that are meant to be run within
other external client applications. Both scenarios are legitimate reasons for rehosting the WF designer; however, there
can be a greater impact in building workflows that can be reused in more than one application.
432
Chapter 10 ■ Rehosting the Workflow Designer
Building workflows that can be distributed and run within other software is a very powerful feature of WF. In
order for the workflows to be used within other software applications, the application rehosting the WF designer
needs to provide a way to save the workflows. As workflow activities are added to the WorkflowDesigner object, the
XAML built behind the scene represents the logical relationship of the workflow activities. WF provides the same
functionality to rung workflows built using XAML, similar to how workflows that are built using C# are executed, but
first the XAML needs to be saved and stored. This section will show you how to save a workflow as XAML on the file
system so it can be retrieved by other applications.
</WFC:ToolboxItemWrapper>
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.If
</WFC:ToolboxItemWrapper.ToolName>
</WFC:ToolboxItemWrapper>
<WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
<WFC:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Flowchart
</WFC:ToolboxItemWrapper.ToolName>
433
Chapter 10 ■ Rehosting the Workflow Designer
</WFC:ToolboxItemWrapper>
</WFC:ToolboxCategory>
</WFC:ToolboxControl>
</DockPanel>
</Grid>
Code can now be added to create the functionality for managing workflows through the UI. The code has been
changed a bit to accommodate functionality for managing workflows. Workflows will be stored as XAML files within
the same file path as the application’s execution file. Listing 10-15 shows the code for managing the folder where the
workflows will be stored.
Next, you need a method to instantiate the WorkflowDesigner object each time a new workflow is loaded. This
try
{
_wd = new WorkflowDesigner();
_wd.Context.Services.GetService<DesignerConfigurationService>().AnnotationEnabled = true;
_wd.Context.Services.GetService<DesignerConfigurationService>().TargetFrameworkName = new
System.Runtime.Versioning.FrameworkName(".NET Framework", new Version(4, 5));
//_wd.Load(wf);
//_wd.Context.Services.GetService<DesignerView>().WorkflowShellBarItemVisibility
= ShellBarItemVisibility.None;
PropertyBorder.Child = _wd.PropertyInspectorView;
DesignerTab.Content = _wd.View;
WFOutline.Child = _wd.OutlineView; // Adds the Outline View
}
catch (Exception ex)
{
throw ex;
}
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// registering metadata
(new DesignerMetadata()).Register();
}
434
Chapter 10 ■ Rehosting the Workflow Designer
Functionality for each of the buttons can now be implemented based on each of the button’s click event.
Listing 10-17 illustrates code to create a new workflow. The code calls the BuildNewDesigner code illustrated
in Listing 10-16 to instantiate the WorkflowDesigner object. This is important for calling the Load method on a
WorkflowDesigner, because each time it is anticipating loading a new workflow, the WorkflowDesigner must
be instantiated. The BuildBaseActivity defined in Listing 10-12 is then called within the Load method of the
WorkflowDesigner, which returns an ActivityBuilder object that is to be loaded within the rehosted WF designer.
BuildNewDesigner();
_wd.Load(BuildBaseActivity());
}
catch (Exception ex)
{
throw ex;
}
}
Listing 10-18 illustrates the code for saving a workflow that has been loaded into a WorkflowDesigner. The code
first checks to see if a workflow name has been provided; if not, the user is prompted to provide a workflow name.
The application’s executable file path is used for the location by declaring Directory.GetCurrentDirectory()
along with the a particular folder and name provided for storing the workflow. The Flush method is executed on the
WorkflowDesigner object so that the XAML representing the workflow is pushed into the WorkflowDesigner’s Text
property. If the file path for saving a workflow does not exist, it is created. The WorkflowDesigner’s Save method is
called to save the XAML file to the specified file path.
if (!Directory.Exists(Directory.GetCurrentDirectory() + WorkflowFolder))
Directory.CreateDirectory(Directory.GetCurrentDirectory() +
WorkflowFolder);
435
Chapter 10 ■ Rehosting the Workflow Designer
_wd.Save(wfFile);
MessageBox.Show("Workflow has been saved!");
}
}
else
MessageBox.Show("Please press 'Create' to build a new workflow!");
}
else
{
MessageBox.Show("Please enter a workflow name!");
}
}
catch (Exception ex)
{
throw ex;
}
Workflows can also be deleted once they are created. Listing 10-19 illustrates that a workflow name must be
{
try
{
if (!string.IsNullOrWhiteSpace(txtWorkflowName.Text))
{
if (!File.Exists(wfFile))
MessageBox.Show("Workflow does not exist!");
else
{
File.Delete(wfFile);
MessageBox.Show("Workflow has been removed!");
}
}
else
{
MessageBox.Show("Please enter a workflow name!");
}
}
catch (Exception ex)
{
throw ex;
}
}
436
Chapter 10 ■ Rehosting the Workflow Designer
Finally, an existing workflow can be loaded so it can be modified further. Listing 10-20 illustrates how a
workflow’s XAML file is checked to see if it exists. If the workflow exists, a new WorkflowDesigner object is again
instantiated because the XAML file has to be loaded within the WorkflowDesigner. Once the workflow is loaded, the
rehosted WF designer reflects the loaded workflow.
if (!File.Exists(wfFile))
MessageBox.Show("Workflow does not exist!");
else
{
BuildNewDesigner();
_wd.Load(wfFile);
}
}
else
{
MessageBox.Show("Please enter a workflow name!");
}
}
catch (Exception ex)
{
throw ex;
}
}
}
}
Running the application after implementing the functionality for managing workflows will display the toolbox so
that workflows can be created (see Figure 10-32).
437
Chapter 10 ■ Rehosting the Workflow Designer
Figure 10-33 shows how selecting the Create button loads a default workflow into the designer. Once the
workflow is loaded, the Outline view for the workflow becomes visible based on the default activities loaded into the
WF designer. I also changed the value of the Implementation property of the ActivityBuilder to Implementation =
new Flowchart(). As a new workflow is loaded, it only loads a Flowchart activity that can be extended.
438
Chapter 10 ■ Rehosting the Workflow Designer
CustomWorkflow is the name given to the new workflow and it can now be modified by adding a new If
activity to the workflow. Clicking on the Save button within the toolbar saves the workflow to the file system
(see Figure 10-34).
439
Chapter 10 ■ Rehosting the Workflow Designer
The workflow has now been saved on the local file path where the executable running the application is located
and within the specified folder of CreatedWorkflows. Figure 10-35 shows the file opened in Notepad and the XAML
used to save the workflow. Shutting down the application and then restarting it again, the workflow illustrated in
Figure 10-34 is loaded into the WF designer so it can be modified. After modifying the workflow, it can be saved again
and reloaded again at a later time. Clicking the Delete button will remove the workflow file from the file path.
440
Chapter 10 ■ Rehosting the Workflow Designer
441
Chapter 10 ■ rehosting the WorkfloW Designer
using System.Text;
using System.Threading.Tasks;
using System.Activities;
using System.Activities.XamlIntegration;
using Chapter10.Model;
namespace ProcessOrders
{
class Program
{
static void Main(string[] args)
{
string[] products =
new string[]
{
"Widget1",
"Widget2",
"Widget3",
"Widget4",
"Widget5"
};
decimal[] prices =
new decimal[]
442
Chapter 10 ■ Rehosting the Workflow Designer
{
5.40m,
10.25m,
2.00m,
4.30m,
6.45m
};
while (1 == 1)
{
var rand = new Random();
var currentIndex = rand.Next(0, 4);
Console.WriteLine("Order for {0}, costing {1}"
, products[currentIndex]
, string.Format("{0:C}", prices[currentIndex]));
System.Threading.Thread.Sleep(3000);
GetLatestWorkflow(
new Order
{
Price = prices[currentIndex],
Product = products[currentIndex]
});
}
}
if (!string.IsNullOrWhiteSpace(latestWF))
{
var wf
= ActivityXamlServices.Load(wfPath + latestWF);
443
Chapter 10 ■ Rehosting the Workflow Designer
Listing 10-22 defines the Order object that is created and passed into the workflow as arguments. It is also
referenced within the same project used to create the application that rehosts the WF designer.
Listing 10-22. Order Object Used to Pass Parameters into the Workflow
using System;
10.Model
{
public class Order
{
public string Product { get; set; }
public decimal Price { get; set; }
}
The XAML file hosting the WF designer is changed so that the WF toolbox of activities now includes a WriteLine
activity. The XAML in Listing 10-23 includes the activity.
The last bit of code changes the ActivityBuilder to include a Flowchart activity within the workflow and creates
an InArgument<Order> that indicates the Order object specified in Listing 10-24 as the type of object that will be
passed in as an argument of the workflow.
Name = txtWorkflowName.Text!=string.Empty?txtWorkflowName.Text:txtWorkflowName.Text,
Implementation = new Flowchart(),
Properties =
{
new DynamicActivityProperty
444
Chapter 10 ■ Rehosting the Workflow Designer
{
Name = "inArgNewOrder",
Type = typeof(InArgument<Order>),
Attributes =
{
new RequiredArgumentAttribute(),
},
Value = new InArgument<Order>()
}
}
};
The next time the application that is rehosting the WF designer is opened and the Created button is clicked, a
new Flowchart workflow will be created within the WF designer and a WriteLine activity will be added within the
WF toolbox. Clicking on the Arguments tab at the bottom of the workflow indicates a default parameter of type Order,
which can be passed into the workflow while it is being loaded within the WF runtime (see Figure 10-37).
Adding a new WriteLine activity to the workflow and setting its Text property to
"Passed in order product "+inArgNewOrder.Product+" and price: "+inArgNewOrder.Price.ToString()
the workflow can be saved. Running the order processor again now shows the description written from the workflow
that was loaded into the application (see Figure 10-38).
445
Chapter 10 ■ Rehosting the Workflow Designer
446
Chapter 10 ■ Rehosting the Workflow Designer
Decisions on how to manage orders that are received can now be incorporated and defined during runtime.
Using the If activity provides flexibility in determining how an order is handled. In this next example, you are going
to use the If activity to control the flow of a received order and use the WriteLine activity to simulate an activity by
writing out to the console what should happen. This is a good way of letting a non-technical user provide a model
for how a process should be performed. As an order is received, the flow of the order will be indicated through the
console using the WriteLine activities that are executed.
Here is the business logic that will be applied:
1. Orders over $10.00 must be approved.
2. Customers who place orders between $2 and $5 dollars should be sent current sales
reminders included in the order.
3. Customer’s that order Widget2 should receive a one year warranty for Widget2.
For orders that are over $10, an If activity is added to the workflow and its Condition property is set to
inArgNewOrder.Price>10. A WriteLine activity can then be added within the Then branch of the If activity to indicate
what needs to happen. In a real world application, a composite activity composed of out-of-box activities could be
used to define the approval process or even better, custom activities could be developed to handle the approval
process as well; however, in this case the WriteLine activity is used to simply demonstrate how the flow of an order
can be changed dynamically during runtime (see Figure 10-40).
447
Chapter 10 ■ Rehosting the Workflow Designer
Figure 10-40. Defining the workflow for handling orders over $10
The Else branch of the If activity illustrated in Figure 10-40 will handle orders placed between $2 and $5 so that
current sales reminders are sent to the customer along with the order (see Figure 10-41).
448
Chapter 10 ■ Rehosting the Workflow Designer
Figure 10-41. Defining the workflow for handling orders between $2 and $5 dollars
Now that the workflow checks for the appropriate price points, the last rule that needs to be implemented within
the workflow is to check for orders where Widget2 was ordered so that the customer receives a one year warranty.
Clicking the CustomWorkflow indicated in Figure 10-41, which is above the workflow, the workflow changes its view
within the designer to the root of the workflow. Another If activity can be added beneath the existing If activity
illustrated in Figure 10-42 so the last business rule can be created.
Since the last rule only needs to check for an order that includes Widget2, there is no need for this rule to be a
part of the price checks dictated by the other two rules. Pressing the Save button to save the workflow, the new rules
created immediately take effect (see Figure 10-43).
Figure 10-43 illustrates that the new rules are working correctly. The first message indicates that sales reminders
should be sent to the customer. This is because the order price was $4.30, which is between $2 and $5 dollars. The
next message indicates that an order needs approval because the order price was over $10; however the console
also indicates that the same order that is over $10 was also an order for Widget2, therefore the customer should also
receive a warranty.
Summary
Business logic built for software applications using workflows no longer has to rely on developers to integrate new
logic as business processes change. By rehosting WF components within custom software, end users are given the
power to make business logic changes as they see fit. The power of WF comes from the visualization it provides in
building workflows. WF extends this power of workflow visualization and orchestration to end users, who may be
non-technical but may require the same advantages as developers in building custom workflows.
This chapter focused on how the WF designer can be rehosted within a custom WPF application using code and
also XAML, so changes could be made via XML rather than changing code. Some new features were added within the
new WF designer in WF4.5 and this chapter discussed how these enhancements could also be added when rehosting
the WF designer. The application demonstrated how to manage workflows that were built while rehosting the WF
designer by saving the XAML files representing the workflows to the file system so other applications could execute
the workflow. The highlight of the chapter was the creation of an ordering application that simulated orders being
received. Workflows were built for defining business logic outside of the application, and then you added this logic
while the application was running so the workflow could manage how the application processed orders.
The next chapter will show how workflows can be used to define business logic within WCF services. WCF
services that are authored as workflows can also maintain state, thereby defining SOA architectures using workflows
rather than code.
450
Chapter 11
Some service calls initiated from a client are intended to be quick transactions. A client application calls the service,
receives a response, and the transaction between the client and the service is completed. However, there are also
scenarios when a service needs to maintain state, even after it has been called from a client application. This chapter
will explain the importance of building services that maintain state by using WF workflows to create services. But first
let’s talk about what services are and the history of extending services through .NET.
“Services” is a common term in software development, and it can have different meanings depending on the
context in which it is used. When I think of services, I try to think in everyday terms. If a business provides a service,
it is offering either a capability, or goods, that someone else wants. The same principles apply to building services
in software. An application offers services that another application needs to use. Software services can be provided
to other applications within the same development domain, but the capability of extending services over network
boundaries was first introduced within Microsoft.NET through ASP.NET Web Services.
ASP.NET Web Services allow applications to extend their functionality to other applications outside of their
development domain and network without having to integrate with them directly. Instead, services are exposed over
HTTP, so no matter where a service is hosted, a client can interact with the service over the Internet.
Some benefits of extending external services include the following:
• Less infrastructure to implement, reducing the overhead of doing business.
• Ease of subscribing and delivering clients business services.
• Availability to more clients outside of a network.
Originally short for “Simple Object Access Protocol,” but latterly redefined as just a name. See http://www.w3.org/TR/soap/
1
451
Chapter 11 ■ Stateful WCf ServiCeS uSing WorkfloW
WCF Fundamentals
A WCF service requires certain components called the ABC’s for implementation (Figure 11-2).
ServiceEndpoint
EndpointAddress
Binding
ContactDescription
452
Chapter 11 ■ Stateful WCF Services Using Workflow
Each of these components works independently of the others, and that is what makes the model for implementing
services with WCF so powerful. A service contract can have many different bindings and addresses for accessing
a service.
Address
An endpoint is specified by an address that indicates the location of a service through a Uniform Resource Identifier
(URI). A URL consists of four unique parts:
• Scheme: Used to indicate the transport protocol like HTTP or TCP.
• Machine: Name like www.bing.com.
• Port: Such as 8080 (Optional)
• Path: Such as /localhost/myservice.svc.
An address is either defined though code or configured. Listing 11-1 shows how an endpoint address can be defined
using C# for a service contract IService and a binding using basicHttpBinding indicating that the transport
protocol used will be over HTTP. The endpoint is added to a ServiceHost object, which is used to host a WCF service
within a Microsoft .NET executable.
Listing 11-2 indicates an endpoint address being configured using an endpoint element and setting the address
attribute. The binding is using basicHttpBinding, which also indicates that the transport protocol used will be HTTP.
Endpoints can be specified in two ways, either an absolute address can be used as indicated in Listing 11-1 or a base
address can be used for the ServiceHost object, and then an address can be specified for the endpoints.
Binding
One of the major features of WCF is separating out the transport protocol from the service. This allows the same
service to expose different bindings. Bindings are used to represent the transport protocol so clients can interact with
a service. Listing 11-1 and Listing 11-2 also illustrate how bindings can be added either through code or configured by
specifying BasicHttpBinding. The purpose of separating the binding from the service is so a binding can be selected
based on the requirements needed for exposing a WCF service. Some of the more common transport protocols that
are used with WCF services are
• basicHttpBinding: This binding supports clients that need continual support for ASP.NET Web
Services. The binding also supports communication with existing ASP.NET Web Services.
Transport security can be configured on the binding and the transport protocol used is HTTP.
• wsHttpBinding: If ASP.NET Web services do not need to be supported and other WCF services
will be calling a service over the Internet, then wsHttpBinding can be used as a binding for
transporting messages over HTTP.
453
Chapter 11 ■ Stateful WCF Services Using Workflow
• netTcpBinding: Clients can expect better performance through the intranet by specifying
netTcpBinding. This binding cannot be hosted within IIS 5 and 6, but is supported to be
hosted within IIS 7.
• netNamedPipeBinding: Supports clients that are on the same machine as a hosted service and
establishes a secure environment for communication. This binding cannot be hosted within
IIS 5 and 6, but is supported to be hosted within IIS 7.
• netMsmqBinding: Supports queuing functionality that is provided through Microsoft Message
Queuing (MSMQ) for supporting reliable transactions. netMsmqBinding allows for disconnected
scenarios where a client and service both do not have to be online at the same time. This
binding cannot be hosted within IIS 5 and 6, but is supported to be hosted within IIS 7.
• wsDualHttpBinding: Supports duplex communication between a client and hosted service,
so a service can communicate to a client through callback messages. This binding cannot be
hosted within IIS 5 and 6, but is supported to be hosted within IIS 7.
for a service is implemented through an interface object. An interface provides a contract that a class must
{
[OperationContract(IsOneWay=true)]
void ApplyForJob(JobApplication application);
[OperationContract]
bool PerformBackgroundCheck(BackgroundCheck CandidateBackground);
[OperationContract]
bool EmailCandidateFeedBackFeedback feedback)
[OperationContract]
bool InterviewCandidate(Manager manager,Candidate candidate);
[OperationContract]
bool HireEmployee(JobApplication application);
}
Notice that the there are attributes identified as ServiceContract and OperationContract. ServiceContract
indicates that the interface supports a WCF service and that the interface is the contract for how the service is set up.
OperationContract indicates each of the methods that will be used within the service. Other attribute information
can be included for each of the operations for different results. For instance, if a method is not expected to return
a result, like ApplyForJob represented in Listing 11-3, then IsOneWay=true can be used to decorate the operation
contract, indicating that there should not be a response returned back to the client.
Once a service contract is built, a class represented as the actual service that will perform the business logic can
be written to implement a service contract. Listing 11-4 represents the service that implements the service contract
represented in Listing 11-3.
454
Chapter 11 ■ Stateful WCF Services Using Workflow
{
void ApplyForJob(JobApplication application) {...}
bool PerformBackgroundCheck(BackgroundCheck CandidateBackground) {...}
bool EmailCandidateFeedBack(Feedback feedback) {...}
bool InterviewCandidate(Manager manager,Candidate candidate) {...}
bool HireEmployee(JobApplication application){...}
}
WCF services can also share data among the service and clients just as the service implementation in Listing 11-4
illustrates, using parameters and data returned from called functions. A DataContract is required in order for data to
be communicated back and forth from service to host. A DataContract defines the data structure to be used and is
represented as nothing more than a class defining an object (see Listing 11-5).
{
[DataMember]
public int BackgroundId{ get; set;}
[DataMember]
public int CandidateId{ get; set;}
[DataMember]
public bool PassedBackground{ get; set;}
[DataMember]
public string Comments{ get; set;}
Listing 11-5 shows that the CandidateBackground class uses the DataContract attribute to indicate that it can be
used between the service and its clients; however, additional data contracts would need to be created for Manager,
JobApplication, and Feedback. Each of the properties uses the DataMember attribute to indicate that the property is a
member of the DataContract and used within clients.
455
Chapter 11 ■ Stateful WCF Services Using Workflow
Each of the blue boxes in Figure 11-3 represents a process that is defined within the contract IJobApplication,
illustrated in Listing 11-3. IJobApplication is implemented by the service defined in Figure 11-4; however, there
are some limitations with this approach. WCF does not manage state between service calls, so each time a service
is called, a client request is sent to the service through a service method, and sometimes the service method may
respond with information regarding the initial call back to the client. Throughout a service call transaction, state is not
maintained between service calls. WF provides the framework for building long-running business processes that can
be executed effectively over long periods of time. A better solution for building a WCF service would be to model the
business process within WF using a workflow, while at the same time configuring the workflow to provide the same
functionality as the code represented in Listing 11-4 and exposing it as a service.
With the release of Microsoft .NET 3.5, Microsoft decided to provide the tools necessary for easily integrating
WCF and WF so services could expose business logic that was built using workflows. WF has its own project templates
within Visual Studio for building workflows that can be configured for extending business logic just as a WCF service
and can provide the same features for exposing services over the Internet and intranet the same way a WCF service
can. However, the real magic other than being able to declaratively build services using WF is breathing the breath of
life into a service and allowing it to live longer than just a simple transaction.
Workflow-First
There are a couple of features that are difficult to address when developing a straight WCF service for managing job
applications (Figure 11-3). After a candidate applies for a job the candidate needs to be interviewed. The interview is
a human process and cannot be handled automatically. A person must evaluate the candidate and then make a hiring
decision. A pure implementation of a WCF service cannot handle long-running business logic from when a candidate
456
Chapter 11 ■ Stateful WCF Services Using Workflow
is interviewed until a decision is made to hire the candidate, or between doing a background check and hiring the
candidate as an employee if the check turns out successful. A simple solution for managing logic within a service that
requires more time, would be building a workflow service that can manage state for the service.
Defining a workflow as a WCF service is a common pattern called “workflow-first.” When following a
workflow-first approach, a workflow service that is authored automatically defines the service contract that will
be used to define the WCF service. Figure 11-43, later in this chapter, uses the WCF Test Client for hosting the job
application workflow service and shows that the workflow has generated the service contract IJobApplication,
which implements service operations. WF 4.5 introduces a new pattern called “contract-first,” which generates WF
activities based on an existing service contract. The contract-first pattern will be covered later in the chapter after
demonstrating the workflow-first pattern.
Let’s take a look at how this can be accomplished. Figure 11-4 illustrates a workflow that models the business
process indicated in Figure 11-3; however, it provides a different approach for exposing a WCF service compared to
Listing 11-4. You will quickly notice that the workflow has been implemented using a flowchart flow control, which is
covered in Chapter 5.
457
Chapter 11 ■ Stateful WCF Services Using Workflow
Figure 11-4 just uses three different WCF calls for managing a job application through a client application
compared to the WCF service in Listing 11-4. The workflow first exposes a service method called ApplyForJob that
initiates the workflow instance for a job application. The service then waits for the InterviewCandidate service
method to be called so the interview of the candidate can begin. Once the results are back from interviewing the
candidate, a decision based on the interview can determine that the candidate is not a good fit for the job that the
candidate was applying for. In this case, the candidate does not pass the interview and the workflow simply completes
its execution; however, if the candidate interviewed well, then the business logic must perform a background check
that is exposed as the WCF service method called DoBackgroundCheck, which is the last method to be called within
the process. If the candidate passes a background check, then the workflow completes when the candidate becomes
an employee by being hired for the job through the HireCandidate activity.
There is also business logic represented in Figure 11-5 for providing feedback to a candidate. The
InterviewCandidate and DoBackgroundCheck service calls also provide a custom ProvideFeedbackToCandidate
activity that is used to notify a candidate of results after interviewing and having a background check processed.
Stateful Services
Combining WCF and WF promotes stateful services, which means a service can actually live for as long as the service
needs to maintain state based on interaction with the clients who subscribe to the service. Stateful WCF services
leverage the capabilities of workflows that are extended as services but provide a way for managing workflow
instances that become idle and storing them rather than keeping them within memory. WF is considered the internal
part of the service, which contains all of the internals for controlling the business logic and behavior for a service.
WCF is considered the exposed or external part of the service that communicates with clients and WF.
458
Chapter 11 ■ Stateful WCF Services Using Workflow
Visual Studio provides a first class experience for integrating WCF with WF, and the easiest way to build and test
workflows built to run as WCF services is the WCF Workflow Service Application project template (see Figure 11-6).
The template is primarily used for building workflow services that are intended to be hosted within IIS and managed
using Windows Server AppFabric. To learn more about AppFabric, check out Chapter 13.
When a WCF Workflow Service Application is used to build workflow services, the workflow extension is .xamlx,
which indicates that the workflow is intended to be hosted within IIS, and when the project is run, the workflow
service is loaded within the WCF test client so it can be debugged.
Correlation
Just because WCF and WF are combined does not necessarily mean that the service implemented will maintain state.
A workflow can expose service calls the same way a normal WCF service would: a workflow instance is created with
each call to the service and is completed soon after. Although a service can still take advantage of building complex
business logic by modeling it as a workflow, a service must handle correlation for managing the same workflow
instance that was created from an earlier WCF call.
Correlation is how a workflow service instance is identified from the other workflow service instances that
are being managed within the WF runtime. In the case of the workflow in Figure 11-4, a unique property like a
JobApplicationId can be used. In order to start a workflow service instance, the first service method that needs to be
called is ApplyForJob.
Since no other service calls for the workflow service can create a workflow service instance, the
CanCreateInstance property for the ReceiveAndSendReply activity needs to be checked, as illustrated in Figure 11-7,
to indicate that it is the service call that can create the workflow service instance. The ServiceContractName property is
also set to IJobApplication to identify the contract name that will be used to implement the WCF service.
459
Chapter 11 ■ Stateful WCF Services Using Workflow
Figure 11-7 also indicates within the Variables section a default CorrelationHandler, _handle1. This
CorrelationHandler is automatically created for a ReceiveAndSendReply activity. The name _handle1 can be
changed to something more meaningful for the unique data that will be used to correlate the workflow service
instance, and since it will be used for other service calls, its scope can be changed to the Flowchart so that other
service calls within the flowchart workflow can also use it as a correlation handler (see Figure 11-8).
Figure 11-8. Renaming the default CorrelationHandle and setting its scope
460
Chapter 11 ■ Stateful WCF Services Using Workflow
The service call ApplyForJob, in Listing 11-4, indicates that a JobApplication object needs to be passed with the
ApplyForJob service call. Correlation can be configured for the workflow to use the JobApplicationId value that is
assigned within the workflow service. The entities that make up the data for the JobApplication object are indicated
in Listing 11-6.
How the business entities are defined can really make a difference in making a workflow easy or complicated.
If the entity objects are not well thought out, additional workflow variables and workflow activities like the
assign activity and custom code activities could be needed to set values for business entities that could be
unnecessarily required and can complicate workflow orchestration.
namespace Exercise1.Core
{
public class JobApplicationStatus
{
public Guid JobApplicationId { get; set; }
public JobApplication SubmittedApplication { get; set; }
public bool PassedInterview { get; set; }
public bool PassedBackgroundCheck { get; set; }
public JobPosting JobAppliedTo { get; set; }
public Candidate ApplyingCandidate { get; set; }
}
461
Chapter 11 ■ Stateful WCf ServiCeS uSing WorkfloW
The JobApplication object that is passed in with the ApplyForJob service call will initiate the workflow
service instance with the data it needs throughout the lifecycle of the workflow. Therefore, a new variable of type
JobApplicationStatus needs to be created for holding the JobApplication object that is passed in (see Figure 11-9).
The Variable type is set to the type of JobApplicationStatus for the variable varJobApplicationStatus
object. If it is not listed as illustrated in Figure 11-9 within the dropdown box, Browse for Types can be selected for
browsing for the JobApplicationStatus type within the solution (see Figure 11-10).
462
Chapter 11 ■ Stateful WCF Services Using Workflow
The JobApplication parameter needs to be set for the service call ApplyForJob. There is a Content property on
the ReceiveAndSendReply activity. Clicking on Define allows for the content definition to be either a message
or parameters. A content definition for a Message contains a message data and message type property. The
message data property can be set to an existing variable within the workflow that will hold the data that is received
from a client; its type is the data type, which is usually the type for the variable being set. Adding Parameter
content definitions expect data contracts. Figure 11-11 illustrates adding an application parameter as indicated
for the service call ApplyForJob in Listing 11-4. The parameter is of type JobApplication and it will be assigned to
the variable varJobApplicationStatus.SubmittedApplication that was created in Figure 11-9. When a client
references the workflow service, a JobApplication parameter will be required for making the service call.
463
Chapter 11 ■ Stateful WCF Services Using Workflow
After creating the JobApplication parameter that will be passed in for the ApplyForJob service call,
JobApplicationStatus variable property SubmittedApplication will be set that corresponds with the
that was passed. Figure 11-12 illustrates how an Assign activity is being used to initialize the
that needs to be set within the service rather than from the client.
Figure 11-13 illustrates the Assign activity properties for setting the job application ID to the
varJobApplicationStatus variable.
464
Chapter 11 ■ Stateful WCF Services Using Workflow
Correlation is now ready to be set up for the ApplyForJob service call that is defined with a ReceiveAndSendReply
activity, but first there are two things to consider. Will the value for the correlation handler be set from the client
or inside the workflow? In the case of a job application, the JobApplicationID will be set within the workflow, so a
CorrelationInitializer will need to be set up within the SendResponse activity. The CorrelationInitializer
initializes the correlation handler with a data value that will be used for correlating workflow service instances.
Clicking on the SendReplyToReceive template designer, pre-defined Send and Receive activities housed within a
parent Sequence activity are defined, and selecting Content, a parameter can be created that will be returned to a
client (see Figure 11-14).
The ApplicationId that is set will be a type System.Guid and can be used by the client for referencing the
job application with other service calls within the workflow. Figure 11-15 illustrates how to add a parameter
called applicationId, which has a type System.Guid and sets the value for the parameter to
varJobApplicationStatus.JobApplicationId, which will be used for correlating workflow service instances.
465
Chapter 11 ■ Stateful WCF Services Using Workflow
Figure 11-15. Setting the parameter that will be returned for a service call
A CorrelationInitializer can now be added for the correlation handler, hndJobApplicationId, that
was created earlier. Figure 11-16 illustrates that the applicationId parameter that is returned to the client will be
handler hndJobApplicationId.
Luckily the XPath query can be selected, and once it is selected, the Key value is set to JobApplicationId (see
Figure 11-17).
466
Chapter 11 ■ Stateful WCF Services Using Workflow
Testing Correlation
Correlation for workflow service instances is now set up, and the job application ID value that the workflow will use
for correlating workflow service instances is defined within the workflow itself; however, correlation data can be set
externally by the client instead. The next step is to provide a way for client applications to check the status of a job
application. A parallel activity is required so that the job application process can continue, while at the same time
a job application status can be checked to see the current status for an application. The word “while” in the previous
sentence is appropriate because a While activity is also needed so the GetJobApplicationStatus service call can
continue to be called. Finally, a Pick activity is needed so the GetJobApplicationStatus service method can be
called continuously while the condition for the While activity is met (see Figure 11-18).
Figure 11-18. Checking a job application status while a job application business process is running
467
Chapter 11 ■ Stateful WCF Services Using Workflow
Add another variable called varWorkflowComplete, which has its scope set to the parallel activity and is a
Boolean type with a default value of false. The new variable sets the condition property of the While activity,
so it will keep running while the condition is met. Figure 11-18 illustrates a ReceiveAndSendReply activity being
dragged to the designer fabric and added within Branch1 of the Pick activity. The OperationName property that
identifies the service call is set to GetJobApplicationStatus and the ServiceContractName property is set to
IJobApplication, which is the same contract name the service call ApplyForJob implements. This is a new service
call that is not part of the original service contract defined in Listing 11-3; however, it demonstrates that the service
contract can be defined on the fly by declaratively building a workflow. The GetJobApplicationStatus service call
needs to pass in the job application ID that was returned as a reference ID when originally calling the ApplyForJob
service call. Clicking on the Content property for the new Receive activity, a message content definition is used
this time instead of building listing a parameter, just to demonstrate how it works; however, a parameter could be used
as well. To accept the Guid formatted job application ID that will be passed in with the service call, a new variable
called varJobApplicationId is created with a variable type of Guid. Its scope can stay within the Sequence
activity, which serves as the parent activity of the Receive activity. Since the hndJobApplicationId and the
will be used across the Parallel activity, the variable's scope needs to
Parallel for both the hndJobApplicationId and varJobApplication variables (see Figure 11-19).
Creating the varJobApplication and changing scope for the other two variables
Clicking on the Content property of the Receive activity brings up the Content Definition indicated in Figure 11-20,
so a message or parameters can be added for the GetJobApplicationStatus service call. Selecting Message, the message
data can be set to varJobApplicationId, and System.Guid can be added as the message type.
Next, correlation for the GetJobApplicationStatus service method needs to be created for the job application
ID that will be passed in, and correlated with hndJobApplicationId. Figure 11-21 illustrates the CorrelatesWith
property being set to hndJobApplicationId and an XPath query being created. Remember that the Key value needs to
be set to JobApplicationId, as discussed earlier when setting up correlation in Figure 11-16.
468
Chapter 11 ■ Stateful WCF Services Using Workflow
The final step before correlation can be tested is setting the Content property for the SendReply activity used to
send information back to the client when the GetJobApplicationStatus service call is called. Figure 11-22 illustrates
creating a return type of JobApplicationStatus for the service call. The variable varJobApplicationStatus, which is
set after calling the ApplyForJob service call, is set as the return value.
Figure 11-22. Setting the JobApplication object returned from the service call
Figure 11-23 illustrates the WCF test client automatically hosting the Job application service.
469
Chapter 11 ■ Stateful WCF Services Using Workflow
Building the service client by adding a service reference to the workflow service is no different than adding a
regular WCF service. Any application that can reference and consume a WCF service is capable of working with Job
application service, however to make sure correlation is working correctly, I will show you a simple windows form
application that is built to test the functionality for the service. Buttons are used to test each of the service calls for the
service so at this only two are needed. One button is added to create a new job application and another for checking
the status of a job application (see Figure 11-24).
A service reference can be made to the JobApplicationService while it is being hosted within the WCF test
client, by right-clicking on the Service References folder within the windows form project.
Figure 11-25 indicates the service reference and the two service calls, ApplyForJob and
GetJobApplicationStatus, that are implemented for the IJobApplication contract. The code in Listing 11-7 shows
how the service is instantiated so the two service calls can be made.
Listing 11-7. Windows Form Code for Implementing the JobApplicationService Service
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;
471
Chapter 11 ■ Stateful WCf ServiCeS uSing WorkfloW
namespace JobPostingClient
{
public partial class Form1 : Form
{
private JobPostingService.JobApplicationClient jobApplicationService = null;
public Form1()
{
InitializeComponent();
jobApplicationService = new JobPostingService.JobApplicationClient();
}
Setting breakpoints within the code on the code brackets after each of the two service calls indicates if the
service is working correctly. Running the Windows form and clicking the cmdCreateApplication button that
calls the ApplyForJob service call returns a string instead of the System.Guid return type that was set within the
workflow. Figure 11-26 indicates within the LOCALS (VS2012 going for the “Metro” look with capital headings)
window that the JobApplicationRef variable set by the service call ApplyForJob returns a string that indicates the
service call works.
472
Chapter 11 ■ Stateful WCF Services Using Workflow
Clicking on the cmdCheckStatus button calls the GetJobApplicationStatus service call and returns a
JobApplicationStatus object that correlates with the job application ID returned from the ApplyForJob service call
that was called in Figure 11-26.
The results in Figure 11-27 illustrate that the job application ID that was passed in as a parameter to the
GetJobApplicationStatus service call returned a JobApplicationStatus object, with the correct information
pertaining to the candidate that applied to the job highlighted in blue.
The results returned in Figure 11-27 indicate that the service is maintaining state of the workflow service instance
and not retrieving data from external data stores. This means that all processing has been consolidated and is being
handled within the workflow rather than relying on external processes for handling business logic for a candidate
posting for a job.
473
Chapter 11 ■ Stateful WCF Services Using Workflow
■■Note System.Guid is the type for the parameter that is set within the workflow; however, the client will receive a
string value type instead.
Persistence
Persisting workflow instances is the main ingredient that allows for stateful services to maintain state. Out of the
box, WF persistence uses SQL Server scripts that are provided for building a database and the logic for managing
the state of a workflow instances. Workflow instances are managed as clients interact through a WCF service, which
then creates a WF instance. A workflow instance represents an instance of a workflow that is created as a client call
is made from a client through a WCF service. After a workflow instance is created, the state of the workflow instance
Figure 11-28. Service is no longer aware of the workflow service instance after restarting
■■Tip Chapter 8 is entirely focused on persisting workflows so use it as a reference for this section to further your understanding
around persisting workflows.
474
Chapter 11 ■ Stateful WCF Services Using Workflow
The solution to this problem is simply adding persistence, which comes out of the box. In Chapter 8, you went
through running the scripts for building the persistence store within SQL Server. You can now simply configure the
workflow to use persistence, so you no longer have to worry about the service going down and not have a persisted
snapshot of the last step for the workflow. Listing 11-8 shows the web.config and how persistence is added within the
service behavior element.
Uncommenting the WorkflowInstanceStore section, which is bold within Listing 11-8, will cause the service to
use the pre-existing persistence store that was created in Chapter 8. Persistence has now been configured so now the
workflow can continue to grow and additional functionality required for implementing the job application business
process can be added.
475
Chapter 11 ■ Stateful WCF Services Using Workflow
The next step for the workflow is providing feedback based on the interview for a candidate who has applied
to a job posting. A new Receive activity is used to indicate that an interview has been done and the results for the
interview are provided as a simple true or false indicating the candidate passed or failed the interview for the job
application. Feedback is not required in response to the person rating the interview with the candidate, therefore the
Send activity is not required. The content definition adds the two parameters indicated in Figure 11-29.
The first parameter, ApplicationId, is used to correlate the right job application with the right workflow service
PassedInterview, simply gives thumbs up or down and sets the current job
PassedInterview for varJobApplicationStatus. Figure 11-30 shows how
ApplicationId parameter that is passed with the InterviewCandidate service call.
Figure 11-30. Correlation based on the ApplicationId parameter passed in for the service call
476
Chapter 11 ■ Stateful WCF Services Using Workflow
Workflow Termination
Figure 11-4 shows that the workflow completes in regards to the candidate’s interview or background check
failing, and provides feedback to a candidate. Ending the workflow this way provides a natural way to complete
the job application process; however I also want to show how this can also be done using a TerminateWorkflow
activity. Based on the results for the interview that was set for the PassedInterview property of the
varJobApplicationStatus variable, a FlowDecision activity is used to control flow for either terminating the
workflow or continuing on and performing a background check for the candidate. After dragging a FlowDecision
activity on to the designer canvas, set its DisplayName property to Passed Interview, and its Condition
property to varJobApplication.PassedInterview property that was set in Figure 11-29. A TerminateWorkflow
activity can also be added to the workflow and connected to the false node for the FlowDecision activity. The
TerminateWorkflow activity's Exception property is set to a new ApplicationException object and the Reason
property is set to a string that indicates why the workflow is being terminated (see Figure 11-31).
Figure 11-31. Setting the Exception and Reason properties for the TerminateWorkflow activity
Figure 11-32 illustrates an updated workflow after adding and configuring the FlowDecision and
TerminateWorkflow activities. The client code can now be updated to make a call to the InterviewCandidate
service call by updating the service reference for the Windows form application; adding a new button and event for
when the button is clicked; adding the service call InterviewCandidate; and passing in a valid job application ID and
a Boolean value indicating if the interview was successful or not within the click event.
477
Chapter 11 ■ Stateful WCF Services Using Workflow
■■Tip Because the TerminateWorkflow activity is used for throwing an exception, it does not provide a graceful way to end a
workflow, because the exception thrown by the activity has to be accounted for. The best practice for using the TerminateWorkflow
activity is when an exception has occurred within the workflow. Letting a workflow complete is the natural way to end its process.
478
Chapter 11 ■ Stateful WCF Services Using Workflow
The Receive activity defines the service call PerformBackgroundCheck and implements IBackgroundCheck.
The service will accept a string that represents a SSN and returns a Boolean showing whether or not a SSN has a felony
record. Figure 11-34 shows two variables that are created for holding the SSN and returning the results for the service.
479
Chapter 11 ■ Stateful WCF Services Using Workflow
Figure 11-34. Variables for holding and returning results from the service
The assign activity uses a random number generator to generate a value for the ConvictedOfFelony by setting it
to Convert.ToBoolean(new Random().Next(0,2)).
Content for the activity has been set up as a message that accepts an SSN of type string. A parameter could have
been used as well but since all the service needs is the SSN, a message was used instead (see Figure 11-35).
The Reply activity just returns the value for the variable ConvictedOfFelony that is set in Figure 11-36.
Figure 11-36. Setting the message data that will be returned from the service
SendAndReceiveReply Activity
Now that the Background check service has been created, the Job application service needs to communicate to
the Background check service and request a background check for a candidate. A SendAndReceiveReply activity is
required for a workflow to communicate to another WCF service and receive a reply. Sometimes a service may not
respond instantly and that has to be considered while building the workflow. When a SendAndReceiveReply activity
is added to the designer canvas, a Send and Receive activity are automatically added to the workflow. Figure 11-37
illustrates the Send activity’s OperationName property being set to PerformBackgroundCheck, which is the service call
with in the Background check workflow service. The System.ServiceModel.Endpoint property for the Send activity
480
Chapter 11 ■ Stateful WCF Services Using Workflow
has two properties: AddressUri, which identifies the endpoint address where the background check service is located,
and Binding, which indicates the type of binding to use over the selected transport protocol for communicating with
the background check service.
The message is added as the content definition for the Send activity and the message data that is sent is the
SSN value stored in the variable varJobApplicationStatus.SubmittedApplication.ApplyingCandidate.SSN
(see Figure 11-38).
Figure 11-38. Setting the data sent from the send activity
After the Send activity sends a request for a background check for a SSN, the ReceiveReplyForSend activity
will wait until it receives a response back from the background check service, and once the response is received
the message content definition that was created, will set the results of the background check to the variable
varJobApplicationStatus.PassedBackgroundCheck (see Figure 11-39).
481
Chapter 11 ■ Stateful WCf ServiCeS uSing WorkfloW
Based on the results from the background check, a candidate will be hired; however, if the candidate does not
pass the background check, the candidate is not hired and the job application process completes. To help make the
Decision activity is used for controlling the flow of the
JobApplicationStatus.PassedBackgroundCheck
illustrated in Figure 11-40.
After setting the FlowDecision, which will decide whether or not to perform a background check on the
candidate, the FlowDecision node that flows for a false condition should flow to the Assign activity, indicating to
the candidate that they did not pass the job application process. The FlowDecision node that flows for a result of true
based on its flow results should flow into a custom HireCandidate activity. Listing 11-9 shows the basic code used
to simulate hiring a candidate as a JobApplicationStatus object is passed into the HireCandidate activity with the
appropriate data for adding custom business logic for adding an employee.
482
Chapter 11 ■ Stateful WCF Services Using Workflow
using System.Text;
using System.Threading.Tasks;
using Excercise1.Core;
namespace Exercise1.CustomActvities
{
public class HireCandidate:CodeActivity
{
[RequiredArgument]
public InArgument<JobApplicationStatus> ApplicationStatus { get; set; }
Figure 11-41 shows the two breakpoints that will stop workflow execution while running the workflow in debug
mode. A checkbox control was added to the client to indicate a passed or failed interview (see Figure 11-42).
Figure 11-41. Adding the condition for hiring a candidate for the job
To test the workflows and how they work together, right click on the wfJobApplicationService.xamlx workflow
and click “Set As Start Page.” Running the project will automatically load the WCF Test Client. Clicking “File Add
Service” pops open the dialog box for starting more services. Figure 11-43 shows how the background check service
has been started within the WCF Test Client.
483
Chapter 11 ■ Stateful WCF Services Using Workflow
After the services have started, the service reference to the job application service needs to be updated by right-
clicking the service reference and selecting Update Service Reference. After the service has been updated, the client
application can be started. Clicking the Create Job Application button will kick off a new job application. After the job
application is created, checking the checkbox will indicate to the workflow that the interview passed and leaving it
unchecked indicates that interview failed; the behavior within the workflow will mimic the same logic indicated by the
activity breakpoints set in Figure 11-41. If a candidate passed an interview, the job application calls the Background
check workflow and passes the SSN of the candidate. After the Background check workflow waits and then replies
with a random true or false value indicating the success of the background check. If the Background check fails, then
the workflow completes, but if the candidate passes the background check, then the workflow executes the custom
HireCandidate activity (see Figure 11-44).
484
Chapter 11 ■ Stateful WCF Services Using Workflow
Almost all of the requirements have been modeled within the workflow for handing the job application process
except communicating results back to the candidate for both the interview and the background check. Listing 11-10
indicates the code used to simulate e-mailing feedback to a candidate for the results of both an interview and
background check.
namespace Exercise1.CustomActvities
{
public class ProvideFeedbackToCandidate:CodeActivity
{
[RequiredArgument]
public InArgument<string> MessageToCandidate { get; set; }
[RequiredArgument]
public InArgument<string> EmailAddress { get; set; }
To implement the candidate feedback logic, a Sequence activity needs to be added before each of the
FlowDecision activities (see Figure 11-45).
485
Chapter 11 ■ Stateful WCF Services Using Workflow
Sequence activities as the parents for holding the feedback logic to candidates
The ProvideFeedbackToCandidate activity takes two arguments, an EmailAddress property which is set to
(which is the candidates
e-mail address that was provided when the job application was submitted), and a MessageToCandidate property
indicating the message text that will be shared with the client. An If activity determines the message that is sent to
the candidate, so for the interview feedback, varJobApplicationStatus.PassedInterview is used to determine the
verbiage for the message sent to the candidate. Figure 11-46 shows the interview feedback business logic.
486
Chapter 11 ■ Stateful WCF Services Using Workflow
The workflow is now complete based on the requirements defined earlier in the chapter for managing a job
application process. Adding additional breakpoints within the HireCandidate and ProvideFeedbacktoCandidate
custom code activities adds additional debugging capabilities for stepping through the code. Adding a tracking
profile will also provide transparency for monitoring the flow of the workflow as well. Running the job application
and background check workflows within the WCF Test Client allows the process to be tested against a client. After
clicking the Create Job Application button illustrated in Figure 11-44, checking the Passed Interview checkbox, and
then providing the feedback about the interview by clicking on the Interview Feedback button, the first breakpoint
is hit within the ProvideFeedbackToCandidate custom activity with a message to the candidate and their e-mail
address (see Figure 11-49).
487
Chapter 11 ■ Stateful WCF Services Using Workflow
After notifying the candidate that they have passed the interview, the workflow hits the next breakpoint on the
with the description Do Background Check (see Figure 11-50).
The background check takes ten seconds, simulating a human decision for checking background information.
The next breakpoint is on the ProvideFeedbackToCandidate activity, again for notifying the candidate about the
background check, which is simulated by a random number that generates a true or false from the Background check
service. Figure 11-51 indicates that the background check passed and that the candidate is on his or her way to getting
hired for the job.
488
Chapter 11 ■ Stateful WCF Services Using Workflow
Finally the candidate is officially hired when the HireCandidate activity executes, indicating that the
candidate has passed both the interview and the background check, and provides information about the candidate so
it can be added within an HR system (see Figure 11-52).
Clicking the Check Status button illustrated in Figure 11-44 for the client application shows the results within the
client application the status of the job application (see Figure 11-53).
489
Chapter 11 ■ Stateful WCF Services Using Workflow
One way to terminate the job application process is to add another PickBranch activity to the Pick activity
within the While activity, and drop just a Receive activity within it. The OperationName is TerminateJobApplication
and the ServiceContractName property needs to be IJobApplication. The Receive activity's content
definition can be the same as the GetJobApplicationStatus, which has the message data set to
varJobApplicationId and the message type to System.Guid. Correlation can also be set up to correlate the value
passed in with the hndJobApplicationId CorrelationHandle. A TerminateWorkflow activity is added within the
Action section of the PickBranch activity and its Exception property will be set to new ApplicationException()
and the Reason is set to "Client terminated the workflow" (see Figure 11-55).
490
Chapter 11 ■ Stateful WCF Services Using Workflow
Figure 11-55. Terminating the workflow during the job application process
The Flowchart activity also needs to be modified to indicate to the While activity that it no longer needs to
process the GetJobApplicationStatus service call. Since the varWorkflowComplete has scope within the Parallel
activity, it can be set within the Flowchart activity by using an Assign activity, which is connected to the
HireCandidate activity illustrated in Figure 11-56.
491
Chapter 11 ■ Stateful WCf ServiCeS uSing WorkfloW
During the process of job application, the results can be checked throughout the process. If the client decides
to terminate the workflow during the process, the workflow will be terminated. When the workflow completes and
a candidate is hired, the varWorkflowComplete variable is changed to true and the status of the workflow can be
checked one more time. Checking the status after the final check will result in the returned message in Figure 11-54.
A contract-first workflow relies on an existing service contract, so it can generate WF activities that can be used to
build a custom workflow service. Table 11-1 indicates the WCF attributes and how they map to contract-first.
492
Chapter 11 ■ Stateful WCF Services Using Workflow
493
Chapter 11 ■ Stateful WCF Services Using Workflow
To demonstrate how contract-first works, I will use the same service contract defined in Listing 11-3 earlier
in the chapter. The first step is to create a new WCF Workflow Service Application project within VS2012 (see
Figure 11-6). Best practices for building WCF services calls for a separate project for segregating WCF service
contracts, so a reference to another project where the service contract resides can be made. To keep things simple,
though, the IJobApplicationService service contract can be added to the new workflow service project, so the
project can import the service contract. Figure 11-57 shows how right-clicking on the project provides a menu
option called “Import Service Contract”.
Clicking “Import Service Contract” tells VS2012 to filter all references to service contracts that are either local
to the project or referenced within other projects. IJobApplicationService is located within the same project (see
Figure 11-58).
494
Chapter 11 ■ Stateful WCF Services Using Workflow
Once a service contract is selected, VS2012 lets you know that something wonderful has happened and that
generated activities based on the service contract will appear within the WF toolbox the next time the project is rebuilt
(see Figure 11-59). Rebuilding the project will show the new activities, which align to each of the operation contracts
of the service contract.
495
Chapter 11 ■ Stateful WCF Services Using Workflow
The WF toolbox will contain the generated activities that can be used to define a workflow for a job application
process after rebuilding the project (see Figure 11-60).
Once one of the generated WF activities is added to the WF designer, you will notice that the parameters have
Figure 11-61. Already defined application parameter based on the operation contract “ApplyForJob”
Validation
The premise for defining a service contract is, of course, creating a service that implements it, but by default there is
no validation that additional changes to the generated activities will reflect the implementation of the existing service
contract. For example, Figure 11-21 shows that the ApplyForJob_Receive activity has only one parameter called
“application,” but you can add another parameter and still have the workflow service compile. In order to validate
that the workflow service continues to implement the service contract, even as the workflow is being authored, a
new property on the workflow service root called “ImplementContracts” must be set to the service contract that the
workflow should implement (see Figure 11-62).
496
Chapter 11 ■ Stateful WCF Services Using Workflow
Figure 11-62. ImplementedContracts property for validating a workflow service implements a service contract
Clicking the ImplementedContracts property brings up an editor so the service contract can be searched. Figure 11-63
shows that IJobApplicationService has been selected, validating that the workflow service should implement that
contract.
Figure 11-63. Setting the ImplementedContracts property for validating the workflow service implements the
service contract
Now when the ApplyForJob_Receive activity is modified by adding a new parameter called “SSN,” the workflow
designer indicates that the workflow service no longer implements the contract (see Figure 11-64).
497
Chapter 11 ■ Stateful WCF Services Using Workflow
Workflow error validating the contract is not being implemented because of a new SSN parameter
Figure 11-65. The workflow is still throwing errors because all the operations have not been added to the workflow
498
Chapter 11 ■ Stateful WCF Services Using Workflow
■■Tip The contract-first pattern is a great new feature that can kick-start authoring workflows and should be primarily used while
converting WCF services to workflows. Following a workflow-first pattern provides a more natural way to author workflows when a
service contract does not already exist. When WF activities are generated around a service contract, depending on how the service
contract is defined, the generated WF activities could require modification. This is because existing WCF services are primarily authored
around receiving and sending data, with minimal consideration of implementation as an executing business process.
Summary
WCF services pick up on the shortcomings that became apparent with using ASP.NET Web Services and provide
a framework that allows developers to build services that can be used over additional network protocols, provide
message reliability, and support distributed transactions. However, the real power of extending services to clients
combines the rich features of WCF along with key features from WF. WF introduces to WCF services the ability to
provide transparency for service business logic by modeling business processes and maintaining state for the business
logic a service provides.
This chapter walked through the steps and key points of building a workflow that maintains state throughout the
lifecycle of job applications. It also demonstrated that a workflow service can be built and then tested to validate that
it is working correctly. Then it showed how a workflow can call another workflow and wait for its response.
A new feature in WF4.5, called contract-first, was also introduced by showing how custom workflow activities
can be generated through WF4.5 by using an existing WCF contract. Once WF activities are generated from a service
contract, a custom workflow can be authored quickly by using the new activities. The workflow can also be validated
against a service contract to make sure it is implementing the contract correctly.
This chapter has laid some of the groundwork for the next chapter, which will show how workflows can be hosted
within Azure, optionally as WCF services.
499
Chapter 12
What is the cloud? How can it help my business? These are the questions I hear from customers who are interested in
either moving to the cloud or contemplating an investment in using the cloud to start a business. Generally what I tell
these customers is that the cloud levels the playing field so that different size companies can have access to the same
amount of IT infrastructure and computer power available to them on-demand. Therefore, limitation on computing
power and scalability is no longer a question.
Once cloud technology started becoming more popular, workflow enthusiasts began experimenting with the
capabilities the cloud could offer for building and running workflows within Windows Azure. If you are not familiar
with the cloud, think about it as being able to utilize servers that are hosted off premise or not physically located with
in an organization for running software. The servers that are used are virtual and behave just like a physical server
without a user even knowing it, so you can remote desktop into them just like a physical server. The difference is that
virtual servers run in memory and a physical server can have many virtual servers running within it.
What does the cloud mean to you from a developer’s perspective? Cloud computing offers developers the
freedom to do what we do best, which is developing software. A major concern I have always had when delivering
software solutions to clients is the physical infrastructure. I have never been interested in setting it up, and happily the
cloud removes the stress of having to worry about how this is done.
One of the main goals of Windows Azure is to provide a development experience that does not change the
way developers write software. I can write software just as I always do and decide later if and when I want to
deploy my software to the cloud. Windows Azure also provides a way for my software to grow as the usage of my
applications grows. This is done by configuration rather than more processors or memory. Once a change is made
to the configuration, the results are immediate, so there is no waiting around until someone gets the time to make
the change. The same thing applies to scaling down an application. Maybe peak times for clients accessing my
applications are seasonal, such as a sporting event like the Super Bowl. In order to save money, I might want to scale
down my application so it does not use up as much memory since I am being billed based on application utilization.
Now that you have an idea how cloud computing works, the rest of this chapter will explain the advantages of
exposing workflows through the cloud using Windows Azure by demonstrating different scenarios of how to build and run
workflows within the cloud. In this chapter, I will cover configuring Windows Azure and the different components within
Azure that can be used to host workflows within the cloud. Once a foundation for Azure is established, I will go over the
different business scenarios and patterns and practices for providing solutions using workflows hosted within Azure.
Windows Azure
Windows Azure is Microsoft’s cloud technology and it provides different delivery models for providing software
solutions that run within the cloud. The different delivery models are as follows:
• Software as a Service (SaaS): Services provided through software that clients can subscribe to.
These services are usually domain specific, such as finance, sales, and human resources. As
new clients subscribe to the service, there is little to no overhead for the software
service provider.
501
Chapter 12 ■ Workflows in Windows Azure
• Platform as a Service (PaaS): Provides a development services that can be either tied together
or used separately for building SaaS applications. Some of the essential services that Windows
Azure provides are services for running web applications and worker services for running
external business processes.
• Infrastructure as a Service (IaaS): Provides the components for hosting virtual hardware and
networks. Azure allows virtual machines to be hosted and scaled out depending on the growth
of the business it supports.
Each of these service offerings is illustrated in Figure 12-1. You may be asking yourself, what is the difference
between the cloud and simply finding a nearby data center that provides server leasing? Companies that lease servers
have two models for getting by with as few servers as possible. The first model allows the client leasing the server to
manage the software and upkeep for the server. The second model is where the hosting company manages the server
in return for a fee, so the client does not need to worry about the maintenance. Cloud technology is different because
Azure Portal
The latest Windows Azure was announced by Microsoft at the TechEd North America 2012 conference. One of the
most noticeable changes is the look and feel of the Azure Portal. The Azure Portal is the main web site used for
working with the many features that Windows Azure provides. The last portal provided was built using Microsoft’s
Silverlight; the new portal has been designed using JavaScript and HTML5, the latest release of HTML. The portal’s
web address is http://manage.windowsazure.com/, but in order to work within the Azure Portal, you must have a
Windows Live account. Figure 12-2 illustrates the login screen that a user is redirected to in order to either create a
new account or sign in with an existing one.
502
Chapter 12 ■ WorkfloWs in WindoWs azure
While I was writing this chapter, Microsoft promoted a Windows Azure 90-day free trial. To check if it is
still available, visit www.windowsazure.com/en-us/pricing/free-trial/. With the free trial you get access
to the following:
• 750 small compute hours a month
• 10 shared web sites
• 1GB SQL relational database
• 20GB with 1,000,000 storage transactions
• Unlimited bandwidth inbound with 20GB outbound
Once a new or existing account is used to log in, you can access the portal. If the account used to log in is eligible
for the promotion, the first step is to create a new Azure account (see Figure 12-3).
503
Chapter 12 ■ Workflows in Windows Azure
A mobile phone number is required to verify the new account. Entering a mobile number causes a verification
code to be sent via a phone call or text message. Once the text message is received, the code can be added to verify the
account (see Figure 12-4).
504
Chapter 12 ■ Workflows in Windows Azure
The final step is billing information. Since this is a trial, you will not be billed; however, if you decide to upgrade,
you can use the credit card information as the source of payment. After the account is set up and the trial is approved,
you can view the new Azure Portal, as illustrated in Figure 12-5.
505
Chapter 12 ■ Workflows in Windows Azure
The new portal has been designed to perform better than the previous one and navigation between all the
services is more intuitive. If you have worked with Azure before, some of the features are still consistent with what
was provided earlier; however, there are some new features that were just released within the Azure preview. First,
the trial comes with 10 free web sites. Building web sites was not a feature that was provided until now. The second
major feature is the ability to create VMs or virtual machines within Azure, without having to worry about any of the
complexities of building them. Base operating systems can be selected and built within minutes, without having to
install the operating system manually. Even though these new features are off topic for what will be covered in this
chapter, you should be aware of them as you start learning more about what Azure has to offer.
Cloud Services
Figure 12-3 indicates that the trial offering for using Windows Azure offers 750 hours of cloud services. Cloud services
provide the platform services, or Platform as a Service (PaaS), for building software. Cloud services alleviate the effort
• High availability (HA) for making sure that the hosted software is always available and is
resilient to system failure.
• Administration for hosting the software, which is minimal.
• Scalability for the software as its usage grows from demand.
Cloud services use virtualization to run instances of Windows Server for hosting applications, which is different
Web Roles
Web roles run within IIS are designed to host web applications that provide services for clients over HTTP or HTTPS;
therefore, a web role is where code is typically written for building a web application that resides when using cloud
services. More than one web role can be created and run at a given time.
Worker Roles
A worker role runs on a separate thread from a web role and is typically used to run background business processes for
a web role in order to provide a level of work for the application. The worker role is continuously being executed, for
example, as a client executes specific functionality from a web application that is hosted within a web role; tasks can
be generated that the worker role can handle asynchronously on a separate thread. When a worker role is added to a
project within Visual Studio, the default WorkerRole class illustrated in Listing 12-1 is provided, which basically loops
through every 10 seconds and writes “Working” within the compute emulator UI (which will be discussed later).
506
Chapter 12 ■ Workflows in Windows Azure
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
namespace WorkerRole1
{
public class WorkerRole : RoleEntryPoint
{
public override void Run()
{
// This is a sample worker implementation. Replace with your logic.
Trace.WriteLine("WorkerRole1 entry point called", "Information");
while (true)
{
Thread.Sleep(10000);
Trace.WriteLine("Working", "Information");
}
}
return base.OnStart();
}
}
}
■■Tip The workflow instance state should not be maintained within Azure roles because of the efforts made through
Azure to manage the virtual instance. Therefore, instance states should be maintain within the services provided for
data management like queues, blobs, tables, and SQL databases.
Role Scalability
Each role is designed to be run either together or separately depending on the requirements of the software that is
hosted; however, because they are essentially virtual machines at the core, the charge applies to each hour a role is
running. There can also be more than one of each type of role running simultaneously, and each instance of the same
type of role running would use the same code. The reason for running more than one role, either a web or worker
role, is to provide better performance and high availability in the case of instance failure. Both web and worker roles
provide the code and configuration for handling the different parts of software functionality.
507
Chapter 12 ■ Workflows in Windows Azure
Roles can also be configured to handle scalability defined from client utilization. The size of the VM can be
changed based on the number of CPU cores, memory, and file system size dedicated to running a VM instance.
Table 12-1 illustrates the different sizes that can be selected for scaling out cloud service roles.
Virtual CPU Cores Memory Disk space for Local Disk Space For Local Allocated
Machine Size Storage Resources in Storage Resources Bandwidth
Web and Worker Roles in a VM Role (Mpbs)
ExtraSmall Shared 768MB 19,480MB (6,144 MB 20GB 5
reserved for system files)
Small 1 1.75GB 229,400MB (6,144MB 165GB 100
reserved for system files)
3.5GB 500,760MB (6,144 MB 340GB 200
reserved for system files)
4 7GB 1,023,000MB (6,144 MB 850GB 400
reserved for system files)
14GB 2,087,960MB (6,144MB 1890GB 800
reserved for system files)
is because software can scale better when different methods for managing data are strategically implemented.
Windows Azure provides three different and unique choices for managing different data within applications. It may
seem that these data storage options are only for applications hosted within Windows Azure but that is not the case.
Each of the storage methods that are discussed can also be used for applications not running within Windows Azure,
like an application running within on-premise data centers or within client-hosted applications like mobile devices,
laptops, and tablets.
Table Storage
Table storage is used for storing large amounts of data that can be organized in a tabular format, but the data stored
does not need to be structured as relational data within a database like SQL Server. For instance, the other day
my wife, who works in the education field, was asking me about storing student data within a spreadsheet. I was
impressed at how well she had organized each column within the spreadsheet to represent a characteristic for the
student data and thought how table storage would make a good candidate. Another example for using table storage is
storing logging information that records an application’s performance. Although a case can be made to store logging
data within a database, it might make more sense to hold large amounts of logging data within table storage. For one
thing, it will help the application scale because there is less demand on the database; moreover, because space is
plentiful, it can be a cheaper option as well.
508
Chapter 12 ■ Workflows in Windows Azure
Blob Storage
During your development career, you may have written software that required interaction with media files. One viable
approach might be to use a file system to store the files so that the software could access them on demand. However,
over time, as the need for storage starts growing, using the file system can become unmanageable. Blob storage is
Windows Azure’s solution for storing files within the cloud, and it also provides a cheaper model for implementing
file storage. One good example of using blob storage is to provide image illustrations for products sold from an
e-commerce site. Each image associated with a single product can be stored within blog storage rather than taking up
space within the file system or serializing the image files as binary within the database.
SQL Databases
So far I have discussed data storage methods that are a part of Windows Azure and therefore use APIs to interact
with the respective storage types. Windows Azure also provides SQL databases, which are just like the SQL Server
databases that developers are already familiar with for storing data that is relational. Relational data consists of
different tables that share relationships. For example, an employee table can logically be related or have a foreign key
relationship to a job role table, which associates an employee with a job position the employee has within a company.
■■Note Both blob and table storage have APIs that can be extended to manage storage. As I will demonstrate later, blob
storage can be used to work with workflows in Azure.
509
Chapter 12 ■ Workflows in Windows Azure
The Windows Azure SDK installs emulators for testing locally software written to be hosted within Azure.
Figure 12-7 illustrates the emulators running within the taskbar for Windows 7.
510
Chapter 12 ■ Workflows in Windows Azure
Azure Workflows
Running workflows within the cloud sheds new light on exposing software and services to more clients. There is
no boundary in the cloud like there might be for software that runs within an organization’s network domain. It is
very easy to write software and expose it to the Internet via the cloud. Since workflows can be exposed as services,
a broader range of clients can now subscribe to workflow services that are published out to the cloud. Even though
Azure has been around for a couple of years, hosting workflows within Azure is nothing new. In fact, hosting
workflows within the cloud is something that die-hard workflow developers have been doing for some time. WF and
Azure make the perfect marriage, and as each of the technologies matures, Microsoft will continue to make sure that
the relationship between the two grows stronger by providing more functionality to support the two technologies and
make the integration process easier.
■■Note It is important to mention that during the same time of writing this book, the guest OS images within Azure do
not support .NET 4.5. The release of WF4.5 does not change the development story or hosting patterns from what I will
share and demonstrate.
511
Chapter 12 ■ Workflows in Windows Azure
On the other hand, Azure provides the capabilities for WF services to be managed off-premise or outside of a
business’s data center and also provides all of the resources and infrastructure needed for scalability. The WF runtime
requires an executing process so it can be hosted, using the following WF hosts:
• WorkflowApplication
• WorkflowServiceHost
• WorkflowInvoker
Table 12-2 shows some common patterns around hosting workflows within Azure. Quite commonly it may seem
more natural to host workflows using WorkflowApplication within an ASP/MVC .NET application. This is a common
pattern for applications built outside the cloud. In this case, an ASP/MVC .NET application is required to run within a
web role so it can be hosted within the cloud. WorkflowApplication can also be used for hosting workflows within a
worker role so the features gained (like tracking, bookmarks, etc.) can be utilized from the WF runtime.
WorkflowServiceHost is generally hosted directly within a web role, so the WF runtime can use the web role’s IIS
WorkflowServiceHost can also be self-hosted within a worker
can no longer be reloaded from a persistence store. There is a persistence table called LockOwnersTable so a workflow
can only be hosted by one host at a time. During a web role deployment, the instance name becomes part of the host
name and gets written to the lock owner name. If the VM gets recycled, there could be a change based on the lock
owner name where workflow instances cannot be reloaded from the persistence store. Once .NET 4.5 is added within
the guest OS for Azure, it will address this issue.
512
Chapter 12 ■ WorkfloWs in WindoWs azure
After selecting the OK button, the roles that can be added to the project are presented. Figure 12-9 indicates that
a worker role is being selected to run as a background process within the cloud service. After selecting the role and
clicking the arrow button pointing to the Windows Azure Cloud Service solution listbox on the right side, the worker
role called WorkerRole1 is added. Right-clicking on the selected worker role allows it to be renamed if desired.
513
Chapter 12 ■ Workflows in Windows Azure
Caution When adding a new Worker Role project, you might notice that the Microsoft.WindowsAzure.StorageClient
using statement within the provided default code.
To fix this bug, you can reference the missing namespace by referencing it from the path, C:\Program Files\Microsoft
SDKs\Windows Azure\.NET SDK\2012-06\bin.
Starting the solution after adding the workflow will execute the WorkerRole code in Listing 12-1. As VS2012 starts
the Azure emulators, a message from the taskbar indicates that the emulators have started. The compute emulator can
be viewed by right-clicking on the blue Azure icon within the taskbar, as indicated in Figure 12-7, and selecting “Show
Compute Emulator UI.” The compute emulator will show that WorkerRole1, built within VS2012, is running. Clicking
on WorkerRole1 will show a small console window with trace information being logged. Clicking the top of console
window will provide a bigger console so the trace activity can be viewed better.
Notice the trace information, “Information: Working”, represented in Figure 12-10. This indicates that the default
WorkerRole code is executing (see Listing 12-2).
514
Chapter 12 ■ Workflows in Windows Azure
Since the worker role is always executing, technically it is a great place to host a workflow, and to do so, the code
in Listing 12-2 can be changed to the code in Listing 12-3, which executes a workflow for processing items that are
brought into a pawn shop by a customer to be pawned. A Customer object, which is passed in as a WF argument, is
passed with the workflow and sets the customer’s date of birth. Depending on how the workflow receives data for
execution, this is probably not an efficient way to process business logic using data that is supplied externally from the
workflow. Listing 12-3 indicates that a workflow will be executed every 10 seconds, which leaves very little control over
the initiation and ending of the workflow’s execution because the execution of a workflow instance could take longer
than the interval of every 10 seconds. Listing 12-3 also reveals a shortcoming regarding how data will be provided
externally to the workflow. Another scenario might be calling out to external services or reading data from other
external data sources like files or databases from within the workflow, but even this implementation limits the control
of the workflow’s execution. The workflow needs a consistent way of receiving data that can be passed in as a WF
argument. This is where queued messages play a significant role within Azure.
515
Chapter 12 ■ Workflows in Windows Azure
Tip If you are not running Visual Studio as an administrator while running an Azure project, VS2012 will throw a
The Windows Azure compute emulator must be run elevated. Please
restart Visual Studio in elevated administrator mode in order to run the project.” To run VS2012 as an Administrator, close
the instance of VS2012 and click the VS2012 icon to restart it, but this time right-click on VS2012 icon and select
“Run as Administrator.”
516
Chapter 12 ■ Workflows in Windows Azure
Selecting an ASP.Net web role as illustrated in Figure 12-12 will add a project (which is probably familiar when
compared to a standard ASP.NET project).
Figure 12-13 shows the WebRole1 project that was added and all of the default pages and folders that are
included.
In this scenario, the message queue will be stacked with messages that are created within the web application.
To illustrate how messages are created, the code in Listing 12-4 is added to the Default.aspx web page as well as the
appropriately named textboxes within the HTML to build customer information for pawned items.
namespace WebRole1
{
public partial class _Default : Page
518
Chapter 12 ■ Workflows in Windows Azure
{
private const string QueueName = "customerqueue";
private CloudStorageAccount StorageAccount = null;
CustomerQueue =
queueClient.GetQueueReference(QueueName);
CustomerQueue.CreateIfNotExist();
}
catch (Exception ex)
{
throw ex;
}
return CustomerQueue;
}
protected void Page_Load(object sender, EventArgs e)
{
}
protected void cmdSubmit_Click(object sender, EventArgs e)
{
try
{
var customer = new Customer()
{
DOB = Convert.ToDateTime(txtDOB.Text),
DriversLicenseNumber = txtDriverLicense.Text,
FirstName = txtFirstName.Text,
LastName = txtLastName.Text,
OwnersSSN = txtSSN.Text,
CustomerPawns = new List<CustomerPawn>
{
new CustomerPawn()
{
PawnedItems = new List<PawnedItem>
{
new PawnedItem{
ItemName = txtItemName.Text,
PawnedAmount = Convert.ToDecimal(txtAmount.Text),
ModelNumber = txtModelNumber.Text
}
}
}
}
};
519
Chapter 12 ■ Workflows in Windows Azure
While reviewing Listing 12-4, the first thing to mention is the following line of code:
CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString"));
Note that DataConnectionString represents the setting used to indicate that development storage should
12" xmlns="http://schemas.microsoft.com/
" osFamily="1" osVersion="*" schemaVersion="2012-05.1.7">
<Role name="WorkerRole1">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="DataConnectionString" value="UseDevelopmentStorage=true" />
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="" />
</ConfigurationSettings>
</Role>
<Role name="WebRole1">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="DataConnectionString" value="UseDevelopmentStorage=true" />
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"
value="UseDevelopmentStorage=true" />
</ConfigurationSettings>
</Role>
</ServiceConfiguration
■■Caution It is important to make sure that the ConfigurationSettings are the same between
ServiceConfiguration.Local.cscfg and ServiceConfiguration.Cloud.cscfg, so the setting for
DataConnectionString has been added in both files. If not, Visual Studio will indicate that they are not in synch
and the solution will not run.
520
Chapter 12 ■ Workflows in Windows Azure
The rest of the code for setting up a queue in Listing 12-4 is pretty straightforward. The InitiateAzureQueue()
function initiates a new queue called customerqueue, which allows messages to be added to a queue using an Azure
storage account. In this case, the development storage is used locally to simulate how the queues function within
Azure, but once this code is deployed to the cloud, a storage account must be created to host the queue within
Azure. After the storage account is established, a CloudQueueClient object is created and used to reference to the
customerqueue queue. If the queue does not exist, then it is created. After the queue is created, the CloudQueue object
is returned and messages can be added to it.
Within the Page_Load event for the Default.aspx page, a new Customer object is instantiated and loaded
with data that the customer wants to pawn. After the Customer object is loaded, the queue is initiated by calling
InitiateAzureQueue, so a new CloudQueueMessage is created and added to the queue. The message that is queued
consists of JSON, which is created by serializing the Customer object. The code in Listing 12-6 illustrates the code used
for serializing a Customer object to JSON and deserializing JSON as string data back to a Customer object. Listing 12-6
uses code extensions, which allows the ToJson function to be called from any Customer object and is demonstrated
in Listing 12-4. The FromJson function can be called from any string object and will be demonstrated later as the
message from the queue is later read from a worker role. The magic of serialization and deserialization is handled by
using the DataContractJsonSerializer.
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
namespace Apress.Chapter12.DataModel
{
public static class extCustomer
{
public static string ToJson(this Customer customer)
{
using (var ms = new MemoryStream())
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer
(typeof(Customer));
serializer.WriteObject(ms, customer);
ms.Position = 0;
return new StreamReader(ms).ReadToEnd();
}
}
■■Note JSON stands for JavaScript Object Notation and provides a lean way to serialize data into a formatted string
data type.
Making the processing thread go to sleep for a bit after checking for a message is good practice and reduces
while (true)
{
try
{
522
Chapter 12 ■ WorkfloWs in WindoWs azure
{
Thread.Sleep(10000);
Trace.WriteLine("No message in queue! Waiting 10 seconds...", "Information");
}
else
{
try
{
var newCustomer = wfData.AsString.FromJson() as Customer;
var activity = new Apress.Chapter12.WF.ProcessPawnedItems();
var inargs =
new Dictionary<string, object> { { "argNewCustomer", newCustomer } };
WorkflowInvoker wfInvoker = new WorkflowInvoker(activity);
wfInvoker.Invoke(inargs);
customerQueue.DeleteMessage(wfData);
Trace.WriteLine("Processed Message!", "Information");
}
catch (Exception ex)
{
throw ex;
}
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message,"Exception");
}
}
}
return base.OnStart();
}
523
Chapter 12 ■ Workflows in Windows Azure
CustomerQueue =
queueClient.GetQueueReference(QueueName);
CustomerQueue.CreateIfNotExist();
}
catch (Exception ex)
{
throw ex;
}
return CustomerQueue;
}
Now that a workflow can accept WF arguments built from the queue of new customers, let’s take a look at the
workflow that will process customer orders within the pawn shop. Figure 12-14 illustrates a flowchart workflow
that uses a custom WF activity that inherits from CodeActivity<result>. Listing 12-8 shows the code used for
activity.
Figure 12-14. Workflow for saving new customers and their pawned items
namespace Apress.Chapter12.WF
{
public sealed class SaveCustomer : CodeActivity<bool>
{
public InArgument<Customer> NewCustomer { get; set; }
524
Chapter 12 ■ Workflows in Windows Azure
The code that actually adds a customer and pawned items is defined within a separate C# project,
Apress.Chapter12.DataModel. It uses Entity Framework 4 and a Code First model for building the database and
tables based on the entity classes defined to handling the data plumbing for SQL Server.
There are three entities used for defining a customer and items that the customer pawns. Customer.cs, illustrated
in Listing 12-9, defines the properties that need to be captured for a customer.
namespace Apress.Chapter12.DataModel
{
public class Customer
{
public Customer()
{
CustomerPawns = new Collection<CustomerPawn>();
}
[Key]
public int CustomerId { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string OwnersSSN { get; set; }
[Required]
public string DriversLicenseNumber { get; set; }
525
Chapter 12 ■ Workflows in Windows Azure
[Required]
public DateTime DOB { get; set; }
PawnedItem.cs, illustrated in Listing 12-10, defines the properties needed to save information about the items
that a customer is pawning.
12.DataModel
{
public class PawnedItem
{
public PawnedItem()
{
DatePawned = DateTime.Now;
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PawnedItemId { get; set; }
[Key]
public int CustomerPawnId { get; set; }
[Required]
public string ModelNumber { get; set; }
[Required]
public string ItemName { get; set; }
public DateTime DatePawned { get; set; }
}
}
CustomerPawn.cs, illustrated in Listing 12-11, defines the properties needed to bridge the relationship between
the customer and the items that are being pawned.
526
Chapter 12 ■ Workflows in Windows Azure
namespace Apress.Chapter12.DataModel
{
public class CustomerPawn
{
public CustomerPawn()
{
PawnedItems = new Collection<PawnedItem>();
}
[Key]
public int CustomerPawnId { get; set; }
[Required]
public int CustomerId { get; set; }
[Required]
public virtual ICollection<PawnedItem> PawnedItems { get; set; }
}
}
Listing 12-12 shows the code that Entity Framework uses to define the database tables and relationships between
each of the tables.
namespace Apress.Chapter12.DataModel
{
public sealed class Pawning : DbContext
{
public DbSet<CustomerPawn> CustomerPawns { get; set; }
public DbSet<PawnedItem> PawnedItems { get; set; }
public DbSet<Customer> Customers { get; set; }
modelBuilder.Entity<PawnedItem>()
.HasKey(p => new { p.CustomerPawnId, p.PawnedItemId });
modelBuilder.Entity<Customer>()
.HasMany<CustomerPawn>(pawnedItems => pawnedItems.CustomerPawns);
}
}
}
527
Chapter 12 ■ Workflows in Windows Azure
Now when the solution is run, customer data defined within a web role is queued and then processed out of
the queue within the worker role. The worker role then sends the queued data to a workflow, which will process the
information about the customer and the item being pawned.
Cloud Workflows
So far I have walked through running workflows within VS2012 with the aid of the Azure SDK to simulate how the
application should execute running in Azure. Once the application is deployed to Azure, there should be no reason
why a solution hosted in Azure should not execute the same way it executed while running within VS2012 using the
Azure SDK. However, there are some configurations that require tweaks so that the application actually uses the
features available within Azure, such as SQL databases and storage.
To add a SQL database, log on to the Windows Azure portal at http://manage.windowsazure.com/. When the
portal’s main screen loads, select the New menu located at the bottom left of the portal. After the menu slides up,
Figure 12-15 shows that after selecting Quick Create, the server name for the new database has already been
528
Chapter 12 ■ Workflows in Windows Azure
Listing 12-13. Connection String Used to Connect EF Code First to the Azure Database
<connectionStrings>
<add name="Pawning" providerName="System.Data.SqlClient" connectionString="Server=tcp:
l1yab5tqj4.database.windows.net,1433;Database=PawnShop;User ID=<UserID>;Password=<Password>;
Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" />
</connectionStrings>
After a storage account has been created, the key that was automatically generated with the account must be
used when accessing the storage account (see Figure 12-17).
529
Chapter 12 ■ Workflows in Windows Azure
Clicking Manage Keys will pop up the two keys that were generated. There are two, so one can be generated while
the other stays active and can still be used in production. Frequent generation of the keys provides better security for
the storage account.
When running Azure solutions within Visual Studio, the Azure SDK handles the storage features for
development. These settings are configured using the ServiceConfiguration files within the cloud project. Opening
ServiceConfiguration.Cloud.cscfg and changing UseDevelopmentStorage=true to the Primary access key in
Figure 12-17 provides the security necessary so Azure can use the storage account created in Figure 12-16. The
DefaultEndpointsProtocol needs to be specified as either http or https, which will indicate the protocol the storage
account will use to connect, and the AccountName indicates the account used for storage (see Listing 12-14).
Publishing to Azure
Deploying solutions to the cloud has become even easier with the Azure SDK v1.7 for Visual Studio. Right-clicking an
Azure project within Visual Studio provides a Publish menu command; however, the project first needs to know the
Azure subscription that is intended for the published project to run under. If credentials do not exist on the current
computer used for building the solution, Visual Studio will prompt the user to log on to the Azure Portal so the
credentials can be downloaded. Figure 12-18 indicates that there are no Azure subscription credentials installed on
the local machine.
530
Chapter 12 ■ Workflows in Windows Azure
Clicking “Sign in to download credentials” prompts a new browser window to open so credentials can be
downloaded by signing into the Azure Portal. After signing in, the credentials are prompted to be downloaded,
as illustrated in Figure 12-19.
531
Chapter 12 ■ Workflows in Windows Azure
The page in Figure 12-19 lists the steps required to import the credentials and choose the right credentials but
here is the cool part: the pawn shop solution requires services from Azure to run, so cloud services must be set up
before the solution can be deployed. This can be done through the Azure Portal, but if cloud services do not exist,
Visual Studio is smart enough to check during the deployment process and provide a way for creating cloud
services as well.
It is best to select a location for cloud services that is within the same region as the other components that will be
used, like the SQL Server and storage that were created earlier (see Figure 12-20).
After cloud services are created, Visual Studio automatically starts the deployment. The Windows Azure Activity
Log displays the status or progress as the solution is being deployed. If the Azure Portal is opened at the same time
cloud services are being created through Visual Studio, refreshing the portal page will update, showing the latest
status about the cloud service. Once the solution is deployed to the cloud service, the site URL, which is found on the
Dashboard portal page for the cloud service, can be used for browsing the site (see Figure 12-21).
532
Chapter 12 ■ WorkfloWs in WindoWs azure
■ Tip Creating cloud services also creates a storage account with the same name that is defined for the cloud service,
so there may not be a need to create a separate storage account first.
Figure 12-22 illustrates the web page running within the cloud. Now that the solution is published, the web
application running within Azure can process customers and items pawned by recording data from the web page,
queuing the data and passing it to the workflow so the data can then be stored within SQL Server. Figure 12-23 shows
how to view the data within a SQL database within Azure and shows that the information entered in Figure 12-22 has
been successfully processed using the workflow.
533
Chapter 12 ■ Workflows in Windows Azure
534
Chapter 12 ■ Workflows in Windows Azure
Figure 12-23. Azure SQL database with data loaded using a workflow
535
Chapter 12 ■ Workflows in Windows Azure
Earlier in the chapter I gave an overview of blob storage and how it can be used for storing files. Since workflows
can also be XAML files, blob storage also provides a great place to manage workflows so they can be executed within
Azure. Azure provides a standard API for working with blob storage, and it can be customized to meet the needs
of custom applications running within Azure and applications running outside of Azure as well. For instance,
Listing 12-7 illustrates code that limits workflow execution because there is no way to dynamically change the
workflow while a worker role is executing. By using blob storage, a worker role can instead check for a workflow stored
as a blob and retrieve it so it can be executed. While the worker role is executing, an application outside of Azure that
rehosts the WF designer can modify the workflow and save it in blob storage so the workflow can be retrieved later.
New logic can be performed from an updated workflow after it is retrieved from blob storage within the worker role.
The next step is to make sure that the SaveCustomer activity is provided within the rehosted activity toolbox.
• Apress.Chapter12.DataModel
• Apress.Chapter12.WF
• EntityFramework
And then by adding a Window.Resources entry within the XAML markup called PawnShopAssembly.
<Window.Resources>
<sys:String x:Key="WFAssembly">System.Activities, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35</sys:String>
<sys:String x:Key="PawnShopAssembly">Apress.Chapter12.WF, Version=1.0.0.0,
Culture=neutral</sys:String>
</Window.Resources>
Finally, you must add the SaveCustomer activity to the toolbox using the XAML markup.
A default Customer WF argument can also be set as a default argument with the code in Listing 12-15. As a new
workflow is initialized, it will already have an argument added as an InArgument so a new Customer object can be
passed into the workflow.
536
Chapter 12 ■ Workflows in Windows Azure
Name = txtWorkflowName.Text!=string.Empty?txtWorkflowName.Text:
txtWorkflowName.Text,
Implementation = new Flowchart(),
Properties =
{
new DynamicActivityProperty
{
Name = "argNewCustomer",
Type = typeof(InArgument<Apress.Chapter12.DataModel.Customer>),
Attributes =
{
new RequiredArgumentAttribute(),
},
Value = new InArgument<Apress.Chapter12.DataModel.Customer>()
}
}
};
return builder;
}
catch (Exception)
{
throw;
}
}
■■Tip Make sure to add references within the project rehosting the WF designer to the appropriate assemblies. In this
case, Apress.Chapter12.DataModel and Apress.Chapter12.WF are required to be referenced in order to have the
SaveCustomer activity available and rehosted.
At this point, the workflow author can take advantage of the SaveCustomer activity using a default Customer
WF argument for the workflow. Saving the workflow requires initiating blob storage when the application starts by
creating a global variable for holding the CloudBlobContainer object and setting it within the class’s constructor,
RehostedWFThroughMarkup.
537
Chapter 12 ■ Workflows in Windows Azure
public RehostedWFThroughMarkup()
{
InitializeComponent();
_blobContainer = InitiateBlobStorage();
}
The code in Listing 12-16 is used to initialize the blob storage. ConfigurationManager.AppSettings
allows blob storage to be configured using a configuration file. In this case, an app.config file is used. The
CreateContainerReference() call applies a unique name for the blob container, workflows, so that the blob
container can be uniquely referenced. If the blob container of workflows does not exist, then it is created, setting
public access to only clients with the right credentials.
{
CloudBlobContainer blobContainer = null;
try
{
var StorageAccount =
CloudStorageAccount.Parse(ConfigurationManager.AppSettings["DataConnectionString"]);
if (blobContainer.CreateIfNotExist())
{
var permissions = new BlobContainerPermissions() { PublicAccess =
BlobContainerPublicAccessType.Off };
blobContainer.SetPermissions(permissions);
}
return blobContainer;
}
catch (Exception ex)
{
throw ex;
}
}
The app.config file configures access to the blob storage account by specifying a connection string within the
appSettings section of the file. The account key and name can be found within the Azure Portal.
<appSettings>
<add key="DataConnectionString" value="DefaultEndpointsProtocol=https;AccountName=<AccountName>;
AccountKey=<AccountKey>"/>
</appSettings>
538
Chapter 12 ■ Workflows in Windows Azure
Finally, the code used to save the XAML file representing a workflow using the rehosted WF designer is illustrated
in Listing 12-17. It is the same code from Chapter 10, except the code for saving the XAML file to the file system has been
commented out and replaced with new code that allows the generated file to be saved within blob storage. A reference
to the blob container is obtained and then the XAML representing the workflow is uploaded to blob storage.
// if (!Directory.Exists(Directory.GetCurrentDirectory() +
WorkflowFolder))
// Directory.CreateDirectory(Directory.GetCurrentDirectory() +
WorkflowFolder);
// //else //Directory exists
// // File.OpenWrite(wfFile);
// _wd.Save(wfFile);
//}
var blobRef = _blobContainer.GetBlobReference(txtWorkflowName.Text + ".xaml");
blobRef.UploadText(_wd.Text);
}
else
MessageBox.Show("Please press 'Create' to build a new workflow!");
}
else
{
MessageBox.Show("Please enter a workflow name!");
}
}
catch (Exception ex)
{
throw ex;
}
}
539
Chapter 12 ■ Workflows in Windows Azure
while (true)
{
try
{
var inargs =
new Dictionary<string, object> { { "argNewCustomer", newCustomer } };
WorkflowInvoker wfInvoker = new WorkflowInvoker(activity);
540
Chapter 12 ■ Workflows in Windows Azure
wfInvoker.Invoke(inargs);
customerQueue.DeleteMessage(wfData);
Trace.WriteLine("Processed Message!", "Information");
}
catch (Exception ex)
Now, as the worker role reads a workflow serialized within blob storage, a workflow authored from the rehosted
designer and created with the name PawnShopWorkflow, as illustrated in Figure 12-24, can be consumed. The new
workflow is then downloaded from blob storage, so as new customer information is gathered through the web page,
queued customer information is retrieved from within the worker role. The customer data is then executed within
the workflow.
Now let’s pretend that local pawn shops have had a problem with items being pawned by customers under 21.
The rules for processing new customers are handled through the workflow so the logic can be changed to make sure
that only customers 21 and up can pawn items. Figure 12-25 shows that the workflow has been modified by adding an
If activity to check if the date of birth passed in on the customer object indicates that the customer is 21 or over. I also
added a new WF argument that will return a string to the workflow host. Assign activities were added to indicate to
the workflow host when customers are old enough to pawn items and when they are too young. To handle the new WF
argument called argResponse, the worker role code was modified for hosting the workflow (see Listing 12-19).
541
Chapter 12 ■ Workflows in Windows Azure
if (args["argResponse"]!=null)
Trace.WriteLine(args["argResponse"]);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message, "Information");
}
Figure 12-26 shows that when a customer under 21 tries to pawn an item, the feedback from the workflow saying
“Customer is not 21” is traced within the worker role.
542
Chapter 12 ■ WorkfloWs in WindoWs azure
Figure 12-26. Logging WorkerRole information about a customer being too young
543
Chapter 12 ■ Workflows in Windows Azure
Figure 12-27. Worker role template that integrates Service Bus queue code
Listing 12-20 illustrates the updated code for processing Service Bus messages within the worker role. The
OnStart function sets up the configuration for the Service Bus and makes sure the queue PawnQueue is created.
The Run method then checks for new messages on the queue. When a message is received, it performs its own
receivedMessage.GetBody<Customer> deserializes the message into a Customer object
while (!IsStopped)
{
try
{
// Receive the message
BrokeredMessage receivedMessage = null;
receivedMessage = Client.Receive();
if (receivedMessage != null)
{
var newBlob = blobContainer.GetBlobReference("PawnShopWorkflow.xaml");
var ms = new System.IO.MemoryStream();
newBlob.DownloadToStream(ms);
ms.Position = 0;
544
Chapter 12 ■ Workflows in Windows Azure
var inargs =
new Dictionary<string, object> { { "argNewCustomer", newCustomer } };
WorkflowInvoker wfInvoker = new WorkflowInvoker(activity);
try
{
var args = wfInvoker.Invoke(inargs);
if (args["argResponse"]!=null)
Trace.WriteLine(args["argResponse"]);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message, "Information");
}
// Process the message
Trace.WriteLine("Processing", receivedMessage.SequenceNumber.ToString());
receivedMessage.Complete();
}
}
catch (MessagingException e)
{
if (!e.IsTransient)
{
Trace.WriteLine(e.Message);
throw;
}
Thread.Sleep(10000);
}
catch (OperationCanceledException e)
{
if (!IsStopped)
{
Trace.WriteLine(e.Message);
throw;
}
}
}
}
545
Chapter 12 ■ Workflows in Windows Azure
{
namespaceManager.CreateQueue(QueueName);
}
Since the web application creates the Azure Queues that are processed within the worker role, Listing 12-21
BrokeredMessage instead. In order for a client
QueueClient is created
{
try
{
var customer = new Customer()
{
DOB = Convert.ToDateTime(txtDOB.Text),
DriversLicenseNumber = txtDriverLicense.Text,
FirstName = txtFirstName.Text,
LastName = txtLastName.Text,
OwnersSSN = txtSSN.Text,
CustomerPawns = new List<CustomerPawn>
{
new CustomerPawn()
{
PawnedItems = new List<PawnedItem>
{
new PawnedItem{
ItemName = txtItemName.Text,
PawnedAmount = Convert.ToDecimal(txtAmount.Text),
ModelNumber = txtModelNumber.Text
}
}
}
}
};
546
Chapter 12 ■ Workflows in Windows Azure
}
catch (Exception ex)
{
throw ex;
}
Up to this point in the chapter I have demonstrated how workflows can be authored and modified during runtime
and hosted within an Azure worker role so that other applications can execute the workflows for driving business logic.
Workflows were first authored and later enhanced through an application that rehosts the WF designer. Workflows
were then stored in Azure’s blob storage. The workflows were later used to process business logic for data that was
queued up using Azure Queues and also Azure Service Bus from a web application is hosted in Azure. The next part of
the chapter will change focus to authoring longer-running workflows that can be persisted within Azure.
Cloud Persistence
Setting up WF persistence in Azure’s cloud is similar to how you do it on-premise or locally; however, there are some
important differences. For instance, the update 4.0.1 for Microsoft .NET Framework 4 Runtime Update fixed the
following issues:
• Sqlworkflowinstancestoreschema.sql used allow_page_locks, which is not a supported
keyword in SQL Azure and would fail the installation. allow_page_locks is removed from the
script via the update.
• When network issues happen or the connection was lost, there could be reliability issues
because the retry logic was not customized for SQL Azure. To handle this, a new public
property called MaxConnectionRetries is available within the SqlWorkflowInstanceStore
class. It changes the number of reconnection attempts for connecting to SQL Server. By
default, there are 3 attempts during 1 second intervals. The recommended attempts for SQL
Azure are greater or equal to 15.
547
Chapter 12 ■ Workflows in Windows Azure
Since VS2012 is installed, 4.0.1 update is not required, and the scripts that are supplied with .NET 4.5 can be
used. The first step is to create another database from the Azure Portal. This can also be done remotely by connecting
to the Azure database server through SQL Management Studio. For a recap on creating a database in Azure, review
Figure 12-15 from earlier in the chapter and review Chapter 8 for illustrations on running the supplied persistence
scripts within SQL Management Studio. Remember that each script should be run in the following order:
1. SqlWorkflowInstanceStoreSchema.sql
2. SqlWorkflowInstanceStoreLogic.sql
3. SqlWorkflowInstanceStoreSchemaUpgrade.sql
Each of the scripts should not have any issues and should indicate that they completed successfully. After the
scripts have run, refresh SQL Management Studio to validate that the database has been updated appropriately.
Tip Remember that persistence can be configured. To make sure that the maxConnectionRetries is set to a
<sqlWorkflowInstanceStore maxConnectionRetries="15".../>.
If the customer is approved, then the workflow enters the customer information in SQL Server. The workflow in
Figure 12-28 needs to be durable because the approval process can take a long period of time. Persistence will be used
to make sure the workflow can handle running over the duration of time it takes to approve or reject a customer.
548
Chapter 12 ■ Workflows in Windows Azure
Although the workflow in Figure 12-28 will be hosted in Azure, Visual Studio makes the process smooth
and easy. In fact, workflow services that were demonstrated in Chapter 11 could be moved easily to the cloud as
well. Microsoft’s goal for using Azure is to build on the existing knowledge developers have of writing software.
Therefore, to get started, we will build a new solution using the WCF Workflow Service Application template, as
illustrated in Figure 12-29. Next, the Apress.Chapter12.DataModel project needs to be added, which was built
earlier in the chapter.
549
Chapter 12 ■ Workflows in Windows Azure
The first operation, CreateCustomerApplication, uses a ReceiveAndSendReply and accepts a parameter called
NewCustomer of type Customer. A string is sent back indicating that the customer application has been received
(see Figure 12-30).
550
Chapter 12 ■ Workflows in Windows Azure
551
Chapter 12 ■ Workflows in Windows Azure
The last part of the workflow uses an If activity that checks if the customer is approved to pawn items or rejected.
If the customer application is approved, then the SaveCustomer activity used earlier in the chapter is used to save the
customer and item the customer wants to pawn to SQL Server (see Figure 12-32).
Figure 12-33 illustrates the complete workflow that will be deployed to Azure but it still has to be configured to
552
Chapter 12 ■ WorkfloWs in WindoWs azure
The workflow service project comes with a default web.config that can be modified for configuring persistence.
Listing 12-22 shows the modified web.config for configuring WF persistence and setting up the connection string so
customer data can be written to the Pawnshop database in SQL Server.
553
Chapter 12 ■ Workflows in Windows Azure
</connectionStrings>
<system.web>
<compilation debug="true" strict="false" explicit="true" targetFramework="4.0"></compilation>
<customErrors mode="Off"></customErrors>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<sqlWorkflowInstanceStore connectionString="Server=tcp:l1yab5tqj4.database.
runnableInstancesDetectionPeriod="00:02:00" instanceCompletionAction="DeleteAll"
instanceLockedExceptionAction="AggressiveRetry" instanceEncodingOption="GZip" />
<workflowIdle timeToPersist="00:00:05" timeToUnload="00:00:30" />
<!-- To avoid disclosing metadata information, set the values below to false before
deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" />
<!-- To receive exception details in faults for debugging purposes, set the value below to
true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory,
EntityFramework" />
</entityFramework>
</configuration>
554
Chapter 12 ■ Workflows in Windows Azure
Now that persistence has been configured, a web role will be used to host the workflow within Azure using IIS.
The Azure tools for Visual Studio allows this very easily by right-clicking the workflow service project and selecting
Add Windows Azure Cloud Service Project, as illustrated in Figure 12-34.
Figure 12-34. Adding a web role for hosting the workflow service project
After the new Azure service project is added, it will now contain the workflow service project as an Azure web
role. Opening ServiceDefinition.csdef will show that the role is indeed a web role, as illustrated in Listing 12-23.
The workflow is now ready to be pushed to Azure.
Earlier in the chapter, Figure 12-18 illustrated how to set up Azure subscription so solutions can be deployed
to Azure. Once the solution compiles, it is ready to be deployed by right-clicking on the new cloud project and then
clicking on Publish, as illustrated in Figure 12-35.
555
Chapter 12 ■ Workflows in Windows Azure
After the publishing of the solution completes, the service can be checked to make sure it has been created
correctly using Internet Explorer. Figure 12-36 shows how to go to the URL that Azure provides and add the workflow
name and extension at the end of the URL. In this case, the workflow is named wfPawnService.xmlx, so the address is
http://flowfocusservice.cloudapp.net/wfPawnService.xamlx (see Figure 12-36).
556
Chapter 12 ■ Workflows in Windows Azure
Figure 12-36. Checking that the service has been created on Azure
Reusing the web application that was created earlier in the chapter, it can simply be run locally in Visual Studio
to interact with the new service. The first step is to add a service reference to the ASP.NET project created earlier. So
far, ASP.NET has been used to handle messages from Azure and Service Bus queues, and now it will work directly
with the workflow service hosted within Azure. After creating a service reference to the web project, the bold code in
Listing 12-24 can be used to communicate with the service.
Listing 12-24. Code for Submitting Customer Applications and Approving Them
protected void cmdSubmit_Click(object sender, EventArgs e)
{
var pawnService = new PawnSvc.PawnServiceClient();
try
{
var customer = new Customer
{
DOB = Convert.ToDateTime(txtDOB.Text),
DriversLicenseNumber = txtDriverLicense.Text,
FirstName = txtFirstName.Text,
LastName = txtLastName.Text,
557
Chapter 12 ■ Workflows in Windows Azure
OwnersSSN = txtSSN.Text,
CustomerPawns = new List<CustomerPawn>
{
new CustomerPawn()
{
PawnedItems = new List<PawnedItem>
{
new PawnedItem{
ItemName = txtItemName.Text,
PawnedAmount = Convert.ToDecimal(txtAmount.Text),
ModelNumber = txtModelNumber.Text
}
}
}
}
};
lblAppResults.Text = pawnService.CreateCustomerApplication(customer);
//Setting up the Service Bus Queue
//string connectionString = CloudConfigurationManager.GetSetting("Microsoft.
throw;
}
}
558
Chapter 12 ■ Workflows in Windows Azure
Running the ASP.NET application locally, data can be submitted to the service based on the data the customer
enters. Feedback from the service shows that the application was submitted based on the “Your application has been
received” string returned and loaded into the label on the page (see Figure 12-37).
■■Caution There is no Distributed Transaction Controller (DTC) within Azure, which WF4 relies on heavily. As a result,
a best practice when using workflow services make sure the PersistBeforeSend property on SendReply is checked in
order to ensure consistency between a sent response to a client and the persisted workflow.
Opening SQL Management Studio and connecting to the SQL database hosted within Azure for persisting
workflows shows that there is one persisted workflow instance. This customer application has been persisted, as
illustrated in Figure 12-38. By clicking the Approve checkbox and then clicking the Approve button, the record in
Figure 12-38 will disappear because the workflow has now completed.
559
Chapter 12 ■ Workflows in Windows Azure
Checking the tables that are in the Pawnshop database will now show that the records have been added from the
workflow and that the data matches the data submitted in Figure 12-37 (see Figure 12-39).
560
Chapter 12 ■ Workflows in Windows Azure
Figure 12-39. Records added from the workflow service hosted in Azure
561
Chapter 12 ■ Workflows in Windows Azure
• Tracking and monitoring: Provided through the REST API, client library, and Azure Portal for
service health, configuration, and status of running workflows.
• Instance management: Health monitoring and management, performance and scale
management.
• Fully declarative authoring: Expanded activity library, expression translation, and a new
declarative data modeling feature.
• REST and Service Bus messaging: Integrated messaging capabilities for Azure messaging
models of REST web services and Azure’s Service Bus. This includes inbound/outbound
message coordination with workflow persistence for reliability and integrity.
• Managed service reliability: Around-the-clock operations support, fault-tolerant design,
cross-data center disaster recovery, service and platform upgrades and management, and
standards compliance.
To learn more about Workflow 1.0 Beta, please visit http://msdn.microsoft.com/en-us/library/
.
through web and worker roles using the different WF runtime hosts and patterns for hosting durable and non-durable
workflows. Finally, the chapter talked about the direction Microsoft is taking in providing a standard model for hosting
workflows on-premise and within Azure.
562
Chapter 13
There are no significant improvements in WF4.5 with regards to Microsoft AppFabric 1.1 for Windows Server, but
this book would not be complete without pointing out that AppFabric provides the perfect vehicle for running and
managing WF4.5 workflows. This chapter will first explain why AppFabric was created, and then the caveats that might
arise while installing it. Finally, AppFabric Server will be used to host WF4.5 workflows to demonstrate the tooling that
it offers for running and managing workflows.
WF provides the framework for hosting, tracking, and persisting workflow instances, but building the
infrastructure for hosting workflows and providing transparency for how workflow instances execute can be
challenging. As WF matured throughout the years, it became painfully obvious to developers that a robust solution
for hosting and managing workflows was needed out of the box. A standardized solution for hosting and managing
workflows would make a better case for building applications and solving business needs using WF.
Before AppFabric, a workflow monitoring solution had to be custom built, tested, and implemented.
Architecting a solution that provides and manages a dynamic transparency model can be complicated, and sometimes
the solution is over-engineered or becomes too complex to manage workflow execution. AppFabric provides an
out-of-the-box solution for managing workflow execution “on premises” or on local servers and for providing a
hosting solution that has high availability (HA) and reliable services that can scale.
Architectural Components
AppFabric builds on top of Internet Information Service (IIS) 7.0 and gives developers an infrastructure for deploying
and hosting workflow services that scale and are easier to manage by extending capabilities of Windows Server
services. Although this chapter will focus primarily on the capabilities for hosting services within AppFabric, caching
is also supported. Figure 13-1 gives an illustration how caching is handled within a cache cluster using AppFabric.
563
Chapter 13 ■ Hosting Workflows in Windows Server
• Caching Services: Ability to cache information so frequently read data can be reduced
between applications and data stores like SQL Server. Figure 13-1 illustrates how caches have
names like “Inventory” and “Catalog” and that within a cache there can be regions, such as
13“Sports” and “Arts.”
• Hosting Services: Additional support for services built using workflows that builds on the
capabilities of Windows Process Activation Service (WAS), which serves as the configuration
and process manager for hosting Windows Communication Services (WCF) and within IIS 7.0.
Hosting Services
Figure 13-2 gives an overview of the architectural components provided with AppFabric and shows the services and
components used to host and manage workflows as services; however, these capabilities have related instrumentation
for managing them within AppFabric and are provided out of the box. Hosting workflows as services rely on WAS and
take advantage of its hosting functionality and managing workflows through the Workflow Management Service (WMS).
The tooling that is provided reduces the need to manually change configuration files that would normally have to be
done through custom workflow management solutions.
564
Chapter 13 ■ Hosting Workflows in Windows Server
Figure 13-2 illustrates that AppFabric resides within IIS, which provides for hosting web applications like
ASP.NET; however, the goal for AppFabric is to provide a way to easily deploy WCF and WF solutions as long
running services through persistence and monitoring.
• ASP.NET: Ability to receive WCF and WF messages over HTTP.
• WCF and WF: The primary focus of AppFabric, it provides the foundation for building
SOA applications, which support long-running processes and interoperability with other
technologies.
• Runtime Databases: Provides a data store that is secure, scalable, and reliable for storing data
about SOA applications.
Some of the familiar capabilities that are provided within WF include
• Persistence: Preserves data across system failures or restarts. AppFabric provides its own
preconfigured database for persisting workflows within SQL Server.
• Hosting: Integrates with the WorkflowServiceHost provider within IIS and WAS.
• Monitoring: Provides tooling for controlling the level of transparency for managing execution
data for workflow instances. AppFabric provides its own preconfigured database for
monitoring workflows within SQL Server.
565
Chapter 13 ■ Hosting Workflows in Windows Server
IIS Manager
The IIS Manager within AppFabric provides the user interface for managing and configuring WCF and WF services,
including MSDeploy for deploying services that need to be hosted within WAS. Functionality exposed through the
IIS manager can be driven by scripting custom PowerShell cmdlets, commands that are executed from the PowerShell
command prompt to perform configuration and other tasks within an operating system or application.
EventCollection services can run on a single server for loading ETW events within the monitor store.
web.config. The service subscribes to multiple WCF and WF applications and sends the events to
■■Note AppFabric Workflow Management Service is similar to the Windows service that hosts the same WMS used
when hosting workflows as services through the WorkflowServiceHost.
566
Chapter 13 ■ Hosting Workflows in Windows Server
By providing multiple persistence and monitoring databases, performance can be increased to handle larger
volumes of activity. Each instance store can be routed to a certain service or logically related services that have been
deployed within AppFabric (see Figure 13-4).
567
Chapter 13 ■ Hosting Workflows in Windows Server
Deployment Types
The type of deployment for AppFabric should solely be driven by the business requirements and needs of the
hosted applications. AppFabric provides options for different types of deployments and guidance as to which type of
deployment suits certain application business requirements. The two types of deployment are as follows:
• Single server
• Server farm
568
Chapter 13 ■ Hosting Workflows in Windows Server
569
Chapter 13 ■ Hosting Workflows in Windows Server
Scalable Services
When providing long-running processes, it is important that the services provided are able to withstand system
failures, restarts, and other unmanageable events that render applications unavailable. With single server
deployments, persisted workflow instances can wait until the available server is back up and running; however,
until the server is back up, applications deployed on a single server are unavailable. Workflow instances deployed
on a server farm running AppFabric can continue execution on any of the available servers within the farm. If the
execution of a workflow instance is being processed on one server and a message arrives on another server for
the same workflow instance, there are specific rules for handling race conditions where persistence will move the
executing workflow instance to the computer waiting to process a message to the workflow so it can also be processed
within the workflow instance.
Installing AppFabric
During the beta period for AppFabric, it was given the codename “Dublin” for hosting capabilities of WCF and WF;
for its caching capabilities, it was given the codename “Velocity.” Although the name has changed a couple of times
since then, today it is simply referred to as AppFabric for Windows Server—not to be confused with Windows Azure
AppFabric. Windows Azure AppFabric, which is no longer branded as a single technology, refers to various features
570
Chapter 13 ■ Hosting Workflows in Windows Server
offered through Windows Azure. To learn more about WF and Windows Azure, refer to Chapter 12. AppFabric for
Windows Server is referred to as AppFabric 1.1. The most current download, which is a free install, can be found at
Microsoft’s Download Center via www.microsoft.com/download/en/details.aspx?id=27115. This latest release of
AppFabric includes the following features, which affect its caching capabilities:
• Read-through and write-behind provider support
• Graceful shutdown
• Domain account support
• New ASP.NET session state and output caching providers
• Compression
• Multiple cache client application configuration sections
Upgrading
There is good news if you need to upgrade from AppFabric 1.0: Microsoft does provide support for doing so. However,
make sure you read the possible and known issues (found in the release notes from the same link that provides the
installation setup). The other option is to use the uninstall tool that can be downloaded from http://go.microsoft.com/
fwlink/?LinkId=188172 . The uninstall tool provides a cleanup utility that removes AppFabric 1.0 so a fresh installation of
AppFabric 1.1 can be installed.
Hardware Requirements
There are two setups provided from the link. The setup file WindowServerAppfabricSetupx64.exe is obviously for
64-bit systems and WindowServerAppfabricSetupx32.exe is for 32-bit systems. The minimum hard disk space is
2GB and single processors require a computer with an Intel Pentium-compatible CPU that is 1GHz or faster. Dual
processors require 900GHz or faster, and quad processors require 700 MHz or faster.
Software Requirements
Both 32- and 64-bit setups can be installed on the following operating systems:
• Windows 7
• Windows Server 2008 Service Pack 2
• Windows Server 2008 R2
• Windows Vista Service Pack 2
571
Chapter 13 ■ Hosting Workflows in Windows Server
Microsoft .NET Framework 4 needs to be installed as a prerequisite along with the following:
• Internet Information Service (IIS) 7
• Hotfix #980423
• IIS Web Deployment tool
• Windows PowerShell 2.0 (final version)
• Not required for Windows 7 and Windows Server 2008 R2
All features of AppFabric require Microsoft .NET Framework 4, but some features like the caching service can use
Microsoft .NET Framework 3.5. The cache client can also use .NET Framework 3.5 with Service Pack 1.
Note Although AppFabric can use .NET 4.0, in order to run services built with WF4.5, .NET 4.5 is required so they can
AppFabric.
In this exercise AppFabric 1.1 will be installed on a single server running Windows 7; it already has SQL Server
2008 R2 and Visual Studio 2012 installed. Since Visual Studio 2012 is installed, there is no reason to install
another instance of SQL Server, if SQL Server Express was installed during the installation of Visual Studio 2012.
Visual Studio also installed .NET Framework 4.5, so that is taken care of as well. Also, since Windows 7 is the
operating system that will be hosting Windows Server AppFabric 1.1, there is no need to Windows Powershell 2.0.
1. Before installing AppFabric 1.1, the first step is to make sure the machine has all of the
latest critical Windows updates installed. In Windows 7, clicking on the Windows orb and
entering “update” into the search box brings up a window that automatically selects
Window Update. Pressing Enter will then open up the update panel (see Figure 13-7).
572
Chapter 13 ■ hosting WorkfloWs in WindoWs server
2. selecting “Check for updates” will search for any updates that the computer does not
currently have loaded. Make sure to get all of the critical Windows updates13.
Microsoft .net framework 4.5 needs to be installed for hosting services built using
Wf4.5, this demo already has visual studio 11 installed so .net 4.5 is already installed.
likewise, Windows powershell 2.0 is also already installed; however, if you are running
Windows server 2008 or Windows vista, make sure to download and install Windows
powershell 2.0. for more information on getting Windows powershell 2.0, go to
http://support.microsoft.com/kb/968929/en-us.
3. Make sure that at least internet information services (iis) 7 is installed (see figure 13-8).
if it is not installed, clicking on the Windows orb and entering “add” into the search box
will bring up a list of options. select “add or remove programs” and then “turn Windows
features on or off.”
573
Chapter 13 ■ Hosting Workflows in Windows Server
Find the Internet Information Services checkbox and expand it (see Figure 13-9).
Make sure that the following key components are selected. If IIS was not installed, it
might take a little time to get the feature installed, so while IIS is installing, go to
www.microsoft.com/download/en/details.aspx?id=27115 and install Microsoft
AppFabric 1.1 for Windows Server. Remember that there are two installs: one for 32-bit
(x86) and one for 64-bit operating systems.
574
Chapter 13 ■ Hosting Workflows in Windows Server
4. After IIS and its components have been installed and the setup file for AppFabric 1.1
has been downloaded, click on the WindowsServerAppFabric setup file that was
downloaded and accept the terms and license agreement. Figure 13-10 shows the
wizard that will guide the installation for AppFabric 1.1.
575
Chapter 13 ■ Hosting Workflows in Windows Server
5. The next step refers to customer experience and if you are willing to contribute. If you
like helping, check “Yes” to send information about your experience and select the “Yes”
radio button to participate in the program. Then select Next.
6. Features are the next part of the wizard. Since caching is beyond the scope of the demo
in this chapter, Cache Client can be unchecked. The default location for where AppFabric
will be installed is fine. The Administration Tools are the tooling that will be added within
IIS, so IIS will look different once AppFabric is installed. Select Next after the selected
features mimic the selection in Figure 13-11.
576
Chapter 13 ■ Hosting Workflows in Windows Server
7. Prerequisites are the next part of the wizard; if any components were missed before the
AppFabric 1.1 installation wizard started, they are caught within this part of the wizard.
Figure 13-12 indicates that IIS Manager for Remote Administration is not installed.
577
Chapter 13 ■ Hosting Workflows in Windows Server
578
Chapter 13 ■ Hosting Workflows in Windows Server
The setup wizard will start after selecting an IIS Manager setup. The end user license
needs to be accepted and the default installation path can be used. The next step installs
the IIS Manager. The install should complete rather quickly.
8. After the IIS Manager has been successfully installed, click back to the AppFabric 1.1
install and click the Refresh button, which will indicate to the install wizard to check
again to make sure IIS Manager for Remote Administration has been installed. The screen
should refresh with a message indicating that no issues were detected. Press the Next
button to continue the installation.
9. The Confirmation screen confirms that the installation will install the listed dependent
Windows components. Clicking the Install button will confirm that it is ok for the wizard to
install the components (see Figure 13-14).
579
Chapter 13 ■ Hosting Workflows in Windows Server
10. The Progress screen is next, and it shows that the installation has started installing
AppFabric 1.1 and all its necessary components.
11. Finally, the Results screen indicates any issues that might have occurred during
installation. If there were no issues, the Results screen should look like Figure 13-15.
12. Make sure that the checkbox for “Launch configuration tool” is selected, and then click
Finish. This will open the configuration wizard (see Figure 13-16). The wizard will help
configure the AppFabric server for Event Collection configuration for managing the
monitoring and persistence data stores. Also, another opportunity to help Microsoft make
AppFabric better is offered. Select Next to start the configuration process.
580
Chapter 13 ■ Hosting Workflows in Windows Server
13. Hosting Services is the first part of the AppFabric server that can be configured. This is
where the monitoring and persistence configuration can be set. This is much easier than
trying to configure persistence and providing a custom monitoring solution, since there
are only three settings that need to be completed.
• Service accounts that will perform the work.
• Data provider namespace that will be used for the data stores.
• Security around the data stores.
14. Select “Set Monitoring configuration” and select System.Data.SqlClient as the
monitoring provider (see Figure 13-17).
581
Chapter 13 ■ Hosting Workflows in Windows Server
15. Click the Configure button and select “Register AppFabric monitoring store in root web.
config” and “Initialize monitoring store.”
16. Enter the database name AppFabMonitor as the database. If Windows authentication is
set up for SQL Server, the default settings for using Windows authentication can remain.
If not, then select SQL Server authentication and add the credentials that need to be
used. Selecting OK will confirm the configuration that was added for monitoring (see
Figure 13-18).
582
Chapter 13 ■ hosting WorkfloWs in WindoWs server
17. if configuration for monitoring was not successful, the configuration wizard will indicate
what issue is occurring. figure 13-19 indicates that the wizard did not see the sQl agent
service running, so in order for the appfabric dashboard to display event data, it needs
to be started.
583
Chapter 13 ■ Hosting Workflows in Windows Server
Clicking the Windows orb again and typing “service” will find the Component Services
program that can be clicked and opened. Find the Services section from the Console
Root and expand the services. Scroll down until the SQL Server Agent service is located.
Right-click the service and select Start (see Figure 13-20). After the service starts, clicking
OK on the message box will allow the configuration screen to come back up, indicating
that the default monitoring store is registered.
18. Select the “Set persistence configuration” checkbox. For the persistence provider, select
sqlStoreProvider; select the Configure button for persistence.
19. Select “Register AppFabric persistence store in root web.config” and “Initialize
persistence store.” Enter AppFabPersist as the database name that will be used for
persistence.
20. If Windows authentication is set up for the SQL Server, the default settings for using
Windows authentication can remain. If not, then select SQL Server authentication and
add the credentials that need to be used. Select OK to confirm the configuration for
persistence (see Figure 13-21).
584
Chapter 13 ■ Hosting Workflows in Windows Server
585
Chapter 13 ■ Hosting Workflows in Windows Server
21. At this point both monitoring and persistence have been configured and initialized. Select
Next to start the IIS Manager. This is the final step that just confirms that additional
configuration settings for the Hosting feature can be made. Make sure “Start Internet
Information Services (IIS) Manager” is selected and then select Finish.
AppFabric 1.1 has now been successfully installed as a single server instance. When the IIS Manager opens,
you will notice a new section at the top of IIS named “AppFabric,” as shown in Figure 13-23.
Since monitoring and persistence were configured, Microsoft SQL Server Management Studio can be used to see
the new databases, AppFabPersist and AppFabMonitor, that have been created (see Figure 13-24).
586
Chapter 13 ■ Hosting Workflows in Windows Server
Deploying to AppFabric
AppFabric and Visual Studio 2012 simplify the steps for deploying workflow services. Visual Studio 2012 provides a
workflow project template called WCF Workflow Service Application. This project provides a template for building
workflow services that will be hosted within AppFabric (see Figure 13-25).
Figure 13-26 illustrates that once the WCF workflow project loads, it provides a workflow that has a different
extension than the XAML workflows. An extension for a WCF workflow is .xamlx and is one way to tell that the
workflow is intended to be used as a WCF service and that it is intended to be hosted as a WCF service within
AppFabric. The project is called Workflow.JeepParts because of a recent process I experienced for ordering parts for
my Jeep at a local warehouse store. The parts can be ordered onsite at the store, and once they are ordered, a list of
parts is sent to a huge warehouse where someone on the warehouse staff picks the parts for the order. I picked up the
parts by driving to the warehouse and going to a location the store called “Will Call.” There I waited for someone in
the warehouse to bring me the parts I had ordered. If I did not want to pick them up, or if I created the order over the
phone, the parts were still picked; however they were placed back on shelves after a certain time expired from when
the order was created.
587
Chapter 13 ■ Hosting Workflows in Windows Server
The workflow for ordering and picking up Jeep parts is a simple Flowchart workflow, so more attention can be
13-27).
588
Chapter 13 ■ Hosting Workflows in Windows Server
Figure 13-28 illustrates that clicking on the Sequential service activity reveals the ReceiveRequest and
SendResponse activities that are used for creating the order and sending notification that the order has been created
along with an OrderID, which is a System.Guid type.
Figure 13-28. Creating the order and sending the OrderId back to the client
After the Jeep order has been created, Figure 13-29 indicates that the order then waits for one minute using a
Delay activity for the Jeep parts to be picked up by the customer. A Receive activity is used to indicate that the parts
have been picked up. If the parts are not picked up after a minute, the parts are restocked.
589
Chapter 13 ■ Hosting Workflows in Windows Server
Figure 13-29 also illustrates that once an order is picked up, a custom activity called TrackOrderPickup tracks
the OrderID and date and time the order was picked up. Listing 13-1 shows the code used to create a custom tracking
record. While the service runs, the tracking record can emit the custom tracking information for the OrderID and the
time the order was picked up within the warehouse.
using System;
using System.Activities;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Activities.Tracking;
namespace Workflow.JeepParts
{
public class TrackOrderPickup : CodeActivity
{
protected override void Execute(CodeActivityContext context)
{
var record = new CustomTrackingRecord("WorkingLevel")
590
Chapter 13 ■ Hosting Workflows in Windows Server
{
Data =
{
{"OrderId", context.WorkflowInstanceId},
{"DateTimePickedUp", DateTime.Now}
}
};
context.Track(record);
}
}
}
One cool feature of using a WCF Workflow Service Application is that the project can be run within Visual Studio.
Pressing F5 starts the WCF workflow project and automatically hosts the Workflow as a service within the WCF Test
Client (see Figure 13-30).
591
Chapter 13 ■ Hosting Workflows in Windows Server
While hosting the service within the WCF Test Client, an actual test client can be created to see how the workflow
service responds to calls issued by a client. Once the service has been tested and is ready to be deployed, the next step
is creating the deployment package that AppFabric requires for importing new application services.
Visual Studio makes it extremely simple to deploy workflow services to AppFabric using MSDeploy. A project can
be deployed by first building a deployment package by right-clicking on the project and selecting “Build Deployment
Package.” After the deployment package has been created, “Publish succeeded” is indicated within the bottom left
corner of Visual Studio 2012.
Importing the service is just as simple: right-click on the location for where the service should be deployed, select
Deploy, and then Import Application. In this case it will be deployed within the default web site (see Figure 13-31).
AppFabric will then walk through a wizard by first requesting where the package is located. After supplying the
package location, AppFabric checks the contents of the package and provides a last change for what will be installed.
Any file can be prevented from being installed by unchecking it (see Figure 13-32).
592
Chapter 13 ■ hosting WorkfloWs in WindoWs server
The next step of the wizard is the Enter Application Package Information screen, which asks for the application
name that will be used to reference the service (see Figure 13-33).
593
Chapter 13 ■ Hosting Workflows in Windows Server
The last step indicates that the service has been installed and reports the progress that was made to install the
service. There is also a Detail tab in case any of the steps need to be reviewed. The Workflow.JeepParts service is
now installed and can be referenced using a service client. A client application can simply reference the service within
Visual Studio, which will provide a proxy class to communicate with the hosted service. Figure 13-34 illustrates how
to reference the service deployed to AppFabric and the two calls that are supported within the service contract the
service implements.
AppFabric Dashboard
AppFabric’s dashboard is hosted within the IIS Manager and provides a standard view for managing and viewing
information on the health for WCF and WF services that are hosted within AppFabric. All of the data is real-time,
based on persistence and monitoring data stores. Individual workflow service instances can be drilled down into
for more information and provide an intuitive process for diagnosing and understanding how a service instance is
executing. After installing AppFabric, the first thing to notice is the Application and Service count located in the top
right corner. Applications includes web sites and the default count is 1 since there is one site, which is the default web
site. There are no services currently being hosted so the service count is 0.
594
Chapter 13 ■ Hosting Workflows in Windows Server
Moving down a little further, there are two dropdown boxes, one for View and one for Time Period. The View can
be toggled between the following two options:
• Local: Metric values originating from the local computer.
• All: Data for a selected scope, based on any computer that processed the service invocation.
The Time Period displays values for the time based on the selected time period value. The dashboard provides
two types of metrics:
• Summary Metrics: Contained within the shaded part of the section, these metrics represent
the summary information for data located directly below.
• Expanded Area Metrics (EAM): Located directly below the shaded area, these metrics tie to
the summary metrics and represent the detailed data for the summary metrics. There are also
pages that show the detail view for clicked on metrics:
• Individual Metrics Enumeration Pages: Provide detail information for solving critical issues.
The pages are navigated to by clicking on any of the links within the summary metrics section.
The AppFabric Dashboard is divided into three summary metric sections, as shown in Figure 13-35.
595
Chapter 13 ■ Hosting Workflows in Windows Server
• Persisted WF Instances:
• Active: Shows a count of WF instances that have a state of Active within a selected time
frame.
• Idle: Shows a count of WF instances that have a state of Idle within a selected time frame.
• Suspended: Shows a count of WF instances that have a state of Suspended. This is an
important metric because WF instances that are suspended could have been caused by
errors within a certain service or application within a selected time frame.
• WCF Call History:
• Completed: Number of service calls that completed within a selected time frame.
• Throttle Hits: Number of throttle hits for counters like MaxConcurrentCalls,
MaxConcurrentInstances, etc. within a selected time frame.
• Errors: Number of errors for service calls within a selected time frame.
• WF Instance History:
• Activations: Number of instances that began but have or have not completed within a
selected time frame.
• Failures: Number of instances that have had failures within a selected time frame. Some
instance may be able to complete if they were persisted and resumed.
• Completions: Number of errors for service calls within a certain time frame.
Action Pane
The Action pane is located in the right side panel of the dashboard, and based on which screen within AppFabric
is up, it dynamically changes menu items. For instance, while an application is selected within the left-handed
Connections pane, the Actions pane resembles Figure 13-36. A couple of menu items that are worth mentioning are
the following:
• Browse *:80 (http): Selecting this from the Actions menu allows the service to be viewed within
an Internet browser, so information about a service can be gathered, including the URL that
needs to be used within a client application.
• Advanced Settings: WF services require net.pipes to be used, so clicking on Advanced
Settings from the Actions menu allows the Enabled Protocols property to be set.
• Import Application: Selecting this from the Actions menu allows service packages to be
installed.
• Configure: Selecting this from the Actions menu allows configuration for functionality like
persistence and monitoring that AppFabric will use as metrics within the dashboard.
596
Chapter 13 ■ Hosting Workflows in Windows Server
Service Configuration
Service configuration is managed by either selecting the Configure menu item within the Actions pane, or by
right-clicking on an installed service, selecting “Manage WCF and WF Services,” and then Configure.
Two important configurations that need to be set are Workflow Persistence and Monitoring. Usually while
building workflow applications, this has to be custom built, but with AppFabric the infrastructure already exists but
still needs to be configured. In the earlier exercise, you encountered a wizard that was used to configure workflow
monitoring (Figure 13-17) and persistence (Figure 13-21). However, what really happened was that monitoring and
persistence stores were created as well as the connections string for reading and inserting data into the database
tables. After installing a service, the monitoring configuration can to be tailored to make sure the right information
is retained about a services execution. Just as Chapter 9 demonstrated how to use different tracking profiles, the
same functionality is provided out of the box within AppFabric, and it too can be changed using predefined tracking
profiles. Logging can also be used as a supplement to having events tracked within the database where a log file
receives of the events based on a predefined file path.
597
Chapter 13 ■ Hosting Workflows in Windows Server
Persistence can also be configured, based on how the database was initially configured after installing AppFabric.
Selecting the SQL Server Store verifies the connection string that will be used, and advanced configuration can be
added that would normally configure persistence within a custom workflow application’s app or web configuration
file, such as:
• Encode instance (GZip)
• Keep instances after completion
• Action on instance lock exception
• Host lock renewal period (in seconds)
• Runnable instances detection period (in seconds)
Caution When adding net.pipes to the Enabled Protocols property from the Advance Settings menu on the Actions
For example, to add
to an already existing http it would be http,net.pipe.
13-37 illustrates that an application has been deployed onto the server.
598
Chapter 13 ■ Hosting Workflows in Windows Server
Instead of configuring persistence at the HYPERVWINSEVEN2 server or the default web site level, persistence
has been set up at the Workflow.JeepParts service application level. Although the choice has to be made to share
a persistence store at higher levels than the application service level, it is nice to have the flexibility to dedicate a
persistence store per service if required. Sharing a persistence store might be a good option if there aren’t many
service applications sharing the same persistence store.
Persistence is configured by either right-clicking on a service and selecting “Manage WCF and WF Services” or by
using the Configure menu within the Actions pane. Once the Configure WCF and WF for Application screen appears,
selecting Workflow Persistence allows you to change the configuration for persistence. Even though the persistence
data store was configured and initialized, the services deployed onto the server still have to be configured to use
the new persistence data store. Figure 13-38 illustrates selecting a SQL Server Store that was configured in the first
exercise after installing AppFabric.
Figure 13-38 also shows another persistence store that was configured (although this was not done in the first
exercise). Using the configuration tool for AppFabric only allows the configuration of one default persistence store.
Within the first exercise you may recall checking the setting Register AppFabric persistence store in root web.config,
illustrated within Figure 13-21. What this setting does is build the configuration for the persistence store within the
web.config within the root directory for the .NET runtime. The file can be found at C:\Windows\Microsoft.NET\
Framework\v4.0.30319\Config.
599
Chapter 13 ■ Hosting Workflows in Windows Server
Opening the file within Visual Studio 2012 and scrolling down towards the bottom of the file, Listing 13-2
represents the configuration for persistence, which includes both SQL Server stores that can be used for persisting
workflow instances for the service. There is also the configuration for initializing the monitoring data store.
<instanceStores>
<add name="defaultSqlPersistenceStore" provider="SqlPersistenceStoreProvider"
connectionStringName="ApplicationServerWorkflowInstanceStoreConnectionString" />
<add name="CustomPersistStore" provider="SqlPersistenceStoreProvider" connectionString="Data
Source=HYPERVWINSEVEN2;Initial Catalog=AppFabPersist2;Integrated Security=True" />
</instanceStores>
<connectionStrings>
<add connectionString="Data Source=HYPERVWINSEVEN2;Initial Catalog=AppFabMonitor;Integrated
</connectionStrings>
Windows PowerShell cmdlets for adding configuration or functionality, the script can be used as a type of automation
so configuration changes do not need to be manually performed.
Windows PowerShell can be found by clicking on the Windows orb and searching for “power.” The first program
that is found is Windows PowerShell. Clicking on it opens the command prompt for running PowerShell commands.
To initialize Windows PowerShell cmdlets that can be used for AppFabric, the import-model ApplicationServer
command must be run, as illustrated in Figure 13-39.
One feature that is accomplished using a Windows PowerShell command is initializing additional persistence
data stores so that WF services can choose from more than just the default persistence store, as indicated in
Figure 13-38. To add another persistence store, the Windows PowerShell command Add-ASAppSqlInstanceStore
must be used (see Figure 13-40).
600
Chapter 13 ■ Hosting Workflows in Windows Server
The changes reflected by the Add-ASAppSqlInstanceStore command are immediately reflected within the
root web.config configuration illustrated in Listing 13-2. Changes to the new configuration for the new persistence
store can also be done by running Set-ASAppSqlInstanceStore,as illustrated in Figure 13-41 to change the
ConnectionString property.
■■Note WCF services do not use persistence—only the services built using WF. Because workflow services persist,
they are known as “stateful services” compared to a WCF service, which simply makes accepts a call and completes.
WCF and WF do share the capability for tracking events.
Monitoring WF Instances
Monitoring can also be configured at a server, site, or site application level, just like persistence. Once monitoring as
an initialized instance data store, a service can be configured to use an initialized monitoring instance data store
(see Figure 13-42).
601
Chapter 13 ■ Hosting Workflows in Windows Server
Figure 13-42 illustrates a couple of features and settings that can be applied to monitor WF events. The first
feature that can be turned on or off is writing events to a database. If the checkbox is not checked, then events will
not be stored within the monitoring database that was initialized earlier. The Connection string property refers
to the connection string that was configured after AppFabric was installed. There are two other connection strings,
LocalSqlServer and ApplicationServerWorkflowInstanceStoreConnectionString, that might be selectable;
however, make sure that ApplicationServerMonitoringConnectionString is selected, as illustrated in Figure 13-42.
A monitoring level can also be selected based on how much monitoring information needs to be applied to
monitor a service. There are five different levels of monitoring, including a level for turning monitoring off. Other than
turning monitoring off, the following monitoring levels can from the minimal amount of monitoring available out of
the box to the most that can be tracked within AppFabric.
• Errors Only: This setting only reports on errors and critical warning events.
• Health Monitoring: This setting is the default setting for monitoring and should be used for
populating metrics within the AppFabric Dashboard. Error events are inherited from the
Errors Only profile.
• End-to-End Monitoring: This setting subscribes to the flow of events within workflows
and between other WCF services. Health monitoring events are inherited from the Health
Monitoring profile.
602
Chapter 13 ■ hosting WorkfloWs in WindoWs server
• Troubleshooting: This setting is required for subscribing to events that can assist with
application-level diagnostics. This setting should not be left running within production
applications because it can affect performance of the application. End-to-end monitoring
events are inherited within this setting as well.
Each of these profiles is just a predefined tracking profile (covered in Chapter 9). The profiles
reside within the root web.config located at C:\Windows\Microsoft.NET\Framework\
v4.0.30319\Config. Listing 13-3 shows the each of the tracking profiles that are stored
within the root web.config.
<tracking>
<profiles>
<trackingProfile name="ErrorsOnly Tracking Profile">
<workflow activityDefinitionId="*">
<workflowInstanceQueries>
<workflowInstanceQuery>
<states>
<state name="UnhandledException" />
<state name="Aborted" />
</states>
</workflowInstanceQuery>
</workflowInstanceQueries>
<faultPropagationQueries>
<faultPropagationQuery faultSourceActivityName="*"
faultHandlerActivityName="*" />
</faultPropagationQueries>
</workflow>
</trackingProfile>
<trackingProfile name="HealthMonitoring Tracking Profile">
<workflow activityDefinitionId="*">
<workflowInstanceQueries>
<workflowInstanceQuery>
<states>
<state name="Started" />
<state name="Completed" />
<state name="Terminated" />
<state name="Canceled" />
<state name="Unsuspended" />
<state name="Persisted" />
<state name="Aborted" />
<state name="UnhandledException" />
</states>
</workflowInstanceQuery>
</workflowInstanceQueries>
<activityStateQueries>
<activityStateQuery activityName="*">
603
Chapter 13 ■ Hosting Workflows in Windows Server
<states>
<state name="Closed" />
</states>
</activityStateQuery>
</activityStateQueries>
<faultPropagationQueries>
<faultPropagationQuery faultSourceActivityName="*"
faultHandlerActivityName="*" />
</faultPropagationQueries>
<customTrackingQueries>
<customTrackingQuery name="*" activityName="*" />
</customTrackingQueries>
</workflow>
</trackingProfile>
<trackingProfile name="EndToEndMonitoring Tracking Profile">
<workflow activityDefinitionId="*">
<workflowInstanceQueries>
<workflowInstanceQuery>
<states>
<state name="*" />
</states>
</workflowInstanceQuery>
</workflowInstanceQueries>
<activityStateQueries>
<activityStateQuery activityName="*">
<states>
<state name="Executing" />
<state name="Closed" />
</states>
</activityStateQuery>
</activityStateQueries>
<faultPropagationQueries>
<faultPropagationQuery faultSourceActivityName="*"
faultHandlerActivityName="*" />
</faultPropagationQueries>
<customTrackingQueries>
<customTrackingQuery name="*" activityName="*" />
</customTrackingQueries>
</workflow>
</trackingProfile>
<trackingProfile name="Troubleshooting Tracking Profile"
implementationVisibility="All">
<workflow activityDefinitionId="*">
<workflowInstanceQueries>
<workflowInstanceQuery>
604
Chapter 13 ■ Hosting Workflows in Windows Server
<states>
<state name="*" />
</states>
</workflowInstanceQuery>
</workflowInstanceQueries>
<activityStateQueries>
<activityStateQuery activityName="*">
<states>
<state name="*" />
</states>
</activityStateQuery>
</activityStateQueries>
<activityScheduledQueries>
<activityScheduledQuery activityName="*"
childActivityName="*" />
</activityScheduledQueries>
<cancelRequestedQueries>
<cancelRequestedQuery activityName="*"
childActivityName="*" />
</cancelRequestedQueries>
<faultPropagationQueries>
<faultPropagationQuery faultSourceActivityName="*"
faultHandlerActivityName="*" />
</faultPropagationQueries>
<bookmarkResumptionQueries>
<bookmarkResumptionQuery name="*" />
</bookmarkResumptionQueries>
<customTrackingQueries>
<customTrackingQuery name="*" activityName="*" />
</customTrackingQueries>
</workflow>
</trackingProfile>
</profiles>
</tracking>
605
Chapter 13 ■ Hosting Workflows in Windows Server
Troubleshooting Monitoring
Setting up monitoring within AppFabric can be complicated. The exercise in this chapter simply configured and
created a monitoring store that can be used within AppFabric. However, configuring the monitoring store to be used
for tracking a service can become difficult; and it can also be hard to debug why events are not being tracked for
workflow instances if the whole monitoring process is not fully understood.
Figure 13-45 is an overall illustration of how AppFabric monitoring works. A WF tracking provider emits tracking
records and, based on the tracking profile that is used, the ETW Tracking Participant sends the subscribed tracking
events to the ETW so the tracking events and data within a workflow instance can be logged. Remember that the
ETW Tracking Participant comes out of the box with WF. AppFabric then logs the tracked events within the database
that was either initialized after installing AppFabric for monitoring or a database that is initialized afterwards using
Windows PowerShell.
Complications for monitoring WF instances are noticeable when reading the AppFabric Dashboard and realizing
that there are no metrics being tallied for workflow instances being tracked, so let’s dig a little deeper into how
AppFabric performs monitoring. Figure 13-46 illustrates that Event Collection Service collects events from the ETW
and writes the data to a staging table within the monitoring database that was initialized, but sometimes the data
does not get there because of permission issues. If there are no issues, a SQL Agent job grabs the events data and send
it to the normalized monitoring database tables, so the provided database views then represent the data within the
AppFabric Dashboard.
607
Chapter 13 ■ Hosting Workflows in Windows Server
If monitoring metrics are not being loaded, the first thing to check is that the AppFabric Event Collection Service
Figure 13-48 illustrates that the SQL Server Agent Service is running.
608
Chapter 13 ■ Hosting Workflows in Windows Server
Once it is clear that both the AppFabric Event Collection Service and the SQL Server Agent Service are running,
the ETW needs to be checked to see if there are any issues for the AppFabric Event Collection Service. Service
issues are logged within Event Viewer ➤ Applications and Services Logs ➤ Microsoft ➤ Windows ➤ Application
Server-System Services ➤ Admin. If there are any issues with services, they are logged here. Figure 13-49 shows a couple
of issues; looking under the General tab while selecting an Error gives a description for the problem with a service.
The errors in Figure 13-49 illustrate that there is a security issue for the login NT AUTHORITY\LOCAL SERVICE
and the monitoring database AppFabMonitor that was initialized. This account is selected by default when the wizard
is used to set up the monitoring data store. To make sure this is the problem, open the configuration program that
can be found within All Programs and AppFabric for Windows Server. Figure 13-50 illustrates how the AppFabric
Event Collection service account can be changed by selecting an account with permission to log in to the monitoring
database.
609
Chapter 13 ■ Hosting Workflows in Windows Server
Checking the AppFabric Dashboard will determine if changing the service account fixed the issue of displaying
13-51 illustrates the four SQL Server agent jobs that were installed with AppFabric.
Right-clicking on any of the jobs and selecting View History will show the job’s execution history. Figure 13-52
illustrates that there are issues where the SQL Server agent job cannot obtain information about a certain user that
the job is running under. Issues like this can be fixed by removing the initialized database by running the PowerShell
cmdlet Remove-ASMonitoringSqlDatabase, providing the Database and Server parameters for the monitoring
database that was created using AppFabric Configuration, then running the Initialize-ASMonitoringSqlDatabase
cmdlet illustrated in Figure 13-44. Running the PowerShell cmdlet as a local user sets the permissions for the SQL
job’s execution.
610
Chapter 13 ■ Hosting Workflows in Windows Server
After recreating the monitoring store and revisiting the SQL job’s log file, the jobs should start logging successful
events (see Figure 13-53).
Understanding WF Metrics
Now that persistence and monitoring have been set up successfully, the AppFabric Dashboard should be showing
metrics not only for persisted workflow instances, but also tracking events based on the tracking profile that was
selected. By default, Health Monitoring is selected so services that are hosted within AppFabric that are called by
client applications should reflect metrics about service activity.
Figure 13-54 shows that there is one service deployed within the default web site called Workflow.JeepParts.
The dashboard shows that there is some tracking information that already exists. One WCF service call has been
made to the Workflow.JeepParts service indicated by the Completed metric within the WCF Call History section of
the dashboard. There is a workflow instance that has been activated based on the Activation metric and it has been
persisted, as indicated by the Idle metric. All of this information is based on the Workflow.JeepParts service13.
611
Chapter 13 ■ Hosting Workflows in Windows Server
Using the Refresh link located at the top right of the dashboard, new information about the workflow instance
13-55).
Clicking on metrics within the WCF Call History does not show as much monitoring information since there is
not as much to a plain WCF service compared to a WF service since the service uses a workflow and more information
needs to be tracked to add transparency for managing the workflow service.
612
Chapter 13 ■ hosting WorkfloWs in WindoWs server
Clicking on either the Activation or Completion metric drills into the service by opening the Tracked WF
Instances screen. There is a search feature at the top of the screen that can be used for filtering tracked instances.
Clicking the Completion metric automatically puts the filter criteria in for filtering Statuses that have the
value Completed. Clicking the tracked instance adds additional menu choices within the left Actions pane
(see Figure 13-56). Clicking “View Tracked Events” opens the Tracked Events screen, which has all of the
tracked events logged for the workflow instance.
All of the events that have been subscribed to from the default Health Monitoring tracking profile are logged
within the Tracked Events screen. Figure 13-57 shows the list of tracked events for the workflow instance service.
613
Chapter 13 ■ Hosting Workflows in Windows Server
■■Caution While testing services within AppFabric and gathering metrics within the dashboard, depending on the
latest activity, you may not see the metrics displayed that were displayed earlier. Increasing the time period using the
drop-down will increase visibility for past metrics. If the time period was set for 24hrs, that time may have passed;
setting the time period to Last 7 Days will find the older metrics.
Figure 13-58. PowerShell commands for managing the monitoring data store
614
Chapter 13 ■ Hosting Workflows in Windows Server
• Enable instance control: Enables commands that can be signaled to workflow instances from
within AppFabric like cancel, suspend, and resume.
• Unload instance when idle: Indicates if workflow instances should be unloaded after they
become idle. Number of seconds can be entered through the list box for how long after an idle
a workflow instance should be unloaded.
• Persist instance when idle: Indicates if workflow instances should be persisted after they
become idle. Number of seconds can be entered through the list box for how long after an idle
a workflow instance should be persisted after being idle.
• Action on unhandled exception: Indicates the type of action that should be performed for
unhandled exceptions. The choices are as follows:
• Abandon
• Abandon and suspend
• Cancel
• Terminate
615
Chapter 13 ■ Hosting Workflows in Windows Server
Auto-Start Feature
The auto-start feature for AppFabric is built on top of IIS 7.5 and provides better performance by improving host
responsiveness. By setting services to auto-start, services are initialized within applications to start before a client
initiates the first call to a hosted service. Setting the auto-start for all services within an application can be done in
three different ways:
• PowerShell cmdlet: Using the AppFabric Windows PowerShell console and using the
Set-ASApplication command with the –AutoStartMode parameter and setting it to either
All for all services within the application or Custom for specific services within the application.
• IIS Manager: Clicking on an application within Manager for starting all services within the
application.
• Configured: Certain services can be configured to auto-start using the web.config.
Another scenario for setting services to auto-start is for unforeseen conditions like a server that has been
13-60).
Summary
This chapter focused on hosting workflow services using MicrosoftWindows Server AppFabric instead of building
a custom solution to host and manage workflows. Before AppFabric, there was no good solution for hosting and
managing workflows as services. It can be used for providing a robust solution for hosting workflows on-premise or
within a proprietary data center. AppFabric also extends IIS 7.0 and Windows Processing Activation Service (WAS) and
provides an easier way to deploy workflows as services through MSDeploy. AppFabric provides a first class experience
of providing non-technical team members the ability to configure workflow execution, manage persistence, and
provide a level of monitoring required for workflow instance execution.
AppFabric is a great solution for running enterprise-class solutions that need to host WCF and workflow services
because it offers single server deployment or server farm deployment that provides high availability for hosting
reliable services that can be scaled across many servers within a server farm. Most of the functionality that is manually
done to configure AppFabric can also be accomplished by running PowerShell commands.
I want to thank you for taking the time to read this book. A lot of time and effort went into writing it, as I wanted to
make sure the latest material was included, based on the bits that Microsoft released around WF4.5. I hope you have
enjoyed reading the information I have presented about WF4.5, and that it helps you with hurdles you may have come
across or with future challenges when building workflows in WF4.5.
616
Index
617
■ Index
618
■ Index
nD n F, G
Database contengency, 567 First In First Out (FIFO), 2
DefaultEndpointsProtocol, 530 Flowchart workflows
DefaultInstanceOwner, 323 business process modeling
Design time validation adding Visual C# class library, 165
activity arguments, 271 Assign activity, 177
CacheMetadata, 272 Customer.cs file, 166
data validation property, 272 customer orders workflow, 176
result property, addition activity, 270, 272 customers type, 175
SalesCommission activity properties, 273 DataModel references, 172
sales percentage, 273–274 downloading NuGet, 168
Distributed Transaction Controller (DTC), 559 ForEach<T> activity, 176
DropSqlPersistenceProviderLogic, 300 GetCustomeOrders.cs file, 173–174
DropSqlPersistenceProviderSchema, 300 GetCustomerOrders, 174
DropSqlWorkflowInstanceStoreLogic, 300 managing NuGet packages, 171–172
619
■ Index
620
■ Index
621
■ Index
622
2
■ Index
623
■ Index
624
■ Index
625
■ Index
626
Pro WF 4.5
Bayer White
Pro WF 4.5
Copyright © 2013 by Bayer White
This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or part of the material
is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting,
reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval,
electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed.
Exempted from this legal reservation are brief excerpts in connection with reviews or scholarly analysis or material
supplied specifically for the purpose of being entered and executed on a computer system, for exclusive use by the
purchaser of the work. Duplication of this publication or parts thereof is permitted only under the provisions of the
Copyright Law of the Publisher’s location, in its current version, and permission for use must always be obtained from
Springer. Permissions for use may be obtained through RightsLink at the Copyright Clearance Center. Violations are
liable to prosecution under the respective Copyright Law.
vii
■ Contents
WF Components��������������������������������������������������������������������������������������������������������������������������26
Workflow Runtime����������������������������������������������������������������������������������������������������������������������������������������������� 26
Defining Workflows���������������������������������������������������������������������������������������������������������������������������������������������� 44
Workflow Designer���������������������������������������������������������������������������������������������������������������������������������������������� 46
Persistence����������������������������������������������������������������������������������������������������������������������������������56
Tracking Workflows���������������������������������������������������������������������������������������������������������������������56
A Lap Around WF4.5��������������������������������������������������������������������������������������������������������������������57
Activities�������������������������������������������������������������������������������������������������������������������������������������������������������������� 57
Summary�������������������������������������������������������������������������������������������������������������������������������������62
■Chapter 3: Windows Workflow Activities������������������������������������������������������������������������63
Activity Basics�����������������������������������������������������������������������������������������������������������������������������64
Data Management����������������������������������������������������������������������������������������������������������������������������������������������� 65
Activity Life Cycle������������������������������������������������������������������������������������������������������������������������������������������������ 67
Authoring Activities���������������������������������������������������������������������������������������������������������������������������������������������� 67
Testing Activities�������������������������������������������������������������������������������������������������������������������������������������������������� 80
Communicating with Activities���������������������������������������������������������������������������������������������������������������������������� 83
Implementing Activities���������������������������������������������������������������������������������������������������������������86
Debugging Activities�������������������������������������������������������������������������������������������������������������������������������������������� 86
Error Handling������������������������������������������������������������������������������������������������������������������������������������������������������ 92
Summary�����������������������������������������������������������������������������������������������������������������������������������106
■■Chapter 4: State Machine Workflows����������������������������������������������������������������������������109
State Machine Components������������������������������������������������������������������������������������������������������110
State Machine Workflow������������������������������������������������������������������������������������������������������������������������������������ 110
State������������������������������������������������������������������������������������������������������������������������������������������������������������������ 111
Transitions��������������������������������������������������������������������������������������������������������������������������������������������������������� 116
Final State��������������������������������������������������������������������������������������������������������������������������������������������������������� 118
Auto-Connect����������������������������������������������������������������������������������������������������������������������������������������������������� 119
Auto-Insert��������������������������������������������������������������������������������������������������������������������������������������������������������� 119
Debugging State Machine States���������������������������������������������������������������������������������������������������������������������� 119
viii
■ Contents
Summary�����������������������������������������������������������������������������������������������������������������������������������157
■■Chapter 5: Flowchart Workflows�����������������������������������������������������������������������������������159
Flow Activities���������������������������������������������������������������������������������������������������������������������������159
Flowchart����������������������������������������������������������������������������������������������������������������������������������������������������������� 160
FlowDecision����������������������������������������������������������������������������������������������������������������������������������������������������� 162
FlowSwitch <T> Activity������������������������������������������������������������������������������������������������������������������������������������ 164
Summary�����������������������������������������������������������������������������������������������������������������������������������203
■■Chapter 6: Versioning and Updating Workflows������������������������������������������������������������205
Persistence Maturity�����������������������������������������������������������������������������������������������������������������205
Side-by-Side Workflow Execution���������������������������������������������������������������������������������������������207
Adding Definition Identities������������������������������������������������������������������������������������������������������������������������������� 214
Summary�����������������������������������������������������������������������������������������������������������������������������������256
ix
■ Contents
x
■ Contents
Summary�����������������������������������������������������������������������������������������������������������������������������������398
■■Chapter 10: Rehosting the Workflow Designer��������������������������������������������������������������399
Rehosting Components�������������������������������������������������������������������������������������������������������������400
WF Designer������������������������������������������������������������������������������������������������������������������������������������������������������ 400
WF Toolbox��������������������������������������������������������������������������������������������������������������������������������������������������������� 402
WF Properties���������������������������������������������������������������������������������������������������������������������������������������������������� 403
Managing Workflows�����������������������������������������������������������������������������������������������������������������432
Setting Up the UI for Managing Workflows�������������������������������������������������������������������������������������������������������� 433
Summary�����������������������������������������������������������������������������������������������������������������������������������450
■■Chapter 11: Stateful WCF Services Using Workflow������������������������������������������������������451
Windows Communication Foundation (WCF)�����������������������������������������������������������������������������451
WCF Fundamentals�������������������������������������������������������������������������������������������������������������������������������������������� 452
Service and Data Contracts������������������������������������������������������������������������������������������������������������������������������� 454
xi
■ Contents
Summary�����������������������������������������������������������������������������������������������������������������������������������499
■■Chapter 12: Workflows in Windows Azure��������������������������������������������������������������������501
Windows Azure��������������������������������������������������������������������������������������������������������������������������501
Azure Portal������������������������������������������������������������������������������������������������������������������������������������������������������� 502
Cloud Services��������������������������������������������������������������������������������������������������������������������������������������������������� 506
Data Management��������������������������������������������������������������������������������������������������������������������������������������������� 508
Cloud Workflows������������������������������������������������������������������������������������������������������������������������528
Configuring Azure Storage��������������������������������������������������������������������������������������������������������������������������������� 529
Publishing to Azure�������������������������������������������������������������������������������������������������������������������������������������������� 530
Workflows in Blob Storage�������������������������������������������������������������������������������������������������������������������������������� 535
Service Bus and Workflows������������������������������������������������������������������������������������������������������������������������������� 543
Hosting Durable Workflows������������������������������������������������������������������������������������������������������������������������������� 547
xii
■ Contents
Deployment Types���������������������������������������������������������������������������������������������������������������������568
Single Server Deployment��������������������������������������������������������������������������������������������������������������������������������� 569
Server Farm Deployment����������������������������������������������������������������������������������������������������������������������������������� 569
Installing AppFabric�������������������������������������������������������������������������������������������������������������������570
Upgrading���������������������������������������������������������������������������������������������������������������������������������������������������������� 571
Hardware Requirements������������������������������������������������������������������������������������������������������������������������������������ 571
Software Requirements������������������������������������������������������������������������������������������������������������������������������������� 571
Installation and Configuration���������������������������������������������������������������������������������������������������������������������������� 572
Deploying to AppFabric�������������������������������������������������������������������������������������������������������������587
AppFabric Dashboard����������������������������������������������������������������������������������������������������������������594
Action Pane�������������������������������������������������������������������������������������������������������������������������������������������������������� 596
Persisted WF Instances������������������������������������������������������������������������������������������������������������������������������������� 598
Monitoring WF Instances����������������������������������������������������������������������������������������������������������������������������������� 601
Understanding WF Metrics�������������������������������������������������������������������������������������������������������������������������������� 611
Purging Tracked Events������������������������������������������������������������������������������������������������������������������������������������� 614
Workflow Host Management����������������������������������������������������������������������������������������������������������������������������� 614
Auto-Start Feature��������������������������������������������������������������������������������������������������������������������������������������������� 616
Summary�����������������������������������������������������������������������������������������������������������������������������������616
Index���������������������������������������������������������������������������������������������������������������������������������617
xiii
About the Author
Bayer White is a Microsoft Integration MVP with 15 years of experience architecting enterprise solutions using
Microsoft .NET technologies for various business industries including Forestry, Textiles, Financial, and Health Care.
Since the initial release of Windows Workflow Foundation (WF) in the first beta of .NET Framework 3.0, his focus
has been on automating clients’ businesses through technology by modeling business processes using WF. He is
known within the Florida developer community for speaking at Florida code camps and national conferences like
DevConnections, Professional Association for SQL Server (PASS), and VSLive. He also blogs and writes articles
about WF.
When Bayer is not focused on technology, he enjoys spending as much time as he can with his wife and two
kids. Occasionally he also gets time to enjoy the outdoors through camping and bird hunting. His blog is at
www.humanworkflow.com and his e-mail is bwhite@flowfocus.com.
xv
About the Technical Reviewer
xvii
Acknowledgments
I never realized the complexity or the amount of effort it takes to write a book until now, and I want to take a moment
to thank family and friends who helped me throughout my journey.
I want to thank the fine people at Apress, including Jonathan Hassell, for giving me the opportunity and
encouragement to write this book as the sole author. It’s been a goal of mine for a long time. I also want to thank Tom
Welsh, who is not only the best editor on the planet but also an author’s “big brother,” for encouraging me through
each chapter. I want to give a warm thanks to Jeff Sanders for painstakingly looking over my shoulder at the technical
content of each chapter and Christine Ricketts for trying to keep me on schedule.
Finally, I want to thank the WF Team at Microsoft for the great job they did with the release of WF4.5. I personally
would like to thank Jurgen Willis for making sure I had resources available from the team. Thanks to Hani Khoshdel-
Nikkhoo and Dave Cliff for making themselves available to field my questions. Most of all, I want to thank Leon
Welicki for taking the time for weekly calls to make sure I was headed in the right direction with my book.
xix