WF MigrationCookbook: WorkflowHosting

Introduction

In the .NET Framework 4, Microsoft is releasing the second major version of Windows Workflow Foundation (WF). WF was released in .NET 3.0 (this included the types in the System.Workflow.* namespaces; let’s call this WF3) and enhanced in .NET 3.5. WF3 is also part of the .NET Framework 4, but it exists there alongside new workflow technology (this includes the types in the System.Activities.* namespaces; let’s call this WF4).

This paper presents guidance for redesigning WF3 hosting codeas WF4 hosting code. The goal is to cover the key differences in workflow hosting between WF3 and WF4.

Please see the “WF Migration Overview” document for an introduction to WF3 to WF4 migration, and links to related documents. The latest version of these documents can be found at

The information contained in this document relates to pre-release software product, which may be substantially modified before its first commercial release. Accordingly, the information may not accurately describe or reflect the software product when first commercially released. This document is provided for informational purposes only, and Microsoft makes no warranties, express or implied, with respect to this document or the information contained in it.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

2009 Microsoft Corporation. All rights reserved.

Microsoft, Windows, Visual Studio, and the .NET logo are trademarks of the Microsoft group of companies.All other trademarks are property of their respective owners.

Workflow Hosting

Hostingrefers totheset of WF featuresthrough which workflow instances are created, run, and managed. This includes APIs to create instances of workflows, enable tracking and persistence,subscribe to instance lifecycle events, and perform operations such as ‘terminate’ and ‘cancel’ on a workflow instance.

In WF3,workflow hosting is achieved usingeither the WorkflowRuntime typefor .NET application hostingor the WorkflowServiceHost type for hosting workflow services.

In WF4,there are several choices for hosting workflows. This document will focus on migration of hosting code for workflow applications. For details on migrating workflow services hosting code, see the WF Migration Guidance document for Workflow Services.

Workflow Applications

In WF3, WorkflowRuntime is the hostfor workflow applications. WorkflowRuntime is a multi-instance host that provides proxies to individual workflow instances in the form of WorkflowInstance objects.

WF4 provides a set of hosting choices, described at a high level in the table below. Please consult the .NET4 product documentation for full documentation of these hosting choices and their APIs.

WF4 host container / Comments
WorkflowInvoker /
  • Simple “method call” style of workflow execution for short-lived workflows (persistence is not allowed)

WorkflowApplication /
  • Single-instance host with asynchronousexecution
  • Supports persistence
  • Provides a set of instance operations and notifications of instance lifecycle events

WorkflowServiceHost
(System.ServiceModel.Activities namespace) /
  • Multi-instance host for WF4 workflow servicesand also for workflows that are not services
  • Supports persistence
  • Exposes endpoints for instance operations
  • Supports configuration
  • Windows Server AppFabric provides IIS/WAS deployment, configuration, management, and monitoring support

WorkflowInstance
(System.Activities.Hosting namespace) /
  • Abstract base class for creating custom hosts
  • Provides access tolowest level of hosting capabilities (interactions with WF4 runtime)

Example: A workflow application

In this example, we will compare a typical workflow hosting scenario in WF3 with hosting options available in WF4.

Below is an example of WF3 workflow hosting code that subscribes to the completed and terminated events, and runs an instance of a workflow defined by the MyWorkflow type:

using System;

using System.Text;

using System.Threading;

using System.Workflow.Runtime;

namespace Cookbook_WorkflowHosting_WF3

{

class Program

{

static void Main()

{

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

AutoResetEvent waitHandle = new AutoResetEvent(false);

workflowRuntime.WorkflowCompleted += delegate(
object sender, WorkflowCompletedEventArgs e)

{

waitHandle.Set();

};

workflowRuntime.WorkflowTerminated += delegate(

object sender, WorkflowTerminatedEventArgs e)

{

Console.WriteLine(e.Exception.Message);

waitHandle.Set();

};

WorkflowInstance instance = workflowRuntime.CreateWorkflow(

typeof(MyWorkflow));

instance.Start();

waitHandle.WaitOne();

}

}

}

}

In WF4, equivalent hosting code usingSystem.Activities.WorkflowApplication is shown below:

using System;

using System.Activities;

using System.Threading;

