Introduction To Windows Workflow Foundation
Introduction To Windows Workflow Foundation
Introduction To Windows Workflow Foundation
constantly looking for tools and frameworks to simplify the process and help us focus on the business challenges we are trying to solve. We have moved from writing code in machine languages such as assembler to higher level languages like C# and Visual Basic that ease our development, remove lower level concerns such as memory management, and increase our productivity as developers. or !icrosoft developers, the move to ."#$ allows the Common %anguage &untime 'C%&( to allocate memory, cleanup unneeded ob)ects and handle low level constructs like pointers. !uch of the comple*ity of an application lives in the logic and processing that goes on behind the scenes. +ssues such as asynchronous or parallel e*ecution and generally coordinating the tasks to respond to user re,uests or service re,uests can ,uickly lead application developers back down into the low level coding of handles, callbacks, synchroni-ation etc. As developers, we need the same power and fle*ibility of a declarative programming model for the internals of an application as we have for the user interface in Windows .resentation oundation 'W. (. Windows Workflow oundation 'W ( provides the declarative framework for building application and service logic and gives developers a higher level language for handling asynchronous, parallel tasks and other comple* processing. /aving a runtime to manage memory and ob)ects has freed us to focus more on the important business aspects of writing code. %ikewise, having a runtime that can manage the comple*ities of coordinating asynchronous work provides a set of features that improves developer productivity. W is a set of tools for declaring your workflow 'your business logic(, activities to help define the logic and control flow, and a runtime for e*ecuting the resulting application definition. +n short, W is about using a higher level language for writing applications, with the goal of making developers more productive, applications easier to manage, and change ,uicker to implement. $he W runtime not only e*ecutes your workflows for you, it also provides services and features important when writing application logic such persistence of state, bookmarking and resumption of business logic, all of which lead to thread and process agility enabling scale up and scale out of business processes. or more conceptual information on how using W to build your applications can make you more productive, + recommend you read 0$he Workflow Way0 by 1avid Chappell, found in the Additional &esources section. Whats New in WF4 +n version 2 of the !icrosoft3 ."#$ ramework, Windows Workflow oundation introduces a significant amount of change from the previous versions of the technology that shipped as part of ."#$ 4.5 and 4.6. +n fact, the team revisited the core of the programming model, runtime and tooling and has re7architected each one to increase performance and productivity as well as to address the important feedback garnered from customer engagements using the previous versions. $he significant changes made were necessary to provide the best e*perience for developers adopting W and to enable W to continue to be a strong foundational component that you can build on in your applications. + will introduce the high level changes here, and throughout the paper each topic will get more in depth treatment. Before + continue, it is important to understand that backwards compatibility was also a key goal in this release. $he new framework components are found primarily in the 8ystem.Activities.9 assemblies while the backwards compatible framework components are found in the 8ystem.Workflow.9 assemblies. $he 8ystem.Workflow.9 assemblies are part of the ."#$ ramework 2 and provide complete backward compatibility so you can migrate your application to ."#$ 2 with no changes to your workflow code. $hroughout this paper + will use the name W 2 to refer to the new components found in the 8ystem.Activities.9 assemblies and W 4 to refer to the components found in the 8ystem.Workflow.9 assemblies. Designers
:ne of the most visible areas of improvement is in the workflow designer. ;sability and performance were key goals for the team for the V8 <5=5 release. $he designer now supports the ability to work with much larger workflows without a degradation in performance and designers are all based on Windows .resentation oundation 'W. (, taking full advantage of the rich user e*perience one can build with the declarative ;+ framework. Activity developers will use >A!% to define the way their activities look and interact with users in a visual design environment. +n addition, rehosting the workflow designer in your own applications to enable non7developers to view and interact with your workflows is now much easier. Data Flow +n W 4, the flow of data in a workflow was opa,ue. W 2 provides a clear, concise model for data flow and scoping in the use of arguments and variables. $hese concepts, familiar to all developers, simplify both the definition of data storage, as well as the flow of the data into and out of workflows and activities. $he data flow model also makes more obvious the e*pected inputs and outputs of a given activity and improves performance of the runtime as data is more easily managed. Flowchart A new control flow activity called lowchart has been added to make it possible for developers to use the lowchart model to define a workflow. $he lowchart more closely resembles the concepts and thought processes that many analysts and developers go through when creating solutions or designing business processes. $herefore, it made sense to provide an activity to make it easy to model the conceptual thinking and planning that had already been done. $he lowchart enables concepts such as returning to previous steps and splitting logic based on a single condition, or a 8witch ? Case logic. Programming odel
$he W programming model has been revamped to make it both simpler and more robust. Activity is the core base type in the programming model and represents both workflows and activities. +n addition, you no longer need to create a Workflow&untime to invoke a workflow, you can simply create an instance and e*ecute it, simplifying unit testing and application scenarios where you do not want to go through the trouble of setting up a specific environment. inally, the workflow programming model becomes a fully declarative composition of activities, with no code7beside, simplifying workflow authoring. Windows !ommunication Foundation (W!F) Integration $he benefits of W most certainly apply to both creating services and consuming or coordinating service interactions. A great deal of effort went into enhancing the integration between WC and W . "ew messaging activities, message correlation, and improved hosting support, along with fully declarative service definition are the ma)or areas of improvement. "etting #tarted with Workflow $he best way to understand W is to start using it and applying the concepts. + will cover several core concepts around the underpinnings of workflow, and then walk through creating a few simple workflows to illustrate how those concepts relate to each other. Workflow #tructure Activities are the building blocks of W and all activities ultimately derive from Activity. A terminology note 7 Activities are a unit of work in W . Activities can be composed together into larger Activities. When an Activity is used as a top7level entry point, it is called a 0Workflow0, )ust like !ain is simply another function that represents a top level entry point to C%& programs. or e*ample, igure = shows a simple workflow being built in code. 8e,uence s @ new 8e,uence
A Activities @ A new Write%ine A$e*t @ 0/ello0B, new 8e,uence A Activities @ A new Write%ine A$e*t @ 0Workflow0B, new Write%ine A$e*t @ 0World0B B B B BC Figure $% & sim'le workflow "otice in igure = that the 8e,uence activity is used as the root activity to define the root control flow style for the workflow. Any activity can be used as the root or workflow and e*ecuted, even a simple Write%ine. By setting the Activities property on the 8e,uence with a collection of other activities, + have defined the workflow structure. urther, the child activities can have child activities, creating a tree of activities that make up the overall workflow definition. Workflow (em'lates and the Workflow Designer W 2 ships with many activities and Visual 8tudio <5=5 includes a template for defining activities. $he two most common activities used for the root control flow are 8e,uence and lowchart. While any Activity can be e*ecuted as a workflow, these two provide the most common design patterns for defining business logic. igure < shows an e*ample of a se,uential workflow model defining the processing of an order being received, saved and then notifications sent to other services.
Figure )% #e*uential workflow design $he lowchart workflow type is being introduced in W 2 to address common re,uests from e*isting users such as being able to return to previous steps in a workflow and because it more closely resembles the conceptual design done by analysts and developers working on defining business logic. or e*ample, consider a scenario involving user input to your application. +n response to the data provided by the user, your program should either continue in the process or return to a previous step to prompt for the input again. With a se,uential workflow, this would involve something similar to what is shown in igure 4 where a 1oWhile activity is used to continue processing until some condition is met.
Figure +% #e*uential for decision ,ranching $he design in igure 4 works, but as a developer or analyst looking at the workflow, the model does not represent the original logic or re,uirements that were described. $he lowchart workflow in igure 2 gives a similar technical outcome to the se,uential model used in igure 4, but the design of the workflow more closely matches the thinking and re,uirements as originally defined.
Figure 4% Flowchart workflow Data flow in workflows $he first thought most people have when they think about workflow is the business process or the flow of the application. /owever, )ust as critical as the flow is the data that drives the process and the information that is collected and stored during the e*ecution of the workflow. +n W 2, the storage and management of data has been a prime area of design consideration. $here are three main concepts to understand with regard to dataD Variables, Arguments, and #*pressions. $he simple definitions for each are that variables are for storing data, arguments are for passing data, and e*pressions are for manipulating data. -aria,les . storing data Variables in workflows are very much like the variables you are used to in imperative languagesD they describe a named location for data to be stored and they follow certain scoping rules. $o create a variable, you first determine at what scope the variable needs to be available. Eust as you might have variables in code that are available at the class or method level, your workflow variables can be defined at different scopes. Consider the workflow in igure 6. +n this e*ample, you can define a variable at the root level of the workflow, or at the scope defined by the Collect eed 1ata se,uence activity.
Figure /% -aria,les sco'ed to activities &rguments . 'assing data Arguments are defined on activities and define the flow of data into and out of the activity. Fou can think of arguments to activities much like you use arguments to methods in imperative code. Arguments can be +n, :ut, or +n?:ut and have a name and type. When building a workflow, you can define arguments on the root activity which enables data to be passed into your workflow when it is invoked. Fou define arguments on the workflow much as you do variables using the argument window. As you add activities to your workflow, you will need to configure the arguments for the activities, and this is primarily done by referencing in7scope variables or using e*pressions, which + will discuss ne*t. +n fact, the Argument base class contains an #*pression property which is an Activity that returns a value of the argument type, so all of these options are related and rely on Activity. When using the workflow designer to edit arguments you can type e*pressions representing literal values into the property grid, or use variable names to reference an in7scope variable as shown in igure G, where email&esult and emailAddress are variables defined in the workflow.
Figure 0% !onfiguring arguments on activities 12'ressions . acting on data #*pressions are activities you can use in your workflow to operate on data. #*pressions can be used in places where you use Activity and are interested in a return value, which means you can use e*pressions in setting arguments or to define conditions on activities like the While or +f activities. &emember that most things in W 2 derive from Activity and e*pressions are no different, they are a derivative of ActivityH$&esultI meaning they return a value of a specific type. +n addition, W 2 includes several common e*pressions for referring to variables and arguments as well as Visual Basic e*pressions. Because of this layer of speciali-ed e*pressions, the e*pressions you use to define arguments can include code, literal values, and variable references. $able = provides a small sampling of the types of e*pressions you might use when defining arguments using the workflow designer. When creating e*pressions in code there are several more options including the use of lambda e*pressions. #*pression 0hello world0 =5 8ystem.8tring.Concat'0hello0, 0 0, 0world0( 0hello 0 J 0world0 arg+nput8tring var&esult 0helloD 0 J arg+nput8tring (a,le $% 12am'le e2'ressions 3uilding 4our first workflow "ow that + have covered the core concepts around Activity and data flow, + can create a workflow using these concepts. + will start with a simple hello world workflow to focus on the concepts rather than the true value proposition of W . $o start, create a new ;nit $est pro)ect in Visual 8tudio <5=5. +n order to use the core of W , add a reference to the 8ystem.Activities assembly, and add using statements for 8ystem.Activities, 8ystem.Activities.8tatements, and 8ystem.+: in the test class file. $hen add a test method as shown in igure K to create a basic workflow and e*ecute it. L$est!ethodM #*pression $ype %iteral string value %iteral +nt4< value +mperative method invocation Visual Basic e*pression Argument reference 'argument name( Variable reference 'variable name( %iterals and arguments?variables mi*ed
public void $est/elloWorld8tatic'( A 8tringWriter writer @ new 8tringWriter'(C Console.8et:ut'writer(C 8e,uence wf @ new 8e,uence A Activities @ A new Write%ine A$e*t @ 0/ello0B, new Write%ine A$e*t @ 0World0B B BC Workflow+nvoker.+nvoke'wf(C Assert.+s$rue'8tring.Compare' 0/elloNrNnWorldNrNn0, writer.Oet8tringBuilder'(.$o8tring'(( @@ 5, 0+ncorrect string written0(C B Figure 5% !reating hello world in code $he $e*t property on the Write%ine activity is an +nArgumentHstringI and in this e*ample + passed a literal value to that property. $he ne*t step is to update this workflow to use variables and pass those variables to the activity arguments. igure P shows the new test updated to use the variables. L$est!ethodM public void $est/elloWorldVariables'( A 8tringWriter writer @ new 8tringWriter'(C Console.8et:ut'writer(C 8e,uence wf @ new 8e,uence A
Variables @ A new VariableHstringIA1efault @ 0/ello0, "ame @ 0greeting0B, new VariableHstringI A 1efault @ 0Bill0, "ame @ 0name0 B B, Activities @ A new Write%ine A $e*t @ new VisualBasicValueHstringI'0greeting0(, new Write%ine A $e*t @ new VisualBasicValueHstringI' 0name Q N0OatesN00(B B BC Workflow+nvoker.+nvoke'wf(C B Figure 6% !ode workflow with varia,les +n this case, the variables are defined as type VariableHstringI and given a default value. $he variables are declared within the 8e,uence activity and then referenced from the $e*t argument of the two activities. Alternatively, + could use e*pressions to initiali-e the variables as shown in igure R where the VisualBasicValueH$&esultI class is used passing in a string representing the e*pression. +n the first case, the e*pression refers to the name of the variable, and in the second case the variable is concatenated with a literal value. $he synta* used in te*tual e*pressions is Visual Basic, even when writing code in C#. Activities @ A new Write%ine A $e*t @ new VisualBasicValueHstringI'0greeting0(, $e*tWriter @ writer B, new Write%ine A $e*t @ new VisualBasicValueHstringI'0name Q N0OatesN00(, $e*tWriter @ writer B B Figure 7% 8sing e2'ressions to define arguments While code e*amples help illustrate important points and generally feel comfortable to developers, most people will use the designer to create workflows. +n the designer, you drag activities onto the design surface and use an updated but familiar property grid to set arguments. +n addition, the workflow designer contains e*pandable regions at the bottom to edit arguments for the workflow and variables. $o create a similar workflow in the designer, add a new pro)ect to the solution, choosing the Activity %ibrary template, and then add a new Activity. When the workflow designer is first displayed, it does not contain any activities. $he first step in defining the activity is to choose the root activity. or this e*ample, add a lowchart activity to the designer by dragging it from the lowchart category in the toolbo*. +n the lowchart designer, drag two Write%ine activities from the toolbo* and add them one below the other to the lowchart. "ow
you have to connect the activities together so the lowchart knows the path to follow. Fou do this by first hovering over the green 0start0 circle at the top of the lowchart to see the grab handles, then click and drag one to the first Write%ine and drop it on the drag handle that appears at the top of the activity. 1o the same to connect the first Write%ine to the second Write%ine. $he design surface should look like igure =5.
Figure $9% :ello Flowchart la4out :nce the structure is in place, you need to configure some variables. By clicking on the design surface and then clicking on the Variables button near the bottom of the designer, you can edit the collection of variables for the lowchart. Add two variables called 0greeting0 and 0name0 to list and set the default values to 0/ello0 and 0Bill0 respectively S be sure to include the ,uotes when setting the values as this is an e*pression so literal strings need to be ,uoted. igure == shows the variables window configured with the two variables and their default values.
Figure $$% -aria,les defined in workflow designer $o use these variables in the Write%ine activities, enter 0greeting0 'without the ,uotes( in the property grid for the $e*t argument of the first activity and 0name0 'again without the ,uotes( for the $e*t argument on the second Write%ine. At runtime, when the $e*t arguments are evaluated, the value in the variable will be resolved and used by the Write%ine activity. +n the test pro)ect, add a reference to the pro)ect containing your workflow. $hen you can add a test method like that shown in igure =< to invoke this workflow and test the output. +n igure =<, you can see the workflow is being created by instantiating an instance of the created class. +n this case the workflow was defined in the designer and compiled into a class deriving from Activity which can then be invoked. L$est!ethodM public void $est/ello lowChart'(
A 8tringWriter tWriter @ new 8tringWriter'(C Console.8et:ut'tWriter(C Workflows./ello low wf @ new Workflows./ello low'(C Workflow+nvoker.+nvoke'wf(C ??Asserts omitted B Figure $)% (esting the Flowchart 8o far + have been using variables in the workflow, and using them to supply values to arguments on the Write%ine activities. $he workflow can also have arguments defined which allows you to pass data into the workflow when invoking it and to receive output when the workflow completes. $o update the lowchart from the previous e*ample, remove the 0name0 variable 'select it and press the 1elete key( and instead create a 0name0 Argument of type string. Fou do this in much the same way, e*cept you use the Arguments button to view the arguments editor. "otice that the arguments can also have a direction and you do not need to supply a default value as the value will be passed into the activity at runtime. Because you are using the same name for the argument as you did for the variable, the Write%ine activities $e*t arguments remain valid. "ow at runtime, those arguments will evaluate and resolve the value of the 0name0 argument on the workflow and use that value. Add an additional argument of type string with a direction of :ut and a name of 0fullOreeting0C this will be returned to the calling code.
Figure $+% Defining arguments for the workflow +n the lowchart, add an Assign activity and connect it to the last Write%ine activity. or the $o argument, enter 0fullOreeting0 'no ,uotes( and for the Value argument, enter 0greeting J name0 'without ,uotes(. $his will assign the concatenation of the greeting variable with the name argument to the fullOreeting output argument. "ow in the unit test, update the code to supply the argument when invoking the workflow. Arguments are passed to the workflow as a 1ictionaryHstring,ob)ectI where the key is the name of the argument. Fou can do this simply by changing the call to invoke the workflow as shown in igure =2. "otice that the output arguments are also contained in a 1ictionaryHstring, ob)ectI collection keyed on the argument name. +1ictionaryHstring, ob)ectI results @ Workflow+nvoker.+nvoke'wf, new 1ictionaryHstring,ob)ectI A A0name0, 0Bill0 B B
(C string outValue @ resultsL0fullOreeting0M.$o8tring'(C Figure $4% Passing arguments to a workflow "ow that you have seen the basics of how to put together activities, variables and arguments, + will guide you on a tour of the activities included in the framework to enable more interesting workflows focused on business logic instead of low level concepts. (our of the workflow activit4 'alette With any programming language, you e*pect to have core constructs for defining your application logic. When using a higher level development framework like W , that e*pectation does not change. Eust as you have statements in ."#$ languages such as +f?#lse, 8witch and While, for managing control flow you also need those same capabilities when defining your logic in a declarative workflow. $hese capabilities come in the form of the base activity library that ships with the framework. +n this section, + will give you a ,uick tour of the activities that ship with the framework to give you an idea of the functionality provided out of the bo*. &ctivit4 Primitives and !ollection &ctivities When moving to a declarative programming model, it is easy to begin wondering how to do common ob)ect manipulation tasks that are second nature when writing code. or those tasks where you find yourself working with ob)ects and needing to set properties, invoke commands or manage a collection of items, there are a set of activities designed specifically with those tasks in mind. Activity Assign 1elay +nvoke!ethod Write%ine Add$oCollectionH$I 1escription Assigns a value to a location S enabling setting variables. 1elays the path of e*ecution for a specified amount of time. +nvokes a method on a ."#$ ob)ect or static method on a ."#$ type, optionally with a return type of $. Writes specified te*t to a te*t writer S defaults to Console.:ut Adds an item to a typed collection.
&emove romCollectionH$I &emoves an item from a typed collection. ClearCollectionH$I #*ists+nCollectionH$I &emoves all items from a collection. &eturns a Boolean value indicating if the specified item e*ists in the collection. !ontrol flow activities When defining business logic or business processes, having control of the flow of e*ecution is critical. Control flow activities include basics such as 8e,uence which provides a common container when you need to e*ecute steps in order, and common branching logic such as the +f and 8witch activities. $he control flow activities also include looping logic based on data ' or#ach( and Conditions'While(. !ost important for simplifying comple* programming are the parallel activities, which all enable multiple asynchronous activities to get work done at the same time. Activity 8e,uence While?1oWhile 1escription or e*ecuting activities in series #*ecutes a child activity while a condition 'e*pression( is true
or#achH$I
+terates over an enumerable collection and e*ecutes the child activity once for each item in the collection, waiting for the child to complete before starting the ne*t iteration. .rovides typed access to the individual item driving the iteration in the form of a named argument.
+f 8witchH$I .arallel
#*ecutes one of two child activities depending on the result of the condition 'e*pression(. #valuates an e*pression and schedules the child activity with a matching key. 8chedules all child activities at once, but also provides a completion condition to enable the activity to cancel any outstanding child activities if certain conditions are met.
.arallel or#achH$I +terates over an enumerable collection and e*ecutes the child activity once for each item in the collection, scheduling all instances at the same time. %ike the or#achH$I, this activity provides access to the current data item in the form of a named argument. .ick 8chedules all child .ickBranch activities and cancels all but the first to have its trigger complete. $he .ickBranch activity has both a $rigger and an ActionC each is an Activity. When a trigger activity completes, the .ick cancels all its other child activities. $he two e*amples below show several of these activities in use to illustrate how to compose these activities together. $he first e*ample, igure =6, includes the use of the .arallel or#achH$I to use a list of ;&%s and asynchronously get the &88 feed at the specified address. After the feed is returned, the or#achH$I is used to iterate over the feed items and process them. "ote that the code declares and defines a VisualBasic8ettings instance with references to the 8ystem.8ervice!odel.8yndication types. $his settings ob)ect is then used when declaring VisualBasicValueH$I instances referencing variables types from that namespace to enable type resolution for those e*pressions. Also of note is that the body of the .arallel or#ach activity takes an ActivityAction which are mentioned in the section on creating custom activities. $hese actions use 1elegate+nArgument and 1elegate:utArgument in much the same way that activities use +nArgument and :utArgument.
Figure $/% !ontrol flow activities $he second e*ample, igure =G, is a common pattern of waiting for a response with a timeout. or e*ample, waiting for a manager to approve a re,uest, and sending a reminder if the response has not arrived in the allotted time. A 1oWhile activity causes the repetition of waiting for a response while the .ick activity is used to e*ecute both a !anager&esponse activity and a 1elay activity at the same time as triggers. When the 1elay completes first, the reminder is sent, and when the !anager&esponse activity completes first, the flag is set to break out of the 1oWhile loop.
Figure $0% Pick and DoWhile activities $he e*ample in igure =G also shows how bookmarks can be used in workflows. A bookmark is created by an activity to mark a place in the workflow, so that processing can resume from that point at a later time. $he 1elay activity has a bookmark which is resumed after a particular amount of time has passed. $he !anager&esponse activity is a custom activity which waits for input and resumes the workflow once the data arrives. $he messaging activities, discussed shortly, are the main activities for bookmarking e*ecution. When a workflow is not actively processing work, when it is only waiting for bookmarks to be resumed, it is considered idle and can be persisted to a durable store. Bookmarks will be discussed in more detail in the section about creating custom activities. igration or developers who are using W 4, the +nterop activity can play a vital role in reusing e*isting assets. $he activity, found in the 8ystem.Workflow.&untime assembly, wraps your e*isting activity type and surfaces the properties on the activity as arguments in the W 2 model. Because the properties are arguments, you can use e*pressions to define the values. igure =K shows the
configuration of the +nterop activity to call a W 4 activity. $he input arguments are defined with references to in7scope variables within the workflow definition. Activities built in W 4 had properties instead of arguments, so each property is given a corresponding input and output argument allowing you to differentiate the data you send into the activity and the data you e*pect to retrieve after the activity e*ecutes. +n a new W 2 pro)ect you will not find this activity in the toolbo* because the target framework is set to ."#$ ramework 2 Client .rofile. Change the target framework in the pro)ect properties to ."#$ ramework 2, and the activity will appear in the toolbo*.
Figure $5% Intero' activit4 configuration Flowchart When designing lowchart workflows there are several constructs that can be used to manage the flow of e*ecution within the lowchart. $hese constructs themselves provide simple steps, simple decision points based on a single condition, or a switch statement. $he real power of the lowchart is the ability to connect these node types into the desired flow. Construct?Activity 1escription lowchart $he container for a series of flow steps, each flow step can be any activity or one of the following constructs, but to be e*ecuted, it must be connected within the lowchart. low1ecision low8witchH$I low8tep .rovides branching logic based on a condition. #nables multiple branches based on the value of an e*pression. &epresents a step in the lowchart with the ability to be connected to other steps. $his type is not shown in the toolbo* as it is implicitly added by the designer. $his activity wraps other activities in the workflow and provides the navigation semantics for the ne*t step's( in the workflow.
+t is important to note that while there are specific activities for the lowchart model, any other activities can be used within the workflow. %ikewise, a lowchart activity can be added to another activity to provide the e*ecution and design semantics of a lowchart, within that workflow. $his means you can have a se,uence with a several activities and a lowchart right in the middle. essaging &ctivities :ne of the ma)or focuses in W 2 is tighter integration between W and WC . +n terms of workflows, that means activities to model messaging operations such as sending and receiving messages. $here are actually several different activities included in the framework for messaging, each with slightly different functionality and purpose. Activity 8end?&eceive &eceiveAnd8end&eply 8endAnd&eceive&eply +nitiali-eCorrelation Correlation8cope 1escription :ne way messaging activities to send or receive a message. $hese same activities are composed into re,uest?response style interactions. !odels a service operation that receives a message and sends back a reply. +nvokes a service operation and receives the response. Allow for initiali-ing correlation values e*plicitly as part of the workflow logic, rather than e*tracting the values from a message. 1efines a scope of e*ecution where a correlation handle is accessible to receive and send activities simplifying the configuration of a handle that is shared by several messaging activities. $ransacted&eceive8cope #nables workflow logic to be included in the same transaction flowed into a WC operation using the &eceive activity. $o invoke a service operation from within a workflow, you will follow the familiar steps of adding a service reference to your workflow pro)ect. $he pro)ect system in Visual 8tudio will then consume the metadata from the service and create a custom activity for each service operation found in the contract. Fou can think of this as the workflow e,uivalent of a WC pro*y that would be created in a C# or Visual Basic pro)ect. or e*ample, taking an e*isting service that searches for hotel reservations with the service contract shown in igure =PC adding a service reference to it yields a custom activity shown in igure =R. L8erviceContractM public interface +/otel8ervice A L:perationContractM %istH/otel8earch&esultI 8earch/otels' /otel8earch&e,uest re,uest1etails(C B Figure $6% #ervice contract
Figure $7% !ustom W!F activit4 !ore information on building workflows e*posed as WC 8ervices section later in this paper. (ransactions and 1rror :andling Writing reliable systems can be difficult, and re,uires that you do a good )ob of handling errors and managing to keep consistent state in your application. or a workflow, scopes for managing e*ception handling and transactions are modeled using activities with properties of type ActivityAction or Activity to enable developers to model error processing logic. Beyond these common patterns of consistency, W 2 also includes activities which allow you to model long running distributed coordination through compensation and confirmation. Activity Cancellation8cope $ransaction8cope $ryCatch?CatchH$I $hrow ðrow 1escription ;sed to allow the workflow developer to react if a body of work gets cancelled. #nables semantics similar to using a transaction scope in code by e*ecuting all child activities in the scope under a transaction. ;sed to model e*ception handling and catch typed e*ceptions. Can be used to throw an e*ception from the activity. ;sed to rethrow an e*ception, generally one that has been caught using the $ryCatch activity. CompensableActivity 1efines the logic for e*ecuting child activities that may need to have their actions compensated for after success. .rovides a placeholder for the compensation logic, confirmation logic, and cancellation handling. Compensate Confirm +nvokes the compensation handling logic for a compensable activity. +nvokes the confirmation logic for a compensable activity. services can be found in the Workflow
$he $ryCatch activity provides a familiar way to scope a set of work to catch any e*ceptions that may occur, and the CatchH$I activity provides the container for e*ception handling logic. Fou can see an e*ample of how this activity is used in igure <5, with each typed e*ception block being defined by a CatchH$I activity, providing access to the e*ception via the named argument seen on the left of each catch. +f the need arises, the ðrow activity can be used to rethrow the caught e*ception, or the $hrow activity to throw a new e*ception of your own.
Figure )9% (r4!atch activit4 $ransactions help ensure consistency in short lived work and the $ransaction8cope activity provides the same sort of scoping you can get in ."#$ code using the $ransaction8cope class. inally, when you need to maintain consistency, but cannot use an atomic transaction across the resources, you can use compensation. Compensation enables you to define a set of work that, if it completes, can have a set of activities to compensate for the changes made. As a simple e*ample, consider the Compensable Activity in igure <= where an email is sent. +f after the activity has completed, an e*ception occurs, the compensation logic can be invoked to get the system back into a consistent stateC in this case a follow up email to retract the earlier message.
Figure )$% !om'ensa,le &ctivit4 !reating and e2ecuting workflows As with any programming language, there are two fundamental things you do with workflowsD define them, and e*ecute them. +n both cases, W provides you with several options to give you fle*ibility and control. O'tions for designing workflows When designing or defining workflows, there are two main optionsD code or >A!%. >A!% provides the truly declarative e*perience and allows for the entire definition of your workflow to be defined in >!% markup, referencing Activities and types built using ."#$. !ost developers will likely use the workflow designer to build workflows which will result in a declarative >A!% workflow definition. Because >A!% is )ust >!%, however, any tool can be used to create it, which is one of the reasons it is such a powerful model for building applications. or e*ample, the >A!% shown in igure << was created in a simple te*t editor, and can either be compiled, or used directly to e*ecute an instance of the workflow being defined.
HpDActivity *DClass@0Workflows./ello8e,0 *mlns@0httpD??schemas.microsoft.com?netf*?<55R?*aml?activities?design0 *mlnsDp@0httpD??schemas.microsoft.com?netf*?<55R?*aml?activities0 *mlnsD*@0httpD??schemas.microsoft.com?winf*?<55G?*aml0I H *D!embersI H *D.roperty "ame@0greeting0 $ype@0pD+nArgument'*D8tring(0 ?I H *D.roperty "ame@0name0 $ype@0pD+nArgument'*D8tring(0 ?I H ?*D!embersI H pD8e,uenceI H pDWrite%ineILgreeting JampC 0 from workflow0MH?pDWrite%ineI H pDWrite%ineILnameMH?pDWrite%ineI H ?pD8e,uenceI H?pDActivityI Figure ))% Workflow defined in ;& < $his same >A!% is generated by the designer, and can be generated by custom tools, making it much easier to manage. +n addition, it is also possible, as was shown previously, to build workflows using code. $his involves the creation of the activity hierarchy through the use of the various activities in the framework and your custom activities. Fou have seen several e*amples already of workflows defined entirely in code. ;nlike in W 4, it is now possible to create a workflow in code and easily seriali-e it to >A!%C providing more fle*ibility in modeling and managing workflow definitions. As + will show, to e*ecute a workflow, all you need is an Activity and that can be an instance built in code, or one created from >A!%. $he end result of each of the modeling techni,ues is either a class deriving from Activity, or an >!% representation that can be deseriali-ed or compiled into an Activity. O'tions for e2ecuting workflows +n order to e*ecute a workflow, you need an Activity that defines the workflow. $here are two typical ways to get an Activity that can be e*ecutedD create it in code or read in a >A!% file and deseriali-e the content into an Activity. $he first option is straightforward and + have already shown several e*amples. $o load a >A!% file you should use the Activity>aml8ervices class which provides a static %oad method. 8imply pass in a 8tream or >aml&eader ob)ect and you get back the Activity represented in the >A!%. :nce you have an Activity, the simplest way to e*ecute it is by using the Workflow+nvoker class as + did in the unit tests earlier. $he +nvoke method of this class has a parameter of type Activity and returns an +1ictionaryHstring, ob)ectI. +f you need to pass arguments into the workflow, you first define them on the workflow and then pass the values along with the Activity, into the +nvoke method as dictionary of name?value pairs. %ikewise, any :ut or +n?:ut arguments defined on the workflow will be returned as the result of e*ecuting the method. igure <4 provides an e*ample of loading a workflow from a >A!% file, passing arguments into the workflow and retrieving the resulting output arguments. Activity mathW C using '8tream math>aml @ ile.:pen&ead'0!ath.*aml0((
A mathW @ Activity>aml8ervices.%oad'math>aml(C B var outputs @ Workflow+nvoker.+nvoke'mathW , new 1ictionaryHstring, ob)ectI A A 0operand=0, 6 B, A 0operand<0, =5 B, A 0operation0, 0add0 B B(C Assert.Are#,ualHintI'=6, 'int(outputsL0result0M, 0+ncorrect result returned0(C Figure )+% Invoking =<oose ;& <= workflow with in and out arguments "otice in this e*ample that the Activity is loaded from a >A!% file and it can still accept and return arguments. $he workflow was developed using the designer in Visual 8tudio for ease, but could be developed in a custom designer and stored anywhere. $he >A!% could be loaded from a database, 8hare.oint library, or some other store before being handed to the runtime for e*ecution. ;sing the Workflow+nvoker provides the simplest mechanism for running short7lived workflows. +t essentially makes the workflow act like a method call in your application, enabling you to more easily take advantage of all the benefits of W without having to do a lot of work to host W itself. $his is especially useful when unit testing your activities and workflows as it reduces the test setup necessary to e*ercise a component under test. Another common hosting class is the WorkflowApplication which provides a safe handle to a workflow that is e*ecuting in the runtime, and enables you to manage long running workflows more easily. With the WorkflowApplication, you can still pass arguments into the workflow in the same way as with the Workflow+nvoker, but you use the &un method to actually start the workflow running. At this point, the workflow begins e*ecuting on another thread and control returns to the calling code. Because the workflow is now running asynchronously, in your hosting code you will likely want to know when the workflow completes, or if it throws an e*ception, etc. or these types of notifications, the WorkflowApplication class has a set of properties of type ActionH$I that can be used like events to add code to react to certain conditions of the workflow e*ecution includingD aborted, unhandled e*ception, completed, idled, and unloaded. When you e*ecute a workflow using WorkflowApplication, you can use code similar to that shown in igure <2 using actions to handle the events. WorkflowApplication wf @ new WorkflowApplication'new lowchart='((C wf.Completed @ delegate'WorkflowApplicationCompleted#ventArgs e( A Console.Write%ine'0Workflow A5B complete0, e.+nstance+d(C BC wf.Aborted @ delegate'WorkflowApplicationAborted#ventArgs e(
A Console.Write%ine'e.&eason(C BC wf.:n;nhandled#*ception @ delegate'WorkflowApplication;nhandled#*ception#ventArgs e( A Console.Write%ine'e.;nhandled#*ception.$o8tring'((C return ;nhandled#*ceptionAction.$erminateC BC wf.&un'(C Figure )4% Workflow&''lication actions +n this e*ample, the goal of the host code, a simple console application, is to notify the user via the console when the workflow completes or if an error occurs. +n a real system, the host will be interested in these events and will likely react to them differently to provide administrators with information about failures or to better manage the instances. Workflow 12tensions :ne of the core features of W , since W 4, has been that it is lightweight enough to be hosted in any ."#$ application domain. Because the runtime can e*ecute in different domains and may need customi-ed e*ecution semantics, various aspects of the runtime behaviors need to be e*ternali-ed from the runtime. $hat is where workflow e*tensions come into play. Workflow e*tensions enable you as the developer writing the host code, if you so choose, to add behavior to the runtime by e*tending it with custom code. $wo e*tension types that the W runtime is aware of are the persistence and tracking e*tensions. $he persistence e*tension provides the core functionality for saving the state of a workflow to a durable store and retrieving that state when it is needed. $he persistence e*tension shipping with the framework includes support for !icrosoft 8T% 8erver, but e*tensions can be written to support other database systems or durable stores. Persistence +n order to use the persistence e*tension, you must first setup a database to hold the state, which can be done using the 8T% scripts found in Uwindir UN!icrosoft."#$N rameworkNv2.5.454=RNs,lNHlanguageI 'e.g. cDNwindowsNmicrosoft.netNframeworkNv2.5.454=RNs,lNenN(. After creating a database, you e*ecute the two scripts to create both the structure '8,lWorkflow+nstance8tore8chema.s,l( and the stored procedures '8,lWorkflow+nstance8tore%ogic.s,l( within the database. :nce the database is setup, you use the 8,lWorkflow+nstance8tore along with the WorkflowApplication class. Fou first create the store and configure it, then supply it to the WorkflowApplication as shown in igure <6. WorkflowApplication application @ new WorkflowApplication'activity(C +nstance8tore instance8tore @ new 8,lWorkflow+nstance8tore'
V01ata [email protected]%#>.XC+ntegrated 8ecurity@$rue0(C +nstanceView view @ instance8tore.#*ecute' instance8tore.Create+nstance/andle'(, new CreateWorkflow:wnerCommand'(, $ime8pan. rom8econds'45((C instance8tore.1efault+nstance:wner @ view.+nstance:wnerC application.+nstance8tore @ instance8toreC Figure )/% &dding the #>< Persistence Provider $here are two ways the workflow can get persisted. $he first is through direct use of the .ersist activity in a workflow. When this activity e*ecutes it causes the workflow state to be persisted to the database, saving the current state of the workflow. $his affords the workflow author control over when it is important to save the current state of the workflow. $he second option enables the hosting application to persist the workflow state when various events occur on the workflow instanceC most likely when the workflow is idle. By registering for the .ersistable+dle action on the WorkflowApplication, your host code can then respond to the event to indicate if the instance should be persisted, unloaded, or neither. igure <G shows a host application registering to get notified when the workflow is idle and causing it to persist. wf..ersistable+dle @ 'waie( @I .ersistable+dleAction..ersistC Figure )0% 8nloading the workflow when it idles :nce a workflow has idled and been persisted, the W runtime can unload it from memory, freeing up resources for other workflows that need processing. Fou can choose to have your workflow instance unload by returning the appropriate enumeration from the .ersistable+dle action. :nce a workflow is unloaded, at some point the host will want to load it back out of the persistence store to resume it. +n order to do that, the workflow instance must be loaded using an instance store and the instance identifier. $his will in turn result in the instance store being asked to load the state. :nce the state is loaded, the &un method on the WorkflowApplication can be used, or a bookmark can be resumed as shown in igure <K. or more on bookmarks, see the custom activities section. WorkflowApplication application @ new WorkflowApplication'activity(C application.+nstance8tore @ instance8toreC application.%oad'id(C application.&esumeBookmark'read%ineBookmark, input(C Figure )5% ?esuming a 'ersisted workflow :ne of the changes in W 2 is how the state of workflows is persisted and relies heavily on the new data management techni,ues of arguments and variables. &ather than seriali-ing the entire activity tree and maintaining state for the lifetime of the workflow, only in scope variables and argument values, along with some runtime data such as bookmark information, are persisted. "otice in igure <K that when the persisted instance is reloaded, in addition to using the instance store, the Activity that defines the workflow is also passed. #ssentially, the state from the database is applied to the Activity supplied. $his techni,ue enables much greater fle*ibility for versioning and results in better performance as the state has a smaller memory footprint. (racking
.ersistence enables the host to support long running processes, load balance instances across hosts and other fault tolerant behaviors. /owever, once the workflow instance has completed, the state in the database is often deleted as it is no longer useful. +n a production environment, having information about what a workflow is currently doing and what it has done is critical to both managing workflows and gaining insight into the business process. Being able to track what is happening in your application is one of the compelling features of using the W runtime. $racking consists of two primary componentsD tracking participants and tracking profiles. A tracking profile defines what events and data you want the runtime to track. .rofiles can include three primary types of ,ueriesD
Activity8tateTuery S used to identify activity states 'e.g. closed( and variables or arguments to e*tract data Workflow+nstanceTuery S used to identify workflow events Custom$rackingTuery S used to identify e*plicit calls to track data, usually within custom activities
As an e*ample, igure <P shows a $racking.rofile being created which includes an Activity8tateTuery and a Workflow+nstanceTuery. "otice that the ,ueries indicate both when to collect information, and also what data to collect. or the Activity8tateTuery, + included a list of variables that should have their value e*tracted and added to the tracked data. $racking.rofile profile @ new $racking.rofile A "ame @ 08imple.rofile0, Tueries @ A new Workflow+nstanceTuery A 8tates @ A 090 B B, new Activity8tateTuery A Activity"ame @ 0Write%ine0, 8tates@A 090 B, Variables @ A0$e*t0 B B B BC Figure )6% !reating a tracking 'rofile A tracking participant is an e*tension that can be added into the runtime and is responsible for processing tracking records as they are emitted. $he $racking.articipant base class defines a property to provide a $racking.rofile and a $rack method that handles the tracking. igure <R shows a limited custom tracking participant that writes data to the console. +n order to use the
tracking participant, it must be initiali-ed with a tracking profile, and then added into the e*tensions collection on the workflow instance. public class Console$racking.articipant D $racking.articipant A protected override void $rack'$racking&ecord record, $ime8pan timeout( A Activity8tate&ecord a&ecord @ record as Activity8tate&ecordC if 'a&ecord W@ null( A Console.Write%ine'0A5B entered state A=B0, a&ecord.Activity."ame, a&ecord.8tate(C foreach 'var item in a&ecord.Arguments( A Console. oregroundColor @ ConsoleColor.CyanC Console.Write%ine'0VariableDA5B has valueD A=B0, item.Xey, item.Value(C Console.&esetColor'(C B B B B Figure )7% !onsole tracking 'artici'ant W ships with an #tw$racking.articipant '#$W @ #nterprise $race for Windows( that you can add into the runtime to enable high performance tracking of data. #$W is a tracing system that is a native component in Windows and is used by many components and services in the :8, including drivers and other kernel level code. $he data written to #$W can be consumed using custom code or using tools such as the upcoming Windows 8erver App abric. or users migrating from W 4, this will be a change as there will not be 8T% tracking participant shipping as part of the framework. /owever, Windows 8erver App abric will ship with #$W consumers that will collect the #$W data and store it to a 8T% database. %ikewise, you can build an #$W consumer and store the data in whatever format you prefer, or write your own tracking participant, whichever makes more sense for your environment. !reating custom activities
While the base activity library includes a rich palette of activities for interacting with services, ob)ects, and collections, it does not provide activities for interacting with subsystems such as databases, email servers, or your custom domain ob)ects and systems. .art of getting started with W 2 will be to figure out what core activities you might need or want when building workflows. $he great thing is that as you build core units of work, you can combine them into more coarse grained activities that developers can use in their workflows. &ctivit4 class hierarch4 While an activity is an activity is an activity, it is not true that all activities have the same re,uirements or needs. or that reason, rather than all activities deriving directly from Activity, there is a hierarchy of activity base classes, shown in igure 45, that you can choose from when building a custom activity.
Figure +9% &ctivit4 class hierarch4 or most custom activities, you will either derive from AsyncCodeActivity, CodeActivity, or "ativeActivity 'or one of the generic variants(, or model your activity declaratively. At a high level, the four base classes can be described as followsD
Activity S used to model activities by composing other activities, usually defined using >A!%. CodeActivity S a simplified base class when you need to write some code to get work done. AsyncCodeActivity S used when your activity perform some work asynchronously. "ativeActivity S when your activity needs access to the runtime internals, for e*ample to schedule other activities or create bookmarks.
+n the following sections, + will build several activities to see how to use each of these base classes. +n general, as you think about which base class to use, you should start with the Activity base class and see if you can build your activity using declarative logic as shown in the ne*t section. "e*t, the CodeActivity provides a simplified model if you determine that you have to write some ."#$ code to accomplish your task and the AsyncCodeActivity if you want your activity to e*ecute asynchronously. inally, if you are writing control flow activities like those found in the framework 'e.g. While, 8witch, +f(, then you will need to derive from the "ativeActivity base class in order to manage the child activities.
!om'osing activities using &ctivit4 designer When you create a new activity library pro)ect, or when you add a new item to a W pro)ect and select the Activity template, what you get is a >A!% file with an empty Activity element in it. +n the designer, this presents itself as a design surface where you can create the body of the activity. $o get started with an activity that will have more than one step, it usually helps to drag a 8e,uence activity in as the Body and then populate that with your actual activity logic as the body represents a single child activity. Fou can think of the activity much like you would a method on a component with arguments. :n the activity itself, you can define arguments, along with their directionality, to define the interface of the activity. Variables that you want to use within the activity will need to be defined in the activities that comprise the body, such as the root se,uence + mentioned previously. As an e*ample, you can build a "otify!anager activity that composes two simpler activitiesD Oet!anager and 8end!ail. irst, create a new Activity%ibrary pro)ect in Visual 8tudio <5=5, and rename the Activity=.*aml file to "otify!anager.*aml. "e*t drag a 8e,uence activity from the toolbo* and add it to the designer. :nce the 8e,uence is in place, you can populate it with child activities as shown in igure 4=. '"ote that the activities used in this e*ample are custom activities in a referenced pro)ect and not available in the framework.(
Figure +$% Notif4 manager activit4 $his activity needs to take arguments for the employee name and the message body. $he Oet!anager activity looks up the employeeYs manager and provide the email as an :utArgumentHstringI. inally, the 8end!ail activity sends a message to the manager. $he ne*t step is to define the arguments for the activity by e*panding the Arguments window at the bottom of the designer and entering the information for the two re,uired input arguments.
+n order to compose these items, you need to be able to pass the input arguments specified on the "otify!anager activity to the individual child activities. or the Oet!anager activity, you need the employee name which is an in argument on the activity. Fou can use the argument name in the property dialog for the employee"ame argument on the Oet!anager activity as seen in igure 4=. $he 8end!ail activity, needs the managerYs email address, and the message. or the message, you can enter the name of the argument containing the message. /owever, for the email address, you need a way to pass the out argument from the Oet!anager activity to the in argument for the 8end!ail activity. or this you need a variable. After highlighting the 8e,uence activity, you can use the Variables window to define a variable named Zmgr#mail[. "ow you can enter that variable name both for the !anager#mail out argument on the Oet!anager activity, and the $o in argument on the 8end!ail activity. When the Oet!anager activity e*ecutes, the output data will be stored in that variable, and when the 8end!ail activity e*ecutes, it will read data from that variable as the in argument. $he approach )ust described is a purely declarative model for defining the activity body. +n some circumstances, you might prefer to specify the body of the activity in code. or e*ample, your activity may include a collection of properties that in turn drive a set of child activitiesC a set of named values that drive the creation of a set of Assign activities would be one case where using code would be preferred. +n those cases, you can write a class that derives from activity, and write code in the +mplementation property to create an Activity 'and any child elements( to represent the functionality of your activity. $he end result is the same in both casesD your logic is defined by composing other activities, only the mechanism by which the body is defined is different. igure 4< shows the same "otify!anager activity being defined in code. public class "otify!anager D Activity A public +nArgumentHstringI #mployee"ame A getC setC B public +nArgumentHstringI !essage A getC setC B protected override uncHActivityI +mplementation A get A return '( @I A VariableHstringI mgr#mail @ new VariableHstringI A "ame @ 0mgr#mail0 BC 8e,uence s @ new 8e,uence A Variables @ A mgr#mail B, Activities @ A new Oet!anager A
#mployee"ame @ new VisualBasicValueHstringI'0#mployee"ame0(, &esult @ mgr#mail, B, new 8end!ail A $oAddress @ mgr#mail, !ailBody @ new VisualBasicValueHstringI'0!essage0(, rom @ 0testVcontoso.com0, 8ub)ect @ 0Automated email0 B B BC return sC BC B set A base.+mplementation @ valueC B B B Figure +)% &ctivit4 com'osition using code "otice that the base class is Activity and the +mplementation property is a uncHActivityI to return a single Activity. $his code is very similar to that shown earlier when creating workflows, and that should not be surprising as the goal in both cases is to create a Activity. Additionally, in the code approach arguments can be set with variables, or you can use e*pressions to connect one argument to another as is shown for the #mployee"ame and !essage arguments as they are used on the two child activities. Writing custom &ctivit4 classes +f you determine that your activity logic cannot be accomplished by composing other activities, then you can write a code based activity. When writing activities in code you derive from the appropriate class, define arguments, and then override the #*ecute method. $he #*ecute method gets called by the runtime when it is time for the activity to do its work. $o build the 8end!ail activity used in the previous e*ample + first need to choose the base type. While it is possible to create the functionality of the 8end!ail activity using the Activity base class and composing $ryCatchH$I and +nvoke!ethod activities, for most developers it will be more natural to choose between the CodeActivity and "ativeActivity base classes and write code for the e*ecution logic. Because this activity does not need to create bookmarks or schedule other
activities, + can derive from the CodeActivity base class. +n addition, because this activity will return a single output argument, it should derive from CodeActivityH$&esultI which provides an :utArgumentH$&esultI. $he first step is to define several input arguments for the email properties. $hen you override the #*ecute method to implement the functionality of the activity. igure 44 shows the completed activity class. public class 8end!ail D CodeActivityH8mtp8tatusCodeI A public +nArgumentHstringI $o A getC setC B public +nArgumentHstringI rom A getC setC B public +nArgumentHstringI 8ub)ect A getC setC B public +nArgumentHstringI Body A getC setC B protected override 8mtp8tatusCode #*ecute' CodeActivityConte*t conte*t( A try A 8mtpClient client @ new 8mtpClient'(C client.8end' rom.Oet'conte*t(, $oAddress.Oet'conte*t(, 8ub)ect.Oet'conte*t(, !ailBody.Oet'conte*t((C B catch '8mtp#*ception smtp#*( A return smtp#*.8tatusCodeC B return 8mtp8tatusCode.:kC B B Figure ++% #end ail custom activit4 $here are several things to note about the use of arguments. +\ve declared several standard ."#$ properties using the types +nArgumentH$I. $his is how a code7based activity defines its input and
output arguments. /owever, within the code, + cannot simply reference these properties to get the value of the argument, + need to use the argument as a sort of handle to retrieve the value using the supplied ActivityConte*tC in this case a CodeActivityConte*t. Fou use the Oet method of the argument class to retrieve the value, and the 8et method to assign a value to an argument. :nce the activity completes the #*ecute method, the runtime detects this and assumes the activity is done and closes it. or asynchronous or long running work, there are ways to change this behavior which are e*plained in an upcoming section, but in this case, once the email is sent, the work is complete. "ow let us e*amine an activity using the "ativeActivity base class to manage child activities. + will build an +terator activity that e*ecutes some child activity a given number of times. irst, + need an argument to hold the number of iterations to perform, a property for the Activity to be e*ecuted, and a variable to hold the current number of iterations. $he #*ecute method calls a Begin+teration method to start the initial iteration and subse,uent iterations are invoked the same way. "otice in igure 42 that variables are manipulated the same way as arguments using the Oet and 8et method. public class +terator D "ativeActivity A public Activity Body A getC setC B public +nArgumentHintI &e,uested+terations A getC setC B public VariableHintI Current+teration A getC setC B public +terator'( A Current+teration @ new VariableHintI A 1efault @ 5 BC B protected override void #*ecute'"ativeActivityConte*t conte*t( A Begin+teration'conte*t(C B private void Begin+teration'"ativeActivityConte*t conte*t( A if '&e,uested+terations.Oet'conte*t( I Current+teration.Oet'conte*t(( A conte*t.8cheduleActivity'Body, new CompletionCallback'ChildComplete(, new aultCallback'Child aulted((C
B B B Figure +4% Iterator native activit4 +f you look closely at the Begin+teration method, you will notice the 8cheduleActivity method call. $his is how an activity can interact with the runtime to schedule another activity for e*ecution, and it is because you need this functionality that you derive from "ativeActivity. Also note, when calling the 8cheduleActivity method on the Activity#*ecutionConte*t, two callback methods are provided. Because you do not know which activity will be used as the body, or how long it will take to complete, and because W is a heavily asynchronous programming environment, callbacks are used to notify your activity when a child activity has completed, allowing you to write code to react. $he second callback is a aultCallback which will get invoked if the child activity happens to throw an e*ception. +n this callback, you receive the e*ception and have the ability, via the Activity aultConte*t, to indicate to the runtime that the error has been handled , which keeps it from bubbling up and out of the activity. igure 46 shows the callback methods for the +terator activity where both schedule the ne*t iteration and the aultCallback handles the error to allow e*ecution to continue to the ne*t iteration. private void ChildComplete'"ativeActivityConte*t conte*t, Activity+nstance instance( A Current+teration.8et'conte*t, Current+teration.Oet'conte*t( Q =(C Begin+teration'conte*t(C B private void Child aulted'"ativeActivity aultConte*t conte*t, #*ception e*, Activity+nstance instance( A Current+teration.8et'conte*t, Current+teration.Oet'conte*t( Q =(C conte*t./andle ault'(C B Figure +/% &ctivit4 call,acks When your activity needs to do work that may be long running, ideally that work could be handed off to another thread to allow the workflow thread to be used to continue processing other activities, or used to process other workflows. /owever, simply starting up threads and returning control to the runtime can cause problems as the runtime does not monitor the threads you create. +f the runtime determined that the workflow was idle, the workflow might unload, disposing of your callback methods, or the runtime could assume your activity is done and move to the ne*t activity in the workflow. $o support asynchronous programming in your activity you derive from the AsyncCodeActivity or AsyncCodeActivityH$I.
igure 4G shows an e*ample of an asynchronous activity that loads an &88 feed. $his signature of the e*ecute method is different for the asynchronous activities in that it returns an +Async&esult. Fou write the code in the e*ecute method to begin your asynchronous operation. :verride the #nd#*ecute method to handle the callback when your asynchronous operation is complete and return the result of the e*ecution. public sealed class Oet eed D AsyncCodeActivityH8yndication eedI A public +nArgumentH;riI eed;rl A getC setC B protected override +Async&esult Begin#*ecute' AsyncCodeActivityConte*t conte*t, AsyncCallback callback, ob)ect state( A var re, @ '/ttpWeb&e,uest(/ttpWeb&e,uest.Create' eed;rl.Oet'conte*t((C re,.!ethod @ 0O#$0C conte*t.;ser8tate @ re,C return re,.BeginOet&esponse'new AsyncCallback'callback(, state(C B protected override 8yndication eed #nd#*ecute' AsyncCodeActivityConte*t conte*t, +Async&esult result( A /ttpWeb&e,uest re, @ conte*t.;ser8tate as /ttpWeb&e,uestC Web&esponse wr @ re,.#ndOet&esponse'result(C 8yndication eed local eed @ 8yndication eed.%oad' >ml&eader.Create'wr.Oet&esponse8tream'(((C return local eedC B B Figure +0% !reating as4nchronous activities &dditional activit4 conce'ts
"ow that you have seen the basics of creating activities using the base classes, arguments and variables, and managing e*ecutionC + will touch briefly on some more advanced features you can use in activity development. Bookmarks enable an activity author to create a resumption point in the e*ecution of a workflow. :nce a bookmark has been created, it can be resumed to continue the workflow processing from where it left off. Bookmarks are saved as part of the state, so unlike the asynchronous activities, after a bookmark has been created, it is not an issue for the workflow instance to be persisted and unloaded. When the host wants to resume the workflow, it loads the workflow instance and calls the &esumeBookmark method to resume the instance from where it left off. When resuming bookmarks, data can be passed into the activity as well. igure 4K shows a &ead%ine activity that creates a bookmark to receive input and registers a callback method to be invoked when the data arrives. $he runtime knows when an activity has outstanding bookmarks and will not close the activity until the bookmark has been resumed. $he &esumeBookmark method can be used on the WorkflowApplication class to send data to the named bookmark and signal the BookmarkCallback. public class &ead%ine D "ativeActivityHstringI A public :utArgumentHstringI +nput$e*t A getC setC B protected override void #*ecute'"ativeActivityConte*t conte*t( A conte*t.CreateBookmark'0&ead%ine0, new BookmarkCallback'Bookmark&esumed((C B private void Bookmark&esumed'"ativeActivityConte*t conte*t, Bookmark bk, ob)ect state( A &esult.8et'conte*t, state(C B B Figure +5% !reating a ,ookmark Another powerful feature for activity authors is the concept of an ActivityAction. ActivityAction is the workflow e,uivalent of the Action class in imperative codeD describing a common delegateC but for some, the idea of a template may be easier to understand. Consider the or#achH$I activity which allows you to iterate over a set of data and schedule a child activity for each data item. Fou need some way to allow the consumer of your activity to define the body, and be able to consume the data item of type $. #ssentially, you need some activity that can accept an item of type $ and act on it, ActivityActionH$I is used to enable this scenario, and provides a reference to the argument and the definition of a Activity to process the item. Fou can use ActivityAction in your custom activities to enable consumers of your activity to add their own steps at appropriate points. $his allows you to create workflow or activity templates of a sort, where a consumer can fill in the relevant parts to customi-e the e*ecution for their use. Fou can also use the Activity uncH$&esultI and the related alternatives when the delegate your activity needs to invoke will return a value.
inally, activities can be validated to ensure they are configured properly by checking individual settings, constraining allowable child activities, etc. or the common need to re,uire a particular argument, you can add a &e,uiredArgument attribute to the property declaration in the activity. or more involved validation, in the constructor of your activity, create a constraint and add it to the collection of constraints surfaced on the Activity class. A constraint is an activity written to inspect the target activity and ensure validity. igure 4P shows the constructor for the +terator activity, which validates that the &e,uested+terations property is set. inally, overriding the Cache!etadata method, you can invoke the AddValidation#rror method on the Activity!etdata parameter to add validation errors for your activity. var act @ new 1elegate+nArgumentH+teratorI A "ame @ 0constraintArg0 BC var vct* @ new 1elegate+nArgumentHValidationConte*tI'(C ConstraintH+teratorI cons @ new ConstraintH+teratorI A Body @ new ActivityActionH+terator, ValidationConte*tI A Argument= @ act, Argument< @ vct*, /andler @ new AssertValidation A !essage @ 0+teration must be provided0, .roperty"ame @ 0&e,uested+terations0, Assertion @ new +nArgumentHboolI' 'e( @I act.Oet'e(.&e,uested+terations W@ null( B B BC base.Constraints.Add'cons(C Figure +6% !onstraints &ctivit4 designers :ne of the benefits of W is that it allows you to program your application logic declaratively, but most people do not want to write >A!% by hand, which is why the designer e*perience in W is so important. When building custom activities, you will also likely want to create a designer to provide the display and visual interaction e*perience for consumers of your activity. $he designer for W is based on Windows .resentation oundation which means you have all the power of styling, triggers, databinding and all of the other great tools for building a rich ;+ for your designer. +n
addition, W provides some user controls that you can use in your designer to simplify the task of displaying an individual child activity, or a collection of activities. $he four primary controls areD
Activity1esigner S root W. control used in activity designers Workflow+tem.resenter S used to display a single Activity Workflow+tems.resenter S used to display a collection of child Activities #*pression$e*tBo* S used to enable in place editing of e*pressions such as arguments
W 2 contains an Activity1esigner%ibrary pro)ect template and Activity1esigner item template that can be used to initially create the artifacts. :nce you have the designer initiali-ed, you can begin using the above elements to customi-e the look and feel of your activity by laying out how to display data and visual representations of child activities. Within the designer you have access to a !odel+tem that is an abstraction over the actual activity, and surfacing all properties of the activity. $he Workflow+tem.resenter control can be used to display a single Activity that is part of your activity. or e*ample, to provide the ability to drag an activity onto the +terator activity shown earlier and display the activity contained within the +teractor activity, you can use the >A!% shown in igure 4R, binding the Workflow+tem.resenter to the !odel+tem.Body. igure 25 shows the designer in use on a workflow design surface . HsapDActivity1esigner *[email protected].+terator1esigner0 *mlns@0httpD??schemas.microsoft.com?winf*?<55G?*aml?presentation0 *mlnsD*@0httpD??schemas.microsoft.com?winf*?<55G?*aml0 *[email protected] [email protected] *[email protected] [email protected] HOridI HBorder BorderBrush@0!idnightBlue0 Border$hickness@040 Corner&adius@0=50I HsapDWorkflow+tem.resenter !in/eight@0650 +tem@0ABinding .ath@!odel+tem.Body, !ode@$woWayB0 /int$e*t@0Add body here0 ?I H ?BorderI H?OridI H?sapDActivity1esignerI Figure +7% WorkflowItemPresenter in activit4 designer
Figure 49% Iterator designer Workflow+tems.resenter provides similar functionality for displaying multiple items such as the children in a se,uence. Fou can define a template and orientation for how you want the items displayed, as well as a spacer template to add visual elements between activities such as connectors, arrows, or something more interesting. igure 2= shows a simple designer for a custom se,uence activity that displays the child activities with a hori-ontal line between each. HsapDActivity1esigner *[email protected],uence1esigner0 *mlns@0httpD??schemas.microsoft.com?winf*?<55G?*aml?presentation0 *mlnsD*@0httpD??schemas.microsoft.com?winf*?<55G?*aml0 *[email protected] [email protected] *[email protected] [email protected] HOridI H 8tack.anelI H Border BorderBrush@0Ooldenrod0 Border$hickness@040I H sapDWorkflow+tems.resenter /int$e*t@01rop Activities /ere0 +tems@0ABinding .ath@!odel+tem.ChildActivitiesB0I H sapDWorkflow+tems.resenter.8pacer$emplateI H1ata$emplateI H &ectangle /eight@040 Width@0250 ill@0!idnightBlue0 !argin@060 ?I H ?1ata$emplateI H ?sapDWorkflow+tems.resenter.8pacer$emplateI H sapDWorkflow+tems.resenter.+tems.anelI H +tems.anel$emplateI H8tack.anel :rientation@0Vertical0?I
H ?+tems.anel$emplateI H?sapDWorkflow+tems.resenter.+tems.anelI H ?sapDWorkflow+tems.resenterI H ?BorderI H ?8tack.anelI H ?OridI H?sapDActivity1esignerI Figure 4$% !ustom se*uence designer using WorkflowItemsPresenter
Figure 4)% #e*uence designer inally, the #*pression$e*tBo* control enables in7place editing of activity arguments within the designer in addition to the property grid. +n Visual 8tudio <5=5 this includes intellisense support for the e*pressions being entered. igure 24 shows a designer for the Oet!anager activity created earlier, enabling editing of the #mployee"ame and !anager#mail arguments within the designer. $he actual designer is shown in igure 22. HsapDActivity1esigner *[email protected]!anager1esigner0 *mlns@0httpD??schemas.microsoft.com?winf*?<55G?*aml?presentation0 *mlnsD*@0httpD??schemas.microsoft.com?winf*?<55G?*aml0 *[email protected] [email protected] *[email protected] [email protected] *mlnsDsapc@0clr7namespaceD8ystem.Activities..resentation.ConvertersC [email protected]
H sapDActivity1esigner.&esourcesI HsapcDArgument$o#*pressionConverter *DXey@0Argument$o#*pressionConverter0 *D;id@0swdvDArgument$o#*pressionConverter]=0 ?I H ?sapDActivity1esigner.&esourcesI HOridI H Orid.Column1efinitionsI H Column1efinition Width@0650 ?I HColumn1efinition Width@090 ?I H ?Orid.Column1efinitionsI H Orid.&ow1efinitionsI H &ow1efinition ?I H &ow1efinition ?I H &ow1efinition ?I H ?Orid.&ow1efinitionsI H $e*tBlock VerticalAlignment@0Center0 /ori-ontalAlignment@0Center0I#mployeeDH?$e*tBlockI H sapvD#*pression$e*tBo* Orid.Column@0=0 #*pression@0ABinding .ath@!odel+tem.#mployee"ame, !ode@$woWay, Converter@A8tatic&esource Argument$o#*pressionConverterB, Converter.arameter@+nB0 :wnerActivity@0ABinding .ath@!odel+temB0 !in%ines@0=0 !a*%ines@0=0 !inWidth@0650 /int$e*t@0JltC#mployee "ameJgtC0?I H $e*tBlock Orid.&ow@0=0 VerticalAlignment@0Center0 /ori-ontalAlignment@0Center0I!gr #mailDH?$e*tBlockI H sapvD#*pression$e*tBo* Orid.Column@0<0 Orid.&ow@0=0 ;se%ocation#*pression@0$rue0 #*pression@0ABinding .ath@!odel+tem.!anager#mail, !ode@$woWay,
Converter@A8tatic&esource Argument$o#*pressionConverterB, Converter.arameter@:utB0 :wnerActivity@0ABinding .ath@!odel+temB0 !in%ines@0=0 !a*%ines@0=0 !inWidth@0650 ?I H ?OridI H?sapDActivity1esignerI Figure 4+% 8sing 12'ression(e2t3o2 to edit arguments in the designer
Figure 44% "et anager activit4 designer $he e*ample designers presented here were simple to highlight the specific features but the full power of W. is at your disposal to make your activities easier to configure and more natural to use for your consumers. :nce you have a designer created, there are two ways to associate it with the activityD applying an attribute to the activity class, or implementing the +&egister!etadata interface. $o let the activity definition drive the choice of designer, simply apply the 1esignerAttribute to the activity class and supply the type for the designerC this works e*actly as it did in W 4. or a more loosely coupled implementation, you can implement the +&egister!etadata interface and define the attributes you wish to apply to the activity class and properties. igure 26 shows an e*ample implementation to apply the designers for two custom activities. Visual 8tudio will discover your implementation and invoke it automatically, whereas a custom designer host, discussed ne*t, would call the method e*plicitly. public class !etadata D +&egister!etadata A public void &egister'( A Attribute$ableBuilder builder @ new Attribute$ableBuilder'(C builder.AddCustomAttributes'typeof'8end!ail(, new AttributeLM A new 1esignerAttribute'typeof'8end!ail1esigner((B(C builder.AddCustomAttributes'typeof'Oet!anager(, new AttributeLMA new 1esignerAttribute'typeof'Oet!anager1esigner((B(C !etadata8tore.AddAttribute$able'builder.Create$able'((C B B Figure 4/% ?egistering designers for activities
?ehosting the workflow designer +n the past, developers have often wanted to provide their users with the ability to create or edit workflows. +n previous versions of Windows Workflow oundation, this was possible, but not a trivial task. With the move to W. comes a new designer and rehosting was a prime use case for the development team. Fou can host the designer in a custom W. application like that shown in igure 2G with a few lines of >A!% and a few lines of code.
Figure 40% ?ehosted workflow designer $he >A!% for the window, igure 2K, uses a grid to layout three columns, then puts a border control in each cell. +n the first cell, the toolbo* is created declaratively, but can also be created in code. or the designer view itself, and the property grid, there are two empty border controls in each of the remaining cells. $hese controls are added in code. HWindow *DClass@0Workflow#ditor.!ainWindow0 *mlns@0httpD??schemas.microsoft.com?winf*?<55G?*aml?presentation0 *mlnsD*@0httpD??schemas.microsoft.com?winf*?<55G?*aml0 *mlnsDsys@0clr7namespaceD8ystemCassembly@mscorlib0 *[email protected].$oolbo*C [email protected] $itle@0Workflow #ditor0 /eight@06550 Width@0K550 I H Window.&esourcesI
H sysD8tring *DXey@0Assembly"ame0I8ystem.Activities, [email protected], Culture@neutral, .ublicXey$oken@4=bf4P6Gad4G2e46H?sysD8tringI H sysD8tring *DXey@0!yAssembly"ame0ICustomActivitiesH?sysD8tringI H ?Window.&esourcesI HOrid *D"ame@01esignerOrid0I H Orid.Column1efinitionsI H Column1efinition Width@0<90 ?I H Column1efinition Width@0K90 ?I H Column1efinition Width@0490 ?I H?Orid.Column1efinitionsI H BorderI H saptD$oolbo*ControlI H saptD$oolbo*Control.CategoriesI H saptD$oolbo*Category Category"ame@0Basic0I H saptD$oolbo*+temWrapper Assembly"ame@0A8tatic&esource Assembly"ameB0 I H saptD$oolbo*+temWrapper.$ool"ameI 8ystem.Activities.8tatements.8e,uence H ?saptD$oolbo*+temWrapper.$ool"ameI H ?saptD$oolbo*+temWrapperI H saptD$oolbo*+temWrapper Assembly"ame@0A8tatic&esource !yAssembly"ameB0I H saptD$oolbo*+temWrapper.$ool"ameI CustomActivities.8end!ail H?saptD$oolbo*+temWrapper.$ool"ameI H ?saptD$oolbo*+temWrapperI H ?saptD$oolbo*CategoryI H ?saptD$oolbo*Control.CategoriesI
H ?saptD$oolbo*ControlI H ?BorderI HBorder "ame@01esignerBorder0 Orid.Column@0=0 BorderBrush@08almon0 Border$hickness@040 ?I H Border *D"[email protected]#*pander0 Orid.Column@0<0 ?I H ?OridI H?WindowI Figure 45% Designer rehosting ;& < $he code to initiali-e the designer and property grid is shown in igure 2P. $he first step, in the :n+nitiali-ed method, is to register the designers for all of the activities that will be used in the workflow designer. $he 1esigner!etadata class registers the associated designers for the activities that ship with framework and the !etadata class registers the designers for my custom activities. "e*t, create the Workflow1esigner class and use the View and .roperty+nspectorView properties to access the ;+#lement ob)ects needed to render. $he designer is initiali-ed with an empty 8e,uence, but you could also add menus and load the workflow >A!% from a variety of sources including the file system or a database. public !ainWindow'( A +nitiali-eComponent'(C B protected override void :n+nitiali-ed'#ventArgs e( A base.:n+nitiali-ed'e(C &egister1esigners'(C .lace1esignerAnd.ropOrid'(C B private void &egister1esigners'( A new 1esigner!etadata'(.&egister'(C new CustomActivities..resentation.!etadata'(.&egister'(C B private void .lace1esignerAnd.ropOrid'( A Workflow1esigner designer @ new Workflow1esigner'(C designer.%oad'new 8ystem.Activities.8tatements.8e,uence'((C
1esignerBorder.Child @ designer.ViewC .ropertyOrid#*pander.Child @ designer..roperty+nspectorViewC B Figure 46% Designer rehosting code With this little bit of code and markup, you can edit a workflow, drag and drop activities from the toolbo*, configure variables in the workflow and manipulate arguments on the activities. Fou can also get the te*t of the >A!% by calling lush on the designer, then referencing the $e*t property of the Workflow1esigner. $here is much more you can do, and getting started is much easier than before. Workflow #ervices As mentioned previously, one of the large areas of focus in this release of Windows Workflow oundation is tighter integration with Windows Communication oundation. $he e*ample in the activity walkthrough showed how to call a service from a workflow, and in this section + will discuss how to e*pose a workflow as a WC service. $o begin, create a new pro)ect and select the WC Workflow 8ervice pro)ect. :nce the template is complete, you will find a pro)ect with a web.config file and a file with a >A!%> e*tension. $he >A!%> file is a declarative service or workflow service and like other WC templates, provides a functioning, albeit simple, service implementation that receives a re,uest and returns a response. or this e*ample, + will change the contract to create an operation to receive an e*pense report and return an indicator that the report has been received. $o start, add an #*pense&eport class to the pro)ect like the one shown in igure 2R. L1ataContractM public class #*pense&eport A L1ata!emberM public 1ate$ime irst1ate:f$ravel A getC setC B L1ata!emberM public double $otalAmount A getC setC B L1ata!emberM public string #mployee"ame A getC setC B B Figure 47% 12'ense re'ort contract t4'e Back in the workflow designer, change the type of the 0data0 variable in the variables to the #*pense&eport type )ust defined 'you may need to build the pro)ect for the type to show up in the type picker dialog(. ;pdate the &eceive activity by clicking the Content button and changing the type in the dialog to the #*pense&eport type to match. ;pdate the activity properties to define a new :peration"ame, and 8erviceContract"ame as shown in igure 65.
Figure /9% !onfiguring the receive location "ow the 8end&eply activity can have the Value set to $rue to return the indicator of receipt. :bviously, in a more complicated sample, you could use the full power of workflow to save information to the database, do validation of the report, etc. before determining the response. Browsing to the service with the WC $estClient application shows how the activity configuration defines the e*posed service contract as shown in igure 6=.
:nce you have created a workflow service, you need to host it, )ust as you would with any WC service. $he previous e*ample used the simplest option for hostingD ++8. $o host a workflow service in your own process, you will want to use the Workflow8ervice/ost class found in the 8ystem.8ervice!odel.Activities assembly. "ote that there is another class by this same name in the 8ystem.Workflow8ervices assembly which is used for hosting workflow services built using the W 4 activities. +n the simplest case of self7hosting, you can use code like that shown in igure 6< to host your service and add endpoints. Workflow8ervice/ost host @ new Workflow8ervice/ost'>aml8ervices.%oad'0#*pense&eport8ervice.*aml*0(, new ;ri'0httpD??localhostDRPRK?8ervices?#*pense0((C host.Add1efault#ndpoints'(C host.1escription.Behaviors.Add' new 8ervice!etadataBehavior A /ttpOet#nabled @ true B(C host.:pen'(C Console.Write%ine'0/ost is open0(C Console.&ead%ine'(C Figure /)% #elf@hosting the workflow service +f you want to take advantage of the managed hosting options in Windows, you can host your service in ++8 by copying the >A!%> file into the directory and specifying any necessary configuration information for behaviors, bindings, endpoints, etc. in the web.config file. +n fact, as seen previously, when you create a new pro)ect in Visual 8tudio using one of the 1eclarative Workflow 8ervice pro)ect templates, you will get a pro)ect with a web.config file and the workflow service defined in a >A!%>, ready to deploy. When using the Workflow8ervice/ost class to host your workflow service, you still need to be able to configure and control the workflow instances. $o control the service there is a new standard endpoint called the WorkflowControl#ndpoint. A companion class, WorkflowControlClient, provides a pre7built client pro*y . Fou can e*pose a Work lowControl#ndpoint on your service, and use the WorkflowControlClient to create and run new instances of workflows, or control running workflows. Fou use this same model to manage services on the local machine, or you can use it to manage services on a remote machine. igure 64 shows an updated e*ample of e*posing the workflow control endpoint on your service. Workflow8ervice/ost host @ new Workflow8ervice/ost'0#*pense&eport8ervice.*aml*0, new ;ri'0httpD??localhostDRPRK?8ervices?#*pense0((C host.Add1efault#ndpoints'(C WorkflowControl#ndpoint wce @ new WorkflowControl#ndpoint' new "et"amed.ipeBinding'(, new #ndpointAddress'0net.pipeD??localhost?#*pense?WC#0((C
host.Add8ervice#ndpoint'wce(C host.:pen'(C Figure /+% Workflow !ontrol 1nd'oint Because you are not creating the Workflow+nstance directly, there are two options for adding e*tensions to the runtime. $he first is that you can add some e*tensions to the Workflow8ervice/ost and they will initiali-e correctly with your workflow instances. $he second is using configuration. $he tracking system, for e*ample, can be entirely defined in the application configuration file. Fou define the tracking participants and the tracking profiles in the configuration file, and using a behavior, you apply a particular participant to a service as shown in igure 62. Hsystem.service!odelI H trackingI H participantsI Hadd name@0#tw$racking.articipant0 [email protected].$racking.#tw$racking.articipant, Culture@neutral, .ublicXey$oken@4=bf4P6Gad4G2e460 profile"ame@0/ealth!onitoring]$racking].rofile0?I H ?participantsI H profilesI H tracking.rofile name@0/ealth!onitoring]$racking].rofile0I H workflow activity1efinition+d@090I H workflow+nstanceTueryI H statesI H state name@08tarted0?I H state name@0Completed0?I H ?statesI H ?workflow+nstanceTueryI H ?workflowI H ?tracking.rofileI H ?profilesI H?trackingI ... 8ystem.Activities, [email protected],
HbehaviorsI H serviceBehaviorsI H behavior name@08ample$racking8ample.8ampleW Behavior0I H etw$racking profile"ame@0 /ealth!onitoring]$racking].rofile0 ?I H ?behaviorI H ?serviceBehaviorsI H?behaviorsI ... Figure /4% !onfiguring tracking for services $here are many new improvements to hosting and configuration for WC services that you can take advantage of in your workflows such as 0.svc7less0 services, or services that don\t need a file e*tension, default bindings and default endpoints )ust to name a few. or more information on these and other features in WC 2, refer the companion paper on WC found in the Additional &esources section. +n addition to hosting improvements, Windows Workflow oundation takes advantage of other features in WC C some directly and others indirectly. or e*ample, message correlation is now a key feature in building services which allows you to relate messages to a given workflow instance based on data in the message such as an order number or employee id. Correlation can be configured on the various messaging activities for both the re,uest and response and supports correlating on multiple values in the message. Again, more details on these new features can be found in the companion paper on WC 2. !onclusion $he new Windows Workflow oundation technology that ships with ."#$ ramework 2 represents a ma)or improvement in performance and developer productivity and reali-es a goal of declarative application development for business logic. $he framework provides the tools for short units of complicated work to be simplified, and for building comple* long running services with correlated message, persisted state, and rich application visibility through tracking.