
A few days ago Sander, Edwin and I published an article in the dutch .NET magazine on WF 4 and WCF 4. In the article we couldn’t quite talk about the more advanced concepts of Workflow 4. One of them being workflow tracking. It’s an interesting subject, but you simply can’t cover all the new stuff that is going on in these frameworks in one article. So instead I’m posting it here on my weblog.
Introduction
Developers who have worked with the previous version of Workflow Foundation already know the tracking API. It allows you to track a set of standard events and make your own custom tracking events. This for example makes it possible to give users more insight into the amount order processes that were executed in the past months.
A more practical appliance of tracking for developers and administrators would be a tracking setup that keeps track of suspended workflows, so that we can restart them at a later point in time.
In this article I’m showing how tracking works for Workflow foundation 4 and what’s cool about it.
Tracking components
The tracking API is build around three key components: tracking records, tracking profiles and tracking participants. The following schematic shows how they work together to build a working tracking subsystem.
When a workflow instance runs it will fire tracking records at a tracking participant. These tracking records can be filtered by using a tracking profile configured on the tracking participant.
Note: As you might have noticed there’s no TrackingService. The whole system of runtime services in Workflow 4 is gone, the whole system now works with WCF behaviors and extensions when working with self-hosted workflows.
Standard tracking behavior
Microsoft provides a Event Tracing for Windows (ETW) tracking participant out of the box. This tracking participant writes all received tracking records to the active ETW session. I personally think that this provider is great for most cases.
To configure this tracking participant for a workflow service you need to add the following snippet to the web.config file of your workflow service project.
1: <system.serviceModel>
2: <behaviors>
3: <serviceBehaviors>
4: <behavior>
5: <etwTracking profileName="SuspendedWorkflowTracking"/>
6: <serviceMetadata httpGetEnabled="true"/>
7: <serviceDebug includeExceptionDetailInFaults="true"/>
8: </behavior>
9: </serviceBehaviors>
10: </behaviors>
11: <!-- Other configuration -->
12: </system.serviceModel>
This will add the ETW tracking behavior to all services in the application and configure the tracking behavior to use the tracking profile with the name specified in the profileName attribute.
The last thing you need to do is configure a tracking profile to track any tracking records that get written by the active workflow instances. In the following snippet I configured the tracking profile to keep track of suspended workflows, but you can also check for aborted, completed, cancelled and started workflows.
1: <system.serviceModel>
2: <!--Other configuration-->
3: <tracking>
4: <profiles>
5: <trackingProfile name="SuspendedWorkflowTracking">
6: <workflow activityDefinitionId="*">
7: <workflowInstanceQueries>
8: <workflowInstanceQuery>
9: <states>
10: <state name="Suspended"/>
11: </states>
12: </workflowInstanceQuery>
13: </workflowInstanceQueries>
14: </workflow>
15: </trackingProfile>
16: </profiles>
17: </tracking>
18: </system.serviceModel>
More on tracking profiles
A tracking profile determines which tracking records get offered to the tracking participant. There’s loads of tracking records being written by the workflow instance, so filtering them not only makes it less messy but also improves performance of the tracking participant.
The tracking profile filters incoming tracking records by using a set of queries. There are several types of queries available as you can see in the following list:
- WorkflowInstanceQuery
Can be used to track workflow instance related events. For example Started,Completed,Aborted events are filtered by this query. - CustomTrackingQuery
Use this to create your own custom tracking query in combination with custom tracking records. - ActivityScheduledQuery
Used to track various events related to activities. Includes events to indicate that a particular activity was scheduled, started or completed. - FaultPropagationQuery
You can this query to detect errors that are raised within activities in the workflow. It can help to precisely determine the cause of an aborted workflow. Very handy when things get complex! - CancelRequestedQuery
Every time the workflow tries to cancel one of its children a CancelRequestedRecord is written. It’s possible to filter on this record by using the CancelRequestedQuery. - BookmarkResumptionQuery
This query can be used to track any bookmarks that were resumed in the workflow. Works also great for unit-testing activities that use bookmarks internally.
Customizing tracking behavior
While the ETW tracking participant works great there are still a load of scenarios that require a custom tracking participant. There’s a two ways in which you can customize in the tracking behavior of Workflow Foundation 4.
Custom tracking participants
The first method to customize the tracking behavior of Workflow Foundation 4 is building a custom tracking participant. This is something you should consider when you want to store tracking records in some other storage medium than ETW tracing, like a database or perhaps keep them in memory. This last option is something that is typically done when building a tracking participant for usage inside a unit-test environment.
To build a custom tracking participant you will need to create a new class and derive that class from TrackingParticipant. In the following snippet I created a tracking participant for a unit-test framework I’m working on.
1: /// <summary>
2: /// Specialized tracking participant for the <see cref="WorkflowServiceTestHarness"/> component
3: /// </summary>
4: public class UnitTestTackingParticipant : TrackingParticipant
5: {
6: #region Private fields
7:
8: private WorkflowServiceTestHarness _testHarness;
9:
10: #endregion
11:
12: #region Public constructors
13:
14: /// <summary>
15: /// Initializes a new instance of <see cref="UnitTestTackingParticipant"/>
16: /// </summary>
17: /// <param name="testHarness">Test harness to report the results to</param>
18: public UnitTestTackingParticipant(WorkflowServiceTestHarness testHarness)
19: {
20: _testHarness = testHarness;
21: InitializeTrackingProfile();
22: }
23:
24: #endregion
25:
26: #region Protected methods
27:
28: /// <summary>
29: /// Tracks an event raised by the currently running workflow
30: /// </summary>
31: /// <param name="record"></param>
32: /// <param name="timeout"></param>
33: protected override void Track(TrackingRecord record, TimeSpan timeout)
34: {
35: WorkflowInstanceRecord instanceRecord = record as WorkflowInstanceRecord;
36: WorkflowInstanceUnhandledExceptionRecord exceptionRecord = record as WorkflowInstanceUnhandledExceptionRecord;
37:
38: // Process the records
39: }
40:
41: #endregion
42:
43: #region Private methods
44:
45: private void InitializeTrackingProfile()
46: {
47: WorkflowInstanceQuery query = new WorkflowInstanceQuery();
48:
49: query.States.Add(WorkflowInstanceStates.Completed);
50: query.States.Add(WorkflowInstanceStates.Aborted);
51: query.States.Add(WorkflowInstanceStates.Canceled);
52: query.States.Add(WorkflowInstanceStates.Started);
53: query.States.Add(WorkflowInstanceStates.Suspended);
54: query.States.Add(WorkflowInstanceStates.UnhandledException);
55:
56: TrackingProfile = new TrackingProfile();
57: TrackingProfile.Name = "Unit-test tracking profile";
58:
59: TrackingProfile.Queries.Add(query);
60: }
61:
62: #endregion
63: }
The protected method Track is overridden in the custom tracking participant to process incoming tracking records. To work on specific records you will need to cast them to the appropriate type.
To use the tracking participant you will need to add it to the WorkflowExtensions collection of the WorkflowServiceHost. The following snippet demonstrates this using a custom WCF behavior.
1: public class CustomTrackingBehavior: IServiceBehavior
2: {
3: #region IServiceBehavior Members
4:
5: public void AddBindingParameters(ServiceDescription serviceDescription,
6: ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
7: BindingParameterCollection bindingParameters)
8: {
9:
10: }
11:
12: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
13: {
14: WorkflowServiceHost workflowHost = serviceHostBase as WorkflowServiceHost;
15:
16: if (workflowHost != null)
17: {
18: workflowHost.WorkflowExtensions.Add<CustomTrackingParticipant>(() => new CustomTrackingParticipant());
19: }
20: }
21:
22: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
23: {
24:
25: }
26:
27: #endregion
28: }
Custom tracking records
Instead of writing custom classes that derive from TrackingRecord, Microsoft choose not to let developers write their own derived classes. Instead it’s best practice to initiate custom tracking records inside activities and populate them with the data that’s needed.
Writing a custom tracking record can be done within an activity using the following code:
1: public sealed class MyCustomActivity : CodeActivity
2: {
3: /// <summary>
4: /// Gets or sets the text for the activity
5: /// </summary>
6: public InArgument<string> Text { get; set; }
7:
8: /// <summary>
9: /// Executes the activity
10: /// </summary>
11: /// <param name="context"></param>
12: protected override void Execute(CodeActivityContext context)
13: {
14: CustomTrackingRecord record = new CustomTrackingRecord("MyCustomEvent");
15: record.Data.Add("Text", Text);
16:
17: context.Track(record);
18: }
19: }
I’m not sure at this point what to think of this practice. I would love to build my own tracking records that derived from TrackingRecord instead, but the runtime simply doesn’t allow me to do that at this point. I hope Microsoft is going to fix this in the next release as it is something that makes the tracking API easier to understand and easier to extend.
Conclusion
The tracking subsystem of Workflow Foundation 4 is in my opinion easier to understand and use than it was in .NET Framework 3.5. It doesn’t take much effort to put it to use within Workflow services or inside desktop applications. The extensibility sadly still isn’t what you would expect from a framework that is at version 4. I think however that there’s a good basis for more improvements in .NET framework vNext.
Links
The following links contain more information on the subject:
- Tracking records: http://msdn.microsoft.com/nl-nl/library/ee513990(en-us,VS.100).aspx
- Tracking profiles: http://msdn.microsoft.com/nl-nl/library/ee513989(en-us,VS.100).aspx