namespace Cookbook_WorkflowHosting_WF4

{

class Program

{

static void Main()

{

AutoResetEvent waitHandle = new AutoResetEvent(false);

WorkflowApplication workflowApplication =

new WorkflowApplication(new MyWorkflow());

workflowApplication.Completed = delegate(
WorkflowApplicationCompletedEventArgs e)

{

if (e.TerminationException != null)

{

Console.WriteLine(e.TerminationException.Message);

}

waitHandle.Set();

};

workflowApplication.Run();

waitHandle.WaitOne();

}

}

}

Some differences between WF3 hosting code and WF4 WorkflowApplication hosting code, illustrated in the code above:

  • In WF3, a workflow instance is creating by providing either a Type or XmlReader objects for workflow and rules definitions (or a custom loader). In WF4, the workflow definition is provided directly as an Activity object.
  • In WF4, WorkflowApplication is a proxy to a single workflow instance, whereas the WF3 WorkflowRuntime is a container of workflow instances. Thus, certain operations such as ‘terminate’ that are found on WF3 WorkflowInstance are found on WF4 WorkflowApplication.
  • To begin the execution of a WF3 workflow instance, the WorkflowInstance.Start method is called. In WF4, the WorkflowApplication.Run method is called.
  • WorkflowRuntime has separate events for completed and terminated outcomes. WF4 WorkflowApplication has a single Completed event that handles both outcomes.

WF4 workflows that do not require persistence can be run usingSystem.Activities.WorkflowInvoker. For the previous example, use of WorkflowInvoker looks like this:

using System;

using System.Activities;

namespace Cookbook_WorkflowHosting_WF4

{

class Program

{

static void Main()

{

WorkflowInvoker.Invoke(new MyWorkflow());

// OR

WorkflowInvoker invoker = new WorkflowInvoker(new MyWorkflow());

invoker.Invoke();

}

}

}

WF4 WorkflowServiceHost can also be used to host workflows, even those which are not services.

The WF4 WorkflowServiceHost can be configured with custom WorkflowHostingEndpoint implementations to allow creation of workflow instances and resumption of bookmarks. WorkflowServiceHost can also be configured with a WorkflowControlEndpoint, which exposes instance management operations thatcorrespondto those on WorkflowApplication, with the addition of some host-specific operations such as Suspend.

The following illustrates how to host a workflow in WF4 WorkflowServiceHost, create an instance of the workflow, and execute it (synchronously in this case, though it can also be done asynchronously):

using System;

using System.ServiceModel;

using System.ServiceModel.Activities;

using System.ServiceModel.Channels;

using System.Collections.Generic;

namespace Cookbook_WorkflowHosting_WF4

{

class Program

{

static void Main(string[] args)

{

WorkflowServiceHost host = new WorkflowServiceHost(
new Workflow1(), new Uri("net.pipe://localhost/MyWorkflow"));

WorkflowControlEndpoint controlEndpoint =
new WorkflowControlEndpoint(
new NetNamedPipeBinding(NetNamedPipeSecurityMode.None),
new EndpointAddress(
new Uri("net.pipe://localhost/MyWorkflowControl")

)

);

host.AddServiceEndpoint(controlEndpoint);

CreationEndpoint creationEndpoint = new CreationEndpoint(
new NetNamedPipeBinding(NetNamedPipeSecurityMode.None),
new EndpointAddress(
new Uri("net.pipe://localhost/MyWorkflowControl/Creation")
)

);

host.AddServiceEndpoint(creationEndpoint);

host.Open();

IWorkflowCreation creationClient =
new ChannelFactory<IWorkflowCreation>(
creationEndpoint.Binding,
creationEndpoint.Address).CreateChannel();

Guid instanceId = creationClient.CreateSuspended(null);

WorkflowControlClient controlClient =
new WorkflowControlClient(controlEndpoint);

controlClient.Unsuspend(instanceId);

}

}

}

Below is the implementation of the CreationEndpoint:

using System;

using System.ServiceModel;

using System.ServiceModel.Activities;

using System.ServiceModel.Channels;

using System.Collections.Generic;

namespace Cookbook_WorkflowHosting_WF4

