Welcome to EMC Consulting Blogs Sign in | Join | Help

Owain Wraggs' Blog

I have now left EMC Consulting, you can subscribe to my new blog here: http://blog.endjin.com/author/owain-wragg/

Workflow 4.0 Persistence/ Long Running Workflows

I have recently started to look at the changes introduced with Visual Studio 2010 and .NET Framework 4 Beta 1, the first thing I have looked at is Workflow Foundation 4.0. In this post I will create a simple example which illustrates a long running workflow which persists its state to SQL Server.

The example scenario is as follows; a request requiring approval is submitted to a workflow (via a WCF Service) and the workflow waits until at some point in the future the approval for the request is received and based on whether  the request is approved or not the workflow performs  a difference action. After the initial request has been submitted the workflow instance is persisted and unloaded and it is only reloaded when the approval for the request is submitted.

Below is the WCF service contract exposed by the RequestService, this service is called by clients who submit both requests and request approvals.

    [ServiceContract]

    public interface IRequestService

    {

        [OperationContract]

        void AddRequest(Request request);

 

        [OperationContract]

        void SubmitApproval(RequestApproval requestApproval);

    }

As you can see the service contract is very simple and consists of only two operations, shown below is the service implementation.

    public class RequestService : IRequestService

    {

        private static readonly ProcessRequestWorkflowHost processRequestHost = new ProcessRequestWorkflowHost();

 

        public void AddRequest(Request request)

        {

            processRequestHost.ProcessRequest(request);

        }

 

        public void SubmitApproval(RequestApproval requestApproval)

        {

            processRequestHost.SubmitRequestApproval(requestApproval);

        }

    }

As you can see both operations forward the request to the ProcessRequestWorkflowHost class, this class is responsible for managing the creation, loading and persistence of the workflow instances. For this example I decided to manage the workflow instances manually in order to give me more control over the persistence of the workflow instances and the resumption of the workflow instances when approvals are submitted.

The diagram below shows the workflow that is executed to process a request submission.

 

When the workflow is executed the WriteLine activity is called which simply writes out the passed in request’s details (the request is passed in as an argument to the workflow) to the console application which is hosting the RequestService, then the WaitForRequestApproval activity is executed. This activity is implemented in C# as shown below.

    public class WaitForRequestApproval : NativeActivity<RequestApproval>

    {

        public InArgument<int> RequestId { get; set; }

 

        protected override void Execute(ActivityExecutionContext context)

        {

            // Create the bookmark which waits for the request approval

            string name = "Approval_" + this.RequestId.Get(context).ToString();

 

            context.CreateNamedBookmark(name, new BookmarkCallback(Callback));

        }

 

        /// <summary>

        /// Callback executed when the bookmark is resumed

        /// </summary>

        /// <param name="context"></param>

        /// <param name="bookmark"></param>

        /// <param name="state"></param>

        private void Callback(ActivityExecutionContext context, Bookmark bookmark, object state)

        {

            RequestApproval approval = (RequestApproval)state;

            context.SetValue(this.Result, approval);

        }

    }

The WaitForRequestApproval activity defines a bookmark named “Approval_RequestId”, a bookmark defines a point at which the workflow waits for further information before execution can be continued when the bookmark is resumed the Callback method will be executed and the workflow will continue as normal.

In our example once the bookmark has been resumed an If activity is executed (as illustrated in workflow diagram shown above), the expanded If activity is shown below.

 

The If activity executes one of two WriteLine activities based on evaluating the condition (RequestApproval.IsApproved), these WriteLine activities simple write out the request Id and the approval status to the console.

