Vous êtes sur la page 1sur 42

A Developer's Introduction to Windows Workflow Foundation (WF) in .

NET 4
Overview As software developers know, writing applications can be challenging, and we are constantly looking for tools and frameworks to simplify the process and help us focus on the business challenges we are trying to solve. We have moved from writing code in machine languages such as assembler to higher level languages like C# and Visual Basic that ease our development, remove lower level concerns such as memory management, and increase our productivity as developers. For Microsoft developers, the move to .NET allows the Common Language Runtime (CLR) to allocate memory, cleanup unneeded objects and handle low level constructs like pointers. Much of the complexity of an application lives in the logic and processing that goes on behind the scenes. Issues such as asynchronous or parallel execution and generally coordinating the tasks to respond to user requests or service requests can quickly lead application developers back down into the low level coding of handles, callbacks, synchronization etc. As developers, we need the same power and flexibility of a declarative programming model for the internals of an application as we have for the user interface in Windows Presentation Foundation (WPF). Windows Workflow Foundation (WF) provides the declarative framework for building application and service logic and gives developers a higher level language for handling asynchronous, parallel tasks and other complex processing. Having a runtime to manage memory and objects has freed us to focus more on the important business aspects of writing code. Likewise, having a runtime that can manage the complexities of coordinating asynchronous work provides a set of features that improves developer productivity. WF is a set of tools for declaring your workflow (your business logic), activities to help define the logic and control flow, and a runtime for executing the resulting application definition. In short, WF is about using a higher level language for writing applications, with the goal of making developers more productive, applications easier to manage, and change quicker to implement. The WF runtime not only executes your workflows for you, it also provides services and features important when writing application logic such persistence of state, bookmarking and resumption of business logic, all of which lead to thread and process agility enabling scale up and scale out of business processes. For more conceptual information on how using WF to build your applications can make you more productive, I recommend you read "The Workflow Way" by David Chappell, found in the Additional Resources section. Whats New in WF4 In version 4 of the Microsoft .NET Framework, Windows Workflow Foundation introduces a significant amount of change from the previous versions of the technology that shipped as part of .NET 3.0 and 3.5. In fact, the team revisited the core of the programming model, runtime and tooling and has re-architected each one to increase performance and productivity as well as to address the important feedback garnered from customer engagements using the previous versions. The significant changes made were necessary to provide the best experience for developers adopting WF and to enable WF to continue to be a strong foundational component that you can build on in your applications. I will introduce the high level changes here, and throughout the paper each topic will get more in depth treatment. Before I continue, it is important to understand that backwards compatibility was also a key goal in this release. The new framework components are found primarily in the System.Activities.* assemblies while the backwards compatible framework components are found in the System.Workflow.* assemblies. The System.Workflow.* assemblies are part of the .NET Framework 4 and provide complete backward compatibility so you can migrate your application to .NET 4 with no changes to your workflow code. Throughout this paper I will use the name WF4 to refer to the new components found in the System.Activities.* assemblies and WF3 to refer to the components found in the System.Workflow.* assemblies. Designers One of the most visible areas of improvement is in the workflow designer. Usability and performance were key goals for the team for the VS 2010 release. The designer now supports the ability to work with much larger workflows without a degradation in performance and designers are all based on Windows Presentation Foundation (WPF), taking full advantage of the rich user experience one can build with the declarative UI framework. Activity developers will use XAML to define the way their activities look and interact with users in a visual design environment. In addition, rehosting the workflow designer in your own applications to enable non-developers to view and interact with your workflows is now much easier.

Data Flow In WF3, the flow of data in a workflow was opaque. WF4 provides a clear, concise model for data flow and scoping in the use of arguments and variables. These concepts, familiar to all developers, simplify both the definition of data storage, as well as the flow of the data into and out of workflows and activities. The data flow model also makes more obvious the expected inputs and outputs of a given activity and improves performance of the runtime as data is more easily managed. Flowchart A new control flow activity called Flowchart has been added to make it possible for developers to use the Flowchart model to define a workflow. The Flowchart more closely resembles the concepts and thought processes that many analysts and developers go through when creating solutions or designing business processes. Therefore, it made sense to provide an activity to make it easy to model the conceptual thinking and planning that had already been done. The Flowchart enables concepts such as returning to previous steps and splitting logic based on a single condition, or a Switch / Case logic. Programming Model The WF programming model has been revamped to make it both simpler and more robust. Activity is the core base type in the programming model and represents both workflows and activities. In addition, you no longer need to create a WorkflowRuntime to invoke a workflow, you can simply create an instance and execute it, simplifying unit testing and application scenarios where you do not want to go through the trouble of setting up a specific environment. Finally, the workflow programming model becomes a fully declarative composition of activities, with no code-beside, simplifying workflow authoring. Windows Communication Foundation (WCF) Integration The benefits of WF most certainly apply to both creating services and consuming or coordinating service interactions. A great deal of effort went into enhancing the integration between WCF and WF. New messaging activities, message correlation, and improved hosting support, along with fully declarative service definition are the major areas of improvement. Getting Started with Workflow The best way to understand WF is to start using it and applying the concepts. I will cover several core concepts around the underpinnings of workflow, and then walk through creating a few simple workflows to illustrate how those concepts relate to each other. Workflow Structure Activities are the building blocks of WF and all activities ultimately derive from Activity. A terminology note - Activities are a unit of work in WF. Activities can be composed together into larger Activities. When an Activity is used as a top-level entry point, it is called a "Workflow", just like Main is simply another function that represents a top level entry point to CLR programs. For example, Figure 1 shows a simple workflow being built in code. Sequence s = new Sequence { Activities = { new WriteLine {Text = "Hello"}, new Sequence { Activities = { new WriteLine {Text = "Workflow"}, new WriteLine {Text = "World"} }

} } }; Figure 1: A simple workflow Notice in Figure 1 that the Sequence activity is used as the root activity to define the root control flow style for the workflow. Any activity can be used as the root or workflow and executed, even a simple WriteLine. By setting the Activities property on the Sequence with a collection of other activities, I have defined the workflow structure. Further, the child activities can have child activities, creating a tree of activities that make up the overall workflow definition. Workflow Templates and the Workflow Designer WF4 ships with many activities and Visual Studio 2010 includes a template for defining activities. The two most common activities used for the root control flow are Sequence and Flowchart. While any Activity can be executed as a workflow, these two provide the most common design patterns for defining business logic. Figure 2 shows an example of a sequential workflow model defining the processing of an order being received, saved and then notifications sent to other services.

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

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

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

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

