CHAPTER 3

image

Windows Workflow Activities

A workflow models a business process and coordinates the flow of work to be performed, but it is the role of the workflow’s activities to implement the actual execution of that work. A WF activity provides the most basic unit of work for building execution logic through code and provides a consistent approach rather than just defining logic with standard code.

Chapter 2 covered a couple of activities just to get you familiar with how they work, but if you are used to writing code, you will have noticed that most of the activities implemented many of the same logical patterns as code constructs. The only difference is that activities provide a more declarative way of implementing logic because they visually represent the way they are to be used within a workflow. And just like code, activities are re-usable in the sense that once they are defined, they can be used to model behavior for more than one workflow.

Activities also have an exception handling very similar to how programming languages handle exceptions. I also mentioned that the WF activity is the basic building block for defining a workflow, so understanding activities allows developers to express custom workflows that target specific business processes. This chapter focuses on understanding the different types of WF activities, how they are defined, and how they can be used for building workflows for modeling complex business process scenarios. I will also cover how to build custom activities that are built to be domain specific, meaning that they are geared to focus on a certain type of business model. See Figure 3-1.

9781430243830_Fig03-01.jpg

Figure 3-1.  Activity hierarchy

Activity Basics

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

Table 3-1. System.Activities.Activity

Namespace Description
System.Activities.CodeActivity Mimics WF3.x base activity. Simple execution of code for an activity to execute within a workflow.
System.Activities.NativeActivity Provides access to the WF runtime.
System.Activities.DynamicActivity Creates an “on-the-fly” activity development experience.
System.Activities.AsyncCodeActivity Layers on asynchronous coding model in .Net 4.0 for processing execution of activities asynchronous.

Activities can be built to take advantage of one of the namespaces represented in Table 3-1. Here are the details:

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

Although most of the activities defined will inherit from System.Activities.Activity and will be defined using the workflow designer, other custom activities will derive from the base classes, such as:

  • CodeActivity
  • NativeActivity
  • AsyncCodeActivity

Data Management

The concept for how WF activities process data is similar to regular code. Activities require the following:

  • Variables
  • Arguments
  • Expressions

Variables

Just as a method written in code can have variables defined within it, activities also have variables that they use. Variables are used as placeholders for holding temporary data that can be used as part of the overall results for executing business logic. Variables contain a variable type that represents the type of data that it will house and an optional default value that can be used in case there is no value passed into the variable.

One of the key benefits WF4 added to variables is scope availability. A variable’s activity scope determines what activities can reference the variable within a workflow. Figure 3-2 demonstrates how two different variables, variable1 and variable2, can have limited scope within an activity by using the Scope drop-down menu and selecting the desired parent activity. You will notice that variable2 has a scope of the Sequence activity, which is the container for the Pick activity; however, if the variable is not needed in both branches of the Pick activity, it would be better to reduce the variables scope like the other variable, variable2, which only gives scope to Branch2 within the Pick activity rather than the entire Sequence activity.

9781430243830_Fig03-02.jpg

Figure 3-2.  Variable scoping

Arguments

Arguments are used for receiving and returning data to the WF runtime. Arguments provide functionality within a workflow much like a function coding construct. Data can be passed into a function, and a function can also return data back to the line of code that called it. When workflows expect to receive data as input, the argument is defined as an InArgument object. When data is returned from a workflow, an OutArgument object is used, and when data needs to be received and returned within the same argument for a workflow, an InOutArgument object is used. Therefore, WF arguments can be one of three types of directions within an activity:

  • In: Sends an argument into an activity through the WF runtime.
  • Out: Returns an argument value set within an activity to the WF runtime.
  • In/Out: Works like a referenced value that can be passed into an activity, set with a value, and then returned back from the activity to the WF runtime.

Arguments also have a type attribute, just as variables do, that represents the data that they can hold as well as a default value (which is optional). The tab towards the bottom of the WF designer reveals how to access the arguments for a workflow. Figure 3-3 demonstrates three different string arguments that can be used within the workflow.

9781430243830_Fig03-03.jpg

Figure 3-3.  Variable scoping

Expressions

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

As you read further, you will see how variables, arguments, and expressions assist in activity execution through the activities in the chapters.

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

Activity Life Cycle

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

9781430243830_Fig03-04.jpg

Figure 3-4.  Activity lifecycle

Authoring Activities

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

Imperative Code

The real power of WF is designing workflows declaratively using the WF designer, but workflows can also be declared strictly through code.

Listing 3-1 demonstrates the code for implementing a basic WriteLine activity by instantiating the variable wfActivity, declared as a System.Activities.Activity and setting it to a new WriteLine activity. The WriteLine activity has a Text property that is used to write “Hello from Workflow” to a console window. Finally, WorkflowInvoker.Invoke is used to call the wfActivity just as a simple method call.