{

[ServiceContract(Name = "IWorkflowCreation")]

public interface IWorkflowCreation

{

[OperationContract(Name = "Create")]

Guid CreateSuspended(IDictionary<string, object> inputs);

}

public class CreationEndpoint : WorkflowHostingEndpoint

{

public CreationEndpoint(Binding binding, EndpointAddress address)

: base(typeof(IWorkflowCreation), binding, address)

{

this.IsSystemEndpoint = true;

}

protected override WorkflowCreationContext OnGetCreationContext(
object[] inputs, OperationContext operationContext,
Guid instanceId, WorkflowHostingResponseContext responseContext)

{

WorkflowCreationContext creationContext =
new WorkflowCreationContext();

creationContext.CreateOnly = true; // Instance will be created and suspended

if (operationContext.IncomingMessageHeaders.Action.EndsWith(
"Create"))

{

Dictionary<string, object> arguments =
(Dictionary<string, object>)inputs[0];

if (arguments != null & arguments.Count > 0)

{

foreach (KeyValuePair<string, object> pair in arguments)

{

creationContext.WorkflowArguments.Add(
pair.Key, pair.Value);

}

}

responseContext.SendResponse(instanceId, null);

}

else

{

throw new InvalidOperationException("Invalid Action: " +
operationContext.IncomingMessageHeaders.Action);

}

return creationContext;

}

}

}

Workflow hosting APIs

In the table below are mappings between WF3 and WF4APIs. Note that in WF3 some of these items are found on WorkflowRuntime while others are on WorkflowInstance.

WorkflowApplication offers both synchronous and asynchronous methods for many operations. WF4 also supports certain operations that were not possible in WF3, e.g. WorkflowApplication.Cancel, WorkflowApplication.Persist. See the .NET4 product documentation for complete documentation of WF4 host capabilities.

WF3 / WF4 WorkflowApplication / WF4 WorkflowServiceHost
WorkflowRuntime.AddService / WorkflowApplication.Extensions.Add / WorkflowServiceHost. WorkflowExtensions.Add()
WorkflowRuntime.CreateWorkflow / WorkflowApplication constructor / WorkflowHostingEndpoint
WorkflowInstance.Abort / WorkflowApplication.Abort / WorkflowControlClient. Abandon()
WorkflowInstance.EnqueueItem / WorkflowApplication. ResumeBookmark / WorkflowHostingEndpoint
WorkflowInstance. GetWorkflowQueueData / WorkflowApplication.GetBookmarks
WorkflowInstance.Load() / WorkflowApplication.Load() / -
WorkflowInstance.Resume() / Not supported / WorkflowControlClient. Unsuspend()
WorkflowInstance.Start() / WorkflowApplication.Run() / -
WorkflowInstance.Suspend() / Not supported / WorkflowControlClient. Suspend()
WorkflowInstance. Terminate() / WorkflowApplication. Terminate() / WorkflowControlClient. Terminate()
WorkflowInstance.Unload() / WorkflowApplication.Unload() / -
WorkflowInstance.InstanceId / WorkflowApplication.Id / WorkflowHostingEndpoint

Notes on the above API mappings:

  • There are no explicit Load or Unloadoperations in the WorkflowServiceHost case. In WorkflowServiceHost, workflow instance loading and unloading is completely managed by WorkflowServiceHost. WorkflowServiceHost can be configured to unload idle workflow instances through the WorkflowIdleBehavior (a service behavior).
  • In the WorkflowServiceHost case, several host APIs are exposed through the WorkflowHostingEndpoint extensibility point.
  • Load and Run occur implicitly as part of WorkflowHostingEndpoint and WorkflowControlClient operations in WorkflowServiceHost.

Below is the mapping between instance lifecycle events on WF3 and WF4 hosting APIs.

WF3 WorkflowRuntime / WF4 WorkflowApplication
WorkflowRuntime.WorkflowAborted / WorkflowApplication.Aborted
WorkflowRuntime.WorkflowCompleted / WorkflowApplication.Completed
WorkflowRuntime.WorkflowCreated / -
WorkflowRuntime.WorkflowIdled / WorkflowApplication.Idle
WorkflowApplication.PersistableIdle
WorkflowRuntime.WorkflowLoaded / -
WorkflowRuntime.WorkflowPersisted / Not supported
WorkflowRuntime.WorkflowResumed / Not supported
WorkflowRuntime.WorkflowStarted / -
WorkflowRuntime.WorkflowSuspended / Not supported
WorkflowRuntime.WorkflowTerminated / WorkflowApplication.Completed
WorkflowRuntime.WorkflowUnloaded / WorkflowApplication.Unloaded
WorkflowRuntime.ServicesExceptionNotHandled / WorkflowApplication.OnUnhandledException