Finally shown below is the ProcessRequestWorkflowHost class, this class controls the creation of the workflow instances, the persistence of the workflow instances, the loading of workflow instances and bookmark resumption.

    public class ProcessRequestWorkflowHost

    {

        private IDictionary<int, Guid> requestToInstanceMapping;

 

        public ProcessRequestWorkflowHost()

        {

            // Setup mappings between request and workflow instance id - do this to save managing

            // the mappings else where

            this.requestToInstanceMapping = new Dictionary<int, Guid>();

            this.requestToInstanceMapping.Add(4, new Guid("56A2CA8F-3C64-485E-A0BC-A4F0E880898C"));

            this.requestToInstanceMapping.Add(5, new Guid("F130B19A-1B4B-43DA-B75B-6C58F0CC6419"));

            this.requestToInstanceMapping.Add(13, new Guid("3B70FAFF-5FDC-4159-836F-DFD4DFF2EE4E"));

            this.requestToInstanceMapping.Add(26, new Guid("BFB619F7-2BD7-4D52-99FA-97C20FA95DCE"));

            this.requestToInstanceMapping.Add(55, new Guid("2D15D97F-ABCB-45C5-AC7D-F18A64F1E2E3"));

        }

 

        /// <summary>

        /// Loads a workflow instance with the specified

        /// instance id

        /// </summary>

        /// <param name="instanceId">Workflow instance id</param>

        /// <returns>Workflow instance</returns>

        public WorkflowInstance LoadInstance(Guid instanceId)

        {

            // Setup the persistance

            PersistenceProvider persistenceProvider = this.CreatePersistanceProvider(instanceId);

 

            // Load the ProcessRequest workflow

            WorkflowElement wf = WorkflowXamlServices.Load(File.OpenRead("ProcessRequest.xaml")) as WorkflowElement;

 

            // Retrieve the workflow instance

            WorkflowInstance instance = WorkflowInstance.Load(wf, persistenceProvider);

 

            return instance;

        }

 

        /// <summary>

        /// Processes the specified Request

        /// </summary>

        /// <param name="request">Request to process</param>

        public void ProcessRequest(Request request)

        {

            // Get the workflow instance id to use

            Guid instanceId = this.requestToInstanceMapping[request.Id];

 

            // Setup the arguments that are passed to the workflow

            IDictionary<string, object> input = new Dictionary<string, object>();

            input.Add("Request", request);

 

            // Load the ProcessRequest workflow

            WorkflowElement wf = WorkflowXamlServices.Load(File.OpenRead("ProcessRequest.xaml")) as WorkflowElement;

 

            // Create the workflow instance specifying the arguments and instance id

            WorkflowInstance instance = new WorkflowInstance(wf, input, instanceId);

            // When instance is idle unload and persist the isntance

            instance.OnIdle = () => IdleAction.Unload;

     

            // Setup the persistance

            PersistenceProvider persistenceProvider = this.CreatePersistanceProvider(instanceId);

            instance.Extensions.Add(persistenceProvider);

 

            // Start the workflow instance

            instance.Run();

        }

 

        /// <summary>

        /// Submits the request approval

        /// </summary>

        /// <param name="approval">Request approval</param>

        public void SubmitRequestApproval(RequestApproval approval)

        {

            // Get the workflow instance id to use

            Guid instanceId = this.requestToInstanceMapping[approval.RequestId];

 

            // Load the workflow instance

            WorkflowInstance instance = this.LoadInstance(instanceId);

 

            // Resume the workflow at the bookmark

            string bookmark = "Approval_" + approval.RequestId.ToString();

            instance.ResumeBookmark(bookmark, approval);

        }

 

        /// <summary>

        /// Creates the persistence provider

        /// </summary>

        /// <param name="instanceId">Workflow instance the persistence provider is for</param>

        /// <returns>Persistance provider</returns>

        private PersistenceProvider CreatePersistanceProvider(Guid instanceId)

        {

            string persistenceConnectionString = "Data Source=localhost;Initial Catalog=SampleInstanceStore;Integrated Security=True;Asynchronous Processing=True";

 

            // Create and open the provider factory

            SqlPersistenceProviderFactory factory = new SqlPersistenceProviderFactory(persistenceConnectionString, true, false, new TimeSpan(0, 1, 0));

            factory.Open();

 

            // Create the persistance provider

            PersistenceProvider persistenceProvider = factory.CreateProvider(instanceId);

 

            return persistenceProvider;

        }

    }

The constructor creates a list of mappings between request ids and workflow instance ids; I have done this to save adding further code to manage the relationship between the requests and the workflow instances – so therefore at the moment this example will only work with a specified set of request ids (4, 5, 13, 26 and 55).

The ProcessRequest method is called by the RequestService when a new request is submitted, the method creates the arguments to pass to the ProcessRequest workflow, creates a new workflow instance adds the persistence provider to the instance (enabling the instance to be persisted) and finally executes the workflow. Note that the workflow instance is only persisted when the instance becomes idle, when the OnIdle event is raised by the instance the IdleAction is set to Unload – which specifies that the workflow instance is persisted and then unloaded.

At some point in the future the approval for the request will be submitted, the SubmitRequestApproval method is called by the RequestService passing in the request approval, firstly this method calls the LoadInstance method which retrieves the specified workflow instance from the persistence store, then the previously created bookmark is resumed therefore enabling the workflow to continue to completion.

When developing this sample application I had a few problems getting the persistence to work it turns out this is because for certain persistence scenarios you need to use the Workflow 3.5 SQL Schema and whilst for others the Workflow 4.0 SQL Schema. In short for WorkflowServiceHost usage you need to use the 4.0 schemas but for WorkflowInstance usage the 3.5 schema. Have a look at http://kennyw.com/work/workflow/350 for more information; it seems that this feature will be fixed in beta 2.

I’ve attached the solution to this post; the solution also contains a readme file which explains how to setup the persistence and how to run the application.

Note: - That this post was written using Beta 1 of the .NET Framework 4.0 so therefore changes in future releases of the .NET Framework might break the example application

Comments

 

Kay.Adeyemi said:

Hey Wraggy

Silly question - Whats wrong with letting the Workflow runtime persist the workflow itself....

July 7, 2009 15:21
 

Owain.Wragg said:

Hi Kay

It is doing persistence itself, e.g. persistence only happens when the workflow goes idle

// When instance is idle unload and persist the isntance

instance.OnIdle = () => IdleAction.Unload;

Which means when it’s just sitting around doing nothing it is persisted and unloaded.

July 8, 2009 13:11
 

chanchiwah said:

Hi Owain,

This is a very good example. I'm trying to create a declarative flowchart services consume by asp.net. Will it be possible to use this approach and load the xamlx WorkflowInstance? Thanks!

October 13, 2009 10:09
Anonymous comments are disabled
Powered by Community Server (Personal Edition), by Telligent Systems