Versioning long running workfows part 1
Part 1 Part 2 Part 3 Part 4
One of the cool features of Windows Workflow Foundation is that it allows long running processes. And not just long running as in a few minutes but really long running as in a few years . This is possible true the use of the SqlWorkflowPersistenceService, or in fact any derived class from WorkflowPersistenceService, which is going to save the state of a workflow to disk when it is not actually busy.
So that is pretty cool but it is kind of unlikely that your programs are not going to change over a year so in all likelihood you are going to be deploying newer versions of your assemblies while there are multiple workflow's active. In order to allow multiple versions of a workflow to run we need to understand what is going on under the covers.
To demonstrate the behavior I am going to use a small project with the following layout. The WorkflowConsoleApplication1 is the host and is configured to use SqlWorkflowPersistenceService. The WorkflowLibrary1 is the project containing the actual workflow and this is the project we are going to version.
The main program is pretty simple and looks like this:
static void Main(string args)
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
string connStr = @"Data Source=.\sqlexpress;Initial Catalog=WorkflowPersistence;Integrated Security=True";
SqlWorkflowPersistenceService persistence = new SqlWorkflowPersistenceService(connStr,
true, TimeSpan.FromSeconds(15), TimeSpan.FromMinutes(1));
workflowRuntime.ServicesExceptionNotHandled += (sender, e) =>
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));
Console.WriteLine("Press ennter to stop");
Basically it sets up the SqlWorkflowPersistenceService, starts the runtime, starts a new workflow and waits for the user to terminate the program. It also prints any exceptions that might occur in a workflow runtime service like the SqlWorkflowPersistenceService.
The workflow is real simple. It just loops forever and prints a message that includes the assembly version number every 10 seconds.
What we are going to do is start this program so we have a first instance in the persistence store, update the workflow assembly to version two an have version 1 and 2 run side by side.
The WorkflowPersistenceService part.
The WorkflowPersistenceService actually persists, or dehydrates as it is called, a workflow using the binary serializer. This means that when it recreates, or dehydrates as this is called, the workflow instance it is done in a assembly version depended manner. So the first thing to be aware of is the standard .NET versioning behavior.
For a .NET assembly to be versionable the first prerequisite is that it has a string name. The rules are simple, no strong name = no versioning and the first assembly found will be the one that is used. Now there is a potential problem here because the binary serializer uses the most compact form possible and doesn't store field names, only their value. The object is responsible for reading the data from a stream in the correct order and size. No problem as long as the same type that was serialized is actually used to deserialize an object. But suppose a newer type is used that actually expects extra fields? Well it is going to try to read some data that is not actually there and the process is going to fail .
Versioning assemblies is important!
So in order to be able to version the WorkflowLibrary1 assembly we need to add a strong name. This is done in the project settings on the Signing tab as follows:
When we run the application we can see from the following output that the workflow version 1 is running just fine .
Once we have stopped the application its time to upgrade to version 18.104.22.168. Now I am not even going to make any changes to the workflow, all I am going to do is change the assembly major version number of the WorkflowLibrary1 project to 2. This is actually stored as part of the assembly and thus causes the project to recompile.
If we run the application again we are going to see the following output:
While version 22.214.171.124 of the workflow will run just fine version 126.96.36.199 cannot be loaded and the SqlWorkflowPersistenceService reports the following error via the WorkflowRuntime ServicesExceptionNotHandled event.
Could not load file or assembly 'WorkflowLibrary1, Version=188.8.131.52, Culture=neutral, PublicKeyToken=8afb6d596a769080' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
This error is the result of the binary serializer seeing that the workflow was dehydrated with version 184.108.40.206 of the WorkflowLibrary1 so it wants to load that version. And that is nowhere to be found as we only have version 220.127.116.11 which is not good enough.
Enabling side by side execution
The first ting we need to do is make sure we have both versions of the assembly WorkflowLibrary1 available. In order to do so we need to create a folder in the bin\Debug folder with the name Version_1_0_0_0. This is the folder where I am going to keep a copy of the WorkflowLibrary1.dll version 18.104.22.168.
Next we need to tell the .NET runtime where to look for version 22.214.171.124 of the assembly by adding this information to the app.config of our application. This is standard .NET material and in no way specific to Windows Workflow Foundation. The app.config looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<assemblyIdentity name="WorkflowLibrary1" publicKeyToken="8afb6d596a769080" />
<codeBase version="126.96.36.199" href="Version_1_0_0_0/WorkflowLibrary1.dll"/>
With both versions of the assembly and configuring the runtime so it knows where to find the first version of the assembly both versions of the workflow will run happily together .
So how about the GAC?
In this example I use a private location to store multiple versions of the assembly but I could also have used the Global Assembly Cache, of GAC for short, to do this. Both mechanisms allow an assembly to be versioned so which is better? Normally the GAC should be used by assemblies used with multiple applications. In this case, and this is probably the case with most workflow assemblies, the WorkflowLibrary1 assembly only used by our host and not by other applications making a private location the proper place. That said there are certainly scenarios, like shared custom activity assemblies, where the GAC would be the proper place.
So are we all set?
No unfortunately not quite because there are some more issues that might crop up with versioning. So stay tuned for more