Window Workflow Foundation Runtime Services: The Persistence Service

Introduction

This is the first of a series of brief articles intended to illustrate the functionality and customizability of the Windows Workflow Foundation (WF) Runtime Services. This first article is focused on describing WF’s persistence service and ways to customize it. Upcoming articles will cover other WF services such as tracking, communication, timer, and transactions to name a few. The article assumes that you are familiar with WF fundamentals. For an introduction to the WF basics, you can read this interesting article from David Chappell.

WF Runtime Services Overview

The Windows Workflow Foundation runtime engine provides the base set of functionalities required to execute and manage the workflow lifetime. The runtime engine architecture is highly extensible. For example, rather than imposing a specific type of host, the runtime engine can be hosted in almost any kind of Windows process. Furthermore, much of the functionality of the runtime engine, implemented in the individual runtime services, can be easily customized. In fact, developers can easily add/remove services to the runtime engine; and using the WF libraries, developers can actually create their own runtime services.

The following are the default WF services:

Service

Function

Persistence

Save and restore the state of the workflow at certain points to-from a durable medium

Tracking

Tracking services are designed to monitor workflow execution through the exchange of data that can then be persisted to a storage medium or output on a specified stream, the System.Console for instance

Timer

The Timer service within the Windows Workflow Foundation (WF) runtime engine is responsible for managing time-outs such as those required by the Delay activity

Transactions

Provides the transaction support needed for data integrity.

Threading

Dispense physical threads used to execute workflow instances

 

Persistence Service Overview.

By default the workflow hosting application executes the workflow instances in memory without any kind of state maintenance. However there are many scenarios, such as long-running processes for example, that require a mechanism for persisting the state of the workflow during some points of its execution. Using WF terminology, the process of persisting a workflow to a durable medium is known as dehydration while restoring is rehydration.

In WF, persistence is implemented using a runtime service whose main function is to save and restore the state of the workflow at certain points to-from a durable medium. WF provides a default persistence service named SQLStatePersistanceService, however due to the extensible model of the runtime services, developers have a broad set of options in order to deal with state persistence including the ability to create their own persistence service. The following sections explore some of these options. . .

Sample Workflow Overview.

 

This sample WF workflow helps to illustrate the functionality of the persistence service. The workflow consists of a sequence of three activities defined in Table 2.

Activity

Activity Type

Function

Before Serialize

Code

Prints the phrase “Before Serialize” to the console

Delay1

Delay

Suspends the workflow execution for a period of 50 seconds

After Serialize

Code

Prints the phrase “After Serialize” to the console

This figure illustrates a graphical representation of the workflow.

Configuring the Sample Workflow to the Persistence Service

The WF runtime engine uses the SQLStatePersistanceService service to allow the hosting application to save/restore the workflow state to/from a SQL Server database. Microsoft SQL Server is a very common and robust medium to maintain the state of the workflow instances, however you can also use other mediums such as any other relational database, XML files, binary files or just about any mechanism available for storing data. The following are the steps required to configure the sample workflow to use the persistence service:

1.      Create and Configure the Persistence Database

2.      Add the SQLStatePersistanceService Instance to the WorkFlow Runtime

3.      Save the Workflow Instance State

 

1. Create and Configure the Persistence Database

 

One interesting thing to note about the WF installation is that it does not require an existing installation of SQL Server, hence the databases required to use this runtime service are not installed by default. Developers who choose to implement the default persistence service, SQLStatePersistenceService, must first run a series of scripts in order to create and configure the persistence database. The following steps describe how to create the databases for the SQLStatePersistenceService:

a.      Use Microsoft SQL Server Query Analyzer to connect to the database server.

b.      Create a new database by creating a new query with the command: "create database <databasename>".

c.      Execute the query by choosing Query-Execute or by pressing F5.

d.      Choose the database created in step 2 above by selecting it from the database drop down list.

e.      Use File-Open to open the SqlPersistenceService_Schema.sql file located in the SQL script directory described above.

f.        Execute the query by choosing Query-Execute or by pressing F5. This will create the tables for the SqlStatePersistenceService service.

g.       Use File-Open to open the SqlPersistenceService_Logic.sql file located in the SQL script directory described above.

h.      Execute the query by choosing Query-Execute or by pressing F5. This will create the stored procedures for the SqlStatePersistenceService service.

2. Add the SQLStatePersistanceService Instance to the WorkFlow Runtime

The next step is to add an instance of the SQLStatePersistenceService service to the workflow runtime. You can add an instance by either embedding code in the host application itself or in its App.Config file.

Adding a Service Instance by Embedding Code in the Host Application

The following code snippet is used inside a host application in order to illustrates how to add an instance of the SQLStatePersistenceService service to the workflow runtime