Figure 6: Configuring arguments on activities Expressions acting on data Expressions are activities you can use in your workflow to operate on data. Expressions can be used in places where you use Activity and are interested in a return value, which means you can use expressions in setting arguments or to define conditions on activities like the While or If activities. Remember that most things in WF4 derive from Activity and expressions are no different, they are a derivative of Activity<TResult> meaning they return a value of a specific type. In addition, WF4 includes several common expressions for referring to variables and arguments as well as Visual Basic expressions. Because of this layer of specialized expressions, the expressions you use to define arguments can include code, literal values, and variable references. Table 1 provides a small sampling of the types of expressions you might use when defining arguments using the workflow designer. When creating expressions in code there are several more options including the use of lambda expressions. Expression "hello world" 10 System.String.Concat("hello", " ", "world") "hello " & "world" argInputString varResult "hello: " & argInputString Table 1: Example expressions Building your first workflow Now that I have covered the core concepts around Activity and data flow, I can create a workflow using these concepts. I will start with a simple hello world workflow to focus on the concepts rather than the true value proposition of WF. To start, create a new Unit Test project in Visual Studio 2010. In order to use the core of WF, add a reference to the System.Activities assembly, and add using statements for System.Activities, System.Activities.Statements, and System.IO in the test class file. Then add a test method as shown in Figure 7 to create a basic workflow and execute it. Expression Type Literal string value Literal Int32 value Imperative method invocation Visual Basic expression Argument reference (argument name) Variable reference (variable name) Literals and arguments/variables mixed

