Workflow Services Versioning

As you know Windows Workflow Foundation (WF) 4.5 now supports workflow versioning and let’s see how workflow services versioning can be achieved using the WorkflowServiceHostFactory (WSHF) approach.

Hosting workflow services using the WSHF is super easy. Everything can be configured in the host web.config file. One of the tips to share is the file-less activation feature introduced in WF 4.0 which do not require us to create .svc files for our workflows. Here's a snippet of how it looks like.

 <serviceHostingEnvironment multipleSiteBindingsEnabled="true">
  <serviceActivations>     <add factory="System.ServiceModel.Activities.Activation.WorkflowServiceHostFactory"          relativeAddress="./ExpenseWorkflowService.svc"          service="WFVersioning.Workflows.ExpenseWorkflowService" />   </serviceActivations>
</serviceHostingEnvironment>

The reason to show you this is because of the System.ServiceModel.Activities.Activation.WorkflowServiceHostFactory. We will be replacing that with our own later. In case you are unsure of how to configure a workflow service, it looks something like the following:

 <service name="ExpenseWorkflowService"           behaviorConfiguration="WorkflowServiceBehavior">      <endpoint name="basicHttpWorkflowService"              address=""              binding="basicHttpBinding"              contract="IExpenseWorkflowService" />      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>

You must be wondering why it looks like a WCF service configuration. Workflow Services are actually WCF services. You can just treat it like a Designer-enabled WCF if you wish.
To enable persistence for our workflows, we will require to configure the persistence store. Remember to have it configured in the serviceBehavior.

 <serviceBehaviors>   <behavior name="WorkflowServiceBehavior">     <serviceMetadata httpGetEnabled="true" />     <serviceDebug includeExceptionDetailInFaults="true" />      < sqlWorkflowInstanceStore connectionStringName= " workflowStore "                                hostLockRenewalPeriod= " 00:00:30 "                                runnableInstancesDetectionPeriod= " 00:00:05 "                                instanceCompletionAction= " DeleteAll "                                instanceLockedExceptionAction= " AggressiveRetry " 
                              instanceEncodingOption= " GZip "  /> <dataContractSerializer maxItemsInObjectGraph="2147483647" />   </behavior>
</serviceBehaviors>

The above configuration refers to a connection string name workflowStore which must be configured in the connectionStrings settings (Not shown here).
Here's how the Version 1 of the ExpenseWorkflowService looks like for this example:

With the previous configuration, the workflow service should run fine when hosted on IIS/WAS. However, assuming now the requirements have changed and we are required to deploy a newer version of the service which supports cancellation. The Version 2 of our workflow service will look like the following:

This would break any running workflow instances that have not been completed i.e. Expenses that were submitted but pending approval. To support the new workflow, we will first need to create the new version in a new workflow xaml file and give it a different name i.e. ExpenseWorkflowServiceV2. You must give it another name. You cannot rename the old one to reuse the name. It will cause your running instances to fail if you do.
Next, create a custom WorkflowServiceHostFactory in the host project.

 public class ExpenseWorkflowServiceHostFactory: WorkflowServiceHostFactory
{     protected override WorkflowServiceHost CreateWorkflowServiceHost(
       Activity activity, Uri[] baseAddresses)     {
         // Current workflow service.         WorkflowService current = new WorkflowService         {             Name = "ExpenseWorkflowService",             Body = new ExpenseWorkflowServiceV2(),             DefinitionIdentity = new WorkflowIdentity             {                 Name = "Version_2",                 Version = new Version(2, 0, 0, 0)             }         };         // Older version.         WorkflowService version1 = new WorkflowService         {             Name = "ExpenseWorkflowService",             Body = new ExpenseWorkflowService(),         };         // Create WorkflowServiceHost         WorkflowServiceHost host =  new WorkflowServiceHost(current, baseAddresses);         host.SupportedVersions.Add(version1);         return host;     }
}
 

Few things to take note here.

  1. Remember to override the CreateWorkflowServiceHost method that accepts an Activity parameter. The one that accepts WorkflowService is for Xamlx-based services.
  2. Version 1 does not have a DefinitionIdentity because the initial instances created by the default WSHF does not contain any.
  3. Remember to add the older versions to the SupportedVersions collection of the host.

Finally, modify the serviceActivation settings to use our custom WSHF.

 <serviceHostingEnvironment multipleSiteBindingsEnabled="true">   <serviceActivations>     <add factory="WFVersioning.Hosts.Web.ExpenseWorkflowServiceHostFactory"          relativeAddress="./ExpenseWorkflowService.svc"          service="WFVersioning.Workflows.ExpenseWorkflowService" />   </serviceActivations>
</serviceHostingEnvironment>

Once this is done, the workflow service should be able to support both the new and old versions. The behavior will be:

  1. Newer client consuming the service will invoke the new workflow service version. 
  2. Older clients will invoke the older workflow service version (inclusive of new instances). 
  3. Any running instances will continue to use the older workflow service version.

If you still have any doubts on workflow services versioning ,please feel free to drop by to our MVP Serena Yeoh’s blog here.