2

I wonder how to do the version control of declarative workflow in VS 2013. App version control is just working file but when it comes to the workflow I couldn't figure it out how to do it. The basic problem I face is when ever I deploy new version of the workflow along with app upgrade, old WF instances tend to break and fall in to suspended status. Only workaround I can think of is to create a brand new workflow with new functionality and keep the old workflow to deal with old WF instances.

What is the best practice to do declarative workflow version controlling ?

2

You should use a solution based on MSDN article.

I have described it more thoroughly on a blog post.

(I cannot format the code properly for some reason)

  1. Create new console application (.NET 4.5) project
  2. Add the following references to the project (not all are needed, but I didn't filter out unneeded references):

    Project references

  3. Add WorkflowManagementClientExtensions class to the project:

    //------------------------------------------------------------
    // Copyright (c) Microsoft Corporation. All rights reserved.
    //------------------------------------------------------------

    using Microsoft.Activities;
    using Microsoft.Activities.Messaging;
    using Microsoft.Workflow.Client;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Threading;

    namespace Microsoft.Workflow.Samples.Common
    {
    public static class WorkflowManagementClientExtensions
    {
    public static void PublishActivity(this WorkflowManagementClient client, string name, string xamlFilePath)
    {
    client.Activities.Publish( new ActivityDescription(WorkflowUtils.Translate(xamlFilePath)) { Name = name }); }

        public static void PublishWorkflow(this WorkflowManagementClient client, string workflowName, string xamlFilePath, SubscriptionFilter activationFilter = null)
        {
            PublishWorkflow(client, workflowName, xamlFilePath, null, null, activationFilter);
        }
    
        public static void PublishWorkflow(this WorkflowManagementClient client, string workflowName, string xamlFilePath, Collection<ExternalVariable> externalVariables, SubscriptionFilter activationFilter = null)
        {
            PublishWorkflow(client, workflowName, xamlFilePath, externalVariables, null, activationFilter);
        }
    
        public static void PublishWorkflow(this WorkflowManagementClient client, string workflowName, string xamlFilePath, IDictionary<string, string> configValues, SubscriptionFilter activationFilter = null)
        {
            PublishWorkflow(client, workflowName, xamlFilePath, null, configValues, activationFilter);
        }
    
        public static void PublishWorkflow(this WorkflowManagementClient client, string workflowName, string xamlFilePath, Collection<ExternalVariable> externalVariables, IDictionary<string, string> configValues, SubscriptionFilter activationFilter = null)
        {
            // publish the activity description related with the workflow
            client.Activities.Publish(
                new ActivityDescription(WorkflowUtils.Translate(xamlFilePath)) { Name = workflowName });
    
            // now, publish the workflow description
            WorkflowDescription description = new WorkflowDescription
            {
                Name = workflowName,
                ActivityPath = workflowName,
            };
    
            // add external variables
            if (externalVariables != null)
            {
                externalVariables
                    .ToList()
                    .ForEach(ev => description.ExternalVariables.Add(ev));
            }
    
            // add config
            if (configValues != null)
            {
                description.Configuration = new WorkflowConfiguration();
                configValues
                    .ToList()
                    .ForEach(c => description.Configuration.AppSettings.Add(c));
            }
    
            // add activation filter
            if (activationFilter != null)
            {
                description.ActivationDescription = new SubscriptionActivationDescription
                {
                    Filter = activationFilter
                };
            }
    
            // publish!
            client.Workflows.Publish(description);
        }
    
        public static void CleanUp(this WorkflowManagementClient client)
        {
            client.CurrentScope.Delete();
        }
    
        public static string WaitForWorkflowCompletion(this WorkflowManagementClient client, string workflowName, string instanceId, int pollingInterval = 0)
        {
            string currentStatus = string.Empty;
            string lastStatus = string.Empty;
    
            WorkflowInstanceInfo instanceInfo = client.Instances.Get(workflowName, instanceId);
    
            while (true)
            {
                instanceInfo = client.Instances.Get(workflowName, instanceId);
    
                currentStatus = instanceInfo.UserStatus;
    
                if (currentStatus != lastStatus && !string.IsNullOrWhiteSpace(currentStatus))
                {
                    Console.Write("   Current Status: ");
                    WorkflowUtils.Print(currentStatus, ConsoleColor.Cyan);
                    lastStatus = currentStatus;
                }
    
                if (instanceInfo.WorkflowStatus == WorkflowInstanceStatus.Started || instanceInfo.WorkflowStatus == WorkflowInstanceStatus.NotStarted)
                {
                    Thread.Sleep(pollingInterval);
                    continue;
                }
    
                if (instanceInfo.WorkflowStatus == WorkflowInstanceStatus.Completed)
                {
                    Console.WriteLine("\nWorkflow instance completed");
                }
    
                break;
            }
    
            return instanceInfo.UserStatus;
        }
    }
    

    }

  4. Add WorkflowUtils class to the project. Notice that Translate method is loading assembly from Visual Studio PublicAssemblies folder:

    //------------------------------------------------------------
    // Copyright (c) Microsoft Corporation. All rights reserved.
    //------------------------------------------------------------

    using System;
    using System.Text;
    using System.Xaml;
    using System.Xml;
    using System.Xml.Linq;
    using Microsoft.Activities.Design.ExpressionTranslation;
    using Microsoft.Workflow.Client;
    using System.Reflection;

    namespace Microsoft.Workflow.Samples.Common
    {
    public class WorkflowUtils
    {
    public static WorkflowManagementClient CreateForSample(string rootScope, string scopeName)
    {
    var rootClient = new WorkflowManagementClient(new Uri(rootScope));

            return rootClient.CurrentScope.PublishChildScope(scopeName,
                new ScopeDescription()
                {
                    UserComments = string.Format("For {0} sample only", scopeName)
                });
        }
    
        public static XElement Translate(string xamlFile)
        {
            string translatedWorkflowString = null;
    
            Assembly wfAssembly = Assembly.LoadFile(@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.SharePoint.DesignTime.Activities.dll");
            XamlXmlReaderSettings settings = new XamlXmlReaderSettings();
            settings.LocalAssembly = wfAssembly;
    
            using (XamlReader xamlReader = new XamlXmlReader(xamlFile, settings))
            {
                TranslationResults result = ExpressionTranslator.Translate(xamlReader);
                if (result.Errors.Count == 0)
                {
                    StringBuilder sb = new StringBuilder();
                    using (XmlWriter xmlWriter = XmlWriter.Create(sb, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
                    {
                        using (XamlXmlWriter writer = new XamlXmlWriter(xmlWriter, result.Output.SchemaContext))
                        {
                            XamlServices.Transform(result.Output, writer);
                        }
                    }
                    translatedWorkflowString = sb.ToString();
                }
                else
                {
                    throw new InvalidOperationException("Translation errors");
                }
            }
    
            return XElement.Parse(translatedWorkflowString);
        }
    
        public static void PrintDone()
        {
            Print("[Done]", ConsoleColor.Green);
        }
    
        public static void Print(string message, ConsoleColor color)
        {
            ConsoleColor currentColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.WriteLine(message);
            Console.ForegroundColor = currentColor;
        }
    }
    

    }

  5. Now you need to find out the GUID of the module which was used in deploying workflow (prerequisite). This GUID is located in the Module element, Url attribute in Elements.xml file in workflow folder.

  6. Now you need to query workflow resource management database to find out the scope path of your workflow. If you left database name with default value during configuration of workflow farm, this query should get you the scope path (replace the GUID in query with GUID from the previous step):

    SELECT [Path] FROM [WFResourceManagementDB].[dbo].[Scopes] s join [WFResourceManagementDB].[dbo].[Activities] a on s.ScopeId = a.ScopeId where a.Name = 'WorkflowXaml_86c7e42d_4fe1_4b83_9d55_fcbb6dde7b9c'

  7. Now it's time to change x:Class attribute in the workflow.xaml file. Last token in the attribute should contain GUID from step 4. For example: x:Class="In2.Ilas.Sp.Ecase.PukWf.WorkflowXaml_ed00d3bd_4796_41ba_b288_35ce2226f89a"

  8. Finally, console application should have the following code (replace the path from step 5 and workflow name from step 4):

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Client;
    using Microsoft.SharePoint.Client.WorkflowServices;
    using Microsoft.Workflow.Client;
    using Microsoft.Workflow.Samples.Common;
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    using System.Linq;

    namespace ConsoleApplication1
    {
    class Program
    {
    static void Main(string[] args)
    {
    WorkflowManagementClient client = new WorkflowManagementClient("http://zg-sp2013-02:12291/SharePoint/default/030e8465-c66c-4f9f-83af-e64d5a4a799b/61aa1fff-103e-49c8-bc56-395c27ca7f81");
    client.Activities.Publish(new ActivityDescription(WorkflowUtils.Translate("Workflow.xaml")) { Name = "WorkflowXaml_ed00d3bd_4796_41ba_b288_35ce2226f89a" });
    }
    }
    }

  9. Copy Workflow.xaml file to console application's folder.
  10. Run the application.
  • 1
    @BennySkogberg I have updated the post accordingly. I couldn't format the code properly though. – Slaven Semper Sep 10 '15 at 9:12
  • Good Work! I've removed the post notice and previous comments. Thank you! – Benny Skogberg Sep 10 '15 at 12:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.