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
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.