CHAPTER 9

image

Tracking Workflows

One trade-off when working with a declarative modeling framework (compared to writing traditional code) is the ability to understand each step of execution as the corresponding line of code executes at runtime. Without this insight into how things are being processed, it becomes extremely difficult to manage software and make enhancements for existing business processes, especially when they become large and complex. While working with WF, insight into a workflow’s execution quickly becomes a necessity. Some common concerns that come up while working with WF are

  • How do I know when an activity executes?
  • What decisions were made?
  • How long did a process take?

Windows Workflow tracking is the solution WF provides for answering questions like these. Tracking workflows provides a way for configuring standard workflow events and data that needs to be captured using the WF runtime.

Just as workflows provide transparency into logic that is applied to business processes, WF tracking provides the foundation for tracking a workflow instance as it executes through a workflow. All the way from when a workflow starts to when it finishes or is aborted, there are additional events within each activity within a workflow.

WF tracking comes ready to use out of the box, so there is nothing extra required for implementing it. It just needs to be configured based on the data that is required to be tracked. Each version of WF since its inception has come with a version of tracking, as the original authors of WF saw early on the importance of being able to track and gather important data on how a workflow executes. However, since the release of WF4 significant changes have been made in making the tracking service more efficient and the implementation easier to understand and set up.

Essentially, data pertaining to a workflow’s execution is always being generated from the WF runtime. Tracked data can be subscribed to and filtered based on the necessary data that is required to be collected. After obtaining the workflow instance’s tracked data, it can be stored within a database or data file.

Tracking Overview

When WF was originally released, tracking workflow instance data was automatically stored within SQL Server; this changed when WF4 was released and storing tracked data became more generic. Instead of WF handling where the data is stored, WF4 requires the developer to decide the best option for where tracked workflow instance data should be stored based on a given workflow solution. WF4 does, however, provide tracing of a workflow instance by logging tracked data to the Event Tracing for Windows (ETW), as illustrated within Figure 9-1. ETW was first provided with Windows 2000. Through the years, ETW has been enlisted to instrument internal system event tracing tied to the operating system because ETW is an efficient way for high-velocity tracing. ETW also provides support for tracing events for running applications. ETW can be configured dynamically so that applications being traced are not aware, nor are affected, while making changes about specific data that is required to be traced. The traced data that is sent to the ETW is logged in chronological order.

9781430243830_Fig09-01.jpg

Figure 9-1.  Windows Workflow tracking overview

Tracking Records

The WF runtime emits information about a workflow instance’s execution called a tracking record. There are different types of tracking records that the WF runtime provides; they are represented in Table 9-1.

Table 9-1. Different Types of Tracking Records

Tracking Record Detail
Workflow life cycle Emitted as a workflow instance reaches events for a workflow’s lifecycle.
Activity life cycle Emitted as a workflow instance reaches events for an activity’s life cycle.
Bookmark resumption Emitted when a bookmark is resumed for a workflow instance.
Custom tracking Issued when custom data needs to be tracked within a custom activity.

Each tracking record that is emitted from the WF runtime inherits from the abstract class TrackingRecord. Table 9-2 presents the properties provided with the TrackingRecord object that can be used to define metadata about the event being tracked.

Table 9-2. TrackingRecord Properties

Property Description
Annotations Name/value pairs collection of type IDictionary<string,string> that are added when additional information needs to be supplied.
EventTime Defines a date and time for when a tracking record occurred.
InstanceId Defines the ID that is a System.Guid type for a workflow instance.
Level Defines a System.Diagnostics.TraceLevel type for a tracked event. The level represents the tracking record’s purpose. TraceLevel members include Off, Error, Warning, Info, and Verbose.
RecordNumber Sequential number that represents the order for a generated tracking record.

Workflow Lifecycle

Tracking records are used to give transparency into a workflow instance’s execution path, but it is also important to understand the flow of the workflow’s lifecycle. A workflow’s tracked events include when a workflow instance

  • Is aborted
  • Updates a workflow definition (New in WF4.5)
  • Suspends
  • Terminates
  • Encounters an unhandled exception

Therefore there are tracking records that are emitted to indicate when these events occur.

WorkflowInstanceRecord

When the WF runtime is tracking events within a workflow’s lifecycle, the WF runtime uses the tracking record WorkflowInstanceRecord, which inherit from the TrackingRecord object. Along with properties mentioned in Table 9-2 which are already inherited through TrackingRecord, WorkflowInstanceRecord also includes some additional properties (see Table 9-3).

Table 9-3. Unique WorkflowInstanceRecord Properties

Property Description
ActivityDefinitionId Represents the root activity represented as the workflow that produced the tracking record.
State The current stage of the workflow’s life cycle when the tracking record was created.
WorkflowDefinitionIdentity A new property within WF4.5 of type System.Activities.WorkflowIdentity that represents the Name, Package, and Version for a workflow.

As the state changes for a workflow instance, WorkflowInstanceRecord is emitted from the WF runtime; however, other events within the workflow life cycle trigger additional tracking records to be emitted that inherit from WorkflowInstanceRecord. Each of the tracking records indicated below inherits from WorkflowInstanceRecord, and therefore inherits the properties from Tables 9-2 and 9-3:

  • WorkflowInstanceAbortedRecord is emitted when a workflow instance is aborted. Another property WorkflowInstanceAbortedRecord has is Reason, which is a string type that indicates through the WF runtime why a workflow instance was aborted.
  • WorkflowInstanceSuspendedRecord is emitted when a workflow instance is suspended and also has a Reason property indicating why a workflow instance has been suspended.
  • WorkflowInstanceTerminatedRecord is emitted when a workflow instance is terminated and also has a Reason property indicating why a workflow instance was terminated.
  • WorkflowInstanceUnhandledExceptionRecord is emitted when a workflow instance has encountered an unhandled exception. Instead of having a Reason property, it has an UnhandledException property of type System.Exception indicating the actual exception that was not managed.
  • WorkflowInstanceUpdatedRecord is a new tracking record emitted within WF4.5 and occurs when workflow has been versioned or changed. Three new properties exist on the WorkflowInstanceUpdatedRecord:
  • IsSuccessful is a Boolean value indicating if the updated record for a workflow instance is successful.
  • OriginalDefinitionIdentity is a System.Activities.WorkflowIdentity type indicating the original definition for the workflow identity.
  • BlockingActivities is a collection of blocking activities for the workflow instance being updated.

Activity Lifecycle

Tracking records are also used to provide transparency into a workflow activity’s lifecycle. A workflow’s tracked events include the following:

  • Activity state
  • Scheduled
  • Cancelled
  • Fault Propagation
  • Bookmark Resumption
  • Custom data tracking

Therefore these events have a tracking record that is emitted to indicate when an event occurs. The tracking records mentioned in the next section also inherit from TrackingRecord, so they also inherit the same properties indicated in Table 9-4.

Table 9-4. Unique ActivityStateRecord Properties

Property Description
Activity Represents characteristics about the activity that produced the tracking record. The property type is System.Activites.Tracking.ActivityInfo and it provides the following properties:
  • Id: Unique value representing the activity
  • InstanceId: Runtime ID for the activity instance.
  • Name: Represented with the activity.
  • TypeName: Gets the type name of the activity
Arguments Represents the type IDictionary<string,Object> as the collection of arguments associated with an activity when the tracking record is emitted.
Variables Represents the type IDictionary<string,Object> as the collection of variables associated with an activity when the tracking record is emitted.
State Represents the current stage of the activity when the tracking record is emitted.