WorkflowRuntime workflowRuntime = new WorkflowRuntime();

SqlStatePersistenceService stateservice = new SqlStatePersistenceService("Data Source=localhost;Initial Catalog=WFState;Integrated Security=True");

workflowRuntime.AddService(stateservice);

Adding the Service by Writing Code in the Host Application’s App.Config file

Alternatively we can use the App.Config file in order to add an instance of the SQLStatePersistenceService service:

<WorkflowRuntime Name="SampleApplication" UnloadOnIdle="true">

<Services>

<add type="System.Workflow.Runtime.Hosting.SqlStatePersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConnectionString="Data Source=localhost;Initial Catalog=WFState;Integrated Security=True;" />

</Services>

</WorkflowRuntime>

3. Persisting the Workflow Instance State to a Durable Medium

Once the persistence service is added to the Workflow Runtime Engine it is ready to persist the state of the workflow instance to a durable medium. One way, to force the workflow instance to persist its state is by calling the Unload method of the workflow instance. Be default, this method uses the persistence service associated with it to remove the workflow instance from memory and save its state.

 

Another way of persisting the workflow state, which is implemented in our sample workflow, is to instruct the Workflow Runtime to persist the state of the instance when entering an idle state. However, this event happens only if the UnLoadOnIdle property is set to true. This property must be set in your code prior to calling the StartRuntime method. The code for configuring this property appears below.

workflowRuntime.UnloadOnIdle = true;

workflowRuntime.StartRuntime();

How it does it

The Workflow Runtime engine persists the workflow state by calling the SaveWorkflowInstanceState method of the persistence service associated with the runtime engine. SaveWorkflowInstanceState is a virtual method of the StatePersistenceService class which is the base class of all persistence services in Windows Workflow Foundation.

 

4. Restoring Workflow Instance from a Durable Medium

The WF runtime engine can restore the workflow instance into memory allowing the instance to be scheduled for execution. One way to restore a workflow instance is by calling the Load method of the workflow instance. The runtime engine will also restore a workflow instance to memory upon reaching the end an idle period.

One other way of restoring the Workflow instance to memory is by interacting explicitly with the persistence service. To do this you have to call the GetWorkflow and Loads methods of the WorkflowRuntime class.

Suppose that in our example we instruct the workflow runtime to persist the instance state upon entering an idle state and then we subsequently suspend the workflow execution just when it reaches the After_Serialize code Activity. At this point, we can query the InstanceState table in the database associated with the SqlStatePersistenceService to get the serialized state.

SELECT uidInstanceID, state

FROM InstanceState

If we then restore the instance using the code below we will ge the following output, “After Serialize…”.

workflowRuntime = new WorkflowRuntime();