[TestMethod] public void TestHelloWorldStatic() { StringWriter writer = new StringWriter(); Console.SetOut(writer); Sequence wf = new Sequence { Activities = { new WriteLine {Text = "Hello"}, new WriteLine {Text = "World"} } }; WorkflowInvoker.Invoke(wf); Assert.IsTrue(String.Compare( "Hello\r\nWorld\r\n", writer.GetStringBuilder().ToString()) == 0, "Incorrect string written"); } Figure 7: Creating hello world in code The Text property on the WriteLine activity is an InArgument<string> and in this example I passed a literal value to that property. The next step is to update this workflow to use variables and pass those variables to the activity arguments. Figure 8 shows the new test updated to use the variables. [TestMethod] public void TestHelloWorldVariables() { StringWriter writer = new StringWriter(); Console.SetOut(writer); Sequence wf = new Sequence { Variables = { new Variable<string>{Default = "Hello", Name = "greeting"}, new Variable<string> { Default = "Bill", Name = "name" } }, Activities = { new WriteLine { Text = new VisualBasicValue<string>("greeting"), new WriteLine { Text = new VisualBasicValue<string>( "name + \"Gates\"")} } };

WorkflowInvoker.Invoke(wf); } Figure 8: Code workflow with variables In this case, the variables are defined as type Variable<string> and given a default value. The variables are declared within the Sequence activity and then referenced from the Text argument of the two activities. Alternatively, I could use expressions to initialize the variables as shown in Figure 9 where the VisualBasicValue<TResult> class is used passing in a string representing the expression. In the first case, the expression refers to the name of the variable, and in the second case the variable is concatenated with a literal value. The syntax used in textual expressions is Visual Basic, even when writing code in C#. Activities = { new WriteLine { Text = new VisualBasicValue<string>("greeting"), TextWriter = writer }, new WriteLine { Text = new VisualBasicValue<string>("name + \"Gates\""), TextWriter = writer } } Figure 9: Using expressions to define arguments While code examples help illustrate important points and generally feel comfortable to developers, most people will use the designer to create workflows. In the designer, you drag activities onto the design surface and use an updated but familiar property grid to set arguments. In addition, the workflow designer contains expandable regions at the bottom to edit arguments for the workflow and variables. To create a similar workflow in the designer, add a new project to the solution, choosing the Activity Library template, and then add a new Activity. When the workflow designer is first displayed, it does not contain any activities. The first step in defining the activity is to choose the root activity. For this example, add a Flowchart activity to the designer by dragging it from the Flowchart category in the toolbox. In the Flowchart designer, drag two WriteLine activities from the toolbox and add them one below the other to the Flowchart. Now you have to connect the activities together so the Flowchart knows the path to follow. You do this by first hovering over the green "start" circle at the top of the Flowchart to see the grab handles, then click and drag one to the first WriteLine and drop it on the drag handle that appears at the top of the activity. Do the same to connect the first WriteLine to the second WriteLine. The design surface should look like Figure 10.

Figure 10: Hello Flowchart layout Once the structure is in place, you need to configure some variables. By clicking on the design surface and then clicking on

the Variables button near the bottom of the designer, you can edit the collection of variables for the Flowchart. Add two variables called "greeting" and "name" to list and set the default values to "Hello" and "Bill" respectively be sure to include the quotes when setting the values as this is an expression so literal strings need to be quoted. Figure 11 shows the variables window configured with the two variables and their default values.

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

Figure 13: Defining arguments for the workflow

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

WriteLine AddToCollection<T> RemoveFromCollection<T> ClearCollection<T> ExistsInCollection<T>

Control flow activities When defining business logic or business processes, having control of the flow of execution is critical. Control flow activities include basics such as Sequence which provides a common container when you need to execute steps in order, and common branching logic such as the If and Switch activities. The control flow activities also include looping logic based on data (ForEach) and Conditions(While). Most important for simplifying complex programming are the parallel activities, which all enable multiple asynchronous activities to get work done at the same time. Activity Sequence While/DoWhile ForEach<T> Description For executing activities in series Executes a child activity while a condition (expression) is true Iterates over an enumerable collection and executes the child activity once for each item in the collection, waiting for the child to complete before starting the next iteration. Provides typed access to the individual item driving the iteration in the form of a named argument. Executes one of two child activities depending on the result of the condition (expression). Evaluates an expression and schedules the child activity with a matching key. Schedules all child activities at once, but also provides a completion condition to enable the activity to cancel any outstanding child activities if certain conditions are met. Iterates over an enumerable collection and executes the child activity once for each item in the collection, scheduling all instances at the same time. Like the ForEach<T>, this activity provides access to the current data item in the form of a named argument. Schedules all child PickBranch activities and cancels all but the first to have its trigger complete. The PickBranch activity has both a Trigger and an Action; each is an Activity. When a trigger activity completes, the Pick cancels all its other child activities.

If Switch<T> Parallel

ParallelForEach<T>

Pick

The two examples below show several of these activities in use to illustrate how to compose these activities together. The first example, Figure 15, includes the use of the ParallelForEach<T> to use a list of URLs and asynchronously get the RSS feed at the specified address. After the feed is returned, the ForEach<T> is used to iterate over the feed items and process them. Note that the code declares and defines a VisualBasicSettings instance with references to the System.ServiceModel.Syndication types. This settings object is then used when declaring VisualBasicValue<T> instances referencing variables types from that namespace to enable type resolution for those expressions. Also of note is that the body of the ParallelForEach activity takes an ActivityAction which are mentioned in the section on creating custom activities. These actions use DelegateInArgument and DelegateOutArgument in much the same way that activities use InArgument and OutArgument.

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

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

property is given a corresponding input and output argument allowing you to differentiate the data you send into the activity and the data you expect to retrieve after the activity executes. In a new WF4 project you will not find this activity in the toolbox because the target framework is set to .NET Framework 4 Client Profile. Change the target framework in the project properties to .NET Framework 4, and the activity will appear in the toolbox.

Figure 17: Interop activity configuration Flowchart When designing Flowchart workflows there are several constructs that can be used to manage the flow of execution within the Flowchart. These constructs themselves provide simple steps, simple decision points based on a single condition, or a switch statement. The real power of the Flowchart is the ability to connect these node types into the desired flow. Construct/Activit Description y Flowchart The container for a series of flow steps, each flow step can be any activity or one of the following constructs, but to be executed, it must be connected within the Flowchart. FlowDecision FlowSwitch<T> FlowStep Provides branching logic based on a condition. Enables multiple branches based on the value of an expression. Represents a step in the Flowchart with the ability to be connected to other steps. This type is not shown in the toolbox as it is implicitly added by the designer. This activity wraps other activities in the workflow and provides the navigation semantics for the next step(s) in the workflow.

It is important to note that while there are specific activities for the Flowchart model, any other activities can be used within the workflow. Likewise, a Flowchart activity can be added to another activity to provide the execution and design semantics of a Flowchart, within that workflow. This means you can have a sequence with a several activities and a Flowchart right in the middle.

Messaging Activities One of the major focuses in WF4 is tighter integration between WF and WCF. In terms of workflows, that means activities to model messaging operations such as sending and receiving messages. There are actually several different activities included in the framework for messaging, each with slightly different functionality and purpose. Activity Send/Receive Description One way messaging activities to send or receive a message. These same activities are composed into request/response style interactions. Models a service operation that receives a message and sends back a reply. Invokes a service operation and receives the response. Allow for initializing correlation values explicitly as part of the workflow logic, rather than extracting the values from a message. Defines a scope of execution where a correlation handle is accessible to receive and send activities simplifying the configuration of a handle that is shared by several messaging activities. Enables workflow logic to be included in the same transaction flowed into a WCF operation using the Receive activity.

ReceiveAndSendReply SendAndReceiveReply InitializeCorrelation

CorrelationScope

TransactedReceiveScope

To invoke a service operation from within a workflow, you will follow the familiar steps of adding a service reference to your workflow project. The project system in Visual Studio will then consume the metadata from the service and create a custom activity for each service operation found in the contract. You can think of this as the workflow equivalent of a WCF proxy that would be created in a C# or Visual Basic project. For example, taking an existing service that searches for hotel reservations with the service contract shown in Figure 18; adding a service reference to it yields a custom activity shown in Figure 19. [ServiceContract] public interface IHotelService { [OperationContract] List<HotelSearchResult> SearchHotels( HotelSearchRequest requestDetails); } Figure 18: Service contract

Figure 19: Custom WCF activity

More information on building workflows exposed as WCF services can be found in the Workflow Services section later in this paper. Transactions and Error Handling Writing reliable systems can be difficult, and requires that you do a good job of handling errors and managing to keep consistent state in your application. For a workflow, scopes for managing exception handling and transactions are modeled using activities with properties of type ActivityAction or Activity to enable developers to model error processing logic. Beyond these common patterns of consistency, WF4 also includes activities which allow you to model long running distributed coordination through compensation and confirmation. Activity CancellationScope TransactionScope Description Used to allow the workflow developer to react if a body of work gets cancelled. Enables semantics similar to using a transaction scope in code by executing all child activities in the scope under a transaction. Used to model exception handling and catch typed exceptions. Can be used to throw an exception from the activity. Used to rethrow an exception, generally one that has been caught using the TryCatch activity. Defines the logic for executing child activities that may need to have their actions compensated for after success. Provides a placeholder for the compensation logic, confirmation logic, and cancellation handling. Invokes the compensation handling logic for a compensable activity. Invokes the confirmation logic for a compensable activity.

TryCatch/Catch<T> Throw Rethrow CompensableActivity

Compensate Confirm

The TryCatch activity provides a familiar way to scope a set of work to catch any exceptions that may occur, and the Catch<T> activity provides the container for exception handling logic. You can see an example of how this activity is used in Figure 20, with each typed exception block being defined by a Catch<T> activity, providing access to the exception via the named argument seen on the left of each catch. If the need arises, the Rethrow activity can be used to rethrow the caught exception, or the Throw activity to throw a new exception of your own.

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

Figure 21: Compensable Activity Creating and executing workflows As with any programming language, there are two fundamental things you do with workflows: define them, and execute them. In both cases, WF provides you with several options to give you flexibility and control. Options for designing workflows When designing or defining workflows, there are two main options: code or XAML. XAML provides the truly declarative experience and allows for the entire definition of your workflow to be defined in XML markup, referencing Activities and types built using .NET. Most developers will likely use the workflow designer to build workflows which will result in a declarative XAML workflow definition. Because XAML is just XML, however, any tool can be used to create it, which is one of the reasons it is such a powerful model for building applications. For example, the XAML shown in Figure 22 was created in a simple text editor, and can either be compiled, or used directly to execute an instance of the workflow being defined. <p:Activity x:Class="Workflows.HelloSeq" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/activities"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <x:Members> <x:Property Name="greeting" Type="p:InArgument(x:String)" /> <x:Property Name="name" Type="p:InArgument(x:String)" /> </x:Members> <p:Sequence> <p:WriteLine>[greeting &amp; " from workflow"]</p:WriteLine> <p:WriteLine>[name]</p:WriteLine> </p:Sequence> </p:Activity> Figure 22: Workflow defined in XAML This same XAML is generated by the designer, and can be generated by custom tools, making it much easier to manage. In addition, it is also possible, as was shown previously, to build workflows using code. This involves the creation of the activity hierarchy through the use of the various activities in the framework and your custom activities. You have seen several examples already of workflows defined entirely in code. Unlike in WF3, it is now possible to create a workflow in code and easily serialize it to XAML; providing more flexibility in modeling and managing workflow definitions. As I will show, to execute a workflow, all you need is an Activity and that can be an instance built in code, or one created from XAML. The end result of each of the modeling techniques is either a class deriving from Activity, or an XML representation that can be deserialized or compiled into an Activity. Options for executing workflows In order to execute a workflow, you need an Activity that defines the workflow. There are two typical ways to get an Activity that can be executed: create it in code or read in a XAML file and deserialize the content into an Activity. The first option is straightforward and I have already shown several examples. To load a XAML file you should use the ActivityXamlServices class which provides a static Load method. Simply pass in a Stream or XamlReader object and you get back the Activity represented in the XAML. Once you have an Activity, the simplest way to execute it is by using the WorkflowInvoker class as I did in the unit tests earlier. The Invoke method of this class has a parameter of type Activity and returns an IDictionary<string, object>. If you need to pass arguments into the workflow, you first define them on the workflow and then pass the values along with the Activity, into the Invoke method as dictionary of name/value pairs. Likewise, any Out or In/Out arguments defined on the workflow will be returned as the result of executing the method. Figure 23 provides an example of loading a workflow from a XAML file, passing arguments into the workflow and retrieving the resulting output arguments. Activity mathWF; using (Stream mathXaml = File.OpenRead("Math.xaml")) { mathWF = ActivityXamlServices.Load(mathXaml); } var outputs = WorkflowInvoker.Invoke(mathWF, new Dictionary<string, object> { { "operand1", 5 }, { "operand2", 10 }, { "operation", "add" } }); Assert.AreEqual<int>(15, (int)outputs["result"], "Incorrect result returned"); Figure 23: Invoking "Loose XAML" workflow with in and out arguments

Notice in this example that the Activity is loaded from a XAML file and it can still accept and return arguments. The workflow was developed using the designer in Visual Studio for ease, but could be developed in a custom designer and stored anywhere. The XAML could be loaded from a database, SharePoint library, or some other store before being handed to the runtime for execution. Using the WorkflowInvoker provides the simplest mechanism for running short-lived workflows. It essentially makes the workflow act like a method call in your application, enabling you to more easily take advantage of all the benefits of WF without having to do a lot of work to host WF itself. This is especially useful when unit testing your activities and workflows as it reduces the test setup necessary to exercise a component under test. Another common hosting class is the WorkflowApplication which provides a safe handle to a workflow that is executing in the runtime, and enables you to manage long running workflows more easily. With the WorkflowApplication, you can still pass arguments into the workflow in the same way as with the WorkflowInvoker, but you use the Run method to actually start the workflow running. At this point, the workflow begins executing on another thread and control returns to the calling code. Because the workflow is now running asynchronously, in your hosting code you will likely want to know when the workflow completes, or if it throws an exception, etc. For these types of notifications, the WorkflowApplication class has a set of properties of type Action<T> that can be used like events to add code to react to certain conditions of the workflow execution including: aborted, unhandled exception, completed, idled, and unloaded. When you execute a workflow using WorkflowApplication, you can use code similar to that shown in Figure 24 using actions to handle the events. WorkflowApplication wf = new WorkflowApplication(new Flowchart1()); wf.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { Console.WriteLine("Workflow {0} complete", e.InstanceId); }; wf.Aborted = delegate(WorkflowApplicationAbortedEventArgs e) { Console.WriteLine(e.Reason); }; wf.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e) { Console.WriteLine(e.UnhandledException.ToString()); return UnhandledExceptionAction.Terminate; }; wf.Run(); Figure 24: WorkflowApplication actions In this example, the goal of the host code, a simple console application, is to notify the user via the console when the workflow completes or if an error occurs. In a real system, the host will be interested in these events and will likely react to them differently to provide administrators with information about failures or to better manage the instances. Workflow Extensions One of the core features of WF, since WF3, has been that it is lightweight enough to be hosted in any .NET application domain. Because the runtime can execute in different domains and may need customized execution semantics, various aspects of the runtime behaviors need to be externalized from the runtime. That is where workflow extensions come into play. Workflow extensions enable you as the developer writing the host code, if you so choose, to add behavior to the runtime by extending it with custom code. Two extension types that the WF runtime is aware of are the persistence and tracking extensions. The persistence extension

