Stephen Kaufman's WebLog

Look who's BizTalk'in - Notes on all things integration

Windows Workflow Tracking and the TrackingExtract functionality

Windows Workflow Tracking and the TrackingExtract functionality

Rate This
  • Comments 1
New Page 1

I was writing a custom tracking service for the Windows Workflow engine for my client that will be used to track the rules that fired.  One of the things that I wanted to do was to track the values of the properties that my rules would touch.  I would then create an xml document which would be stored in a row in my tracking database. 

 

The challenge was to find a way to get the internal tracking functionality to grab these property values.  In addition, I only wanted the properties that I was interested in and not every property on the class.  I found that the tracking infrastructure provides this functionality in the WorkFlowDataExtract class. 

 

I did a search using my favorite search engine to see if there was anything documented on this subject.  I was surprised at the lack of information on this functionality.  There was one post on the WF Forums, which is a great resource and I highly recommend looking at these posts for information, but, unfortunately, it didn't expand on this class.  Therefore, this seemed like the perfect opportunity for a blog entry.

 

First, lets look at what the class does.  There are actually two classes that provide TrackingExtract functionality.  They are the WorkflowDataTrackingExtract class and the ActivityDataTrackingExtract class.  These classes take the name of a property or field that should be extracted from the root activity of the workflow and sent to the tracking service when a tracking point is matched. 

 

The difference between these two classes is that the ActivityDataTrackingExtract class is bound directly to a specific activity whereas the WorkflowDataTrackingExtract can be assigned anywhere across the workflow.  Remember though that these classes are 'bound' to the tracking point and can be used in either the Extracts properties of either the UserTrackPoint or ActivityTrackPoint classes.

 

The data that is extracted is placed in either the ActivityTrackingRecord or the UserTrackingRecord. 

 

Use the Member method to specify the field or property to extract.  You can also associate additional information with the extracted data by using the Annotations functionality.

 

So, I wanted to accept a list of properties that were important to the workflow developer, in this case I used the List<string> generic type, which was passed in when instantiating the RulesTrackingService.  This list gets created in the program.cs file in the Main method as listed in the following code:

 

static void Main()

    {

        WorkflowRuntime workflowRuntime = new WorkflowRuntime();

        string connectionString = "Initial Catalog=TrackingStore;Data Source=localhost; Integrated Security=SSPI;";

        List<string> trackedProperties = new List<string>();

        trackedProperties.Add("orderValue");

        trackedProperties.Add("discount");

        workflowRuntime.AddService(new RulesTrackingService(connectionString,trackedProperties));

        ...............

    }

 

Once the RulesTrackingService gets instantiated there are two interesting pieces of code.  The first is the GetTrackingChannel method.  This returns a RuleTrackingChannel object (which inherited TrackingChannel) which is where you will provide the code to operate on the tracking data.  In my case, I took the object data and serialized it into XML so that I could place it in a row in the database. The second is the the GetProfile method.  Within this method, I have a foreach loop where I cycle through the List<> and for each entry I create a new WorkflowDataTrackingExtract object passing the property on the constructor and then add the extract object to the userTrackPoint.Extracts collection as shown in the code below.

 

public class RulesTrackingService : TrackingService

{

...........

...........

    public RulesTrackingService(string connectionString, List<string> TrackedProperties)

    {

        this._connectionString = connectionString;

        trackedProperties = TrackedProperties;

    }

    protected override TrackingChannel GetTrackingChannel(TrackingParameters parameters)

    {

    if (trackedProperties != null)

    {

        return new RuleTrackingChannel(parameters, this._connectionString, trackedProperties);

    }

    else

    {

        return new RuleTrackingChannel(parameters, this._connectionString);

    }

}

    private static TrackingProfile GetProfile()

    {

        TrackingProfile profile = new TrackingProfile();

        profile.Version = new Version("1.0.0");

        UserTrackPoint userTrackPoint = new UserTrackPoint();

        UserTrackingLocation userLocation = new UserTrackingLocation();

        ..........

        ..........

        userTrackPoint.MatchingLocations.Add(userLocation);

        foreach (string trackedProp in trackedProperties)

        {

            WorkflowDataTrackingExtract wkflowExtract = new WorkflowDataTrackingExtract(trackedProp);

            userTrackPoint.Extracts.Add(wkflowExtract);

        }

        profile.UserTrackPoints.Add(userTrackPoint);

        return profile;

    }

}

 

The data that is returned from the tracking point for each of the tracked properties represents the data before the rule runs.  This was great for the 'before' snapshot but I also needed to see the 'after' snapshot.  For this functionality, I added a Code activity onto my workflow after the Policy activity.  In this activity I pass the object (in this case it is crr) that the rules operate on to the TrackData property which creates a UserTrackingRecord as shown in the code below.

 

private void codeActivity1_ExecuteCode(object sender, EventArgs e)

{

    this.TrackData("WholeObject", crr);

}

 

Earlier I talked about the TrackingChannel functionality.  In this class I check to see if the tracking object is my custom type or if it is a base UserTrackingRecord (which is what the .TrackData creates).  If it is the UserTrackingRecord I again serialize and place this in a different table in my database.  I place it in a different table since there will be one of these for each policy whereas there will be many records for the rules tracking records.  I place keys on the tables so that they can be related and selected at a later time to actually investigate what occurred in the rules processing.

 

I now have a tracking service that tracks each rule that fires, including the before shapshot, as well an entry for the data after all of the rules have fired on the object.

 

 

  • In my previous blog entry on

    Tracking and the TrackingExtract functionality I talked about the ability...
Page 1 of 1 (1 items)