Introduction
I wanted to know if it is possible to upgrade a workflow instance by overriding the
default deserialization mechanism of the SQL workflow persistence service. I
searched for ways on how to upgrade persisted workflow instances but most of
what I’ve found is about running different workflow versions side-by-side or
using a feature called dynamic update. If I only want a simple change like renaming
the name or property of a non-workflow type that I use in a workflow, I don’t
want to run different workflow versions at the same time (side by side versioning).
For example, I have an Order object that I use in a state machine workflow. A
workflow instance is started, became idle, and is persisted to the database.
Then, you realize that the Order class should have another property to address
some defect. I believe that implementing side by side versioning is too tedious
just for this, and the persisted workflow instance could be loaded using the
latest version.
Sample Application
I’ve created a sample application to demonstrate some problems in workflow deserialization
that occurs when you try to make some changes on a type definition, and some
possible solutions or workarounds. The following figure shows the main window
of the sample WPF application.

Figure 1. Sample Application
We have a state machine workflow that is hosted as a WCF service. This workflow contains
4 states: Initial, New, Processed, and Completed. The service interface provides
3 operations: CreateOrder, ProcessOrder, and CloseOrder. Each operation is implemented
by a ReceiveActivity in the workflow. When we click on the New Order button,
the workflow is started and an Order object is assigned to a property of the
workflow. The current state is then set to the New state. In our WPF application,
the current state is distinguished by the thick black border. When the workflow
becomes idle, the instance is persisted to a SQL Server database using a SqlWorkflowPersistenceService
object that is added earlier as a service of the workflow runtime. You can confirm
this by looking at the InstanceState table.