provides the core functionality for saving the state of a workflow to a durable store and retrieving that state when it is needed. The persistence extension shipping with the framework includes support for Microsoft SQL Server, but extensions can be written to support other database systems or durable stores. Persistence In order to use the persistence extension, you must first setup a database to hold the state, which can be done using the SQL scripts found in %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<language> (e.g. c:\windows\microsoft.net\framework\v4.0.30319\sql\en\). After creating a database, you execute the two scripts to create both the structure (SqlWorkflowInstanceStoreSchema.sql) and the stored procedures (SqlWorkflowInstanceStoreLogic.sql) within the database. Once the database is setup, you use the SqlWorkflowInstanceStore along with the WorkflowApplication class. You first create the store and configure it, then supply it to the WorkflowApplication as shown in Figure 25. WorkflowApplication application = new WorkflowApplication(activity); InstanceStore instanceStore = new SqlWorkflowInstanceStore( @"Data Source=.\SQLEXPRESS;Integrated Security=True"); InstanceView view = instanceStore.Execute( instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30)); instanceStore.DefaultInstanceOwner = view.InstanceOwner; application.InstanceStore = instanceStore; Figure 25: Adding the SQL Persistence Provider There are two ways the workflow can get persisted. The first is through direct use of the Persist activity in a workflow. When this activity executes it causes the workflow state to be persisted to the database, saving the current state of the workflow. This affords the workflow author control over when it is important to save the current state of the workflow. The second option enables the hosting application to persist the workflow state when various events occur on the workflow instance; most likely when the workflow is idle. By registering for the PersistableIdle action on the WorkflowApplication, your host code can then respond to the event to indicate if the instance should be persisted, unloaded, or neither. Figure 26 shows a host application registering to get notified when the workflow is idle and causing it to persist. wf.PersistableIdle = (waie) => PersistableIdleAction.Persist; Figure 26: Unloading the workflow when it idles Once a workflow has idled and been persisted, the WF runtime can unload it from memory, freeing up resources for other workflows that need processing. You can choose to have your workflow instance unload by returning the appropriate enumeration from the PersistableIdle action. Once a workflow is unloaded, at some point the host will want to load it back out of the persistence store to resume it. In order to do that, the workflow instance must be loaded using an instance store and the instance identifier. This will in turn result in the instance store being asked to load the state. Once the state is loaded, the Run method on the WorkflowApplication can be used, or a bookmark can be resumed as shown in Figure 27. For more on bookmarks, see the custom activities section. WorkflowApplication application = new WorkflowApplication(activity); application.InstanceStore = instanceStore; application.Load(id); application.ResumeBookmark(readLineBookmark, input); Figure 27: Resuming a persisted workflow One of the changes in WF4 is how the state of workflows is persisted and relies heavily on the new data management techniques of arguments and variables. Rather than serializing the entire activity tree and maintaining state for the lifetime of the workflow, only in scope variables and argument values, along with some runtime data such as bookmark information, are persisted. Notice in Figure 27 that when the persisted instance is reloaded, in addition to using the instance store, the Activity that defines the workflow is also passed. Essentially, the state from the database is applied to the Activity supplied.

This technique enables much greater flexibility for versioning and results in better performance as the state has a smaller memory footprint. Tracking Persistence enables the host to support long running processes, load balance instances across hosts and other fault tolerant behaviors. However, once the workflow instance has completed, the state in the database is often deleted as it is no longer useful. In a production environment, having information about what a workflow is currently doing and what it has done is critical to both managing workflows and gaining insight into the business process. Being able to track what is happening in your application is one of the compelling features of using the WF runtime. Tracking consists of two primary components: tracking participants and tracking profiles. A tracking profile defines what events and data you want the runtime to track. Profiles can include three primary types of queries: ActivityStateQuery used to identify activity states (e.g. closed) and variables or arguments to extract data WorkflowInstanceQuery used to identify workflow events CustomTrackingQuery used to identify explicit calls to track data, usually within custom activities

