Aline’s Tech Blog

All my geekness is here :-)

Custom persistence with custom tracking (vb.net source code) July 1, 2008

Filed under: WF — Aline @ 8:37 pm
Tags: , , ,

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!

 

One Response to “Custom persistence with custom tracking (vb.net source code)”

  1. shashank Says:

    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


Leave a Reply