ActivityStateRecord

As a workflow instance runs, it is equally important to gain transparency into each activity that is executed within a workflow. The ActivityStateRecord is emitted when an activity is executed. The ActivityStateRecord provides detailed data about an activity to easily understand the flow of a workflow; it also includes the properties described in Table 9-4.

ActivityScheduledRecord

When a workflow starts its execution, the WF runtime schedules the root activity of the workflow. After a workflow starts, activities that contain other child activities can also schedule their children activities. The WF runtime maintains scheduled activities within a queue/stack of activities. ActivityScheduledRecord is emitted when an activity is scheduled. Table 9-5 shows the properties that distinguish the tracking record.

Table 9-5. Unique ActivityScheduledRecord Properties

Property Description
Activity Represents characteristics about the scheduling activity that produced the tracking record. The property type is System.Activites.Tracking.ActivityInfo and it provides the following properties:
  • Id: Unique value representing the activity.
  • InstanceId: Runtime ID for the activity instance.
  • Name: Represented with the activity.
  • TypeName: Gets the type name of the activity.
Child Represents characteristics about the child activity that is scheduled that produced the tracking record. The property is also a type of System.Activites.Tracking.ActivityInfo.

FaultPropagatedRecord

When code has an exception, it bubbles up the exception until it is finally handled or it simply fails. However, there is a software trail that is built during this process called the StackTrace, and the same concept is applied when the WF runtime emits a FaultPropagateRecord. A FaultPropagateRecord contains data about a fault that occurred within a workflow activity. Table 9-6 identifies the key properties of the tracking record.

Table 9-6. Unique FaultPropagatedRecord Properties

Property Description
Fault Represents the System.Exception that produced the tracking record.
FaultHandler Gets the fault handler. The property type is System.Activites.Tracking.ActivityInfo and it provides the following properties:
  • Id: Unique value representing the activity.
  • InstanceId: Runtime ID for the activity instance.
  • Name: Represented with the activity.
  • TypeName: Gets the type name of the activity.
FaultSource Represents the activity that generated the fault. The property type is also System.Activites.Tracking.ActivityInfo.
IsFaultSource A Boolean type value that represents if the handler was the first handler for the fault.

BookmarkResumptionRecord

Bookmarks are used within a workflow when a workflow instance requires an event, sometimes an event that provides data that a workflow instance requires to continue processing. BookmarkResumptionRecord is emitted when a bookmark is resumed within a workflow instance. Table 9-7 identifies the key properties of the tracking record.

Table 9-7. Unique BookmarkResumptionRecord Properties

Property Description
BookmarkName Name of the bookmark that is resumed when producing the tracking record.
BookmarkScope Gets the scope ID, which is a System.Guid type tied to the bookmark.
Owner Represents the activity that was waiting on the bookmark to resume. The property type is System.Activites.Tracking.ActivityInfo and it provides the following properties:
  • Id: Unique value representing the activity.
  • InstanceId: Runtime ID for the activity instance.
  • Name: Represented with the activity.
  • TypeName: Gets the type name of the activity.
Payload A System.Object type representing the value that was passed with the bookmark as it is resumed.

CustomTrackingRecord

There are times when custom information needs to be returned within a tracking record. A CustomTrackingRecord is a tracking record used within custom activities for tracking custom data deemed important to a workflow author. Table 9-8 describes the properties used for tracking custom data.

Table 9-8. Unique CustomTrackingRecord Properties

Property Description
Data Represents the collection of data that is defined as type IDictionary<string,Object>.
Name The unique name that identifies the custom tracking record.
Activity Represents the custom activity that custom data is gathered. It’s type is System.Activites.Tracking.ActivityInfo and it provides the following properties:
  • Id: Unique value representing the activity.
  • InstanceId: Runtime ID for the activity instance.
  • Name: Represented with the activity.
  • TypeName: Gets the type name of the activity.

State machine workflows also have their own tracking record called StateMachineStateRecord. It was introduced with the release of the Microsoft .NET Framework 4 Platform Update 1; however, it also is provided with WF4.5. It inherits from CustomTrackingRecord but provides its own unique properties to track specific state machine information (see Table 9-9).

Table 9-9. Unique StateMachineStateRecord Properties

Property Description
StateMachineName Represents the name of the state machine activity that contains the state.
StateName Gets the state name for the executing state when the tracking record is created.

image Note   The ReceiveMessageRecord and SendMessageRecord also inherit from CustomTrackingRecord and are used for tracking when messages are received and sent within a workflow service instance.

Tracking Profile

All of the tracking records mentioned above are published through the WF runtime, but in order to subscribe to tracking orders, a tracking profile has to be built. A tracking profile indicates which tracking records need to be published based on workflow instances. Tracking profiles contain the queries used to select which tracking records are needed as well as filtering for specific information within a particular tracking record.

There are two approaches for building a tracking profile. One is a standard approach that requires a tracking profile that subscribes to a generic set of tracking records, and the other is tailored around a subset of data that is specific for understanding the exact flow of workflow instances.

Tracking profiles can be built using XML elements, placed within a standard .NET configuration like a Web.config or App.config file when using workflows hosted as WCF services, using the WorkflowServiceHost. However, tracking profiles are built through code when the workflows are hosted using WorkflowInvoker or WorkflowApplication. The main advantage of building tracking profiles within a configuration file is that the profile can be configured or modified during runtime without affecting the running workflow service’s execution. Listing 9-1 indicates a tracking profile used to track all of a workflow instance’s life cycle stages while being hosted as a WF service or using the WorkflowServiceHost for hosting a workflow.

Listing 9-1.  Tracking Profile Tracking Workflow Lifecycle

<tracking>
    <profiles>
      <trackingProfile name="Custom Tracking Profile">
        <workflow>
            <workflowInstanceQueries>
                <workflowInstanceQuery>
                  <states>
                    <state name="*"/>
                  </states>
                </workflowInstanceQuery>
            </workflowInstanceQueries>
        </workflow>
      </trackingProfile>
    </profiles>
  </tracking>

As a workflow instance processes, each state of the workflow instance lifecycle is tracked. The reason why each state is tracked is because of the syntax <state name="*"/> and the wildcard syntax it uses, which indicates to get all states. The same tracking profile can be built using the C# syntax in Listing 9-2.

Listing 9-2.  CustomTracking Profile

static TrackingProfile BuildTrackingProfile()
{
    try
         {
                   var profile = new TrackingProfile
                {
                    Name = "Custom Tracking Profile",
                      Queries =
                      {
                          new WorkflowInstanceQuery
                          {
                             States = {"*"}
                          }
                      }
                };
                return profile;
         }
         catch (Exception ex)
         {
                throw ex;
         }
    }

ImplementationVisibility

Tracking profiles have a way of subscribing to child activity tracking records that are emitted for children activities contained within composite activities; however, by default it is turned off. ImplementationVisibility is the property that can be found of the following tracking profiles:

  • ActivityScheduledRecord
  • ActivityStateRecord
  • FaultPropagationRecord
  • CancelRequestedRecord

The ImplementationVisibility property can be set to either

  • RootScope: The root activity of a composite activity emits tracking records.
  • All: All activities within a composite activity emit tracking records.

Listing 9-3 shows a TrackingProfile object supplied with an ImplementationVisibility property changed to System.Activities.Tracking.ImplementationVisibility.All.

Listing 9-3.  Changing the Default ImplementationVisibility Property to All

