One of the really important new features in Windows Workflow Foundation 4.5 is the capability to version workflows and workflow instances. You get to choose what you want to do, either keep running existing instances with their original workflow definition or upgrade them to the latest workflow definition.
What should you do, upgrade exiting instances or run multiple definitions side by side?
Both the upgrade to latest and keep running with the original definition make sense in different scenarios. If you find a bug in your workflow definition the update scenario is probably what you are looking for. However if your business contract conditions change it makes sense to keep the older workflow instances with the original workflow definition and start new new workflows with the new definition. Both make sense, it’s just a matter of the requirements.
In this blog post I am going to explain how to do an upgrade of an existing workflow instance. In a future blog post I will go into side by side execution.
The basic steps
When upgrading a workflow instance we are always concerned with the following basic steps”
- Create your initial workflow definition.
- Start one or more instances using the this workflow definition.
- Prepare the original workflow definition for update.
- Make some changes to the workflow definition.
- Create an update map between the original workflow definition and the new one.
- Update exiting workflow instances to reflect the new workflow definition.
Create your initial workflow definition
This is the simple step as anyone that has been using Windows Workflow Foundation 4 has already been doing this. For this example I am using a real simple workflow that prints the version number it was started with and the current version.
1: private static WorkflowApplication CreateWorkflowApplication()
3: var identity = new WorkflowIdentity
5: Version = new Version(1, 0)
8: var workflow = new TheWorkflow();
9: var app = new WorkflowApplication(workflow, identity);
10: app.InstanceStore = CreateSqlWorkflowInstanceStore();
11: app.PersistableIdle = e => PersistableIdleAction.Persist;
12: app.OnUnhandledException = e =>
15: return UnhandledExceptionAction.Abort;
18: return app;
Start one or more instances using the this workflow definition
Again no big deal here, just run the workflow application.
1: private static void StartNewWorkflow()
3: WorkflowApplication app = CreateWorkflowApplication();
Prepare the original workflow definition for update
In this step we prepare the workflow definition, the XAML file, for update using the DynamicUpdateServices.PrepareForUpdate() on an ActivityBuilder. This step adds a copy of the workflow definition in a DynamicUpdateInfo.OriginalActivityBuilder element so we can see the differences later when we are done. Right now we can use the workflow to run a workflow and everything would appear unchanged.
1: private static void PrepareWorkflowForUpdate()
3: ActivityBuilder builder = LoadWorkflowDefinition();
9: private static ActivityBuilder LoadWorkflowDefinition()
11: ActivityBuilder builder;
12: using (var xamlReader = new XamlXmlReader(FileName))
14: var builderReader = ActivityXamlServices.CreateBuilderReader(xamlReader);
15: builder = (ActivityBuilder)XamlServices.Load(builderReader);
17: return builder;
20: private static void SaveWorkflowDefinition(ActivityBuilder builder)
22: using (var writer = File.CreateText(FileName))
24: var xmlWriter = new XamlXmlWriter(writer, new XamlSchemaContext());
25: using (var xamlWriter = ActivityXamlServices.CreateBuilderWriter(xmlWriter))
27: XamlServices.Save(xamlWriter, builder);
Make some changes to the workflow definition
In the workflow I am going to make a small change to reflect that it is workflow definition is version 1.1 and in the code I am going to use the same version 1.1.
If I try to resume the previously saved workflow definition this results in an VersionMismatchException.
Unhandled Exception: System.Activities.VersionMismatchException: The WorkflowIdentity (‘; Version=1.0′) of the loaded instance does not match the WorkflowIdentity (‘; Version=1.1′) of the provided workflow definition. The instance can be loaded using a different definition, or updated using Dynamic Update.
Create an update map between the original workflow definition and the new one
In order to upgrade the saved workflow instance we first need to create an update map. In this step we use the DynamicUpdateServices.CreateUpdateMap() function to create a map of the changes made since DynamicUpdateServices.PrepareForUpdate() was called. There is no way to create an update map based on anything else then the internal DynamicUpdateInfo. This means if you forgot to call DynamicUpdateServices.PrepareForUpdate() and save the workflow but instead just started making changes you are going to experience some problems. My first expectation was I could create an update map by comparing and older version of the workflow, for example from the TFS history, but that just isn’t possible using DynamicUpdateServices.CreateUpdateMap(). One solution here might be to use DynamicUpdateInfo.SetOriginalActivityBuilder().
Another slightly surprising thing is there is no native save functionality for a DynamicUpdateMap. In this case I am using a DataContractSerializer to save the update map in a way I can load it again.
1: private static void CreateWorkflowUpdateMap()
3: ActivityBuilder builder = LoadWorkflowDefinition();
4: DynamicUpdateMap updateMap = DynamicUpdateServices.CreateUpdateMap(builder);
9: private static void SaveUpdateMap(DynamicUpdateMap updateMap)
11: var fileName = Path.ChangeExtension(FileName, "map");
12: var serializer = new DataContractSerializer(typeof(DynamicUpdateMap));
13: using (var stream = File.Open(fileName, FileMode.Create))
15: serializer.WriteObject(stream, updateMap);
Update exiting workflow instances to reflect the new workflow definition
With the update map in place we can finally go and update workflow instances saved in the instance store. The code isn’t hard, just load the workflow instance using WorkflowApplication.Load() passing in the DynamicUpdateMap to use, make sure to unload the workflow again and you are good to go.
As you can see the workflow state still contains the reference to version 1.0 from when it started but the current workflow definition used is 1.1.
Of course there are changes that might be impossible to make, for example deleting the currently executing activity would leave the workflow in an invalid state. Using the WorkflowApplicationInstance.CanApplyUpdate() function you can check if an update is actually possible before doing so.