Notes on the above instance lifecycle event mappings:

  • Created, Loaded, and Started events do not exist on WorkflowApplication. WorkflowApplication is a proxy to a single instance, and creating, loading, and running an instance can only be performed from the host application, eliminating the need for these notifications in WF4.

Workflow input/output parameters

In WF3, workflow inputsareprovided as an optional Dictionary<string, object> in overloads of the CreateWorkflow method on WorkflowRuntime. The string key for each dictionary entry corresponds to the name of a property (with a setter) defined on the workflow root type.

In WF3, workflow outputs are retrieved from the WorkflowCompetedEventArgs.OutputParameters collectionavailable to the handler of the WorkflowRuntime.WorkflowCompleted event. The objects in this collection are the values of public properties defined on the workflow root type.

Here is a WF3 example showing inputs and outputs:

using System;

using System.Text;

using System.Threading;

using System.Workflow.Runtime;

using System.Collections.Generic;

namespace Cookbook_WorkflowHosting_WF3

{

class Program

{

static void Main()

{

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

AutoResetEvent waitHandle = new AutoResetEvent(false);

workflowRuntime.WorkflowCompleted += delegate(
object sender, WorkflowCompletedEventArgs e)

{

string s = (string)e.OutputParameters["OutputString"];

int i = (int)e.OutputParameters["OutputInt"];

Console.WriteLine("Workflow outputs: {0}, {1}", s, i);

waitHandle.Set();

};

Dictionary<string,object> inputArgs =
new Dictionary<string,object>();

inputArgs.Add("InputString", "hello!");

inputArgs.Add("InputInt", 5);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(
typeof(MyWorkflow), inputArgs);

instance.Start();

waitHandle.WaitOne();

}

}

}

}

In WF4, workflow inputs are also provided as an IDictionary<string, object>. The string key for each dictionary entry corresponds to an InArgument or InOutArgument of the root activity object.

WF4 workflow outputs are retrieved through the WorkflowApplicationCompletedEventArgs.Outputs collection. The string key in each entry of the Outputs collection corresponds to an OutArgument or InOutArgument of the root activity object.

Here is a WF3 example showing inputs and outputs:

using System;

using System.Activities;

using System.Threading;

namespace Cookbook_WorkflowHosting_WF4

{

class Program

{

static void Main(string[] args)

{

AutoResetEvent waitHandle = new AutoResetEvent(false);

Dictionary<string, object> inputArgs =

new Dictionary<string, object>();

inputArgs.Add("InputString", "hello!");

inputArgs.Add("InputInt", 5);

WorkflowApplication workflowApplication =

new WorkflowApplication(new Workflow1(), inputArgs);

workflowApplication.Completed = delegate(

WorkflowApplicationCompletedEventArgs e)

{

if (e.TerminationException == null)

{

string s = (string)e.Outputs["OutputString"];

int i = (int)e.Outputs["OutputInt"];

Console.WriteLine("Workflow outputs: {0}, {1}", s, i);

}

waitHandle.Set();

};

workflowApplication.Run();

waitHandle.WaitOne();

}

}

}

The WF4 WorkflowInvoker host also supports dictionaries of inputs and outputs.

For non-service workflows hosted in the WF4 WorkflowServiceHost, inputsand outputs are supported through the WorkflowHostingEndpoint. The example in the Workflow Applications section (above) illustrates how arguments are passed in to workflows hosted in WorkflowServiceHost.

Bookmarks

In WF3, WorkflowInstance provides EnqueueItem and EnqueueItemOnIdle methods through which the host application can deliver data to a workflow queue (a resumption point created by an activity in a workflow instance).

