This first article aims at presenting a very simple solution for custom persistence and tracking services for windows workflow foundation.
The solution presented here has been designed to run with a long-running state machine workflow but could be adapted to any use case. It contains:
- A persistence service that stores the serialized workflow into a binary file,
- A tracking service that stores the state changes into a text file.
This article will only explain how to create and use persistence and tracking services. A more detailed explanation about a simple state machine workflow running with a host demo application will be done in a further article.
-
Persistence service
The persistence service contains all what is necessary to store a workflow to a storage medium (here: a file).
Explanation of the source code:
Imports System.Workflow.Runtime.Hosting Imports System.IO Imports System.Workflow.ComponentModel Imports System.Workflow.Activities Public Class FilePersistenceService Inherits WorkflowPersistenceService Private filePath As String = "..\..\..\SerializedFiles\" Private fileExtension As String = ".wf" Private fileName As String = "PersistedWorkflow"
Loads an activity that is the root of a dynamically created ActivityExecutionContext:
Protected Overrides Function LoadCompletedContextActivity(ByVal scopeId As System.Guid, ByVal outerActivity As System.Workflow.ComponentModel.Activity) As System.Workflow.ComponentModel.Activity Dim workflowBytes As Byte() = Me.LoadFromFile Dim wf As StateMachineWorkflowActivity = CType(WorkflowPersistenceService.RestoreFromDefaultSerializedForm(workflowBytes, Nothing), Workflow.Activities.StateMachineWorkflowActivity) Return wf End Function
Logic for loading a workflow instance. It uses the LoadFromFile function to do this.
Protected Overrides Function LoadWorkflowInstanceState(ByVal instanceId As System.Guid) As System.Workflow.ComponentModel.Activity Dim workflowBytes As Byte() = Me.LoadFromFile Dim wf As Workflow.Activities.StateMachineWorkflowActivity = CType(WorkflowPersistenceService.RestoreFromDefaultSerializedForm(workflowBytes, Nothing), Workflow.Activities.StateMachineWorkflowActivity) Return wf End Function
This function returns a state machine workflow activity and can be used by the host rather than LoadWorkflowInstanceState to specify the workflow type. The type here can be précised regarding what you need.
Public Function Load(ByVal instanceId As Guid) As StateMachineWorkflowActivity Return CType(Me.LoadWorkflowInstanceState(instanceId), StateMachineWorkflowActivity) End Function
Logic for saving a workflow instance. It uses the PersistToFile function to do this.
Protected Overrides Sub SaveWorkflowInstanceState(ByVal rootActivity As System.Workflow.ComponentModel.Activity, ByVal unlock As Boolean) Dim contextGuid As Guid = CType(rootActivity.GetValue(Activity.ActivityContextGuidProperty), Guid) Me.PersistToFile(WorkflowPersistenceService.GetDefaultSerializedForm(rootActivity)) End Sub
Saves an activity that is the root of a dynamically created ActivityExecutionContext:
Protected Overrides Sub SaveCompletedContextActivity(ByVal activity As System.Workflow.ComponentModel.Activity) Dim contextGuid As Guid = CType(activity.GetValue(activity.ActivityContextGuidProperty), Guid) Me.PersistToFile(WorkflowPersistenceService.GetDefaultSerializedForm(activity)) End Sub
Returns the boolean indicating whether the workflow is unloaded (=persisted) when it gets idle.
Protected Overrides Function UnloadOnIdle(ByVal activity As System.Workflow.ComponentModel.Activity) As Boolean Return True End Function
For locking mechanism when there are several instances which share 1 medium. Not implemented.
Protected Overrides Sub UnlockWorkflowInstanceState(ByVal rootActivity As System.Workflow.ComponentModel.Activity) Throw New NotImplementedException() End Sub
Serialize activity instance state to file with option to lock state file.
Private Sub PersistToFile(ByVal serializedWorkflow As Byte()) Dim filename As String = Me.filePath & Me.fileName & Me.fileExtension Dim fileStream As FileStream = Nothing Try If File.Exists(filename) Then File.Delete(filename) End If fileStream = New FileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None) fileStream.Write(serializedWorkflow, 0, serializedWorkflow.Length) Finally If Not fileStream Is Nothing Then fileStream.Close() End If End Try End Sub
Deserialize instance state from file given instance id and outerActivity used in the case of compensation for a completed scope
Private Function LoadFromFile() As Byte() Dim filename As String = Me.filePath & Me.fileName & Me.fileExtension Dim fileStream As FileStream = Nothing Try fileStream = New FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read) fileStream.Seek(0, SeekOrigin.Begin) Dim serializedWorkflow((CType(fileStream.Length, Integer))) As Byte fileStream.Read(serializedWorkflow, 0, serializedWorkflow.Length) Return serializedWorkflow Finally fileStream.Close() End Try End Function End Class
Tracking service
-
-
Tracking Channel
-
The tracking channel contains all the data to transfer to the tracking service (i.e. the information we have to store for our workflow). Source code:
Imports System Imports System.IO Imports System.Threading Imports System.Workflow.ComponentModel Imports System.Workflow.Runtime Imports System.Workflow.Runtime.Tracking Public Class FileTrackingChannel Inherits TrackingChannel Implements IDisposable Private trackingParameters As TrackingParameters = Nothing Private Shared trackingDataFile As String = "" Private streamWriter As StreamWriter = Nothing Private filePath As String = "..\..\..\SerializedFiles\" Private fileExtension As String = ".txt" Private fileName As String = "Track"
The constructor creates a stream writer in order to access the tracking file.
Public Sub New(ByVal parameters As System.Workflow.Runtime.Tracking.TrackingParameters) Me.trackingParameters = parameters FileTrackingChannel.trackingDataFile = Me.filePath & Me.fileName & Me.fileExtension Me.streamWriter = File.CreateText(FileTrackingChannel.trackingDataFile) Me.streamWriter.AutoFlush = True End Sub
Protected Overrides Sub InstanceCompletedOrTerminated() Debug.WriteLine("Workflow Instance Completed or Terminated") End Sub
Write a record when an event is raised.
Protected Overrides Sub Send(ByVal record As System.Workflow.Runtime.Tracking.TrackingRecord) If TypeOf record Is ActivityTrackingRecord Then Me.WriteActivityTrackingRecord(CType(record, ActivityTrackingRecord)) End If If TypeOf record Is WorkflowTrackingRecord Then WriteWorkflowTrackingRecord(CType(record, WorkflowTrackingRecord)) End If If TypeOf record Is UserTrackingRecord Then WriteUserTrackingRecord(CType(record, UserTrackingRecord)) End If End Sub
These next 3 subs write the record regarding the type of the record (workflow, activity, user…)
Private Sub WriteWorkflowTrackingRecord(ByVal workflowTrackingRecord As WorkflowTrackingRecord) WriteToFile("Workflow: " + workflowTrackingRecord.TrackingWorkflowEvent.ToString() + " ") End Sub
Private Sub WriteActivityTrackingRecord(ByVal activityTrackingRecord As ActivityTrackingRecord) WriteToFile(CStr(DateTime.Now) + " Activity: " + activityTrackingRecord.QualifiedName.ToString() + " " + activityTrackingRecord.ExecutionStatus.ToString() + " ") End Sub
Private Sub WriteUserTrackingRecord(ByVal userTrackingRecord As UserTrackingRecord) WriteToFile("User Data: " + userTrackingRecord.UserData.ToString()) End Sub
Function which writes a string to the streamwriter.
Private Sub WriteToFile(ByVal toWrite As String) If streamWriter IsNot Nothing Then streamWriter.WriteLine(toWrite) Else Throw New Exception("trackingDataFile " + trackingDataFile + " doesn't exist") End If End Sub End Class
-
-
Tracking Service
-
The tracking service manages tracking: configures it with a (not mandatory) custom tracking profile, and uses the tracking channel to save data to a medium.
Every method concerning tracking profile configuration below is not more explained because here we didn’t use any custom tracking profile.
Imports System.IO Imports System.Collections.Generic Imports System.Text Imports System.Threading Imports System.Workflow.ComponentModel Imports System.Workflow.Runtime Imports System.Workflow.Runtime.Tracking Imports ClassLibrary.pWorkCenter Public Class FileTrackingService Inherits TrackingService Private _trackingChannel As FileTrackingChannel Public ReadOnly Property TrackingChannel() As FileTrackingChannel Get Return Me._trackingChannel End Get End Property
Gets the tracking profile (a tracking profile can be defined too to determine the events to handle or not. This is not our subject today but you can create a custom profile too)
Protected Overloads Overrides Function GetProfile(ByVal workflowInstanceId As System.Guid) As System.Workflow.Runtime.Tracking.TrackingProfile Return FileTrackingService.GetProfile() End Function
Gets the tracking profile depending on the type of the workflow and the profile version ID.
Protected Overloads Overrides Function GetProfile(ByVal workflowType As System.Type, ByVal profileVersionId As System.Version) As System.Workflow.Runtime.Tracking.TrackingProfile Return FileTrackingService.GetProfile() End Function
Gets the tracking channel to use.
Protected Overrides Function GetTrackingChannel(ByVal parameters As System.Workflow.Runtime.Tracking.TrackingParameters) As System.Workflow.Runtime.Tracking.TrackingChannel If Me._trackingChannel Is Nothing Then Me._trackingChannel = New FileTrackingChannel(parameters) End If Return Me._trackingChannel End Function
Gets the profile passed by reference, and returns true when it succeeded.
Protected Overrides Function TryGetProfile(ByVal workflowType As System.Type, ByRef profile As System.Workflow.Runtime.Tracking.TrackingProfile) As Boolean Try profile = FileTrackingService.GetProfile() Return True Catch ex As Exception Return False End Try End Function
Reloads the profile: Sets it to nothing
Protected Overrides Function TryReloadProfile(ByVal workflowType As System.Type, ByVal workflowInstanceId As System.Guid, ByRef profile As System.Workflow.Runtime.Tracking.TrackingProfile) As Boolean profile = Nothing Return False End Function
Gets the default tracking profile to use.
Private Overloads Shared Function GetProfile() As System.Workflow.Runtime.Tracking.TrackingProfile Dim profile As New TrackingProfile profile.Version = New Version(1, 0) Dim statePoint As New ActivityTrackPoint Dim location As New ActivityTrackingLocation(GetType(Workflow.Activities.StateActivity), New ActivityExecutionStatus() {ActivityExecutionStatus.Executing}) statePoint.MatchingLocations.Add(location) profile.ActivityTrackPoints.Add(statePoint) Return profile End Function End Class
-
Use Persistence combined with Tracking in a host application
This initialization method is called from the constructor of the host application. Its role is to start the machine workflow. We consider that we have a state machine workflow whose type is “StateMachine.BasicStateMachine”.
Private Sub InitializeMachineWorkflow() Me._workflowRuntime = New WorkflowRuntime
Add the persistence and tracking services
Dim pserv As New FilePersistenceService() Me._workflowRuntime.AddService(pserv) Dim tserv As New FileTrackingService() Me._workflowRuntime.AddService(tserv) Try
Get the workflow that has been stored as a file, and has the following Guid. We consider we already have a stored workflow, whose Guid is as follows. If not, an exception will be raised and a new workflow (with new persistence and tracking files) will be created.
Me._workflowRuntime.StartRuntime() Me._workflowInstance = Me._workflowRuntime.GetWorkflow(New Guid("91dcf603-5f08-4499-aaf1-39e3be7baa96"))
When there is a tracking service, it is possible to handle a “StateChanged” event.
AddHandler tserv.TrackingChannel.StateChanged, AddressOf Me.StateChangedHandler Me._workflowInstance.Load() Catch ex As Exception
If the persistence failed, then create and start a new workflow instance.
Me._workflowRuntime.StopRuntime() Me._workflowInstance = Me._workflowRuntime.CreateWorkflow(GetType(StateMachine.BasicStateMachine)) Me._workflowInstance.Start() End Try
Instantiate the machine instance
Dim stateMachineInstance As New StateMachineWorkflowInstance(Me._workflowRuntime, Me._workflowInstance.InstanceId) Me._currentStateName = stateMachineInstance.CurrentStateName End Sub
Be careful, in this code sample, to the “StartRuntime” and “StopRuntime” workflow runtime calls. This is very important to start runtime before getting the persisted workflow instance, and to have runtime stopped before creating a new instance.
In this article we’ve seen the implementation of custom persistence and tracking services. A next one will do this implementation into a full solution. Before this, feel free to make any comment!
hi,
Nice article on custom persistence and tracking.
Have you followed this with full solution…. as mentioned (In this article we’ve seen the implementation of custom persistence and tracking services. A next one will do this implementation into a full solution)….
can you provide the link to it.
thanks,
shashank