Figure 2. Persisted Workflow Instance
As you can see in the preceding figure, the state column corresponds to the serialized
data. This data is in binary form because the SqlWorkflowPersistenceService uses
the BinaryFormatter class for serialization and deserialization.
Type and Assembly Changes
Making changes to an assembly name or type definition that is used by a persisted
object will cause deserialization problems. Some changes that could cause these
problems are signing an assembly, changing the name or version of the assembly,
and changing the namespace or name of a type. In our example, the workflow uses
an Order class, which is shown below.
namespace DataTransferObjects
{
[Serializable]
[DataContract]
public class Order
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Customer { get; set; }
}
}
Listing 1. Order Class
This class is defined in the DataTransferObjects assembly. Let’s say we decided to
sign the assembly. It is necessary to sign assemblies that contain workflow types
or types that are used by a workflow, in case you want to implement workflow
versioning. Going back to our example, the Order is currently in the New state.
Clicking on the Process Order button produces the following exception:
System.IO.FileLoadException: Could not load file or assembly 'DataTransferObjects,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
The located assembly's manifest definition does not match the assembly reference.
The problem here is that the persisted instance references the old assembly which
is unsigned (no public key token). When the workflow runtime tries to load the
workflow from persistence store, it will deserialize the Order object according
to the stored type and assembly names. Since the application can’t find the assembly,
it throws the said exception. To prevent this exception we can override how the
workflow persistence service loads a workflow. The following code listing shows
a class that overrides the LoadWorkflowInstanceState method of the SqlWorkflowPersistenceService
class.
public class CustomLoadPersistenceService : SqlWorkflowPersistenceService
{
NameValueCollection parameters;
public CustomLoadPersistenceService(NameValueCollection parameters)
: base(parameters)
{
this.parameters = parameters;
}
protected override Activity LoadWorkflowInstanceState(Guid id)
{
using (WorkflowPersistenceDataContext db = new WorkflowPersistenceDataContext(parameters["ConnectionString"]))
{
Activity activity;
InstanceState instanceState = db.InstanceStates.Where(i => i.uidInstanceID == id).Single();
byte[] activityBytes = instanceState.state.ToArray();
using (MemoryStream stream = new MemoryStream(activityBytes))
{
stream.Position = 0L;
using (GZipStream zipStream = new GZipStream(stream, CompressionMode.Decompress, true))
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.SurrogateSelector = ActivitySurrogateSelector.Default;
binaryFormatter.Binder = new DeserializationBinder();
activity = Activity.Load(zipStream, null, binaryFormatter);
}
}
return activity;
}
}
}
Listing 2. Override Loading of Persisted Workflow Instance
I used Reflector to figure out how SqlWorkflowPersistenceService loads a workflow
into memory. I copied some of the implementation, used LINQ to SQL to connect
to the persistence database and specified a BinaryFormatter object as a parameter
of the static Load method of the Activity class. The important part here is to
specify a SerializationBinder object to the Binder property of the BinaryFormatter.
According to MSDN Library, the SerializationBinder allows users to control class
loading and mandate what class to load. The following code listing shows our
SerializationBinder.
class DeserializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (assemblyName == "DataTransferObjects, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
{
assemblyName = "DataTransferObjects, Version=1.0.0.0, Culture=neutral, PublicKeyToken=894ebf3d610d51e5";
}
return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
}
}
Listing 3. Change the Type to Load
Before the BinaryFormatter loads an object, it calls the BindToType method and gives
you a way to change what type to load. Using the assembly name and type name
parameters, we can check if the type the BinaryFormatter will load is an older
version, and then return the correct type. The example shown here is a simple
and you can make changes to it like use a configuration file to map old versions
to new versions. You also have to handle generic types differently as the type
name parameter will be complex. Afterwards, we have to add the CustomLoadPersistenceService
object to the workflow runtime’s services instead of using SqlWorkflowPersistenceService.
Going back to our application, clicking on the Process Order button moves the
Order to the Processed state without any exception.
We can also make changes like renaming, adding or removing serializable members.
Let’s say we want to rename the Customer property of the Order class to CustomerName.
We can use a serialization surrogate for this purpose. A serialization surrogate
lets you control how an object is serialized or deserialized without implementing
ISerializable. Here is the serialization surrogate for the Order class.
class OrderSerializationSurrogate : ISerializationSurrogate
{
#region ISerializationSurrogate Members
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context,
ISurrogateSelector selector)
{
Order order = obj as Order;
if (order != null)
{
order.Id = info.GetInt32("<Id>k__BackingField");
try
{
order.CustomerName = info.GetString("<CustomerName>k__BackingField");
}
catch (SerializationException)
{
order.CustomerName = info.GetString("<Customer>k__BackingField");
}
}
return null;
}
#endregion
}
Listing 4. Making Changes on Serializable Members
We only need to implement SetObjectData since we are only interested in controlling
deserialization. I have to use a try-catch statement here because there is no
way currently to check if a specific field exists in the SerializationInfo object.
Currently, the return value of the SetObjectData is ignored by the formatter.
To use the surrogate, we need to make some changes in the LoadWorkflowInstanceState
method.
BinaryFormatter binaryFormatter = new BinaryFormatter();
ActivitySurrogateSelector surrogateSelector = ActivitySurrogateSelector.Default;
surrogateSelector.AddSurrogate(typeof(Order), new StreamingContext(StreamingContextStates.All),
new OrderSerializationSurrogate());
binaryFormatter.SurrogateSelector = surrogateSelector;
binaryFormatter.Binder = new DeserializationBinder();
AppDomain appDomain = AppDomain.CurrentDomain;
appDomain.AssemblyResolve += AppDomain_AssemblyResolve;
activity = Activity.Load(zipStream, null, binaryFormatter);
appDomain.AssemblyResolve -= AppDomain_AssemblyResolve;
surrogateSelector.RemoveSurrogate(typeof(Order), new StreamingContext(StreamingContextStates.All));
Listing 5. Adding a Serialization Surrogate
Note that we have to remove the surrogate after we used it so that it won’t be used
for serializing Order objects. Note that the default ActivitySurrogateSelector
is static. We also have to remove the OrderSerializationSurrogate object so as
to prevent an exception when we try to add it anew on the next call to LoadWorkflowInstanceState.
Workflow and Queue Changes
Now, let’s try to sign the assembly containing the workflow type. It is logical to
think that we need to modify our DeserializationBinder class, same as what we
did with the DataTransferObjects assembly. However, we will still get a FileLoadException
even if we try this. The problem is that our workflows and activities are not
serialized directly. The SqlWorkflowPersistenceService uses a serialization surrogate
called ActivitySurrogate to control serialization of workflows and activities.
The ActivitySurrogate class serializes types that implement IObjectReference
and only reference a workflow or activity. These types are ActivityRef, DanglingActivityRef,
and ActivitySerializedRef. If you try to list the types that go through the BindToType
method, you will see these types instead of workflow or activity types. On deserialization,
the GetRealObject method of these IObjectReference objects will be called to
get the real object, which is the workflow in our case.
One workaround that I thought of is to create a custom serialization surrogate that
will control how the ActivityRef, DanglingActivityRef, and ActivitySerializedRef
are deserialized. During deserialization, references to a type’s old version
will be replaced with the latest version. However, this proves to be difficult
as these 3 types are defined as internal classes. This would require also using
a lot of reflection on private members. One simple workaround is to use the AssemblyResolve
event of the current AppDomain. We can subscribe to this event before calling
the Activity.Load method, where the exception occurs.
AppDomain appDomain = AppDomain.CurrentDomain;
appDomain.AssemblyResolve += AppDomain_AssemblyResolve;
activity = Activity.Load(zipStream, null, binaryFormatter);
appDomain.AssemblyResolve -= AppDomain_AssemblyResolve;
Listing 6. Subscribing to the AssemblyResolve Event
The following code shows the event handler for the AssemblyResolve event. In this
method, we can check the name of the assembly that the application failed to
resolve, and then return the correct assembly.
private Assembly AppDomain_AssemblyResolve(object sender, ResolveEventArgs e)
{
if (e.Name == "WorkflowServiceLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
{
return Assembly.Load("WorkflowServiceLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f65aaaea031e2f5b");
}
return null;
}
Listing 7. Resolve Assembly
Now, let’s say we want to change the namespace of the OrderWorkflow from WorkflowServiceLibrary
to WorkflowServices. If we try to load a persisted workflow instance referencing
the old namespace, we will get the following exception.
System.TypeLoadException: Could not load type 'WorkflowServiceLibrary.OrderWorkflow'
from assembly 'WorkflowServiceLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f65aaaea031e2f5b'.
You might think of using the TypeResolve event of the AppDomain class to resolve
this problem. Unfortunately, this event won’t be raised when a type is not found
in a static assembly. So I recommend not changing the type name of a workflow
or activity.
Moving on, let’s say we want to change an operation contract that is used by a ReceiveActivity.
In our application, the OrderWorkflow implements the following interface.
[ServiceContract]
public interface IOrderWorkflow
{
[OperationContract]
Guid CreateOrder(Order order);
[OperationContract]
void ProcessOrder();
[OperationContract]
void CloseOrder();
}
Listing 8. Order Workflow Service Contract
Supposed we changed the CloseOrder to CompleteOrder operation contract. Trying to
call the CompleteOrder on a persisted workflow instance gives us the following
exception: Operation is not implemented by the service. Usually, we get this
kind of exception in a state machine workflow when we try to call an operation
but not accessible in the current state the workflow instance is in. However,
the problem we have here is a bit different. In our workflow, we are using event-driven
activities and we can add ReceiveActivity or HandleExternalEvent activities inside
them. When an event happens, it is put in a queue that is processed by the workflow.
A queue has a specific name, and this gets serialized when a workflow gets persisted.
Here is the queue name for the CloseOrder operation.
WorkflowServiceLibrary.IOrderWorkflow, WorkflowServiceLibrary, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=f65aaaea031e2f5b|CloseOrder
Notice that the operation name is appended to the type and assembly names when the
queue name is persisted. When we renamed our service operation from CloseOrder
to CompleteOrder, the runtime will not be able to find the queue containing CompleteOrder
when we call the operation. Thus, we should rename CloseOrder to CompleteOrder
before a user can call the operation. Fortunately, we can do this by overriding
the OnActivityExecutionLoad method of the workflow.
public sealed partial class OrderWorkflow : StateMachineWorkflowActivity
{
// Other parts removed for readability
protected override void OnActivityExecutionContextLoad(IServiceProvider provider)
{
QueueHelper.UpdateReferences(provider);
}
}
public class QueueHelper
{
public static void UpdateReferences(IServiceProvider provider)
{
WorkflowQueuingService queueService = provider.GetService(typeof(WorkflowQueuingService))
as WorkflowQueuingService;
FieldInfo persistedQueueStatesInfo = queueService.GetType().GetField("persistedQueueStates", BindingFlags.Instance | BindingFlags.NonPublic);
IDictionary persistedQueueStates = (IDictionary)persistedQueueStatesInfo.GetValue(queueService);
Dictionary<string, string> updatedQueueNamesMap = new Dictionary<string, string>();
IDictionaryEnumerator enumerator = persistedQueueStates.GetEnumerator();
while (enumerator.MoveNext())
{
string currentQueueName = enumerator.Key.ToString();
string updatedQueueName = null;
if (currentQueueName == "WorkflowServiceLibrary.IOrderWorkflow, WorkflowServiceLibrary, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=f65aaaea031e2f5b|CloseOrder")
{
updatedQueueName = "WorkflowServiceLibrary.IOrderWorkflow, WorkflowServiceLibrary, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=f65aaaea031e2f5b|CompleteOrder";
}
if (!string.IsNullOrEmpty(updatedQueueName))
{
// Store in dictionary since we cannot change dictionary while
// enumerating items.
updatedQueueNamesMap.Add(currentQueueName, updatedQueueName);
}
}
foreach (KeyValuePair<string, string> keyValue in updatedQueueNamesMap)
{
string currentQueueName = keyValue.Key;
string updatedQueueName = keyValue.Value;
object value = persistedQueueStates[currentQueueName];
persistedQueueStates.Remove(currentQueueName);
persistedQueueStates.Add(updatedQueueName, value);
if (queueService.Exists(currentQueueName))
{
WorkflowQueue queue = queueService.GetWorkflowQueue(currentQueueName);
FieldInfo queueNameFieldInfo = queue.GetType().GetField("queueName", BindingFlags.Instance | BindingFlags.NonPublic);
if (queueNameFieldInfo != null)
{
queueNameFieldInfo.SetValue(queue, updatedQueueName);
}
}
}
}
}
Listing 9. Update Queue Name
I’ve created a QueueHelper class where you just need to check the old queue name
and update it accordingly. You can use a configuration file for the type mapping
so you don’t need to edit the code every time you change a service interface.
The UpdateReferences method is called inside the OnActivityExecutionContextLoad
method because we can access the workflow queueing service there. This service
contains the persisted queue states (a key-value pair of queue name and object
state). We have to use reflection to access the persistedQueueStates variable
because it is private. Then, we can remove the old queue name and add the new
one. Aside from changing the queue names in persisted queue states, we also need
to change the queue names in WorkflowQueue objects.
How about adding or removing activities to or from the workflow? Supposed we remove
an activity from the workflow. When we load a persisted workflow instance, we
will get the following exception.
System.Runtime.Serialization.SerializationException: The object with ID 33 implements
the IObjectReference interface for which all dependencies cannot be resolved.
The likely cause is two instances of IObjectReference that have a mutual dependency
on each other.
Unfortunately, there is no easy way to convert persisted workflow instances to a
new version if you added or removed activities in the type definition, at least
for workflows with code (not pure XOML). I thought of using the feature called
dynamic update but I have yet to try it. This article focused on WF 3.5 and I’ve
yet to determine if the concepts or techniques presented here can be used with
WF 4.0.