In WF4, workflow queues are conceptually replaced by bookmarks. Bookmarks similarly allow for data to be delivered to workflow instance resumption points created by activities. WorkflowApplication provides a ResumeBookmark method that takes the place of EnqueueItem and EnqueueItemOnIdle. Note that in WF4, bookmarks are onlyresumed when the instance is idle.

Please see the WF Migration Cookbook for Advanced Custom Activities to see details about migrating activities that create workflow queues.

Also, note that the WF4 Interop activity can wrap a WF3 activity that creates a workflow queue; data can be delivered to this workflow queue via the ResumeBookmark method (the name of the bookmark provided is the name of the workflow queue). See the .NET 4 product documentation for the Interop activity for further information.

Persistence

In WF3, SQL persistence (using the SqlWorkflowPersistenceService class) is set up in code like this:

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

string connectionString = …;

bool unloadOnIdle = true;

SqlWorkflowPersistenceService persistenceService =
new SqlWorkflowPersistenceService(connectionString, unloadOnIdle,

new TimeSpan(1,0,0), new TimeSpan(0,0,5));

workflowRuntime.AddService(persistenceService);

}

Persistence can also be set up through the application configuration file in WF3:

<configuration>
<configSections>
<section
name="WorkflowRuntime"
type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection,
System.Workflow.Runtime, Version=3.0.00000.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>

<WorkflowRuntime
<CommonParameters>
<add name="ConnectionString" value="…"/>
</CommonParameters>
<Services>
<add
type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
UnloadOnIdle="true"
/>
</Services>
</WorkflowRuntime

</configuration>

In WF4, SQL persistence (using the SqlWorkflowInstanceStore class) is set up in code using WorkflowApplication like this:

WorkflowApplication workflowApplication = new WorkflowApplication(…);

workflowApplication.PersistableIdle = (e) =>

{

return PersistableIdleAction.Unload;

};

string connectionString = …;

SqlWorkflowInstanceStore instanceStore =
new SqlWorkflowInstanceStore(connectionString);

workflowApplication.InstanceStore = instanceStore;

Please consult the .NET4 product documentation for additional details about persistence in WF4.

The following example shows how to configure and enable persistence on WorkflowServiceHost:

WorkflowServiceHost host = new WorkflowServiceHost(…);

string connectionString = "…";

SqlWorkflowInstanceStore instanceStore =
new SqlWorkflowInstanceStore(connectionString);

host.DurableInstancingOptions.InstanceStore = instanceStore;

WorkflowIdleBehavior idleBehavior = new WorkflowIdleBehavior();

idleBehavior.TimeToUnload = TimeSpan.Zero;

host.Description.Behaviors.Add(idleBehavior);

With the WF4 WorkflowServiceHost, persistence can also be set up through the application configuration file:

<configuration>

<system.serviceModel>

<serviceBehaviors>

<behavior name=””>

<sqlWorkflowInstanceStore connectionString=”…” />

</behavior>

</serviceBehaviors>

</system.serviceModel>

/configuration>

Tracking

For details on the redesign of WF3 tracking-related code, please see the companionWF Migration document for Tracking.

Threading

In WF3, an abstract WorkflowSchedulerService (plus derived types) is exposed as a way for a host to participate in the management of threads. WF4 uses System.Threading.SynchronizationContext as the extensibility point for managing threads. Please consult the .NET4 product documentation for additional details about threading in the WF4 runtime.

External Data Exchange

External data exchange services, along with the CallExternalMethod and HandleExternalEvent activities, are supported in WF4 via the Interop activity. Please see the .NET 4 product documentation and SDK samples for more information and example usage.

Dynamic update

WF3 exposes a capability known as dynamic update which allows changes to the definition of a workflow instance that has not completed its execution. WF4 does not in .NET4 havethis capability.

Summary

WF3 workflow application hosting is built around WorkflowRuntime. WF4 offers several options for hosting workflows, including WorkflowInvoker, WorkflowApplication, and WorkflowServiceHost.

Additional Information

  • Visual Studio 2010 -
  • WF 4 –
  • .NET Framework 4 -
  • WF 4 Migration Guidance -
  • WCF and WF Samples for .NET Framework -
  • WCF and WF 4 Training Kit -
  • Windows Server AppFabric -

WF 4 Migration Cookbook: Hosting (.NET 4 - Beta 2 Release)Page 1