workflowRuntime.AddService(new SqlStatePersistenceService("Data Source=localhost;Initial Catalog=WFState;Integrated Security=True");

workflowRuntime.AddService(new SqlTimerService("Data Source=localhost;Initial Catalog=WFState;Integrated Security=True"));

workflowRuntime.StartRuntime();

CurrentInstance = workflowRuntime.GetWorkflow(new Guid("DC466C9E-5285-4D88-A9A2-FB79EAF81360"));

CurrentInstance.Load();

Developing a Custom Persistence Service.

WF provides bases classes that abstract the basic functionality of each of the default runtime services. The StatePersistenceService represents the abstract class that must be inherited by all persistence services. To develop a custom persistence service developers can inherit from this class and override the methods defined in the following table:

Method

Function

SaveWorkflowInstanceState

Saves the workflow instance state to a data store

SaveCompletedContextActivity

Saves the specified completed scope to a data store

LoadWorkflowInstanceState

Loads the specified state of the workflow instance back into memory

LoadCompletedContextActivity

Loads the specified completed scope back into memory

UnlockWorkflowInstanceState

Unlocks the specified workflow instance state

The following code shows a sample persistence service that serializes the workflow instance state to a file.

public class FilePersistenceProvider: StatePersistenceService

                {

                                public FilePersistenceProvider(string basedir)

                                {

                                                FBaseDir = basedir;

                                }

 

                                private string FBaseDir;

                               

                                public override void SaveWorkflowInstanceState(Activity rootActivity, bool unlock)

                                {

                                                ActivityExecutionContextInfo contextInfo = (ActivityExecutionContextInfo)rootActivity.GetValue(Activity.ActivityExecutionContextInfoProperty);

                                                SerializeActivity(rootActivity, contextInfo.ContextGuid);

                                }

 

                                // load workflow instance state

                                public override Activity LoadWorkflowInstanceState(Guid instanceId)

                                {

                                                object obj = DeserializeActivity(null, instanceId);

                                                return (Activity)obj ;

                                }

                               

                                // unlock workflow instance state.

                                // instance state locking is necessary when multiple runtimes share instance persistence store

                                public override void UnlockWorkflowInstanceState(Activity state)

                                {

                                                //not implemented...

                                }

 

                                // save completed scope activity state

                                public override void SaveCompletedContextActivity(Activity rootActivity)

                                {

                                                ActivityExecutionContextInfo contextInfo = (ActivityExecutionContextInfo)rootActivity.GetValue(Activity.ActivityExecutionContextInfoProperty);

                                                SerializeActivity(rootActivity, contextInfo.ContextGuid);

                                }

 

                                // Load completed scope activity state.

                                public override Activity LoadCompletedContextActivity(Guid activityId, Activity outerActivity)

                                {

                                                object obj = DeserializeActivity(outerActivity, activityId);

                                                return (Activity)obj ;

                                }

 

                                private void SerializeActivity(Activity RootActivity, Guid id)

                                {

                                                string filename = FBaseDir + "\\" + id.ToString() + ".bin";

                                                FileStream stream = new FileStream(filename, FileMode.OpenOrCreate);

                                                RootActivity.Save(stream);

                                                stream.Close();

                                }

 

                                private object DeserializeActivity(Activity RootActivity, Guid id)

                                {

                                                string filename = FBaseDir + "\\" + id.ToString() + ".bin";

                                                FileStream stream = new FileStream(filename, FileMode.Open);

                                                object Result = Activity.Load(stream, RootActivity);

                                                return Result;

                                }

                }

The steps to add this service to the workflow runtime are similar to what we did with the SqlStatePersistenceService. The following code shows how to add the FilePersistenceProvider to the workflow runtime.

WorkflowRuntime workflowRuntime= new WorkflowRuntime();

FilePersistenceProvider customservice = new FilePersistenceProvider("c:\\WF");

workflowRuntime.AddService(customservice);

What does it all Mean?

Persistence is one of the most important services required by workflow applications. Windows Workflow Foundation does not impose a specific persistence service. Instead it provides a highly extensible and flexible framework that allows developers to define and customize the persistence architecture for each specific runtime hosts or group of hosts.

 

 

 

15 Comments

  • Would it be possible to distribute workflow instances via this method over a cluster of computers, ie use this to enable some sort of grid computing? If a workflow doesn't interact with the outside world but only does heavy calculations, there should be no real problem, right? I can't wait to read your article on the threading service!

  • Nice one! Great to get low level access.....

  • Really good article!

  • The article is really good. Please tell more on how to persist on Oracle DB. If possible, with an example.

    Thanks

  • This article is very nice, but I need information on how to persist on Oracle DB. Please look at in to this request.

  • This article is very helpful but do you have any sample codes in making Custom Persistence Service for databases?

  • Agreed. Great article. Any chance of seeing an Oracle example of this?

  • Most of professionnal users will require an ORACLE adapter to use it on Oracle DB...

    If I understood quite right the whole thing, this is namely to create an Oracle Table to save the serialized workflow and its GUID as primary key in a relationnal table

    Tell me if I am wrong !

  • I am having a huge problem using the persistence service in a workflow project that consumes webservices. When I call the Unload method, I get an error telling me that my web service is not marked as serializable. What is going on????? Why does persistence have anything to do with the web reference??

    Can you help me?

  • Is it possible to seperate the persistate and the unload of the workflow?

  • I believe a help ful starter for a begginer

  • I have a problem with a custom implementation of the Windows Workflow Persistence Service that writes to file. If the host application quits while there are workflow instances on idle states due to delay activity, when loads again I need to manually tell the runtime "WF_Runtime.GetWorkflow(InstanceId)" even when the workflow instance time of delay aren't finished yet. Doing so makes everything works, but if not the instances never load again even when the delay activity's time finish. Although if I use an ExternalCall activity the "GetWorkflow"'s call is not necessary. At the same time if I never close the host application both types of activities works fine without the call.
    What do you thing I'm doing wrong?
    The implementation of the file persistence service I'm testing is the one given on the Windows Vista and Framework 3 SDK.

    Thanks in advance, Nils.

  • I don’t see NextTimer field in ur Table. I’m considering how the workflow runtime engine can “complete” the workflow instance when DelayActivity timeout?

    I have problem with it.
    Please give me some advice to solve this one.

  • Thinking about a church wedding? Congratulations, we wish you well as you begin
    your journey into married life. In this section you will find a number of ...

  • I have a different situation. I want to get a deep clone of a persisted workflow and move it to another state. the clone changes the state not the original one. I am fine if the GUID changes for the instance iD.
    Is it anyway doable?

Comments have been disabled for this content.