var profile = new TrackingProfile
                {
                    Name = "Custom Tracking Profile",
               ImplementationVisibility =
            System.Activities.Tracking.ImplementationVisibility.All
                }

The same can be done through the configuration for a tracking profile, as indicated in Table 9-4.

<tracking>
      <trackingProfile name="Custom Tracking Profile" implementationVisibility="All">

TrackingQuery

An earlier section covered the different types of tracking records that are emitted through the WF runtime; however, to subscribe to certain types of tracking records, a TrackingQuery is required to be enlisted for identifying the type of tracking record that should be subscribed to as well as what characteristics of the tracking record should be filtered. Listing 9-2 illustrates that all WorkflowInstanceRecord types should be subscribed to by using the following syntax:

    Queries =
        {
                 new WorkflowInstanceQuery
                 {
                     States = {"*"}
                 }
             }

Each type of tracking query that is used for subscribing to tracking records inherits from System.Activities.Tracking.TrackingQuery:

  • ActivityScheduledQuery
  • ActivityStateQuery
  • BookmarkResumtionQuery
  • CancelRequestedQuery
  • CustomTrackingQuery
  • FaultPropagationQuery
  • WorkflowInstanceQuery

In order to track multiple tracking records, the following C# syntax can be used:

    Queries =
                          {
                              new WorkflowInstanceQuery
                              {
                                 States = {"*"}
                              },
                new ActivityStateQuery
                {
                   States={"*"}
                }
                          }

And when the tracking profile is configured using XML, the queries can be stacked, like so:

<workflowInstanceQueries>
    <workflowInstanceQuery>
        <states>
            <state name="*"/>
        </states>
   </workflowInstanceQuery>
</workflowInstanceQueries>
<activityStateQueries>
    <activityStateQuery>
        <states>
            <state name="*"/>
        </states>
   </activityStateQuery>
</activityStateQueries>

image Note   The default value for the ImplementationVisibility property for a tracking record is RootScope.

Tracking Participant

Tracking participants are the vehicle for delivering tracking records. In WF3.x, tracking was delivered out of the box to SQL Server, but in WF4.x this method is no longer supplied out of the box. Instead, a tracking participant can be created for pushing tracking records to SQL Server or any other database or file system. Tracking participants are added to the WF runtime as an extension, so in order to add a tracking participant to a workflow application hosted either by WorkflowInvoker or WorkflowApplication, the workflow extension needs to be added through code. For workflows that are either hosted as WCF services or hosted using WorkflowServiceHost, extensions can be added through the configuration. Tracking participants execute within the same process as the workflow instance, so it is good practice to make sure code within a tracking participant does not interfere with objects within the workflow or contain processes which may run for long periods of time.

image Note   System.Activities.Tracking.EtwTrackingParticipant is the one tracking participant that is provided out of the box with WF4.x. It loads tracking records that are subscribed to using a tracking profile and emits an Event Tracing for Windows (ETW) event that consumes the tracking record data.

BASIC TRACKING

This exercise demonstrates how to track a workflow using the following:

  • Tracking Records
  • Tracking Profile
  • Tracking Participant