As an example, Figure 28 shows a TrackingProfile being created which includes an ActivityStateQuery and a WorkflowInstanceQuery. Notice that the queries indicate both when to collect information, and also what data to collect. For the ActivityStateQuery, I included a list of variables that should have their value extracted and added to the tracked data. TrackingProfile profile = new TrackingProfile { Name = "SimpleProfile", Queries = { new WorkflowInstanceQuery { States = { "*" } }, new ActivityStateQuery { ActivityName = "WriteLine", States={ "*" }, Variables = {"Text" } } } }; Figure 28: Creating a tracking profile A tracking participant is an extension that can be added into the runtime and is responsible for processing tracking records as they are emitted. The TrackingParticipant base class defines a property to provide a TrackingProfile and a Track method that handles the tracking. Figure 29 shows a limited custom tracking participant that writes data to the console. In order to use the tracking participant, it must be initialized with a tracking profile, and then added into the extensions collection on the workflow instance. public class ConsoleTrackingParticipant : TrackingParticipant { protected override void Track(TrackingRecord record, TimeSpan timeout) { ActivityStateRecord aRecord = record as ActivityStateRecord; if (aRecord != null)

{ Console.WriteLine("{0} entered state {1}", aRecord.Activity.Name, aRecord.State); foreach (var item in aRecord.Arguments) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Variable:{0} has value: {1}", item.Key, item.Value); Console.ResetColor(); } } } } Figure 29: Console tracking participant WF ships with an EtwTrackingParticipant (ETW = Enterprise Trace for Windows) that you can add into the runtime to enable high performance tracking of data. ETW is a tracing system that is a native component in Windows and is used by many components and services in the OS, including drivers and other kernel level code. The data written to ETW can be consumed using custom code or using tools such as the upcoming Windows Server AppFabric. For users migrating from WF3, this will be a change as there will not be SQL tracking participant shipping as part of the framework. However, Windows Server AppFabric will ship with ETW consumers that will collect the ETW data and store it to a SQL database. Likewise, you can build an ETW consumer and store the data in whatever format you prefer, or write your own tracking participant, whichever makes more sense for your environment. Creating custom activities While the base activity library includes a rich palette of activities for interacting with services, objects, and collections, it does not provide activities for interacting with subsystems such as databases, email servers, or your custom domain objects and systems. Part of getting started with WF4 will be to figure out what core activities you might need or want when building workflows. The great thing is that as you build core units of work, you can combine them into more coarse grained activities that developers can use in their workflows. Activity class hierarchy While an activity is an activity is an activity, it is not true that all activities have the same requirements or needs. For that reason, rather than all activities deriving directly from Activity, there is a hierarchy of activity base classes, shown in Figure 30, that you can choose from when building a custom activity.

Figure 30: Activity class hierarchy For most custom activities, you will either derive from AsyncCodeActivity, CodeActivity, or NativeActivity (or one of the generic variants), or model your activity declaratively. At a high level, the four base classes can be described as follows: Activity used to model activities by composing other activities, usually defined using XAML. CodeActivity a simplified base class when you need to write some code to get work done. AsyncCodeActivity used when your activity perform some work asynchronously. NativeActivity when your activity needs access to the runtime internals, for example to schedule other activities or create bookmarks.

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

Figure 31: Notify manager activity This activity needs to take arguments for the employee name and the message body. The GetManager activity looks up the employees manager and provide the email as an OutArgument<string>. Finally, the SendMail activity sends a message to the manager. The next step is to define the arguments for the activity by expanding the Arguments window at the bottom of the designer and entering the information for the two required input arguments. In order to compose these items, you need to be able to pass the input arguments specified on the NotifyManager activity to the individual child activities. For the GetManager activity, you need the employee name which is an in argument on the activity. You can use the argument name in the property dialog for the employeeName argument on the GetManager activity as seen in Figure 31. The SendMail activity, needs the managers email address, and the message. For the message, you can enter the name of the argument containing the message. However, for the email address, you need a way to pass the out argument from the GetManager activity to the in argument for the SendMail activity. For this you need a variable. After highlighting the Sequence activity, you can use the Variables window to define a variable named mgrEmail. Now you can enter that variable name both for the ManagerEmail out argument on the GetManager activity, and the To in argument on the SendMail activity. When the GetManager activity executes, the output data will be stored in that variable, and when the SendMail activity executes, it will read data from that variable as the in argument. The approach just described is a purely declarative model for defining the activity body. In some circumstances, you might prefer to specify the body of the activity in code. For example, your activity may include a collection of properties that in turn drive a set of child activities; a set of named values that drive the creation of a set of Assign activities would be one case where using code would be preferred. In those cases, you can write a class that derives from activity, and write code in the Implementation property to create an Activity (and any child elements) to represent the functionality of your activity. The end result is the same in both cases: your logic is defined by composing other activities, only the mechanism by which the body is defined is different. Figure 32 shows the same NotifyManager activity being defined in code. public class NotifyManager : Activity { public InArgument<string> EmployeeName { get; set; } public InArgument<string> Message { get; set; }

protected override Func<Activity> Implementation { get { return () => { Variable<string> mgrEmail = new Variable<string> { Name = "mgrEmail" }; Sequence s = new Sequence { Variables = { mgrEmail }, Activities = { new GetManager { EmployeeName = new VisualBasicValue<string>("EmployeeName"), Result = mgrEmail, }, new SendMail { ToAddress = mgrEmail, MailBody = new VisualBasicValue<string>("Message"), From = "test@contoso.com", Subject = "Automated email" } } }; return s; }; } set { base.Implementation = value; } } } Figure 32: Activity composition using code Notice that the base class is Activity and the Implementation property is a Func<Activity> to return a single Activity. This code is very similar to that shown earlier when creating workflows, and that should not be surprising as the goal in both cases is to create a Activity. Additionally, in the code approach arguments can be set with variables, or you can use expressions to connect one argument to another as is shown for the EmployeeName and Message arguments as they are used on the two child activities. Writing custom Activity classes If you determine that your activity logic cannot be accomplished by composing other activities, then you can write a code based activity. When writing activities in code you derive from the appropriate class, define arguments, and then override

the Execute method. The Execute method gets called by the runtime when it is time for the activity to do its work. To build the SendMail activity used in the previous example I first need to choose the base type. While it is possible to create the functionality of the SendMail activity using the Activity base class and composing TryCatch<T> and InvokeMethod activities, for most developers it will be more natural to choose between the CodeActivity and NativeActivity base classes and write code for the execution logic. Because this activity does not need to create bookmarks or schedule other activities, I can derive from the CodeActivity base class. In addition, because this activity will return a single output argument, it should derive from CodeActivity<TResult> which provides an OutArgument<TResult>. The first step is to define several input arguments for the email properties. Then you override the Execute method to implement the functionality of the activity. Figure 33 shows the completed activity class. public class SendMail : CodeActivity<SmtpStatusCode> { public InArgument<string> To { get; set; } public InArgument<string> From { get; set; } public InArgument<string> Subject { get; set; } public InArgument<string> Body { get; set; } protected override SmtpStatusCode Execute( CodeActivityContext context) { try { SmtpClient client = new SmtpClient(); client.Send( From.Get(context), ToAddress.Get(context), Subject.Get(context), MailBody.Get(context)); } catch (SmtpException smtpEx) { return smtpEx.StatusCode; } return SmtpStatusCode.Ok; } } Figure 33: SendMail custom activity There are several things to note about the use of arguments. I've declared several standard .NET properties using the types InArgument<T>. This is how a code-based activity defines its input and output arguments. However, within the code, I cannot simply reference these properties to get the value of the argument, I need to use the argument as a sort of handle to retrieve the value using the supplied ActivityContext; in this case a CodeActivityContext. You use the Get method of the argument class to retrieve the value, and the Set method to assign a value to an argument. Once the activity completes the Execute method, the runtime detects this and assumes the activity is done and closes it. For asynchronous or long running work, there are ways to change this behavior which are explained in an upcoming section, but in this case, once the email is sent, the work is complete. Now let us examine an activity using the NativeActivity base class to manage child activities. I will build an Iterator activity that executes some child activity a given number of times. First, I need an argument to hold the number of iterations to perform, a property for the Activity to be executed, and a variable to hold the current number of iterations. The

Execute method calls a BeginIteration method to start the initial iteration and subsequent iterations are invoked the same way. Notice in Figure 34 that variables are manipulated the same way as arguments using the Get and Set method. public class Iterator : NativeActivity { public Activity Body { get; set; } public InArgument<int> RequestedIterations { get; set; } public Variable<int> CurrentIteration { get; set; } public Iterator() { CurrentIteration = new Variable<int> { Default = 0 }; } protected override void Execute(NativeActivityContext context) { BeginIteration(context); } private void BeginIteration(NativeActivityContext context) { if (RequestedIterations.Get(context) > CurrentIteration.Get(context)) { context.ScheduleActivity(Body, new CompletionCallback(ChildComplete), new FaultCallback(ChildFaulted)); } } } Figure 34: Iterator native activity If you look closely at the BeginIteration method, you will notice the ScheduleActivity method call. This is how an activity can interact with the runtime to schedule another activity for execution, and it is because you need this functionality that you derive from NativeActivity. Also note, when calling the ScheduleActivity method on the ActivityExecutionContext, two callback methods are provided. Because you do not know which activity will be used as the body, or how long it will take to complete, and because WF is a heavily asynchronous programming environment, callbacks are used to notify your activity when a child activity has completed, allowing you to write code to react. The second callback is a FaultCallback which will get invoked if the child activity happens to throw an exception. In this callback, you receive the exception and have the ability, via the ActivityFaultContext, to indicate to the runtime that the error has been handled , which keeps it from bubbling up and out of the activity. Figure 35 shows the callback methods for the Iterator activity where both schedule the next iteration and the FaultCallback handles the error to allow execution to continue to the next iteration. private void ChildComplete(NativeActivityContext context, ActivityInstance instance) { CurrentIteration.Set(context, CurrentIteration.Get(context) + 1); BeginIteration(context);

} private void ChildFaulted(NativeActivityFaultContext context, Exception ex, ActivityInstance instance) { CurrentIteration.Set(context, CurrentIteration.Get(context) + 1); context.HandleFault(); } Figure 35: Activity callbacks When your activity needs to do work that may be long running, ideally that work could be handed off to another thread to allow the workflow thread to be used to continue processing other activities, or used to process other workflows. However, simply starting up threads and returning control to the runtime can cause problems as the runtime does not monitor the threads you create. If the runtime determined that the workflow was idle, the workflow might unload, disposing of your callback methods, or the runtime could assume your activity is done and move to the next activity in the workflow. To support asynchronous programming in your activity you derive from the AsyncCodeActivity or AsyncCodeActivity<T>. Figure 36 shows an example of an asynchronous activity that loads an RSS feed. This signature of the execute method is different for the asynchronous activities in that it returns an IAsyncResult. You write the code in the execute method to begin your asynchronous operation. Override the EndExecute method to handle the callback when your asynchronous operation is complete and return the result of the execution. public sealed class GetFeed : AsyncCodeActivity<SyndicationFeed> { public InArgument<Uri> FeedUrl { get; set; } protected override IAsyncResult BeginExecute( AsyncCodeActivityContext context, AsyncCallback callback, object state) { var req = (HttpWebRequest)HttpWebRequest.Create( FeedUrl.Get(context)); req.Method = "GET"; context.UserState = req; return req.BeginGetResponse(new AsyncCallback(callback), state); } protected override SyndicationFeed EndExecute( AsyncCodeActivityContext context, IAsyncResult result) { HttpWebRequest req = context.UserState as HttpWebRequest; WebResponse wr = req.EndGetResponse(result); SyndicationFeed localFeed = SyndicationFeed.Load( XmlReader.Create(wr.GetResponseStream())); return localFeed; } }

Figure 36: Creating asynchronous activities Additional activity concepts Now that you have seen the basics of creating activities using the base classes, arguments and variables, and managing execution; I will touch briefly on some more advanced features you can use in activity development. Bookmarks enable an activity author to create a resumption point in the execution of a workflow. Once a bookmark has been created, it can be resumed to continue the workflow processing from where it left off. Bookmarks are saved as part of the state, so unlike the asynchronous activities, after a bookmark has been created, it is not an issue for the workflow instance to be persisted and unloaded. When the host wants to resume the workflow, it loads the workflow instance and calls the ResumeBookmark method to resume the instance from where it left off. When resuming bookmarks, data can be passed into the activity as well. Figure 37 shows a ReadLine activity that creates a bookmark to receive input and registers a callback method to be invoked when the data arrives. The runtime knows when an activity has outstanding bookmarks and will not close the activity until the bookmark has been resumed. The ResumeBookmark method can be used on the WorkflowApplication class to send data to the named bookmark and signal the BookmarkCallback. public class ReadLine : NativeActivity<string> { public OutArgument<string> InputText { get; set; } protected override void Execute(NativeActivityContext context) { context.CreateBookmark("ReadLine", new BookmarkCallback(BookmarkResumed)); } private void BookmarkResumed(NativeActivityContext context, Bookmark bk, object state) { Result.Set(context, state); } } Figure 37: Creating a bookmark Another powerful feature for activity authors is the concept of an ActivityAction. ActivityAction is the workflow equivalent of the Action class in imperative code: describing a common delegate; but for some, the idea of a template may be easier to understand. Consider the ForEach<T> activity which allows you to iterate over a set of data and schedule a child activity for each data item. You need some way to allow the consumer of your activity to define the body, and be able to consume the data item of type T. Essentially, you need some activity that can accept an item of type T and act on it, ActivityAction<T> is used to enable this scenario, and provides a reference to the argument and the definition of a Activity to process the item. You can use ActivityAction in your custom activities to enable consumers of your activity to add their own steps at appropriate points. This allows you to create workflow or activity templates of a sort, where a consumer can fill in the relevant parts to customize the execution for their use. You can also use the ActivityFunc<TResult> and the related alternatives when the delegate your activity needs to invoke will return a value. Finally, activities can be validated to ensure they are configured properly by checking individual settings, constraining allowable child activities, etc. For the common need to require a particular argument, you can add a RequiredArgument attribute to the property declaration in the activity. For more involved validation, in the constructor of your activity, create a constraint and add it to the collection of constraints surfaced on the Activity class. A constraint is an activity written to inspect the target activity and ensure validity. Figure 38 shows the constructor for the Iterator activity, which validates that the RequestedIterations property is set. Finally, overriding the CacheMetadata method, you can invoke the AddValidationError method on the ActivityMetdata parameter to add validation errors for your activity. var act = new DelegateInArgument<Iterator> { Name = "constraintArg" };

var vctx = new DelegateInArgument<ValidationContext>(); Constraint<Iterator> cons = new Constraint<Iterator> { Body = new ActivityAction<Iterator, ValidationContext> { Argument1 = act, Argument2 = vctx, Handler = new AssertValidation { Message = "Iteration must be provided", PropertyName = "RequestedIterations", Assertion = new InArgument<bool>( (e) => act.Get(e).RequestedIterations != null) } } }; base.Constraints.Add(cons); Figure 38: Constraints Activity designers One of the benefits of WF is that it allows you to program your application logic declaratively, but most people do not want to write XAML by hand, which is why the designer experience in WF is so important. When building custom activities, you will also likely want to create a designer to provide the display and visual interaction experience for consumers of your activity. The designer for WF is based on Windows Presentation Foundation which means you have all the power of styling, triggers, databinding and all of the other great tools for building a rich UI for your designer. In addition, WF provides some user controls that you can use in your designer to simplify the task of displaying an individual child activity, or a collection of activities. The four primary controls are: ActivityDesigner root WPF control used in activity designers WorkflowItemPresenter used to display a single Activity WorkflowItemsPresenter used to display a collection of child Activities ExpressionTextBox used to enable in place editing of expressions such as arguments

WF4 contains an ActivityDesignerLibrary project template and ActivityDesigner item template that can be used to initially create the artifacts. Once you have the designer initialized, you can begin using the above elements to customize the look and feel of your activity by laying out how to display data and visual representations of child activities. Within the designer you have access to a ModelItem that is an abstraction over the actual activity, and surfacing all properties of the activity. The WorkflowItemPresenter control can be used to display a single Activity that is part of your activity. For example, to provide the ability to drag an activity onto the Iterator activity shown earlier and display the activity contained within the Iteractor activity, you can use the XAML shown in Figure 39, binding the WorkflowItemPresenter to the ModelItem.Body. Figure 40 shows the designer in use on a workflow design surface . <sap:ActivityDesigner x:Class="CustomActivities.Presentation.IteratorDesigner" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sap="clr-namespace:System.Activities.Presentation; assembly=System.Activities.Presentation"

xmlns:sapv="clr-namespace:System.Activities.Presentation.View; assembly=System.Activities.Presentation"> <Grid> <Border BorderBrush="MidnightBlue" BorderThickness="3" CornerRadius="10"> <sap:WorkflowItemPresenter MinHeight="50" Item="{Binding Path=ModelItem.Body, Mode=TwoWay}" HintText="Add body here" /> </Border> </Grid> </sap:ActivityDesigner> Figure 39: WorkflowItemPresenter in activity designer

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

<ItemsPanelTemplate> <StackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </sap:WorkflowItemsPresenter.ItemsPanel> </sap:WorkflowItemsPresenter> </Border> </StackPanel> </Grid> </sap:ActivityDesigner> Figure 41: Custom sequence designer using WorkflowItemsPresenter

Figure 42: Sequence designer Finally, the ExpressionTextBox control enables in-place editing of activity arguments within the designer in addition to the property grid. In Visual Studio 2010 this includes intellisense support for the expressions being entered. Figure 43 shows a designer for the GetManager activity created earlier, enabling editing of the EmployeeName and ManagerEmail arguments within the designer. The actual designer is shown in Figure 44. <sap:ActivityDesigner x:Class="CustomActivities.Presentation.GetManagerDesigner" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sap="clr-namespace:System.Activities.Presentation; assembly=System.Activities.Presentation" xmlns:sapv="clr-namespace:System.Activities.Presentation.View; assembly=System.Activities.Presentation" xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters; assembly=System.Activities.Presentation"> <sap:ActivityDesigner.Resources> <sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" x:Uid="swdv:ArgumentToExpressionConverter_1" /> </sap:ActivityDesigner.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition />

<RowDefinition /> </Grid.RowDefinitions> <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center">Employee:</TextBlock> <sapv:ExpressionTextBox Grid.Column="1" Expression="{Binding Path=ModelItem.EmployeeName, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}" MinLines="1" MaxLines="1" MinWidth="50" HintText="&lt;Employee Name&gt;"/> <TextBlock Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">Mgr Email:</TextBlock> <sapv:ExpressionTextBox Grid.Column="2" Grid.Row="1" UseLocationExpression="True" Expression="{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}" MinLines="1" MaxLines="1" MinWidth="50" /> </Grid> </sap:ActivityDesigner> Figure 43: Using ExpressionTextBox to edit arguments in the designer

Figure 44: GetManager activity designer The example designers presented here were simple to highlight the specific features but the full power of WPF is at your disposal to make your activities easier to configure and more natural to use for your consumers. Once you have a designer created, there are two ways to associate it with the activity: applying an attribute to the activity class, or implementing the IRegisterMetadata interface. To let the activity definition drive the choice of designer, simply apply the DesignerAttribute to the activity class and supply the type for the designer; this works exactly as it did in WF3. For a more loosely coupled implementation, you can implement the IRegisterMetadata interface and define the attributes you wish to apply to the activity class and properties. Figure 45 shows an example implementation to apply the designers for two custom activities. Visual Studio will discover your implementation and invoke it automatically, whereas a custom designer host, discussed next, would call the method explicitly. public class Metadata : IRegisterMetadata { public void Register() { AttributeTableBuilder builder = new AttributeTableBuilder(); builder.AddCustomAttributes(typeof(SendMail), new Attribute[] {

new DesignerAttribute(typeof(SendMailDesigner))}); builder.AddCustomAttributes(typeof(GetManager), new Attribute[]{ new DesignerAttribute(typeof(GetManagerDesigner))}); MetadataStore.AddAttributeTable(builder.CreateTable()); } } Figure 45: Registering designers for activities Rehosting the workflow designer In the past, developers have often wanted to provide their users with the ability to create or edit workflows. In previous versions of Windows Workflow Foundation, this was possible, but not a trivial task. With the move to WPF comes a new designer and rehosting was a prime use case for the development team. You can host the designer in a custom WPF application like that shown in Figure 46 with a few lines of XAML and a few lines of code.

Figure 46: Rehosted workflow designer The XAML for the window, Figure 47, uses a grid to layout three columns, then puts a border control in each cell. In the first cell, the toolbox is created declaratively, but can also be created in code. For the designer view itself, and the property grid, there are two empty border controls in each of the remaining cells. These controls are added in code. <Window x:Class="WorkflowEditor.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:sapt="clr-namespace:System.Activities.Presentation.Toolbox;

assembly=System.Activities.Presentation" Title="Workflow Editor" Height="500" Width="700" > <Window.Resources> <sys:String x:Key="AssemblyName">System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String> <sys:String x:Key="MyAssemblyName">CustomActivities</sys:String> </Window.Resources> <Grid x:Name="DesignerGrid"> <Grid.ColumnDefinitions> <ColumnDefinition Width="2*" /> <ColumnDefinition Width="7*" /> <ColumnDefinition Width="3*" /> </Grid.ColumnDefinitions> <Border> <sapt:ToolboxControl> <sapt:ToolboxControl.Categories> <sapt:ToolboxCategory CategoryName="Basic"> <sapt:ToolboxItemWrapper AssemblyName="{StaticResource AssemblyName}" > <sapt:ToolboxItemWrapper.ToolName> System.Activities.Statements.Sequence </sapt:ToolboxItemWrapper.ToolName> </sapt:ToolboxItemWrapper> <sapt:ToolboxItemWrapper AssemblyName="{StaticResource MyAssemblyName}"> <sapt:ToolboxItemWrapper.ToolName> CustomActivities.SendMail </sapt:ToolboxItemWrapper.ToolName> </sapt:ToolboxItemWrapper> </sapt:ToolboxCategory> </sapt:ToolboxControl.Categories> </sapt:ToolboxControl> </Border> <Border Name="DesignerBorder" Grid.Column="1" BorderBrush="Salmon" BorderThickness="3" /> <Border x:Name="PropertyGridExpander" Grid.Column="2" /> </Grid> </Window> Figure 47: Designer rehosting XAML

The code to initialize the designer and property grid is shown in Figure 48. The first step, in the OnInitialized method, is to register the designers for all of the activities that will be used in the workflow designer. The DesignerMetadata class registers the associated designers for the activities that ship with framework and the Metadata class registers the designers for my custom activities. Next, create the WorkflowDesigner class and use the View and PropertyInspectorView properties to access the UIElement objects needed to render. The designer is initialized with an empty Sequence, but you could also add menus and load the workflow XAML from a variety of sources including the file system or a database. public MainWindow() { InitializeComponent(); } protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); RegisterDesigners(); PlaceDesignerAndPropGrid(); } private void RegisterDesigners() { new DesignerMetadata().Register(); new CustomActivities.Presentation.Metadata().Register(); } private void PlaceDesignerAndPropGrid() { WorkflowDesigner designer = new WorkflowDesigner(); designer.Load(new System.Activities.Statements.Sequence()); DesignerBorder.Child = designer.View; PropertyGridExpander.Child = designer.PropertyInspectorView; } Figure 48: Designer rehosting code With this little bit of code and markup, you can edit a workflow, drag and drop activities from the toolbox, configure variables in the workflow and manipulate arguments on the activities. You can also get the text of the XAML by calling Flush on the designer, then referencing the Text property of the WorkflowDesigner. There is much more you can do, and getting started is much easier than before. Workflow Services As mentioned previously, one of the large areas of focus in this release of Windows Workflow Foundation is tighter integration with Windows Communication Foundation. The example in the activity walkthrough showed how to call a service from a workflow, and in this section I will discuss how to expose a workflow as a WCF service. To begin, create a new project and select the WCF Workflow Service project. Once the template is complete, you will find a project with a web.config file and a file with a XAMLX extension. The XAMLX file is a declarative service or workflow service and like other WCF templates, provides a functioning, albeit simple, service implementation that receives a request and returns a response. For this example, I will change the contract to create an operation to receive an expense report and return an indicator that the report has been received. To start, add an ExpenseReport class to the project like the one shown in Figure 49. [DataContract] public class ExpenseReport {

[DataMember] public DateTime FirstDateOfTravel { get; set; } [DataMember] public double TotalAmount { get; set; } [DataMember] public string EmployeeName { get; set; } } Figure 49: Expense report contract type Back in the workflow designer, change the type of the "data" variable in the variables to the ExpenseReport type just defined (you may need to build the project for the type to show up in the type picker dialog). Update the Receive activity by clicking the Content button and changing the type in the dialog to the ExpenseReport type to match. Update the activity properties to define a new OperationName, and ServiceContractName as shown in Figure 50.

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

Figure 51: Contract generated from workflow service Once you have created a workflow service, you need to host it, just as you would with any WCF service. The previous example used the simplest option for hosting: IIS. To host a workflow service in your own process, you will want to use the WorkflowServiceHost class found in the System.ServiceModel.Activities assembly. Note that there is another class by this same name in the System.WorkflowServices assembly which is used for hosting workflow services built using the WF3 activities. In the simplest case of self-hosting, you can use code like that shown in Figure 52 to host your service and add endpoints. WorkflowServiceHost host = new WorkflowServiceHost(XamlServices.Load("ExpenseReportService.xamlx"), new Uri("http://localhost:9897/Services/Expense")); host.AddDefaultEndpoints(); host.Description.Behaviors.Add( new ServiceMetadataBehavior { HttpGetEnabled = true }); host.Open(); Console.WriteLine("Host is open"); Console.ReadLine(); Figure 52: Self-hosting the workflow service If you want to take advantage of the managed hosting options in Windows, you can host your service in IIS by copying the XAMLX file into the directory and specifying any necessary configuration information for behaviors, bindings, endpoints, etc. in the web.config file. In fact, as seen previously, when you create a new project in Visual Studio using one of the Declarative Workflow Service project templates, you will get a project with a web.config file and the workflow service defined in a XAMLX, ready to deploy. When using the WorkflowServiceHost class to host your workflow service, you still need to be able to configure and control the workflow instances. To control the service there is a new standard endpoint called the WorkflowControlEndpoint. A companion class, WorkflowControlClient, provides a pre-built client proxy . You can expose a WorkFlowControlEndpoint on your service, and use the WorkflowControlClient to create and run new instances of workflows, or control running workflows. You use this same model to manage services on the local machine, or you can use it to manage services on a remote machine. Figure 53 shows an updated example of exposing the workflow control endpoint on your service.

WorkflowServiceHost host = new WorkflowServiceHost("ExpenseReportService.xamlx", new Uri("http://localhost:9897/Services/Expense")); host.AddDefaultEndpoints(); WorkflowControlEndpoint wce = new WorkflowControlEndpoint( new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/Expense/WCE")); host.AddServiceEndpoint(wce); host.Open(); Figure 53: Workflow Control Endpoint Because you are not creating the WorkflowInstance directly, there are two options for adding extensions to the runtime. The first is that you can add some extensions to the WorkflowServiceHost and they will initialize correctly with your workflow instances. The second is using configuration. The tracking system, for example, can be entirely defined in the application configuration file. You define the tracking participants and the tracking profiles in the configuration file, and using a behavior, you apply a particular participant to a service as shown in Figure 54. <system.serviceModel> <tracking> <participants> <add name="EtwTrackingParticipant" type="System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" profileName="HealthMonitoring_Tracking_Profile"/> </participants> <profiles> <trackingProfile name="HealthMonitoring_Tracking_Profile"> <workflow activityDefinitionId="*"> <workflowInstanceQuery> <states> <state name="Started"/> <state name="Completed"/> </states> </workflowInstanceQuery> </workflow> </trackingProfile> </profiles> </tracking>. . . <behaviors> <serviceBehaviors> <behavior name="SampleTrackingSample.SampleWFBehavior"> <etwTracking profileName=" HealthMonitoring_Tracking_Profile" />

</behavior> </serviceBehaviors> </behaviors> Figure 54: Configuring tracking for services There are many new improvements to hosting and configuration for WCF services that you can take advantage of in your workflows such as ".svc-less" services, or services that don't need a file extension, default bindings and default endpoints just to name a few. For more information on these and other features in WCF4, refer the companion paper on WCF found in the Additional Resources section. In addition to hosting improvements, Windows Workflow Foundation takes advantage of other features in WCF; some directly and others indirectly. For example, message correlation is now a key feature in building services which allows you to relate messages to a given workflow instance based on data in the message such as an order number or employee id. Correlation can be configured on the various messaging activities for both the request and response and supports correlating on multiple values in the message. Again, more details on these new features can be found in the companion paper on WCF4.

Vous aimerez peut-être aussi