How to: Create a Custom Tracking Participant
Workflow tracking provides visibility into the status of workflow execution. The workflow runtime emits tracking records that describe workflow lifecycle events, activity lifecycle events, bookmark resumptions, and faults. These tracking records are consumed by tracking participants. Windows Workflow Foundation (WF) includes a standard tracking participant that writes tracking records as Event Tracing for Windows (ETW) events. If that does not meet your requirements, you can also write a custom tracking participant. This tutorial step describes how to create a custom tracking participant and tracking profile that capture the output of WriteLine
activities so that they can be displayed to the user.
To create the custom tracking participant
Right-click NumberGuessWorkflowHost in Solution Explorer and choose Add, Class. Type
StatusTrackingParticipant
into the Name box, and click Add.Add the following
using
(orImports
) statements at the top of the file with the otherusing
(orImports
) statements.Imports System.Activities.Tracking Imports System.IO
using System.Activities.Tracking; using System.IO;
Modify the
StatusTrackingParticipant
class so that it inherits fromTrackingParticipant
.Public Class StatusTrackingParticipant Inherits TrackingParticipant End Class
class StatusTrackingParticipant : TrackingParticipant { }
Add the following
Track
method override. There are several different types of tracking records. We are interested in the output ofWriteLine
activities, which are contained in activity tracking records. If theTrackingRecord
is anActivityTrackingRecord
for aWriteLine
activity, theText
of theWriteLine
is appended to a file named after theInstanceId
of the workflow. In this tutorial, the file is saved to the current folder of the host application.Protected Overrides Sub Track(record As TrackingRecord, timeout As TimeSpan) Dim asr As ActivityStateRecord = TryCast(record, ActivityStateRecord) If Not asr Is Nothing Then If asr.State = ActivityStates.Executing And _ asr.Activity.TypeName = "System.Activities.Statements.WriteLine" Then 'Append the WriteLine output to the tracking 'file for this instance. Using writer As StreamWriter = File.AppendText(record.InstanceId.ToString()) writer.WriteLine(asr.Arguments("Text")) writer.Close() End Using End If End If End Sub
protected override void Track(TrackingRecord record, TimeSpan timeout) { ActivityStateRecord asr = record as ActivityStateRecord; if (asr != null) { if (asr.State == ActivityStates.Executing && asr.Activity.TypeName == "System.Activities.Statements.WriteLine") { // Append the WriteLine output to the tracking // file for this instance using (StreamWriter writer = File.AppendText(record.InstanceId.ToString())) { writer.WriteLine(asr.Arguments["Text"]); writer.Close(); } } } }
When no tracking profile is specified, the default tracking profile is used. When the default tracking profile is used, tracking records are emitted for all
ActivityStates
. Because we only need to capture the text one time during the lifecycle of theWriteLine
activity, we only extract the text from theActivityStates.Executing
state. In To create the tracking profile and register the tracking participant, a tracking profile is created that specifies that onlyWriteLine
ActivityStates.Executing
tracking records are emitted.
To create the tracking profile and register the tracking participant
Right-click WorkflowHostForm in Solution Explorer and choose View Code.
Add the following
using
(orImports
) statement at the top of the file with the otherusing
(orImports
) statements.Imports System.Activities.Tracking
using System.Activities.Tracking;
Add the following code to
ConfigureWorkflowApplication
just after the code that adds theStringWriter
to the workflow extensions and before the workflow lifecycle handlers.'Add the custom tracking participant with a tracking profile 'that only emits tracking records for WriteLine activities. Dim query As New ActivityStateQuery() query.ActivityName = "WriteLine" query.States.Add(ActivityStates.Executing) query.Arguments.Add("Text") Dim profile As New TrackingProfile() profile.Queries.Add(query) Dim stp As New StatusTrackingParticipant() stp.TrackingProfile = profile wfApp.Extensions.Add(stp)
// Add the custom tracking participant with a tracking profile // that only emits tracking records for WriteLine activities. StatusTrackingParticipant stp = new StatusTrackingParticipant { TrackingProfile = new TrackingProfile { Queries = { new ActivityStateQuery { ActivityName = "WriteLine", States = { ActivityStates.Executing }, Arguments = { "Text" } } } } }; wfApp.Extensions.Add(stp);
This tracking profile specifies that only activity state records for
WriteLine
activities in theExecuting
state are emitted to the custom tracking participant.After adding the code, the start of
ConfigureWorkflowApplication
will look like the following example.Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication) 'Configure the persistence store. wfApp.InstanceStore = store 'Add a StringWriter to the extensions. This captures the output 'from the WriteLine activities so we can display it in the form. Dim sw As New StringWriter() wfApp.Extensions.Add(sw) 'Add the custom tracking participant with a tracking profile 'that only emits tracking records for WriteLine activities. Dim query As New ActivityStateQuery() query.ActivityName = "WriteLine" query.States.Add(ActivityStates.Executing) query.Arguments.Add("Text") Dim profile As New TrackingProfile() profile.Queries.Add(query) Dim stp As New StatusTrackingParticipant() stp.TrackingProfile = profile wfApp.Extensions.Add(stp) 'Workflow lifecycle handlers...
private void ConfigureWorkflowApplication(WorkflowApplication wfApp) { // Configure the persistence store. wfApp.InstanceStore = store; // Add a StringWriter to the extensions. This captures the output // from the WriteLine activities so we can display it in the form. StringWriter sw = new StringWriter(); wfApp.Extensions.Add(sw); // Add the custom tracking participant with a tracking profile // that only emits tracking records for WriteLine activities. StatusTrackingParticipant stp = new StatusTrackingParticipant { TrackingProfile = new TrackingProfile { Queries = { new ActivityStateQuery { ActivityName = "WriteLine", States = { ActivityStates.Executing }, Arguments = { "Text" } } } } }; wfApp.Extensions.Add(stp); // Workflow lifecycle handlers...
To display the tracking information
Right-click WorkflowHostForm in Solution Explorer and choose View Code.
In the
InstanceId_SelectedIndexChanged
handler, add the following code immediately after the code that clears the status window.'If there is tracking data for this workflow, display it 'in the status window. If File.Exists(WorkflowInstanceId.ToString()) Then Dim status As String = File.ReadAllText(WorkflowInstanceId.ToString()) UpdateStatus(status) End If
// If there is tracking data for this workflow, display it // in the status window. if (File.Exists(WorkflowInstanceId.ToString())) { string status = File.ReadAllText(WorkflowInstanceId.ToString()); UpdateStatus(status); }
When a new workflow is selected in the workflow list, the tracking records for that workflow are loaded and displayed in the status window. The following example is the completed
InstanceId_SelectedIndexChanged
handler.Private Sub InstanceId_SelectedIndexChanged(sender As Object, e As EventArgs) Handles InstanceId.SelectedIndexChanged If InstanceId.SelectedIndex = -1 Then Return End If 'Clear the status window. WorkflowStatus.Clear() 'If there is tracking data for this workflow, display it 'in the status window. If File.Exists(WorkflowInstanceId.ToString()) Then Dim status As String = File.ReadAllText(WorkflowInstanceId.ToString()) UpdateStatus(status) End If 'Get the workflow version and display it. 'If the workflow is just starting then this info will not 'be available in the persistence store so do not try and retrieve it. If Not WorkflowStarting Then Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) WorkflowVersion.Text = _ WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity) 'Unload the instance. instance.Abandon() End If End Sub
private void InstanceId_SelectedIndexChanged(object sender, EventArgs e) { if (InstanceId.SelectedIndex == -1) { return; } // Clear the status window. WorkflowStatus.Clear(); // If there is tracking data for this workflow, display it // in the status window. if (File.Exists(WorkflowInstanceId.ToString())) { string status = File.ReadAllText(WorkflowInstanceId.ToString()); UpdateStatus(status); } // Get the workflow version and display it. // If the workflow is just starting then this info will not // be available in the persistence store so do not try and retrieve it. if (!WorkflowStarting) { WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(this.WorkflowInstanceId, store); WorkflowVersion.Text = WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity); // Unload the instance. instance.Abandon(); } }
To build and run the application
Press Ctrl+Shift+B to build the application.
Press Ctrl+F5 to start the application.
Select a range for the guessing game and the type of workflow to start, and click New Game. Enter a guess in the Guess box and click Go to submit your guess. Note that the status of the workflow is displayed in the status window. This output is captured from the
WriteLine
activities. Switch to a different workflow by selecting one from the Workflow Instance Id combo box and note that the status of the current workflow is removed. Switch back to the previous workflow and note that the status is restored, similar to the following example.Note
If you switch to a workflow that was started before tracking was enabled no status is displayed. However if you make additional guesses, their status is saved because tracking is now enabled.
Please enter a number between 1 and 10 Your guess is too high. Please enter a number between 1 and 10
Note
This information is useful for determining the range of the random number, but it does not contain any information about what guesses have been previously made. This information is in the next step, How to: Host Multiple Versions of a Workflow Side-by-Side.
Make a note of the workflow instance id, and play the game through to its completion.
Open Windows Explorer and navigate to the NumberGuessWorkflowHost\bin\debug folder (or bin\release depending on your project settings). Note that in addition to the project executable files there are files with guid filenames. Identify the one that corresponds to the workflow instance id from the completed workflow in the previous step and open it in Notepad. The tracking information contains information similar to the following.
Please enter a number between 1 and 10 Your guess is too high. Please enter a number between 1 and 10 Your guess is too high. Please enter a number between 1 and 10
In addition to the absence of the user's guesses, this tracking data does not contain information about the final guess of the workflow. That is because the tracking information consists only of the
WriteLine
output from the workflow, and the final message that is displayed is done so from theCompleted
handler after the workflow completes. In next step of the tutorial, How to: Host Multiple Versions of a Workflow Side-by-Side, the existingWriteLine
activities are modified to display the user's guesses, and an additionalWriteLine
activity is added that displays the final results. After these changes are integrated, How to: Host Multiple Versions of a Workflow Side-by-Side demonstrates how to host multiple versions of a workflow at the same time.