This example demonstrates tracking a workflow application so the tracking profile and tracking participant will be wired up using C# code.

  1. Open Visual Studio 2012, and create a new project.
  2. Select the Workflow template to see a list of installed workflow templates.
  3. Select Workflow Console Application, and name it Apress.Chapter9.
  4. Rename the workflow console application to Exercise1. Drag a Flowchart activity onto the designer canvas.
  5. Drag a Writeline activity into the designer canvas and auto-connect it with the Start activity. Set the Text property to Workflow started.
  6. Drag a Delay activity into the designer canvas and auto-connect it with the Writeline activity that was added within the previous step. Set the Duration property to 00:00:05.
  7. Drag another Writeline activity onto the designer canvas and auto-connect it with the Delay activity. Set the Text property to Workflow finished. See Figure 9-2.

    9781430243830_Fig09-02.jpg

    Figure 9-2.  Writeline, Delay, Writeline workflow

  8. Press F5 to run the workflow to make sure everything compiles and the workflow runs correctly. Figure 9-3 illustrates the expected results. If everything runs correctly, the console windows will display “Workflow started!” Five seconds later, after the delay activity’s duration expires, the console should display “Workflow finished!”

    9781430243830_Fig09-03.jpg

    Figure 9-3.  Running the workflow

  9. Right-click on the solution and add new Visual C# class library. Name it ConsoleTrackingParticipant.cs. This class will be used to build the custom tracking participant. Replace the existing code with the following code:
    using System;
     using System.Activities.Tracking;
     using System.Collections.Generic;
     using System.Linq;
     using System.Text;
     using System.Threading.Tasks;

     namespace WorkflowConsoleApplication1
     {
         public sealed class ConsoleTrackingParticipant:TrackingParticipant
         {
             protected override void Track(TrackingRecord record, TimeSpan timeout)
             {
                 try
                 {
                     if (record != null)
                     {
                         if(record is WorkflowInstanceRecord)
                         {
                             WorkflowInstanceRecord InstanceRecord = record as WorkflowInstanceRecord;

                             Console.WriteLine(string.Format("{0} InstanceId: {1} Workflow instance state {2}",
                                 InstanceRecord.ActivityDefinitionId,
                                 record.InstanceId,
                                 InstanceRecord.State));
                         }
                         else if(record is ActivityStateRecord)
                         {
                             ActivityStateRecord ActivityRecord = record as ActivityStateRecord;

                             Console.WriteLine(string.Format("Activity: {0}  ActivityInstanceId: {1} State : {2}",
                                 ActivityRecord.Activity.Name,
                                 record.InstanceId,
                                 ActivityRecord.State));
                         }
                     }
                 }
                 catch (Exception ex)
                 {
                     throw ex;
                 }
             }
         }
     }

    The   ConsoleTrackingParticipant class inherits from   TrackingParticipant and overrides the   Track method. Within the   Track method, the first parameter,   TrackingRecord, is passed in and this is where the tracking participant serves as a vehicle for exposing and saving tracking record data to external sources like the file system or data store like SQL Server. The   ConsoleTrackingParticipant takes the   TrackingRecord parameter and uses it to check for the type of tracking record being passed. When a   WorkflowInstanceRecord or   ActivityStateRecord is passed, the record is interregated and key properties for the tracking records are displayed to the console.
  10. Open the Program.cs file and replace the existing code with the following code:
    using System;
     using System.Linq;
     using System.Activities;
     using System.Activities.Statements;
     using System.Activities.Tracking;
     namespace WorkflowConsoleApplication1
     {

         class Program
         {
             static void Main(string[] args)
             {
                 // Create and cache the workflow definition
                 Activity workflow1 = new Workflow1();
                 ConsoleTrackingParticipant tracker = new ConsoleTrackingParticipant();
                 tracker.TrackingProfile = BuildTrackingProfile();
                 WorkflowInvoker invoker = new WorkflowInvoker(workflow1);
                 invoker.Extensions.Add(tracker);
                 invoker.Invoke();
                 Console.ReadLine();
             }

             static TrackingProfile BuildTrackingProfile()
             {
                 try
                 {
       var profile = new TrackingProfile
                     {
                          ImplementationVisibility= System.Activities.Tracking.ImplementationVisibility.All,
                              Name = "Custom Tracking Profile",
                                Queries =
                                {
                      new WorkflowInstanceQuery
                                     {
                                         States = {"*"}
                                     },
                                     new ActivityStateQuery
                                     {
                                          States={"*"}
                                     }
                                }
                         };
                     return profile;
                 }
                 catch (Exception ex)
                 {
                     throw ex;
                 }
             }
         }
     }

    The new code in  program.cs adds the new  ConsoleTrackingParticipant as an extension to the  WorkflowInvoker host.  WorkflowInvoker ranks the lowest for interaction with the WF runtime. For instance,  WorkflowApplication provides workflow lifecycle events that are fired during execution where WorkflowInvoker does not. This reinforces why WF tracking is so important even for workflows hosted through  WorkflowInvoker for tracking a workflow’s execution and gaining insight on how a workflow instance flows.

    After adding the tracking participant, the tracking profile is built; it subscribes to two types of tracking records,  WorkflowInstanceRecord and  ActivityStateRecord. A  WorkflowInstanceQuery and  ActivityStateQuery are used to query the two types of tracking records so that all states within both tracking records are subscribed to and returned through the tracking participant  ConsoleTrackingParticipant.
  11. Press F5 and run the workflow to see the tracking data presented within the console. Figure 9-4 illustrates the results.

    9781430243830_Fig09-04.jpg

    Figure 9-4.  Console tracking data


    The two types of tracking records WorkflowInstanceRecord and ActivityStateRecord track each of the states entered for the workflow instance and each activity within the workflow. As the workflow instance is loaded into memory, tracking information indicates that the workflow has started, but when the tracking data indicates that the Delay activity starts execution, the WorkflowInstanceRecord emits a record that indicates that the workflow instance has gone idle. For 5 seconds, no more records are emitted to the console; once the Delay duration expires, tracking continues until the workflow finally completes.

    Another concept to discuss is the ImplementedVisibility property. Remember that the code that was added for the tracking profile set the property to ImplementedVisibility.All and the default value is actually RootScope, which means that only the root activity emits a tracking record and not the child activities.

    But this is not the reason why the workflow records and activity records are being emitted. The next steps will demonstrate how tracking records are emitted based on the setting of the ImplementedVisibility property.
  12. To test the point, comment out this line of code
    //ImplementationVisibility = System.Activities.Tracking.ImplementationVisibility.All,

    and press F5 to re-run the workflow instance. The same tracking records are emitted as in Figure 9-4.

    Drag a Sequence activity and add it to the designer canvas (see Figure 9-5). Click and hold the left mouse button and drag a square around the two WriteLine and Delay activities. The three activities should be highlighted in blue. Another way to select the activities is to press Ctrl-A at the same time, releasing the A key while still pressing the Ctrl key and with the mouse clicking on the Sequence activity.

    9781430243830_Fig09-05.jpg

    Figure 9-5.  Selecting multiple activities

  13. Press Ctrl-x at the same time to cut the selected activities. Double-click the Sequence activity and press Ctrl-v at the same time to paste the activities within the Sequence activity (see Figure 9-6).

    9781430243830_Fig09-06.jpg

    Figure 9-6.  Child activities within a Sequence activity

  14. After the activities are contained within the Sequence activity, go back to the Flowchart activity and auto-connect the Sequence activity to the Start activity (see Figure 9-7).

    9781430243830_Fig09-07.jpg

    Figure 9-7.  Auto-connecting the Sequence activity

  15. Press F5 to re-run the workflow instance. Still the same tracking records are emitted as in Figure 9-4.

    The last part of this exercise will create a composite activity to demonstrate the ImplementationVisibility settings.
  16. Right-click the WorkflowConsoleApplication1 and add a new Item. Select the Workflow template and select a new activity library. Change the name of the new activity to compositeActivity.xaml.
  17. Open the Workflow1.xaml file and select the Sequence activity that has the container of children activities. Press Ctrl-x to cut the Sequence activity and add it within the compositeActivity.xaml file. Double-click the Sequence activity to make sure that it still contains its child activities.
  18. Right-click the WorkflowConsoleApplication1 project and select Build. This will add the new composite activity to the activity toolbox.
  19. Open the Workflow1.xaml workflow and drag the compositeActivity activity from the tool box and auto-connect it with the Start activity (see Figure 9-8).

    9781430243830_Fig09-08.jpg

    Figure 9-8.  Flowchart containing a composite activity

  20. With the following code still commented out within the Program.cs file
    //ImplementationVisibility = System.Activities.Tracking.ImplementationVisibility.All,

    press F5 to run the workflow. Notice the difference in results between Figure 9-9 and Figure 9-4.

    9781430243830_Fig09-09.jpg

    Figure 9-9.  Tracking of composite activity

  21. Uncomment the commented code within the Program1.cs file:
    ImplementationVisibility = System.Activities.Tracking.ImplementationVisibility.All,
  22. Press F5 again to run the workflow instance and this time notice that the results are the same as Figure 9-4 as far as the same tracking records that are emitted.

This exercise demonstrated how to implement tracking for a workflow by using a custom tracking profile and tracking participant for tracking a workflow instance that was hosted using WorkflowInvoker. The tracking record information was sent to the console to demonstrate the flow of the workflow. This exercise also demonstrated how to use the ImplementationVisibility property of a TrackingRecord to control whether the root activity of a composite activity is the only activity subscribed to for getting tracking records or if children tracking records also should be subscribed.

WorkflowServiceHost Tracking

The Basic Tracking exercise demonstrated building the tracking profile and adding a tracking participant as a workflow extension through code because WorkflowInvoker was used to host the workflow as an application. But when a workflow is either hosted as a WCF service or the workflow is hosted using the WCF service host WorkflowServiceHost, tracking participants and tracking profiles can be configured using XML, as mentioned earlier. There is one caveat: a tracking participant still requires custom code for adding it as a workflow extension. Therefore a custom service behavior is required, which can be built within a custom class that implements the interface System.ServiceModel.Description.IServiceBehavior. In order to use the custom service behavior, a custom behavior extension element is required, too, that inherits from System.ServiceModel.Configuration.BehaviorExtensionElement.

IServiceBehavior

Before a custom tracking participant can be configured through a configuration file for gathering data on subscribed tracking records from a workflow hosted through WorkflowServiceHost, a tracking participant must be added as an extension using a custom service behavior. The IService interface provides a way to insert and edit custom extensions for a WCF service.  The interface exposes three methods:

  • AddBindingParameters: Provides custom data for binding elements to support the service.
  • ApplyDispatchBehavior: Exposes the serviceHostBase so custom extension objects can be added.
  • Validate: Provides a way to check that the service can run without any issues.

In the next exercise, a generic tracking behavior will be built that can be used for configuring custom tracking participants. The goal for doing this is so different tracking participants can be used and changed out during runtime for a workflow instance.

BehaviorExtensionElement

System.ServiceModel.Configuration.BehaviorExtensionElement enables developers to add their own custom configuration within the System.ServiceModel section for a configuration file. It works with the .NET runtime, so assemblies can use dynamic parameters through configuration. BehaviorExtensionElement provides a way to define custom configuration elements that can be used to customize service or endpoint behaviors. To build a custom behavior extension, a custom class must inherit from BehaviorExtensionElement. There are two members of the object that need to overridden:

  • BehaviorType: Gets the behavior type.
  • CreateBehavior: Creates a behavior extension based on custom configuration properties created and provided to the behavior extension.

image Tip   EtwTrackingParticipant does not require any custom service behaviors or extension elements when configured as a tracking participant for either a workflow service or workflow hosted using WorkflowServiceHost.

TRACKING CONFIGURATION

