Dynamic Update in WF4.5: Sample using a re-hosted Workflow Designer
Introduction
WF 4.5 added Versioning and Dynamic Updates to workflows.
The dynamic update process consists in 2 main steps:
- Defining the update, in terms of changes to the workflow definition. The output of this step is an Update Map
- Applying the Update Map to persisted workflow instances
Step A. above takes place in 3 substeps:
- Calling DynamicUpdateServices.PrepareForUpdate(Activity), where activity is the root activity of the workflow definition
- Making in-memory changes to the workflow definition
- Calling DynamicUpdateServices.CreateUpdateMap(Activity). This creates an update map, which is typically persisted, to be used sometime later to carry out step B. above.
Under the hood
Steps 1. – 3. above must be applied to the same in-memory workflow definition. This is because DynamicUpdateServices.PrepareForUpdate(activity) prepares that specific in-memory workflow definition for update, so the following call to CreateUpdateMap() must happen on the same in-memory instance.
PrepareForUpdate() annotates the in-memory workflow definition with information that will be used at a later time, when CreateUpdateMap() is called, to compute the delta. This is logically equivalent to creating an internal cloned workflow definition.
Note: PrepareForUpdate() and CreateUpdateMap() have overloads that take an ActivityBuilder as an argument, instead of an Activity. This simplifies the usage in conjunction with workflow definitions loaded from XAML (XamlServices.Load() returns an ActivityBuilder object, not an Activity), but it does not change the requirement that changes be applied to the in-memory workflow definition.
The Code Approach
Let’s focus now on step A.2 above, the in-memory changes to the workflow definition.
The easiest approach to the in-memory changes is to hard-code the changes in code. This link exemplifies this process. See, specifically, the “To update StateMachineNumberGuessWorkflow”, “To update FlowchartNumberGuessWorkflow” and | To update SequentialNumberGuessWorkflow” sections.
This approach works just fine. However, by hard-coding the changes into the program, it
- requires a different program for each new change
- requires a programmer to make the change, instead of a business owner
- may require a complex and hard-to-read sequence of statements in order to make a simple change to the workflow definition. This is especially true when the changes are made deep into the hierarchy of activities
In order to overcome these limitations, the changes would have to be made through the Workflow Designer, instead of being hard-coded. The requirement that the changes be made to the in-memory workflow definition rules out the Workflow Designer used in Visual Studio however: the WF Designer used in Visual Studio loads a workflow from a XAML file and saves the change to a XAML file, so it is not usable to make in-memory changes to a workflow definition.
The Re-hosted Workflow Designer Approach
The Workflow Designer, thankfully, is a reusable component that can be hosted in any process. Visual Studio just happens to be one host of Workflow Designer. By re-hosting the Workflow Designer in a custom host, we may be able to allow in-memory changes to the workflow definition through the Workflow Designer.
The Sample Code
The sample code attached to this post is general-purpose: the input is a workflow in XAML and the output, after the user makes changes to the workflow definition in the designer, are:
- the XAML file of the modified workflow
- the update map (XML file)
To use the sample:
- Load a workflow from the FIle…Open Workflow Definition menu item. DynamicUpdateServices.PrepareForUpdate() is called at this stage.
- Make the changes to the workflow definition in the designer. This includes adding activities, modifying existing activities, adding/removing variables and arguments.
- Save the workflow and create the update map using the File…Save and Create Update Map menu item. First, you can save the workflow definition to a .XAML file. Then, if you choose to save the update map, DynamicUpdateServices.CreateUpdateMap() is called and the update map is saved to an XML file.
This is how the program looks like, when a workflow definition is loaded:
A word of caution
A re-hosted Workflow Designer does not always offer the same user experience of the Workflow Designer in Visual Studio, because the Workflow Designer can be configured in many different ways.
For instance, the Services and Items of the Editing Context may be different, or configured differently. Also, a re-hosted Workflow Designer has some limitations. As far as dynamic update of workflows goes, though, these limitations should not be a roadblock.
You’ll also notice that the behavior of the Toolbox in the sample differs from that of Visual Studio:
- at startup, a pre-defined set of common activities is loaded in the Toolbox, grouped into a set of categories
- when a workflow definition is loaded, the activities of the workflow are added to a new category called Auto, displayed at the top of the toolbox
- it is possible to load additional activities by using the File | Add Activities in Assembly… menu item. All the activities in that assembly are added to a new category, named after the assembly, displayed at the bottom of the toolbox. You may want to load the System.Activities.dll assembly to see a broad range of activities (more than the Visual Studio toolbox shows)
- you can remove an entire category or an activity from the toolbox by right-clicking on them and selecting Remove
The sample provided here is not production-ready code: error handling is minimal, it does not have tracing or logging, and it has not been extensively tested.
All this said :-), if you find any issue with the sample, please let me know.
Enjoy!
Comments
Anonymous
December 11, 2013
Hi Carlo,I'm trying to download your project but the file is 0 kb.Can you provide me with a new link please? I have the feeling you have the solution to my problems updating workflows :)Thanks you,Regards,VincentAnonymous
December 11, 2013
Hi Vincent. Thanks for pointing this out.I uploaded the sample again. It should not be empty this time.Anonymous
December 12, 2013
Hi Carlo,Your idea of updating workflows by design is very good, yet i'm looking for a solution to load a workflow design version, let's say v1, and creating an update map with another version, v2, so I have the freedom to develop and test my new workflow design before calling PrepareForUpdate and CreateUpdateMap. I other words, I want to update long-running-processes with a new workflow design. Your idea is very good, but not suitable for my desired environment.You have any idea how I can fix this?Regards,VincentAnonymous
January 19, 2014
I want to know how to apply the update map and the updated workflow definition in my codeAnonymous
January 19, 2014
@Mohamed: the update can be applied by loading the workflow instance (WorkflowApplication.Load()), providing the update map as an argument.See msdn.microsoft.com/.../jj205427(v=vs.110).aspx, in the section "To apply the dynamic updates" and, more specifically, its step 19.Anonymous
March 12, 2014
Hi,I need help in understanding and solving dynamic updates for .Net 4.5 workflowsI'm writing a console application to create a update map and then the idea is to update the persisted InstanceId of workflows using the map.Steps that I have done,• Create a .Net 4.5 Activity simple workflow• Persist the workflow using code to DB.• Saved the existing XAML file and .dll into a folder named as previous version.• Prepared the existing XAML file for update• Using console app, created a .map file from the XAML after adding a writeline activity• Saved the updated definition of XAML into a folder location• Trying to update the persisted workflow instance using the map. This when I’m getting an error“The update map does not match the workflow definition. Please make sure that this is the correct map for this definition, and that the serialized map has not been manually altered.”Code used to update the persisted instance with the mapprivate static void updateWFInstanceWithMap(Guid CurrentInstance, SqlWorkflowInstanceStore CurrentPersistenceStore) { string path = Path.Combine(mapPath, "SequentialActivtiyflow.map"); using (FileStream fs = File.Open(path, FileMode.Open)) { DataContractSerializer serializer = new DataContractSerializer(typeof(DynamicUpdateMap)); object updateMap = serializer.ReadObject(fs); if (updateMap == null) { throw new ApplicationException("DynamicUpdateMap is null."); } map = (DynamicUpdateMap)updateMap; } // Create a workflow application. You must specify the original workflow definition, but // you may provide an updated WorkflowIdentity if desired to reflect the update. WorkflowIdentity identity = new WorkflowIdentity { Name = "Activity1 V1", Version = new Version(1, 2, 0, 0) }; // Load the persisted workflow instance using the original workflow definition // but with an updated WorkflowIdentity. WorkflowApplication wfApplication = new WorkflowApplication(new Activity1(), identity); //WorkflowApplication wfApplication = new WorkflowApplication(new Activity1()); // WorkflowApplication wfApplication = new WorkflowApplication(wf); WorkflowApplicationInstance wfInstance = WorkflowApplication.GetInstance(CurrentInstance, CurrentPersistenceStore); wfApplication.Load(wfInstance, map); wfApplication.Run(); Console.Read(); }Anonymous
August 26, 2014
Hello,When upgrading the instances, I am getting this error. Can you please help?'StateMachine' is not of type 'DynamicActivity'. When loading this instance you must ensure that the activity with name 'StateMachine' implements 'DynamicActivity'.Anonymous
August 28, 2014
@rahul: if you can share with me your instances, and the detailed steps of how to reproduce the behavior that you described, I will have a quick look.Anonymous
August 29, 2014
Carlo, thank you for reply. My current code depends on a lot of DLLs, I will create a cut down version and share the DB + Code.Anonymous
August 13, 2015
The comment has been removedAnonymous
August 13, 2015
Sorry, I have been solved. I edited the workflow xaml, then, in xmlns:local=”clr-namespace:MyActivity;", I added "assembly=MyProject”. And loaded perfect! Thanks!Anonymous
August 13, 2015
The Only problem is after generate the XAML file. When I add it, the project is not compile. Say: "The XAML MSBuild task only processes files that contain an '{schemas.microsoft.com/.../xaml}Class' directive. Please refer to documentation for usage of ‘{schemas.microsoft.com/.../xaml}Class’." But declaration is equals to original XAML. The original is ok. I'll continue with the review.