Listing 3-1.  Simple Hello from Workflow

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Activities.Statements;
namespace Apress.Example.Chapter3
{
   public partial class ImperativeCodeWorkflow
    {
       public void SimpleHelloWorld()
       {
           Activity wfActivity = new WriteLine
            {
                Text = "Hello from Workflow."
            };
           WorkflowInvoker.Invoke(wfActivity);
       }
    }
}

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

Listing 3-2.  Declaring Variables

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Activities.Statements;
using Microsoft.VisualBasic.Activities;
namespace Apress.Example.Chapter3
{
    public partial class ImperativeCodeWorkflow
    {
        public void AdditionActivity()
        {
            Variable<int> Add1 = new Variable<int>
            {
                Name = "Add1",
                 Default = 5
            };
            Variable<int> Add2 = new Variable<int>
            {
                Name = "Add2",
                Default = 5
            };
            Variable<int> Sum = new Variable<int>
            {
                Name = "Sum"

            };

  • Add1: Default value of 5
  • Add2: Default value of 5
  • Sum: Holds the calculated value of Add1 + Add2

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

Listing 3-3.  Sequence Activity in Code

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

This code can be called simply using the following code:

var imperativeCode = new ImperativeCodeWorkflow();
imperativeCode.AdditionActivity();

Dynamic Activities

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

Table 3-2. Important DynamicActivity Properties

Properties Description
DisplayName Used primarily as metadata for describing the activity. Inherited from System.Activities.Activity.
Implementation Abstracts out a way to get or set the activities that make up the business logic executed for the activity.
Name Name of the activity that the WF designer displays.
Properties Sets or gets properties used as arguments for the activity. Takes a collection of type System.Collections.ObjectModel.KeyedCollection(Of String, DynamicActivityProperty)

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

Listing 3-4.  Sequence Activity in Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Activities.Statements;
using Microsoft.VisualBasic.Activities;
using System.Activities.Expressions;
namespace Apress.Example.Chapter3
{
    public partial class DynamicCodeActivity
    {
        public Activity AdditionActivity()
        {
            var argAdd1 = new InArgument<int> ();
            var argAdd2 = new InArgument<int> ();
            var AdditionResult = new OutArgument<int> ();
            Variable<int>varAdd1=new Variable<int>
            {
                Name = "varAdd1",
                 Default = 5
            };
            Variable<int>varAdd2 = new Variable<int>
            {
                Name = "varAdd2",
                Default = 5
            };

The implementation for the workflow activity is pretty much the same as the addition activity from Listing 3-3, except that arguments can now be passed into the activity externally so that any two numbers can be added together through the hosted WF runtime. After the activity is instantiated, the following properties are set for the activity:

  • DisplayName
  • Properties

Listing 3-5 demonstrates adding the DisplayName property, “Add two Integers”, which the WF runtime will use to associate the activity to any exceptions that may occur. There are also two arguments of type InArgument, argAdd1 and argAdd2, and one OutArgument type for returning data back to the WF runtime that are added using the DynamicActivityProperty . The name given to each argument is important because this is what the WF runtime will use for referencing the arguments with the activity.

Listing 3-5.  Instantiatingthe DynamicActivity

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

After the Properties property for the activity has been set, the next property that needs to be set for defining the execution logic for the activity is Implementation.

The Implementation property creates a new Sequence activity with two new variables, varAdd1 and varAdd2, that were defined in Listing 3-4. The Sequence activity adds three Assign activities and one WriteLine activity to its Activities property, which represents included child activities. The first Assign activity assigns the variable varAdd1 to the value passed into the activity through the Add1 InArgument. The second Assign activity assigns the variable varAdd2 to the value also passed into the activity through the Add2 InArgument. The last Assign activity assigns the value generated by the expression executed for adding the two set variables together. The value is assigned to the OutArgument so it can be returned as output from the activity. Finally, the WriteLine activity simply confirms the addition logic that was executed by showing the values for the arguments that were passed in and the argument value that is passed back out from the activity. See Listing 3-6.

Listing 3-6.  Implementing the Dynamic Activity

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

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

Listing 3-7.  Executingthe Dynamic Activity

using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
namespace Apress.Example.Chapter3
{
    class Program
    {
        static void Main(string[] args)
        {
            CallDynamicAdditionActivity();
        }
        private static void CallDynamicAdditionActivity()
        {
            var dynamicActivity = new DynamicCodeActivity();
            var actAddition = dynamicActivity.AdditionActivity();
            var result = WorkflowInvoker.Invoke(actAddition,
                new Dictionary<string, object>{ { "argAdd1", 3 }, { "argAdd2", 5 } });
            Console.WriteLine(string.Format("The workflow returned {0}", result["argAdditionResult"]));
            Console.ReadKey();
            var imperativeCode = new ImperativeCodeWorkflow();
            imperativeCode.AdditionActivity();
        }
    }
}

The dynamic activity returned is then executed by executing WorkflowInvoker.Invoke and passing in two arguments, argAdd1 and argAdd2, that were defined in Listing 3-5. The integers that are passed in will be added together. Notice the Dictionary object’s signature <string, object> that is used for passing arguments into workflows. This string part of the signature is used to set the name of the arguments, which must be known so the arguments can be sent to the activity. The object part of the signature sets the values 3 and 5 which are hard-coded for demonstration purposes; however, they are outside of the implementation for the activity. These values can be set through user input or read in from an outside source, making this activity re-usable for logic that adds two integers together and returns a sum.

image Tip   There might be some confusion between building workflows through imperative code and defining dynamic activities through code. Workflows built through imperative code do not have the flexibility of dynamic activities because they are hard-coded for wiring up of properties and arguments. Dynamic activities provide dependency injection by allowing arguments to be defined outside of the scope of the activities execution.

XAML

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

ActivityXamlServices is a static object that allows XML to be loaded and returns an Activity type that can be processed as if the workflow activity was created through code, but without the workflow having to be compiled. Figure 3-5 demonstrates how a workflow can be created using the WF designer declaratively that models the dynamic activity authored through code.

9781430243830_Fig03-05.jpg

Figure 3-5.  Adding a sequence activity and arguments

The first activity that is added to the WF designer is a Sequence activity that will serve as the container for the other child activities (see Figure 3-5). The workflow also uses the three arguments found in Listing 3-6.

There are also the two variables that will be used for executing the logic for adding the two input arguments (see Figure 3-6).

9781430243830_Fig03-06.jpg

Figure 3-6.  Adding WF variables

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

9781430243830_Fig03-07.jpg

Figure 3-7. Complete workflow

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

Listing 3-8.  XAML for the Custom Addition Activity

<Activity mc:Ignorable="sap sap2010 sads" x:Class= "Apress.Chapter3 .Activity1"
xmlns=" http://schemas.microsoft.com/netfx/2009/xaml/activities "
xmlns:mc=" http://schemas.openxmlformats.org/markup-compatibility/2006 "
xmlns:mca="clr-namespace:Microsoft.CSharp.Activities;assembly= System.Activities"
xmlns:sads=" http://schemas.microsoft.com/netfx/2010/xaml/activities/debugger "
xmlns:sap=" http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation "
xmlns:sap2010=" http://schemas.microsoft.com/netfx/2010/xaml/activities/presentation "
xmlns:scg="clr-namespace:System.Collections.Generic;assembly= mscorlib"
xmlns:sco = "clr-namespace:System.Collections.ObjectModel;assembly = mscorlib"
xmlns:x = " http://schemas.microsoft.com/winfx/2006/xaml ">
  <x:Members>
    <x:Property Name = "argAdd1" Type = "InArgument(x:Int32)" />
    <x:Property Name = "argAdd2" Type = "InArgument(x:Int32)" />
    <x:Property Name = "argAdditionResult" Type = "OutArgument(x:Int32)" />
  </x:Members>

<sap2010:ExpressionActivityEditor.ExpressionActivityEditor> C#</sap2010:ExpressionActivityEditor.ExpressionActivityEditor>
   <sap2010:WorkflowViewState.IdRef> Apress.Chapter3 .Activity1_1</sap2010:WorkflowViewState.IdRef>
   <TextExpression.NamespacesForImplementation>
     <sco:Collection x:TypeArguments = "x:String">
            <x:String> System</x:String>
            <x:String> System.Collections.Generic</x:String>
            <x:String> System.Data</x:String>
            <x:String> System.Linq</x:String>
            <x:String> System.Text</x:String>
     </sco:Collection>
   </TextExpression.NamespacesForImplementation>
   <TextExpression.ReferencesForImplementation>
     <sco:Collection x:TypeArguments = "AssemblyReference">
        <AssemblyReference> Microsoft.CSharp</AssemblyReference>
        <AssemblyReference> System</AssemblyReference>
        <AssemblyReference> System.Activities</AssemblyReference>
        <AssemblyReference> System.Core</AssemblyReference>
        <AssemblyReference> System.Data</AssemblyReference>
        <AssemblyReference> System.Runtime.Serialization</AssemblyReference>
        <AssemblyReference> System.ServiceModel</AssemblyReference>
        <AssemblyReference> System.ServiceModel.Activities</AssemblyReference>
        <AssemblyReference> System.Xaml</AssemblyReference>
        <AssemblyReference> System.Xml</AssemblyReference>
        <AssemblyReference> System.Xml.Linq</AssemblyReference>
        <AssemblyReference> mscorlib</AssemblyReference>
        <AssemblyReference> Apress.Chapter3 </AssemblyReference>
     </sco:Collection>
   </TextExpression.ReferencesForImplementation>
   <Sequence sap2010:WorkflowViewState.IdRef = "Sequence_1">
     <Sequence.Variables>
       <Variable x:TypeArguments = "x:Int32" Default = "5" Name = "varAdd1" />
       <Variable x:TypeArguments = "x:Int32" Default = "5" Name = "varAdd2" />
     </Sequence.Variables>
     <Assign>
       <Assign.To>
         <OutArgument x:TypeArguments = "x:Int32">
            <mca:CSharpReference x:TypeArguments = "x:Int32"> varAdd1</mca:CSharpReference>
         </OutArgument>
       </Assign.To>
       <Assign.Value>
         <InArgument x:TypeArguments = "x:Int32">
            <mca:CSharpValue x:TypeArguments = "x:Int32"> argAdd1</mca:CSharpValue>
         </InArgument>
        </Assign.Value>
    <sap2010:WorkflowViewState.IdRef> Assign_1</sap2010:WorkflowViewState.IdRef>
     </Assign>
     <Assign>
       <Assign.To>
         <OutArgument x:TypeArguments = "x:Int32">
           <mca:CSharpReference x:TypeArguments = "x:Int32"> varAdd2</mca:CSharpReference>
         </OutArgument>
       </Assign.To>
         <Assign.Value>
         <InArgument x:TypeArguments = "x:Int32">
           <mca:CSharpValue x:TypeArguments = "x:Int32"> argAdd2</mca:CSharpValue>
         </InArgument>
       </Assign.Value>
   <sap2010:WorkflowViewState.IdRef> Assign_2</sap2010:WorkflowViewState.IdRef>
</Assign>
<Assign>
       <Assign.To>
         <OutArgument x:TypeArguments = "x:Int32">
           <mca:CSharpReference x:TypeArguments = "x:Int32"> argAdditionResult</mca:CSharpReference>
         </OutArgument>
       </Assign.To>
       <Assign.Value>
         <InArgument x:TypeArguments = "x:Int32">
           <mca:CSharpValue x:TypeArguments = "x:Int32"> varAdd1 + varAdd2</mca:CSharpValue>
         </InArgument>
       </Assign.Value>
   <sap2010:WorkflowViewState.IdRef> Assign_3</sap2010:WorkflowViewState.IdRef>
</Assign>
<WriteLine>
         <InArgument x:TypeArguments = "x:String">
           <mca:CSharpValue x:TypeArguments = "x:String"> string.Format("The sum of {0} and {1} is {2}",
           varAdd1,varAdd2,argAdditionResult)</mca:CSharpValue>
         </InArgument>
   <sap2010:WorkflowViewState.IdRef> WriteLine_1</sap2010:WorkflowViewState.IdRef>
</WriteLine>

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

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

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

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

9781430243830_Fig03-08.jpg

Figure 3-8.  Activity processed as XAML

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

Testing Activities

Let’s now explore how to make sure the WF activities authored will work before they are actually added into a workflow. Unit testing is a methodology commonly used by developers for testing their code before it is implemented within a solution. Unit testing WF activities was a challenge before WF4. However, in in WF4 you can use the WorkflowInvoker to execute an activity, the same way a C# method is executed through code. Visual Studio also comes with a template for a test project (see Figure 3-9) that can be added to a Visual Studio solution.

9781430243830_Fig03-09.jpg

Figure 3-9.  Adding a test project

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

Listing 3-9.  Default Unit Test Code

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

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

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

9781430243830_Fig03-10.jpg

Figure 3-10.  Test project System.Activities reference

As code is being tested, there are ways to confirm different types of results to make sure the right results are returned or not returned. Anticipating different results requires different types of tests. The most common are the following:

  • Positive Testing: Test that proves that code does work.
  • Negative Testing: Test that proves that code does not work. An example is testing a scenario where a file is required so code can read its internals, and testing the scenario for what happens when the file is available.
  • Regression Tests: Goes through a sequence of tests verifying that as a unit each test within the sequence works.

To verify that tests are returning the correct results, it is important to use an Assert statement to confirm certain result patterns. Table 3-3 illustrates the different types of Asserts that are available.

Table 3-3. Assert Types

Assert Types Description
Assert Provides methods for verifying a pass/fail result.
Collection Assert Used for testing collections of objects.
StringAssert Provides methods for testing strings.
AssertFailedException Exception thrown when a test fails.
AssertInconclusiveException Exception thrown when a test result is inconclusive or when the results cannot be defined as a pass or fail.

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

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

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

[TestMethod]
        public void TestMethod1()
        {
     var act = ActivityXamlServices.Load(@"C:ApressChapter3SolutionApress.Example.Chapter3Apress.Example.Chapter3inDebugWorkflow1.xaml");
            var retArg = WorkflowInvoker.Invoke(act, new Dictionary <string, object>
            {
                { "argAdd1", 3 },
                { "argAdd2", 5 }
            });
            var result = Convert.ToInt32(retArg["AdditionResult"]);
            Assert.AreEqual(result, 3 + 5);
        }

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

9781430243830_Fig03-11.jpg

Figure 3-11.  Test results

Communicating with Activities

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

Bookmarks

Bookmarks allow event-driven communication to occur to an activity within a workflow, from an outside source, using the WF runtime as the channel of communication. Bookmarks were introduced in WF4 to settle the complexity around defining what WF3.x referred to as “external events.” The concept of bookmarks is pretty simple as it closely resembles how a real bookmark works for keeping track of what page of a book was the last to be read.

Bookmarks in WF apply the same meaning except that a bookmark in WF holds the last place a workflow executed, usually because the workflow is waiting on some external event to happen so it can start back up. The cool thing about using bookmarks, though, is when a workflow stops and waits for an external event to fire (for example, a manager needs to approve a work order), the workflow is considered idle and can therefore be persisted within SQL Server. Then a day later, when the manager decides he or she is ready to approve the work order, the workflow is loaded back into memory and the workflow picks up exactly where it left off, which is from the bookmark.

There is no out-of-box activity for handling bookmarks, but one can easily be created that handles most of the functionality needed for implementing a bookmark. Listing 3-10 demonstrates how to build a custom activity that works with bookmarks within a workflow. The WaitForResponse activity inherits from NativeActivity <TResult>, which allows any object to be returned from the WF runtime using a Bookmark. Going back to the work order scenario, once the manager approves the work order, a work order object is returned back through the WF runtime to the Bookmark so the work order can continue to be processed. The most important information that defines a Bookmark object is a bookmark’s name. The name is used to reflect on a Bookmark from the WF runtime. Instead of building a bookmark activity for every Bookmark needed within a workflow, building an activity that handles bookmarks is a generic approach, so the name of the Bookmark can be defined through code or using the WF designer rather than a hard-coded value. Once a Bookmark is created, it can pass with it a .NET object, which can be used as data and processed within a workflow. The ResponseName property is used to define the actual bookmark name, so that the WF runtime can associate with the same name to correspond with the activity for external events (see Figure 3-12).

Listing 3-10.  Bookmark Activity

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
namespace Apress.Example.Chapter3
{
    public sealed class WaitForResponse<TResult> : NativeActivity< TResult>
    {
        public WaitForResponse()
            : base()
        {
        }
        public string ResponseName { get; set; }
        protected override bool CanInduceIdle
        { //override when the custom activity is allowed to make he workflow go idle
            get
            {
                return true;
            }
        }
        protected override void Execute(NativeActivityContext context)
        {
            context.CreateBookmark(this.ResponseName, new BookmarkCallback(this.ReceivedResponse));
        }
        void ReceivedResponse(NativeActivityContext context, Bookmark bookmark, object obj)
        {
            this.Result.Set(context, (TResult)obj);
        }
    }
}

9781430243830_Fig03-12.jpg

Figure 3-12.  WaitForResponse Bookmark Activity

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

9781430243830_Fig03-13.jpg

Figure 3-13.  WaitForResponse Bookmark activity

Once the WaitforResponse activity is configured with ResponseName (name of the Bookmark) and Result (usually a variable within the scope of the activity for the object that is passed with the bookmark), a Bookmark is set and ready to receive an external event. Recall that bookmarks cannot be used with the WF host, WorkflowInvoker.Invoke. Instead, the WorkflowApplication host provides a ResumeBookmark method, which is called for initiating an external event from the hosted WF runtime. When ResumeBookmark is called, two arguments can be passed, indicating the following:

  • Bookmark Name: Name of the bookmark set as the ResponseName of the WaitforResponse activity.
  • Object: Value passed in that will be set within the Result property of the WaitforResponse activity. This is important because the Bookmark starts the execution of the workflow up again; therefore the input provided from the Bookmark for the workflow should be used within processing the workflow going forward.

Implementing Activities

The activities that come with WF are considered the out-of-box activities, so getting familiar with how to use them is crucial for building workflows within WF. As mentioned, most of the workflows you will build will be authored from out-of-box activities, and that is primarily because they mimic the same logical patterns as code constructs. This section of the chapter and the chapters following will walk through labs to demonstrate concepts for using the provided activities within WF. This way there are different examples to reference while trying to implement WF activities within custom workflows. Most of the activities demonstrated in this chapter will be from the Primitives category for building a foundation for using the out-of-box activities; however, later chapters will demonstrate more advanced activities. Let’s get started by creating a new Visual Studio 2011 solution.

  1. Open Visual Studio 2012, and create a new Project.
  2. Select the Workflow template to see a list of installed workflow templates.
  3. Select Workflow Console Application, and name it Chapter3.Activities.

This project will be used for walking through the rest of the labs in this chapter.

Debugging Activities

After activities have been thoroughly unit tested, workflows can be built using the tested activities and executed, but developers will also want to step through workflow’s to debug them even further and before they go into production. WF has first-class experience similar to debugging C# code with using breakpoints, except the breakpoints are applied to activities within a workflow rather than lines of code. Breakpoints can be set on an activity, and once the activity receives scope, the workflow execution pauses on the breakpoint. Execution can then be controlled for step-by-step interaction for visually seeing the execution pattern of execution for the workflow.

WF also has a handy out-of-the-box activity called WriteLine, which is great for debugging. The WriteLine activity writes custom information to a console, which can also be used for understanding activity flow. There is also a monitoring extension called Tracking that can be added to the WF runtime to monitor custom information on a workflow; however, WF tracking has its own dedicated chapter later within the book.

DEBUGGING ACTIVITIES

This lab walks through a simple workflow that is used to demonstrate debugging a workflow using breakpoints and WriteLine activities. The workflow will loop 10 times, each time grabbing a different random number between 0–10. A condition will check if the random number is greater or less than 5. There will be various places where breakpoints will be applied and WriteLine activities will be used to indicate the paths for each random number that is generated.

To walk through these activities, use the new solution called Chapter3.Activities created in Visual Studio 2012.

  1. Open the solution Chapter3.Activities.
  2. Add another Workflow Console Application project to it and name it Chapter3.Activities.Debugging.
  3. Open the Program.cs file and add Console.ReadKey() underneath WorkflowInvoker.Invoke(workflow1);
  4. Open up the Workflow1.xaml and drag a DoWhile activity to the designer canvas.
  5. Create two new variables, one called varRandomNumber and the other called varCounter. Both of these variable types will be Int32. Set the default value for varCounter to 0. Set the scope for the variable varRandomNumber to Sequence (see Figure 3-14).

    9781430243830_Fig03-14.jpg

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

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

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

    9781430243830_Fig03-15.jpg

    Figure 3-15.  Confusing scope

  7. Drag an Assign activity to the WF designer and place it within the body of the DoWhile activity. Assign the varRandomNumber variable to a random number each time the DoWhile executes a loop by setting the assign To property to varRandomNumber  and the Value property to new Random().Next(0,10).
  8. Drag an If activity to the WF designer and place it beneath the Assign activity. Set the Condition property to varRandomNumber <5. The condition will check whether the random number returned less or greater than 5.
  9. To make sure that random numbers are being created, place a breakpoint on the Assign activity by right-clicking the activity Breakpoint> Insert Breakpoint (see Figure 3-16).

    9781430243830_Fig03-16.jpg

    Figure 3-16.  Adding an activity breakpoint

  10. Drag another Assign activity the WF designer and place it beneath the If activity. Increase the varCounter variable by 1 each time the DoWhile activity executes a loop by setting the assign To property to varCounter  and the Value property to varCounter + 1.

    Right-click on the project and click Build. After everything builds correctly, press F5 to run the workflow. As the workflow runs, it will pause and highlight the Assign activity, which contains the breakpoint. In this debugging state, the Locals window loads the resources from the workflow and the Immediate window is able to retrieve resources on the workflow as well. Pressing F10 will step through the rest of the workflow 10 more times, getting a total of 10 random numbers. Pressing F5 will process each of the 10 iterations. This demonstrates how to add breakpoints within a workflow, but using the WriteLine activity in conjunction with breakpoints helps with the debugging a workflow even more. See Figure 3-17.

    9781430243830_Fig03-17.jpg

    Figure 3-17.  Debugging an activity using a breakpoint

  11. Drag a WriteLine activity to the WF designer and add it within the Then container of the If activity. Set the Text property to string.Format("Random number {0} is less than 5",varRandomNumber).
  12. Drag another WriteLine activity to the WF designer and add it within the Else container of the If activity. Set the Text property to string.Format("Random number {0} is greater than 5",varRandomNumber).
  13. Right-click on the project and click Build. After everything builds correctly, press F5 to run the workflow. Each time the debugger breaks on the Assign activity, press F5 to finish the executing iteration.

    Each iteration will write information to the console screen about the random number that was selected and the path of execution performed by the If activity. The problem is that the breakpoint is not showing execution on the WriteLine activities. Breakpoints need to be added to each of the WriteLine activities. See Figure 3-18.

    9781430243830_Fig03-18.jpg

    Figure 3-18.  Debugging flow using WriteLine activities

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

    9781430243830_Fig03-19.jpg

    Figure 3-19.  Removing a breakpoint

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

    9781430243830_Fig03-20.jpg

    Figure 3-20.  Breakpoints on WriteLine activities



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

Error Handling

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

Listing 3-11.  Try, Catch, Finally

private static void DoSomeThing()
        {
            try
            {
            }
            catch (Exception ex)
            {
            }
            finally
            {
            }
        }

Listing 3-11 shows an example for how to handle exceptions in code, and its verbiage pretty much describes how the try, catch and finally blocks are executed.

  1. Code is executed within the try block.
  2. If an exception is thrown within the try block, the exception is sent or caught within the catch block so it can be managed.
  3. The finally block is the last section of code that gets executed, regardless if an exception is thrown or not. This is a great place to add code that either needs to release resources like memory or to close connections to external sources like databases or files.

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

WF is declarative, so in order to implement the same functionality for handling exceptions, WF uses out-of-box activities to declaratively add exception management. There are three activities that are used for handling errors with in WF:

  • Rethrow
  • Throw
  • TryCatch

By default, a workflow will throw an error that will bubble up to the application hosting the workflow, as long as it is running the same execution thread. So using WorkflowInvoker.Invoke to host a workflow that has an unmanaged exception will have the exception bubbled up to the hosting application; however, using WorkflowApplication, which runs asynchronously to its hosted application, will not receive the error unless the hosted application subscribes to the WF runtime’s OnUnhandledException event.

HANDLING EXCEPTIONS IN WF

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

  1. Open the solution Chapter3.Activities that you have been using for the other activities
  2. Add another Workflow Console Application project to it and name it Chapter3.Activities.ErrorHandling.
  3. Right-click on the project and add a new item.
  4. Click on the installed template Workflow and add a new code activity, naming it ExceptionActivity. All this activity is going to do is throw an exception, so remove it like so:
    public InArgument <string> Text {get; set; }

          and

    string text = context.GetValue(this.Text);
  5. Within the Execute method, add throw new Exception("Here is an exception"); . The ExceptionActivity code should look like this:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Activities;
    namespace Apress.Example.Chapter3
    {
        public sealed class ExceptionActivity : CodeActivity
        {
            protected override void Execute(CodeActivityContext context)
            {
                throw new Exception("Here is an exception");
            }
        }
    }
  6. Right-click on the project and click Build. This will build the new ExceptionActivity.
  7. Double-click on the Workflow.xaml, and you will notice the ExceptionActivity is now added within the ToolBox under the solution.
  8. Drag and drop the ExceptionActivity on the WF designer for the workflow. See Figure 3-21.

    9781430243830_Fig03-21.jpg

    Figure 3-21.  Exception Activity


    The project is ready to run. The Program.cs contains code that will automatically invoke Workflow1.xaml, but first add a Try/Catch block and a Console.ReadKey(), after the WorkflowInvoker call. The catch block will catch the error within the workflow hosted application.
    using System;
    using System.Linq;
    using System.Activities;
    using System.Activities.Statements;
    namespace Chapter3.Activities
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    Activity workflow1 = new Workflow1();
                    WorkflowInvoker.Invoke(workflow1);
                    Console.ReadKey();
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }
    }
  9. Right-click on the project and click Build. After everything builds correctly, press F5 to run the workflow using the console application as the host. The debugger will break within the ExceptionActivity showing that an exception has occurred. Pressing F10 causes the debugger to break on the error as it bubbles back up, this time within the catch block of the console application hosting the workflow. This simulates what can happen when a workflow fails for an unknown reason. Now let’s prevent this exception from happening.
  10. Stop the application if it is still running and open up the Workflow.xaml file. One of the cool new features of WF4.5 is called Auto surround; basically it handles situations for adding activities to a workflow when there is already an activity, like the ExceptionActivity, and no activity container.
  11. To illustrate Auto surround, drag the TryCatch activity to the WF designer and hover either over or under the ExceptionActivity. The bars indicate that the new activity will be placed either above or below the ExceptionActivity. Dropping the activity under the ExceptionActivity causes a new Sequence activity appear, placing the TryCatch activity directly below the ExceptionActivity and all contained within the Sequence activity.
  12. Drag the existing ExceptionAactivity from below the TryCatch activity and place it within the Try block of the TryCatch activity. The workflow should now look like Figure 3-22.

    9781430243830_Fig03-22.jpg

    Figure 3-22.  TryCatch activity

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

    9781430243830_Fig03-23.jpg

    Figure 3-23.  Catches System.Exception

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

    9781430243830_Fig03-24.jpg

    Figure 3-24.  Handling the error

  16. Click on the Finally block of the TryCatch activity and drag a Writeline activity, placing it within the Finally container of the activity. Set the Text property to “Finally has executed!”
  17. Right-click on the project and click Build. After everything builds correctly, press F5 to run the workflow. You will notice that the debugger still stops on the exception thrown from the ExceptionaActivity, but the exception is not bubbled up to the application hosting the workflow. Instead, the workflow acknowledges that there was an error and sends the message to the console that the workflow has handled the exception.

    Just because exceptions bubble up to the application hosting the workflow does not mean they are not being managed. If the ExceptionActivity produced an exception that needed to provide feedback that invalid data was supplied to the workflow, then the exception might need to be relayed to the application hosting the workflow. Usually System.ApplicationException types of exceptions are used to notify the user of application information within the context of the operation being performed. The next steps will build on current workflow built for handling an exception that needs to be sent to the hosting application.
  18. Open up the ExceptionActivity.cs file and change the line of code from
    throw new Exception("Here is an exception");

    to

    throw new ApplicationException ("Here is an Application Exception");
  19. Right-click on the project and then click Add> New Item> Activity to add a new workflow and name it ApplicationExceptionWorkflow.xaml.
  20. Open the Workflow1.xaml, copy the workflow from the WF designer, and paste it into the ApplicationExceptionWorkflow.xaml.
  21. With the ApplicationExceptionWorkflow.xaml open, add another catch to the Catches block of the activity by clicking on “Add new catch.”
  22. System.ApplicationException is not a selectable choice for the type of Exception to choose so you have to browse for it (see Figure 3-25).

    9781430243830_Fig03-25.jpg

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

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

    9781430243830_Fig03-26.jpg

    Figure 3-26.  Browsing .Net types

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

    9781430243830_Fig03-27.jpg

    Figure 3-27.  Adding the Rethrow activity

  28. Open up the Program.cs file and change
    Activity workflow1 = new Workflow1();

      to
    Activity workflow1 = new ApplicationExceptionWorkflow();

    Add another Catch block above the existing catch block. It needs to be added above the System.Exception catch block because all exceptions inherit from System.Exception, and every exception thrown by the code will always cause this Catch block to fire, unless another catch block above it matching the type of exception fires first.
  29. Add a finally block and add the following code:
    Console.ReadKey();
  30. Right-click on the project and click Build. After everything builds correctly, press F5 to run the workflow. Your code should look like the following:
    using System;
    using System.Linq;
    using System.Activities;
    using System.Activities.Statements;
    namespace Chapter3.Activities
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    Activity workflow1 = new ApplicationExceptionWorkflow();
                    WorkflowInvoker.Invoke(workflow1);
                }
                catch(ApplicationException ex)
                {
                    Console.WriteLine(string.Format("Application Exception --{0}-- has fired!", ex.Message));
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception {0} has been bubbled up!", ex.Message);
                }
                finally
                {
                    Console.ReadKey();
                }
            }
        }
    }


    As the workflow runs, the debugger stops as an ApplicationException is thrown from the ExceptionAactivity. Pressing F10 allows the catch block to accept the exception type of ApplicationException. The WriteLine writes to the console, “Just caught an ApplicationException”, and then the Rethrow activity re-throws the exception to the hosting application. The exception is then caught within the new catch block that also catches ApplicationException types of exceptions, and writes to the console, “Application Exception–Here is an Application Exception!—has fired!

    The Rethrow activity is used for re-throwing an exception within a catch block, but what if you want to throw an error during the execution of a workflow? This is the work that the Throw activity can accomplish. It is probably not too different than the ExceptionActivity you built as a Code activity; the only difference is it allows you to choose the type of Exception that needs to be thrown. These next steps will show how to implement the Throw activity. You will change the workflow into a timer that will alert the console after a preset of seconds has passed.
  31. Open up the ThrowExceptionWorkflow.xaml and add a new WF variable, and set its scope to the TryCatch activity. This will allow all activities access to the variable. Name the variable varSeconds and set its type to Int32.
  32. Add another variable and name it varApplicationException. Select the drop-down for the variable type and select “Browse for Types” to browse for the System.Application type. Once you find it, select it and then set its scope to the TryCatch activity. This variable will hold the exception that will be thrown.
  33. Add a new WF argument and name it argInSeconds. Make sure the direction is set to In and it’s type is Int32.
  34. Drag an If activity onto the WF designer and add the following condition:
    argInSeconds> 0.
  35. Drag the ExceptionActivity and place it in the Else container of the If activity.
  36. Drag a Delay activity and place it in the Then container of the If activity. The Delay activity requires a duration of expiration. Click on the Delay activity and use the property window to set the value for the duration to TimeSpan.FromSeconds(argInSeconds).
  37. Drag an Assign activity and place it right above the Delay activity. Assign the varSeconds variable to the argInSeconds argument by setting the assign To property varSeconds and the Value property argSeconds.
  38. Drag another Assign activity and place it right beneath the Delay activity. Assign the varApplicationException variable to a new ApplicationException that will be alert the console that the number of seconds entered has expired. Set the Assign To property to :varApplicationException and the Value property to new System.ApplicationException(string.Format("{0} seconds has expired! You have been alerted",varSeconds))
  39. Drag a Throw activity and drop it just below the Assign activity just created. Click on the Throw activity and in the property window, click on the ellipse button, and add the following code within the Expression Editor:
    varApplicationException
  40. Click on the catch block that handles the ApplicationException type of exceptions, and delete the Rethrow activity. You do not want this Exception bubbling to the hosting application. Instead you want to write a message to the console to alert that the entered number of seconds is up from when the workflow started.
  41. Click on the Text property of the WriteLine activity and set it to varApplicationException.Message. See Figure 3-28.

    9781430243830_Fig03-28.jpg

    Figure 3-28.  Alert workflow

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

    The extra code added will have a red squiggly under the Dictionary object. This is because the using statement is missing. Press Ctrl and “.” at the same time and the using statement will pop up, allowing it to be selected and added. The number of seconds for how long the workflow will run is set to 5 seconds.
  43. Right-click on the project and click Build. After everything builds correctly, press F5 to run the workflow (see Figure 3-29).

    9781430243830_Fig03-29.jpg

    Figure 3-29.  Success!



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

Summary

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

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.251.57