This exercise will demonstrate how to configure tracking for workflows hosted through the WorkflowServiceHost. A custom tracking extension element and tracking behavior will be created to show how they are used to configure a custom tracking participant.

  1. Open Visual Studio 2012 and create a new project within the previous exercise’s solution Apress.Chapter9.
  2. Select the Workflow template to see a list of installed workflow templates and select workflow console application. Make sure to name of the project is WorkflowConsoleApplication2.
  3. Drag a Flowchart activity onto the designer canvas.
  4. Drag a Receive activity onto the designer canvas and auto-connect it to the Start activity.
  5. Set the Operation Name property for the Receive activity to StartWorkflow (see Figure 9-10).

    9781430243830_Fig09-10.jpg

    Figure 9-10.  Setting the OperationName property

  6. Click on the Receive activity to view the Receive activity’s properties within the property window. Check the CanCreateInstance checkbox and set the ServiceContractName property to ItrackingDemoService (see Figure 9-11).

    9781430243830_Fig09-11.jpg

    Figure 9-11.  Setting CanCreateInstance and ServiceContractName


    This exercise will not use correlation nor pass parameters, so these properties will not change.
  7. Right-click on the WorkflowConsoleApplication2 and add a new Item. Select the Workflow template and select a new activity library. Change the name for the new activity to ProcessWork.xaml.
  8. Drag a Writeline activity into the designer canvas and auto-connect it with the Start activity. Set the Text property to Started processing work.
  9. Drag a Delay activity into the designer canvas and auto-connect it with the Writeline activity that was added within the previous step. Set the Duration property to 00:00:05.
  10. Drag another Writeline activity onto the designer canvas and auto-connect it with the Delay activity. Set the Text property to Finishedprocessing work (see Figure 9-12).

    9781430243830_Fig09-12.jpg

    Figure 9-12.  ProcessWork Workflow

  11. Right-click the project created for this exercise and select “Set as StartUp Project” so the project starts first the next time the solution runs, and then right-click the new project and select Build to make sure the project builds successfully and the ProcessWork activity shows up within the activity toolbox.
  12. Drag the new ProcessWork activity onto the designer canvas and auto-connect it to the existing Receive activity (see Figure 9-13).

    9781430243830_Fig09-13.jpg

    Figure 9-13.  Adding a composite activity to a Receive activity

  13. Right-click the new project and select Build to make sure the project builds successfully.

    The workflow that will be used for this exercise has been built. The next steps for the exercise will show how to configure workflow tracking through configuration. The next file that will be created will allow the tracking participant to be configured based on custom properties that can be set within a configuration file.
  14. Right-click the WorkflowConsoleApplication2 project and add a new class. Rename the class as GenericTrackingExtensionElement.cs and replace the existing code with the following code:
    using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Text;
     using System.Threading.Tasks;

     using System.ServiceModel.Configuration;
     using System.Configuration;
     using System.Activities.Tracking;

     namespace WorkflowConsoleApplication2
     {
        public class GenericTrackingExtensionElement:BehaviorExtensionElement
        {
            [ConfigurationProperty("profileName", DefaultValue= null,IsKey=false, IsRequired = false)]
             public string ProfileName
            {
     get { return (string)this["profileName"]; }
            set { this["profileName"] = value; }
         }

            [ConfigurationProperty("participantTypeName", DefaultValue = null, IsKey = false, IsRequired = false)]
            public string ParticipantTypeName
            {
                get { return (string)this["participantTypeName"]; }
                set { this["participantTypeName"] = value; }
            }

            public override Type BehaviorType
            {
           get
           {
                return typeof(GenericTrackingBehavior);
           }
            }
            protected override object CreateBehavior()
            {
                return new GenericTrackingBehavior(ProfileName, ParticipantTypeName);
            }
        }
     }

    This code creates two properties, profileName and participantTypeName, that can be used for configuring a custom service behavior. The profileName is used for choosing the correct tracking profile and participantTypeName is used to identify the type of tracking participant that should be configured. There are two other members within the BehaviorExtensionElement abstract class that are overridden, BehaviorType property and the function CreateBehavior.  BehaviorType is used to return the type of the service behavior and CreateBehavior returns a new instance of the service behavior, passing the two new properties profileName and participantTypeName.
  15. Right-click the WorkflowConsoleApplication2 project and add a new class. Rename the class as GenericTrackingBehavior.cs and replace the existing code with the following code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using System.ServiceModel.Description;
    using System.ServiceModel.Activities;
    using System.ServiceModel;
    using System.Activities.Tracking;
    using System.ServiceModel.Activities.Tracking.Configuration;
    using System.Web.Configuration;
    using System.Configuration;

    namespace WorkflowConsoleApplication2
    {
        public class GenericTrackingBehavior:IServiceBehavior
        {
            private string trackingProfileName { get; set; }
            public string ParticipantTypeName { get; set; }
         //Constructor which passes in the tracking profile name
            public GenericTrackingBehavior(string profileName,string participantTypeName)
            {
                try
                {
                    ParticipantTypeName = participantTypeName;
                    trackingProfileName = profileName;
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            //method that is implemented through the IServiceBehavior contract
            //Handles adding the tracking participant to the WorkflowServiceHost
            public virtual void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
            {
                WorkflowServiceHost workflowServiceHost = serviceHostBase as WorkflowServiceHost;

                try
                {
                    if (null != workflowServiceHost)
                    {
                        string workflowDisplayName = workflowServiceHost.Activity.DisplayName;
                        TrackingProfile trackingProfile = GetTrackingProfileFromConfig(trackingProfileName, workflowDisplayName);

                        var participant = Activator.CreateInstance(Type.GetType(ParticipantTypeName));
                        TrackingParticipant tp = participant as TrackingParticipant;
                        tp.TrackingProfile = trackingProfile;
                        workflowServiceHost.WorkflowExtensions.Add(tp);
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            TrackingProfile GetTrackingProfileFromConfig(string profileName, string displayName)
            {
                TrackingProfile trackingProfile = null;
                TrackingSection trackingSection = (TrackingSection)WebConfigurationManager.GetSection("system.serviceModel/tracking");
                if (trackingSection == null)
                {
                    return null;
                }

                //Find the profile with the specified profile name in the list of profile found in config
                var match = from p in new List<TrackingProfile>(trackingSection.TrackingProfiles)
                            where p.Name == profileName
                            select p;

                if (match.Count() == 0)
                {
                    throw new ConfigurationErrorsException(string.Format("Could not find a profile with name '{0}'", profileName));
                }
                else
                {
                    trackingProfile = match.First();
                }

                //No matching profile with the specified name was found
                if (trackingProfile == null)
                {
                    //return an empty profile
                    trackingProfile = new TrackingProfile()
                    {
                        ActivityDefinitionId = displayName
                    };
                }

                return trackingProfile;
            }

            public virtual void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
         {
         }
            public virtual void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
            {
            }
        }
    }

    GenericTrackingBehavior implements the interface IServiceBehavior, so it can be used to add new service extensions. In this case, it will use the method ApplyDispatchBehavior to add a new WorkflowExtension to the WorkflowServiceHost, which is very similar to the code that was used in the Basic Tracking exercise. The only difference is that GenericTrackingBehavior accepts the configuration properties of profileName and participateTypeName as they are added to its constructor.GenericTrackingBehavior also checks the tracking configuration to try to find the tracking profile that was designated within the configuration. Even if there is only one tracking record added within the configuration, it still needs to load the profile. GetTrackingProfileFromConfig first finds the tracking section through code of the configuration file. If there is a tracking section added to the configuration, then LINQ is used to find the tracking profile based on the name of the tracking profile that was passed into the constructor. AddBindingParameters and Validate are not used but still have to be implemented for the interface IServiceBehavior. The next step is to set up the host for hosting the workflow service.
  16. Open the Program1.cs file and replace the existing code with the following:
    using System;
     using System.Linq;
     using System.Activities;
     using System.Activities.Statements;
     using System.ServiceModel.Activities;

     using System.ServiceModel;

     namespace WorkflowConsoleApplication2
     {
         [ServiceContract]
         public interface ITrackingDemoService
         {
            [OperationContract(IsOneWay = true)]
           void StartWorkflow();
         }
         class Program
         {
             static void Main(string[] args)
             {
                 // Create and cache the workflow definition
                 Activity workflow1 = new Workflow1();
                 using (var host = new WorkflowServiceHost(workflow1))
                 {
                     host.Open();
                     // Create a client that sends a message to create an instance of the workflow.
                     ITrackingDemoService client = ChannelFactory<ITrackingDemoService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress(" http://localhost:8080/TrackingDemoService "));
                     client.StartWorkflow();
                     Console.ReadLine();
                 }
             }
         }
     }

    There is not much code used for hosting the workflow as a service other than instantiating WorkflowServiceHost. This is because all of the settings are configured within the App.config, which will be added later. Other than starting the workflow service host, most of the code within the Program1.cs file simply calls the workflow service once it has been hosted. The ITrackingDemoService contract is used by the service and is used to build a client that calls the service over BasicHttpBinding using the EndPointAddress URL http://localhost:8080/TrackingDemoService. The next steps set up the app.config file for hosting the workflow service and configuring a custom tracking participant.
  17. Make sure that the project has the Framework references shown in Figure 9-14.

    9781430243830_Fig09-14.jpg

    Figure 9-14.  Framework references

  18. Open the App.config file and replace the existing XML with the following:
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <startup>
           <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
        </startup>
      <system.serviceModel>
        <services>
          <service name="Workflow1" behaviorConfiguration="">
            <endpoint address="" binding="basicHttpBinding"  contract="ITrackingDemoService" />
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:8080/TrackingDemoService"/>
              </baseAddresses>
            </host>
          </service>
        </services>
        <extensions>
          <behaviorExtensions>
            <add name="ConsoleTracking" type="WorkflowConsoleApplication2.GenericTrackingExtensionElement, WorkflowConsoleApplication2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
          </behaviorExtensions>
        </extensions>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <ConsoleTracking profileName="CustomTrackingProfile" participantTypeName="WorkflowConsoleApplication2.ConsoleTrackingParticipant" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <tracking>
          <profiles>
            <trackingProfile name="CustomTrackingProfile">
              <workflow>
                <workflowInstanceQueries>
                  <workflowInstanceQuery>
                    <states>
                      <state name="*"/>
                    </states>
                  </workflowInstanceQuery>
                </workflowInstanceQueries>
              </workflow>
            </trackingProfile>
          </profiles>
        </tracking>
      </system.serviceModel>
    </configuration>

    The first part of the configuration to address is the settings for the workflow exposed as a service. Within the services section, the service with the name “Workflow1” is defined, which must match the workflow name. The service is also set to use an endpoint binding of basicHttpBinding and the contract ITrackingDemoService. The base address is set to http://localhost:8080/TrackingDemoService.

    The next section of the configuration is the extensions section; it adds a behavior extension. The extension is used to customize the configuration for the custom behavior extension that is in the behaviors section. The behavior extension sets the name of the behavior to be used, plus the type, namespace, version, and PublicToken. After setting the behavior extension, the custom service behavior is ready to be configured. The service behavior is configured by using the name of the behavior extension as the XML element. The ProfileName property is set to the name CustomTrackingProfile for the tracking profile configured within the profiles section and the participantTypeName is set to the type WorkflowConsoleApplication2.ConsoleTrackingParticipant for the tracking participant that will be used for tracking the workflow service. The profiles section contains the tracking profile that will be used to track the workflow service.
  19. Select and copy the ConsoleTrackingParticipant.cs file used in Exercise1 within the other project. Add it to the current project WorkflowConsoleApplication2.
  20. Make sure that WorkflowConsoleApplication2 is still set as the startup project and press F5 to run the solution. The results in Figure 9-15 indicate that tracking for the workflow has been configured.

    9781430243830_Fig09-15.jpg

    Figure 9-15.  Tracking data sent to the console


    This exercise showed how to implement tracking for a workflow hosted by a WorkflowServiceHost through configuration.

Filtering Tracking Records

The Basic Tracking exercise demonstrated subscribing to the published tracking records, WorkflowInstanceRecord and ActivityStateRecord. Filtering tracking records is important for subscribing to specific information about a workflow instance’s execution. This section will look at each of the tracking records to understand how specific workflow instance data can be tracked. The Tracking Configuration exercise demonstrated how to track workflows through configuration, which makes it easier for customizing tracking data by changing tracking profiles on the fly.

In order to demonstrate filtering of tracking records, the workflow that will be used will build on the tracking infrastructure that was implemented within the Tracking Configuration exercise; however, I did change out the tracking participant, ConsoleTrackingParticipant, and created another one so the entire tracking record could be serialized to XML and written to the console window. The only thing that needed to change in order to do this was the code within the override Track method, which was replaced with the code in Listing 9-4.

Listing 9-4.  Serializing the Tracking Records

    protected override void Track(TrackingRecord record, TimeSpan timeout)
    {
       string x = string.Empty;
       try
       {
           var serialize = new DataContractSerializer(record.GetType());
            XmlTextWriter writer = new XmlTextWriter(Console.Out)
                                {
                                        Formatting= Formatting.Indented
                                };
           serialize.WriteObject(writer, record);
           writer.WriteRaw(Environment.NewLine);
           writer.Flush();
       }
       catch (Exception ex)
       {
           throw ex;
       }
    }

The examples for filtering tracking will build on the simple workflow service illustrated in Figure 9-16.

9781430243830_Fig09-16.jpg

Figure 9-16.  Simple tracking workflow

Workflow Instance State

The WorkflowInstanceRecord uses the WorkflowInstanceQuery for filtering tracking records. However, let’s first demo all of the records that will be returned when “*” is used as a wildcard, demonstrated with the tracking profile illustrated in Listing 9-5 for pulling all of the workflow instance tracking records with the new tracking participant.

Listing 9-5.  Serializing the Tracking Records

    <trackingProfile name="CustomTrackingProfile">
      <workflow>
        <workflowInstanceQueries>
          <workflowInstanceQuery>
            <states>
                <state name="*"/>
            </states>
          </workflowInstanceQuery>
        </workflowInstanceQueries>
      </workflow>
    </trackingProfile>

Figure 9-17 shows that the workflow instance tracking records have been serialized, showing the properties that are provided on the tracking record.

9781430243830_Fig09-17.jpg

Figure 9-17.  Serialized workflow instance tracking record

To filter the tracking records for when the workflow instance starts, the tracking profile can be changed to

<states>
    <state name="Started"/>
</states>

The result is

    <WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
      <EventTime>2012-03-24T00:32:56.4870156Z</EventTime>
      <InstanceId>3d4cc291-864c-40c3-9772-e5769ec7db60</InstanceId>
      <Level>Info</Level>
      <RecordNumber>0</RecordNumber>
      <ActivityDefinitionId>Workflow1</ActivityDefinitionId>
      <State>Started</State>
      <WorkflowDefinitionIdentity xmlns:d2p1="
http://schemas.datacontract.org/2004/07/System.Activities" i:nil="true" />
</WorkflowInstanceRecord>

Tracking when the workflow also ends requires the following change:

<states>
    <state name="Started"/>
    <state name="Completed"/>
</states>

The result is

    <WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
      <EventTime>2012-03-24T00:45:00.3395546Z</EventTime>
      <InstanceId>026aa5d3-5457-4b1e-8d65-7d37904e2077</InstanceId>
      <Level>Info</Level>
      <RecordNumber>0</RecordNumber>
      <ActivityDefinitionId>Workflow1</ActivityDefinitionId>
      <State>Started</State>
      <WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Activities" i:nil="true" />
    </WorkflowInstanceRecord>
    <WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
      <EventTime>2012-03-24T00:45:00.4176796Z</EventTime>
      <InstanceId>026aa5d3-5457-4b1e-8d65-7d37904e2077</InstanceId>
      <Level>Info</Level>
      <RecordNumber>5</RecordNumber>
      <ActivityDefinitionId>Workflow1</ActivityDefinitionId>
      <State>Completed</State>
      <WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Activities" i:nil="true" />
</WorkflowInstanceRecord>

This type of tracking filter is more practical for tracking starting and completing of a workflow instance. Note that the EventTime property is set and can be used to determine the length of time it took for one workflow instance over others, but what happens when unanticipated issues happen to a workflow instance? Without either tracking all workflow instance records or specifying aborted workflow instances within the tracking record, they won’t be seen. In fact, if the workflow throws an exception, the only indication that something might be wrong is that the Completed state tracking record is never received, therefore the tracking profile needs to add Aborted as a filtered state as well.

<states>
    <state name="Started"/>
    <state name="Aborted"/>
    <state name="Completed"/>
</states>

To test this, add a Throw activity that will simulate an exception within the workflow (see Figure 9-18).

9781430243830_Fig09-18.jpg

Figure 9-18.  Adding a Throw activity

Now the WorkflowInstanceAbortedRecord will be tracked, as illustrated in the following results:

<WorkflowInstanceRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
  <EventTime>2012-03-24T01:54:38.0475625Z</EventTime>
  <InstanceId>285ff549-67e2-4074-a56d-2c3ed596f014</InstanceId>
  <Level>Info</Level>
  <RecordNumber>0</RecordNumber>
  <ActivityDefinitionId>Workflow1</ActivityDefinitionId>
  <State>Started</State>
  <WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Activities" i:nil="true" />
</WorkflowInstanceRecord>
<WorkflowInstanceAbortedRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
  <EventTime>2012-03-24T01:54:38.1881875Z</EventTime>
  <InstanceId>285ff549-67e2-4074-a56d-2c3ed596f014</InstanceId>
  <Level>Info</Level>
  <RecordNumber>6</RecordNumber>
  <ActivityDefinitionId>Workflow1</ActivityDefinitionId>
  <State>Aborted</State>
  <WorkflowDefinitionIdentity xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Activities" i:nil="true" />
  <Reason>Here is an exception</Reason>
</WorkflowInstanceAbortedRecord>

Notice that there is not a Completed state record, because the workflow aborts rather than completes. The WorkflowInstanceAbortedRecord contains the reason, which maps back to the exception message. If an exception caused a workflow to abort, than the WorkflowInstanceUnhandledExceptionRecord can be tracked too by querying its state

<states>
    <state name="UnhandledExeption"/>
</states>

so additional information can be provided for debugging.

Activity State

Tracking can get even more granular by tracking workflow activities. A custom activity called DoSomeWork has been added to the workflow using the following code:

    public class DoSomeWork:CodeActivity
    {
        [RequiredArgument]
        public InArgument<int> MillaSecondsToWait{ get; set; }
    
        protected override void Execute(CodeActivityContext context)
        {
           Thread.Sleep(context.GetValue(SecondsToWait));
        }
        }

Adding the new activity, the milliseconds are set to 5000, simulating 5 seconds for work being processed (see Figure 9-19).

9781430243830_Fig09-19.jpg

Figure 9-19.  Adding a custom code activity DoSomeWork

A new tracking profile is added to the configuration for just tracking activity records:

    <trackingProfile name="ActivityTrackingProfile">
      <workflow>
        <activityStateQueries>
          <activityStateQuery>
            <states>
                <state name="*"/>
            </states>
          </activityStateQuery>
        </activityStateQueries>
      </workflow>
    </trackingProfile>

The service behaviors custom property profileName is changed to ActivityTrackingProfile so the activity records can be tracked.

image Note   Tracking profiles can contain different tracking queries. For example, a tracking profile can contain a workflow instance and activity state queries together.

After running the workflow using the changed profile, all of the activity states for each activity in the workflow get tracked, but you’re interested in tracking just the custom DoSomeWork activity. So instead of getting all of the other records, you can just see the custom activity’s execution. The tracking profile needs to be changed to

   <activityStateQuery activityName="DoSomeWork">
        <states>
          <state name="*"/>
        </states>
   </activityStateQuery>

The results within the console show the DoSomeWork activity's execution.

    <ActivityStateRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
    http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
      <EventTime>2012-03-24T16:29:04.2771797Z</EventTime>
      <InstanceId>5562743c-be60-428c-9751-d933636ee2de</InstanceId>
      <Level>Info</Level>
      <RecordNumber>4</RecordNumber>
      <Activity>
        <Id>1.3</Id>
        <InstanceId>7</InstanceId>
        <Name>DoSomeWork</Name>
        <TypeName>CustomTrackingQueries.DoSomeWork</TypeName>
      </Activity>
      <State>Executing</State>
      <arguments xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
      <variables xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
    </ActivityStateRecord>
    <ActivityStateRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="
http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
      <EventTime>2012-03-24T16:29:09.3240547Z</EventTime>
      <InstanceId>5562743c-be60-428c-9751-d933636ee2de</InstanceId>
      <Level>Info</Level>
      <RecordNumber>5</RecordNumber>
      <Activity>
        <Id>1.3</Id>
        <InstanceId>7</InstanceId>
        <Name>DoSomeWork</Name>
        <TypeName>CustomTrackingQueries.DoSomeWork</TypeName>
      </Activity>
      <State>Closed</State>
      <arguments xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
      <variables xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</ActivityStateRecord>

Notice that the EventTime property is exactly 5 seconds, which represents the duration used to sleep the thread that simulated work being processed. The value MilliSecondsToWait is not included, so the tracking profile can be changed to query the arguments value, like so:

<arguments>
    <argument name="MilliSecondsToWait"/>
</arguments>

The results are

      <arguments xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <d2p1:KeyValueOfstringanyType>
          <d2p1:Key>MilliSecondsToWait</d2p1:Key>
          <d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:int"
>5000</d2p1:Value>
        </d2p1:KeyValueOfstringanyType>
</arguments>

including the value of 5000 for the argument. Variables can also be tracked if required using the <variables> collection query.

image Caution  Make sure to get the Name properties spelling and case right when querying tracking records. The value MillisecondsToWait for the argument to track would not have returned the argument because the argument is MilliSecondsToWait.

Custom Data Tracking

Custom activities can also have custom data tracked that is neither a variable nor an argument. These are scenarios where data needs to be tracked because it either is retrieved from other sources outside of the workflow or it is data that is important to track so it can be referenced later—like transaction numbers or date stamps. A custom activity can create a CustomTrackingRecord and its data property can be used to hold data that needs to be published and later subscribed to using a CustomTrackingQuery.

The DoSomeWork activity has been changed and now includes a new using statement using System.Activities.Tracking; and the following code was added within the Execute method:

     
    var record = new CustomTrackingRecord("WorkingLevel")
                {
                       Data =
                            {
                               {"Scale", 10},
                                  {"ScaleDescription", "Pretty dawg on hard!"}
                               }
                            };
    
    context.Track(record);
    

This code builds a new CustomTrackingRecord  and sets the Data property to two IDictionary<string, Object> values, Scale and ScaleDescription. The tracking profile is changed to query all custom tracking records, like so:

<customTrackingQueries>
        <customTrackingQuery activityName="*"/>
</customTrackingQueries>

The results are

    <CustomTrackingRecord xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns=
    "http://schemas.datacontract.org/2004/07/System.Activities.Tracking">
      <EventTime>2012-03-24T17:25:26.4978828Z</EventTime>
      <InstanceId>5b64b301-201b-49c4-9ee1-4f5d10cc4234</InstanceId>
      <Level>Info</Level>
      <RecordNumber>4</RecordNumber>
      <Activity>
        <Id>1.5</Id>
        <InstanceId>7</InstanceId>
        <Name>DoSomeWork</Name>
        <TypeName>CustomTrackingQueries.DoSomeWork</TypeName>
      </Activity>
      <Name>WorkingLevel</Name>
      <data xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <d2p1:KeyValueOfstringanyType>
          <d2p1:Key>Scale</d2p1:Key>
          <d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:int">10</d2p1:Value>
        </d2p1:KeyValueOfstringanyType>
        <d2p1:KeyValueOfstringanyType>
          <d2p1:Key>ScaleDescription</d2p1:Key>
          <d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:string">Pretty dawg on hard!</d2p1:Value>
        </d2p1:KeyValueOfstringanyType>
      </data>
</CustomTrackingRecord>

This shows the data values that were set within the custom activity DoSomeWork. If there were other custom activities but only custom data from the DoSomeWork activity is required, the tracking profile’s activtyName can be changed, like so:

        <customTrackingQuery activityName="DoSomeWork"/>

Or, if other custom activities within the workflow have a tracking record called WorkingLevel that needs to be tracked, the profile can be changed to

        <customTrackingQuery activityName="*" name="WorkingLevel"/>

which will return back tracked data for WorkingLevel for each activity.

Record Annotations

Tracking stores that are either kept within files or database stores will more than likely contain large amounts of records based on a couple of factors. There can be a large number of workflow instances being tracked depending on the activity for workflow hosted application, or a single tracking store can store tracking for multiple workflow types. For instance, one file could contain all unhandled exceptions, or a general approach for subscribing to all published tracking records may be taken where all workflow activity is tracked. With a high volume of tracking data, there might be times when tracked data needs to be flagged. Some examples could be workflows that are running a particular server and the server IP needs to be added, or indicating workflows that are processing work for certain clients. Annotations can be added within tracking queries, so once tracking information is tracked, it can be given annotations describing what the tracked information is so it can be flagged and referenced for later use. Annotations can be added within a tracking profile in the following manner:

<annotations>
        <annotation name="LevelOfWork" value="These records indicate how hard the author is working!"/>
</annotations>

Annotations can even be used to categorize records, like so:

<annotations>
        <annotation name="Publisher" value="Apress"/>
        <annotation name="BookName" value="Pro WF4.5"/>
        <annotation name="LevelOfWork" value="These records indicate how hard the author is working!"/>
</annotations>

This way other tracking records can share the same categories like Publisher and BookName for consolidating how hard other authors are working.

ETW Tracking Participant

Tracking workflows within Event Tracing for Windows (ETW) is provided out of the box with WF4.x and provides a natural way for viewing tracked workflows within the Event Viewer, a centralized place that provides visualization into the health for the system and custom applications. Many internal processes are logged to the Event Viewer, so it can be used to monitor the health of internal and external processes.

There are two ways of setting up the ETW tracking participant while using it to track workflow services or workflows hosted using WorkflowServiceHost. One way is to use code for workflow applications, hosted by WorkflowApplication or WorkflowInvoker, as demonstrated here:

Activity workflow1 = new Workflow1();
WorkflowInvoker invoker = new WorkflowInvoker(workflow1);
var tracker =
new EtwTrackingParticipant
{
    TrackingProfile = BuildTrackingProfile()
};
Invoker.Extensions.Add(tracker);
Invoker.Invoke();

Another way is through a configuration file when using WorkflowServiceHost:

<behaviors>
   <serviceBehaviors>
        <behavior>
          <etwTracking profileName="Sample Tracking Profile" />
        </behavior>
   </serviceBehaviors>
<behaviors>

Setting up the configuration for the ETW tracking participant is different than setting up other custom tracking participants because there is no need to add it as a custom extension to the WF runtime. Therefore, custom service behavior and element extensions do not need to be built.

The Event Viewer is provided within the operating system and can be found by clicking Start on Windows and typing “Event Viewer” (see Figure 9-20).

9781430243830_Fig09-20.jpg

Figure 9-20.  Searching for the Event Viewer

After opening the Event Viewer, tracking records can be found by expanding the Applications and Services Logs image Microsoft image Windows image Application Server-Applications image Microsoft-Windows-Application Server Applications/Analytic node (see Figure 9-21).

9781430243830_Fig09-21.jpg

Figure 9-21.  Event Viewer location for WF tracking

If the logs are disabled, they can be enabled by right-clicking on the node and selecting Enable Log (see Figure 9-22).

9781430243830_Fig09-22.jpg

Figure 9-22.  Enabling the logs

Using the same project as before to demonstrate tracking queries, the custom service behavior is commented out and the etwTracking is included as well as the same tracking profile for tracking custom tracking data (see Figure 9-23).

9781430243830_Fig09-23.jpg

Figure 9-23.  Configuring ETW tracking

Running the project again will query the same tracking records for custom data like before, but this time the records will be viewable through the Event Viewer (see Figure 9-24).

9781430243830_Fig09-24.jpg

Figure 9-24.  Viewing the tracked records within the log

Summary

Workflow instances run within a black box called the WF runtime, which provides limited information based on the current state within a workflow instance’s life cycle. Workflow tracking publishes different types of tracking records based on most of the characteristics that are important to track, and this is why it is important to have a good understanding of what data is available and how to track workflow instance execution.

Three core concepts for tracking workflow instances were covered in this chapter. The different tracking records provide the detailed data that can be tracked. Tracking participants provide a way to touch, save, and view tracked data. And tracking profiles allow customization of what tracking information is subscribed to from tracking records.

Tracking workflows is also a good way to see how WF4.5 works under the hood, so things like scheduling workflows and asynchronous activity execution can be monitored. Debugging is also greatly improved by tracking workflows because issues can be pinpointed to better understand different flow patterns. Tracked information in WF4.x can be sent to ETW, which provides a natural way for tracking workflow instances; however, using a custom tracking participant, tracked data can also be formatted to XML and stored within the file system or even sent to a database store.

A tracking store is different from using a line-of-business application for storing data, so there are many factors to consider when deciding where tracked data should be stored. WF4.x does provide a nice infrastructure for obtaining tracked information for a workflow, and it should be used as a way to query the flow for workflow instances, debugging issues within workflows, and even providing metadata to other line-of-business applications that need to feed off the workflow’s events and data.

The next chapter will change direction and dive into rehosting the WF designer so that workflows can be authored outside of Visual Studio.

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

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