How to: Create and Run a Long Running Workflow

One of the central features of Windows Workflow Foundation (WF) is the runtime’s ability to persist and unload idle workflows to a database. The steps in How to: Run a Workflow demonstrated the basics of workflow hosting using a console application. Examples were shown of starting workflows, workflow lifecycle handlers, and resuming bookmarks. In order to demonstrate workflow persistence effectively, a more complex workflow host is required that supports starting and resuming multiple workflow instances. This step in the tutorial demonstrates how to create a Windows form host application that supports starting and resuming multiple workflow instances, workflow persistence, and provides a basis for the advanced features such as tracking and versioning that are demonstrated in subsequent tutorial steps.

Note

This tutorial step and the subsequent steps use all three workflow types from How to: Create a Workflow. If you did not complete all three types you can download a completed version of the steps from Windows Workflow Foundation (WF45) - Getting Started Tutorial.

Note

To download a completed version or view a video walkthrough of the tutorial, see Windows Workflow Foundation (WF45) - Getting Started Tutorial.

In this topic

To create the persistence database

  1. Open SQL Server Management Studio and connect to the local server, for example .\SQLEXPRESS. Right-click the Databases node on the local server, and select New Database. Name the new database WF45GettingStartedTutorial, accept all other values, and select OK.

    Note

    Ensure that you have Create Database permission on the local server before creating the database.

  2. Choose Open, File from the File menu. Browse to the following folder: C:\Windows\Microsoft.NET\Framework\4.0.30319\sql\en

    Select the following two files and click Open.

    • SqlWorkflowInstanceStoreLogic.sql

    • SqlWorkflowInstanceStoreSchema.sql

  3. Choose SqlWorkflowInstanceStoreSchema.sql from the Window menu. Ensure that WF45GettingStartedTutorial is selected in the Available Databases drop-down and choose Execute from the Query menu.

  4. Choose SqlWorkflowInstanceStoreLogic.sql from the Window menu. Ensure that WF45GettingStartedTutorial is selected in the Available Databases drop-down and choose Execute from the Query menu.

    Warning

    It is important to perform the previous two steps in the correct order. If the queries are executed out of order, errors occur and the persistence database is not configured correctly.

To add the reference to the DurableInstancing assemblies

  1. Right-click NumberGuessWorkflowHost in Solution Explorer and select Add Reference.

  2. Select Assemblies from the Add Reference list, and type DurableInstancing into the Search Assemblies box. This filters the assemblies and makes the desired references easier to select.

  3. Check the checkbox beside System.Activities.DurableInstancing and System.Runtime.DurableInstancing from the Search Results list, and click OK.

To create the workflow host form

Note

The steps in this procedure describe how to add and configure the form manually. If desired, you can download the solution files for the tutorial and add the completed form to the project. To download the tutorial files, see Windows Workflow Foundation (WF45) - Getting Started Tutorial. Once the files are downloaded, right-click NumberGuessWorkflowHost and choose Add Reference. Add a reference to System.Windows.Forms and System.Drawing. These references are added automatically if you add a new form from the Add, New Item menu, but must be added manually when importing a form. Once the references are added, right-click NumberGuessWorkflowHost in Solution Explorer and choose Add, Existing Item. Browse to the Form folder in the project files, select WorkflowHostForm.cs (or WorkflowHostForm.vb), and click Add. If you choose to import the form, then you can skip down to the next section, To add the properties and helper methods of the form.

  1. Right-click NumberGuessWorkflowHost in Solution Explorer and choose Add, New Item.

  2. In the Installed templates list, choose Windows Form, type WorkflowHostForm in the Name box, and click Add.

  3. Configure the following properties on the form.

    Property Value
    FormBorderStyle FixedSingle
    MaximizeBox False
    Size 400, 420
  4. Add the following controls to the form in the order specified and configure the properties as directed.

    Control Property: Value
    Button Name: NewGame

    Location: 13, 13

    Size: 75, 23

    Text: New Game
    Label Location: 94, 18

    Text: Guess a number from 1 to
    ComboBox Name: NumberRange

    DropDownStyle: DropDownList

    Items: 10, 100, 1000

    Location: 228, 12

    Size: 143, 21
    Label Location: 13, 43

    Text: Workflow type
    ComboBox Name: WorkflowType

    DropDownStyle: DropDownList

    Items: StateMachineNumberGuessWorkflow, FlowchartNumberGuessWorkflow, SequentialNumberGuessWorkflow

    Location: 94, 40

    Size: 277, 21
    Label Name: WorkflowVersion

    Location: 13, 362

    Text: Workflow version
    GroupBox Location: 13, 67

    Size: 358, 287

    Text: Game
    Note

    When adding the following controls, put them into the GroupBox.

    Control Property: Value
    Label Location: 7, 20

    Text: Workflow Instance Id
    ComboBox Name: InstanceId

    DropDownStyle: DropDownList

    Location: 121, 17

    Size: 227, 21
    Label Location: 7, 47

    Text: Guess
    TextBox Name: Guess

    Location: 50, 44

    Size: 65, 20
    Button Name: EnterGuess

    Location: 121, 42

    Size: 75, 23

    Text: Enter Guess
    Button Name: QuitGame

    Location: 274, 42

    Size: 75, 23

    Text: Quit
    TextBox Name: WorkflowStatus

    Location: 10, 73

    Multiline: True

    ReadOnly: True

    ScrollBars: Vertical

    Size: 338, 208
  5. Set the AcceptButton property of the form to EnterGuess.

The following example illustrates the completed form.

WF45 Getting Started Tutorial Workflow Host Form

To add the properties and helper methods of the form

The steps in this section add properties and helper methods to the form class that configure the UI of the form to support running and resuming number guess workflows.

  1. Right-click WorkflowHostForm in Solution Explorer and choose View Code.

  2. Add the following using (or Imports) statements at the top of the file with the other using (or Imports) statements.

    Imports System.Windows.Forms  
    Imports System.Activities.DurableInstancing  
    Imports System.Activities  
    Imports System.Data.SqlClient  
    Imports System.IO  
    
    using System.Windows.Forms;  
    using System.Activities.DurableInstancing;  
    using System.Activities;  
    using System.Data.SqlClient;  
    using System.IO;  
    
  3. Add the following member declarations to the WorkflowHostForm class.

    Const connectionString = "Server=.\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI"  
    Dim store As SqlWorkflowInstanceStore  
    Dim WorkflowStarting As Boolean  
    
    const string connectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI";  
    SqlWorkflowInstanceStore store;  
    bool WorkflowStarting;  
    
    Note

    If your connection string is different, update connectionString to refer to your database.

  4. Add a WorkflowInstanceId property to the WorkflowFormHost class.

    Public ReadOnly Property WorkflowInstanceId() As Guid  
        Get  
            If InstanceId.SelectedIndex = -1 Then  
                Return Guid.Empty  
            Else  
                Return New Guid(InstanceId.SelectedItem.ToString())  
            End If  
        End Get  
    End Property  
    
    public Guid WorkflowInstanceId  
    {  
        get  
        {  
            return InstanceId.SelectedIndex == -1 ? Guid.Empty : (Guid)InstanceId.SelectedItem;  
        }  
    }  
    

    The InstanceId combo box displays a list of persisted workflow instance ids, and the WorkflowInstanceId property returns the currently selected workflow.

  5. Add a handler for the form Load event. To add the handler, switch to Design View for the form, click the Events icon at the top of the Properties window, and double-click Load.

    Private Sub WorkflowHostForm_Load(sender As Object, e As EventArgs) Handles Me.Load  
    
    End Sub  
    
    private void WorkflowHostForm_Load(object sender, EventArgs e)  
    {  
    
    }  
    
  6. Add the following code to WorkflowHostForm_Load.

    'Initialize the store and configure it so that it can be used for  
    'multiple WorkflowApplication instances.  
    store = New SqlWorkflowInstanceStore(connectionString)  
    WorkflowApplication.CreateDefaultInstanceOwner(store, Nothing, WorkflowIdentityFilter.Any)  
    
    'Set default ComboBox selections.  
    NumberRange.SelectedIndex = 0  
    WorkflowType.SelectedIndex = 0  
    
    ListPersistedWorkflows()  
    
    // Initialize the store and configure it so that it can be used for  
    // multiple WorkflowApplication instances.  
    store = new SqlWorkflowInstanceStore(connectionString);  
    WorkflowApplication.CreateDefaultInstanceOwner(store, null, WorkflowIdentityFilter.Any);  
    
    // Set default ComboBox selections.  
    NumberRange.SelectedIndex = 0;  
    WorkflowType.SelectedIndex = 0;  
    
    ListPersistedWorkflows();  
    

    When the form loads, the SqlWorkflowInstanceStore is configured, the range and workflow type combo boxes are set to default values, and the persisted workflow instances are added to the InstanceId combo box.

  7. Add a SelectedIndexChanged handler for InstanceId. To add the handler, switch to Design View for the form, select the InstanceId combo box, click the Events icon at the top of the Properties window, and double-click SelectedIndexChanged.

    Private Sub InstanceId_SelectedIndexChanged(sender As Object, e As EventArgs) Handles InstanceId.SelectedIndexChanged  
    
    End Sub  
    
    private void InstanceId_SelectedIndexChanged(object sender, EventArgs e)  
    {  
    
    }  
    
  8. Add the following code to InstanceId_SelectedIndexChanged. Whenever the user selects a workflow by using the combo box this handler updates the status window.

    If InstanceId.SelectedIndex = -1 Then  
        Return  
    End If  
    
    'Clear the status window.  
    WorkflowStatus.Clear()  
    
    'Get the workflow version and display it.  
    'If the workflow is just starting then this info will not  
    'be available in the persistence store so do not try and retrieve it.  
    If Not WorkflowStarting Then  
        Dim instance As WorkflowApplicationInstance = _  
            WorkflowApplication.GetInstance(WorkflowInstanceId, store)  
    
        WorkflowVersion.Text = _  
            WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity)  
    
        'Unload the instance.  
        instance.Abandon()  
    End If  
    
    if (InstanceId.SelectedIndex == -1)  
    {  
        return;  
    }  
    
    // Clear the status window.  
    WorkflowStatus.Clear();  
    
    // Get the workflow version and display it.  
    // If the workflow is just starting then this info will not  
    // be available in the persistence store so do not try and retrieve it.  
    if (!WorkflowStarting)  
    {  
        WorkflowApplicationInstance instance =  
            WorkflowApplication.GetInstance(this.WorkflowInstanceId, store);  
    
        WorkflowVersion.Text =  
            WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity);  
    
        // Unload the instance.  
        instance.Abandon();  
    }  
    
  9. Add the following ListPersistedWorkflows method to the form class.

    Private Sub ListPersistedWorkflows()  
        Using localCon As New SqlConnection(connectionString)  
            Dim localCmd As String = _  
                "Select [InstanceId] from [System.Activities.DurableInstancing].[Instances] Order By [CreationTime]"  
    
            Dim cmd As SqlCommand = localCon.CreateCommand()  
            cmd.CommandText = localCmd  
            localCon.Open()  
            Using reader As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)  
    
                While (reader.Read())  
                    'Get the InstanceId of the persisted Workflow.  
                    Dim id As Guid = Guid.Parse(reader(0).ToString())  
                    InstanceId.Items.Add(id)  
                End While  
            End Using  
        End Using  
    End Sub  
    
    using (SqlConnection localCon = new SqlConnection(connectionString))  
    {  
        string localCmd =  
            "Select [InstanceId] from [System.Activities.DurableInstancing].[Instances] Order By [CreationTime]";  
    
        SqlCommand cmd = localCon.CreateCommand();  
        cmd.CommandText = localCmd;  
        localCon.Open();  
        using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))  
        {  
            while (reader.Read())  
            {  
                // Get the InstanceId of the persisted Workflow  
                Guid id = Guid.Parse(reader[0].ToString());  
                InstanceId.Items.Add(id);  
            }  
        }  
    }  
    

    ListPersistedWorkflows queries the instance store for persisted workflow instances, and adds the instance ids to the cboInstanceId combo box.

  10. Add the following UpdateStatus method and corresponding delegate to the form class. This method updates the status window on the form with the status of the currently running workflow.

    Private Delegate Sub UpdateStatusDelegate(msg As String)  
    Public Sub UpdateStatus(msg As String)  
        'We may be on a different thread so we need to  
        'make this call using BeginInvoke.  
        If InvokeRequired Then  
            BeginInvoke(New UpdateStatusDelegate(AddressOf UpdateStatus), msg)  
        Else  
            If Not msg.EndsWith(vbCrLf) Then  
                msg = msg & vbCrLf  
            End If  
    
            WorkflowStatus.AppendText(msg)  
    
            'Ensure that the newly added status is visible.  
            WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length  
            WorkflowStatus.ScrollToCaret()  
        End If  
    End Sub  
    
    private delegate void UpdateStatusDelegate(string msg);  
    public void UpdateStatus(string msg)  
    {  
        // We may be on a different thread so we need to  
        // make this call using BeginInvoke.  
        if (InvokeRequired)  
        {  
            BeginInvoke(new UpdateStatusDelegate(UpdateStatus), msg);  
        }  
        else  
        {  
            if (!msg.EndsWith("\r\n"))  
            {  
                msg += "\r\n";  
            }  
            WorkflowStatus.AppendText(msg);  
    
            WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length;  
            WorkflowStatus.ScrollToCaret();  
        }  
    }  
    
  11. Add the following GameOver method and corresponding delegate to the form class. When a workflow completes, this method updates the form UI by removing the instance id of the completed workflow from the InstanceId combo box.

    Private Delegate Sub GameOverDelegate()  
    Private Sub GameOver()  
        If InvokeRequired Then  
            BeginInvoke(New GameOverDelegate(AddressOf GameOver))  
        Else  
            'Remove this instance from the InstanceId combo box.  
            InstanceId.Items.Remove(InstanceId.SelectedItem)  
            InstanceId.SelectedIndex = -1  
        End If  
    End Sub  
    
    private delegate void GameOverDelegate();  
    private void GameOver()  
    {  
        if (InvokeRequired)  
        {  
            BeginInvoke(new GameOverDelegate(GameOver));  
        }  
        else  
        {  
            // Remove this instance from the combo box  
            InstanceId.Items.Remove(InstanceId.SelectedItem);  
            InstanceId.SelectedIndex = -1;  
        }  
    }  
    

To configure the instance store, workflow lifecycle handlers, and extensions

  1. Add a ConfigureWorkflowApplication method to the form class.

    Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication)  
    
    End Sub  
    
    private void ConfigureWorkflowApplication(WorkflowApplication wfApp)  
    {      
    }  
    

    This method configures the WorkflowApplication, adds the desired extensions, and adds handlers for the workflow lifecycle events.

  2. In ConfigureWorkflowApplication, specify the SqlWorkflowInstanceStore for the WorkflowApplication.

    'Configure the persistence store.  
    wfApp.InstanceStore = store  
    
    // Configure the persistence store.  
    wfApp.InstanceStore = store;  
    
  3. Next, create a StringWriter instance and add it to the Extensions collection of the WorkflowApplication. When a StringWriter is added to the extensions it captures all WriteLine activity output. When the workflow becomes idle, the WriteLine output can be extracted from the StringWriter and displayed on the form.

    'Add a StringWriter to the extensions. This captures the output  
    'from the WriteLine activities so we can display it in the form.  
    Dim sw As New StringWriter()  
    wfApp.Extensions.Add(sw)  
    
    // Add a StringWriter to the extensions. This captures the output  
    // from the WriteLine activities so we can display it in the form.  
    StringWriter sw = new StringWriter();  
    wfApp.Extensions.Add(sw);  
    
  4. Add the following handler for the Completed event. When a workflow successfully completes, the number of turns taken to guess the number is displayed to the status window. If the workflow terminates, the exception information that caused the termination is displayed. At the end of the handler the GameOver method is called, which removes the completed workflow from the workflow list.

    wfApp.Completed = _  
        Sub(e As WorkflowApplicationCompletedEventArgs)  
            If e.CompletionState = ActivityInstanceState.Faulted Then  
                UpdateStatus(String.Format("Workflow Terminated. Exception: {0}" & vbCrLf & "{1}", _  
                    e.TerminationException.GetType().FullName, _  
                    e.TerminationException.Message))  
            ElseIf e.CompletionState = ActivityInstanceState.Canceled Then  
                UpdateStatus("Workflow Canceled.")  
            Else  
                Dim Turns As Integer = Convert.ToInt32(e.Outputs("Turns"))  
                UpdateStatus(String.Format("Congratulations, you guessed the number in {0} turns.", Turns))  
            End If  
            GameOver()  
        End Sub  
    
    wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)  
    {  
        if (e.CompletionState == ActivityInstanceState.Faulted)  
        {  
            UpdateStatus(string.Format("Workflow Terminated. Exception: {0}\r\n{1}",  
                e.TerminationException.GetType().FullName,  
                e.TerminationException.Message));  
        }  
        else if (e.CompletionState == ActivityInstanceState.Canceled)  
        {  
            UpdateStatus("Workflow Canceled.");  
        }  
        else  
        {  
            int Turns = Convert.ToInt32(e.Outputs["Turns"]);  
            UpdateStatus(string.Format("Congratulations, you guessed the number in {0} turns.", Turns));  
        }  
        GameOver();  
    };  
    
  5. Add the following Aborted and OnUnhandledException handlers. The GameOver method is not called from the Aborted handler because when a workflow instance is aborted, it does not terminate, and it is possible to resume the instance at a later time.

    wfApp.Aborted = _  
        Sub(e As WorkflowApplicationAbortedEventArgs)  
            UpdateStatus(String.Format("Workflow Aborted. Exception: {0}" & vbCrLf & "{1}", _  
                e.Reason.GetType().FullName, _  
                e.Reason.Message))  
        End Sub  
    
    wfApp.OnUnhandledException = _  
        Function(e As WorkflowApplicationUnhandledExceptionEventArgs)  
            UpdateStatus(String.Format("Unhandled Exception: {0}" & vbCrLf & "{1}", _  
                e.UnhandledException.GetType().FullName, _  
                e.UnhandledException.Message))  
            GameOver()  
            Return UnhandledExceptionAction.Terminate  
        End Function  
    
    wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)  
    {  
        UpdateStatus(string.Format("Workflow Aborted. Exception: {0}\r\n{1}",  
                e.Reason.GetType().FullName,  
                e.Reason.Message));  
    };  
    
    wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)  
    {  
        UpdateStatus(string.Format("Unhandled Exception: {0}\r\n{1}",  
                e.UnhandledException.GetType().FullName,  
                e.UnhandledException.Message));  
        GameOver();  
        return UnhandledExceptionAction.Terminate;  
    };  
    
  6. Add the following PersistableIdle handler. This handler retrieves the StringWriter extension that was added, extracts the output from the WriteLine activities, and displays it in the status window.

    wfApp.PersistableIdle = _  
        Function(e As WorkflowApplicationIdleEventArgs)  
            'Send the current WriteLine outputs to the status window.  
            Dim writers = e.GetInstanceExtensions(Of StringWriter)()  
            For Each writer In writers  
                UpdateStatus(writer.ToString())  
            Next  
            Return PersistableIdleAction.Unload  
        End Function  
    
    wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)  
    {  
        // Send the current WriteLine outputs to the status window.  
        var writers = e.GetInstanceExtensions<StringWriter>();  
        foreach (var writer in writers)  
        {  
            UpdateStatus(writer.ToString());  
        }  
        return PersistableIdleAction.Unload;  
    };  
    

    The PersistableIdleAction enumeration has three values: None, Persist, and Unload. Persist causes the workflow to persist but it does not cause the workflow to unload. Unload causes the workflow to persist and be unloaded.

    The following example is the completed ConfigureWorkflowApplication method.

    Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication)  
        'Configure the persistence store.  
        wfApp.InstanceStore = store  
    
        'Add a StringWriter to the extensions. This captures the output  
        'from the WriteLine activities so we can display it in the form.  
        Dim sw As New StringWriter()  
        wfApp.Extensions.Add(sw)  
    
        wfApp.Completed = _  
            Sub(e As WorkflowApplicationCompletedEventArgs)  
                If e.CompletionState = ActivityInstanceState.Faulted Then  
                    UpdateStatus(String.Format("Workflow Terminated. Exception: {0}" & vbCrLf & "{1}", _  
                        e.TerminationException.GetType().FullName, _  
                        e.TerminationException.Message))  
                ElseIf e.CompletionState = ActivityInstanceState.Canceled Then  
                    UpdateStatus("Workflow Canceled.")  
                Else  
                    Dim Turns As Integer = Convert.ToInt32(e.Outputs("Turns"))  
                    UpdateStatus(String.Format("Congratulations, you guessed the number in {0} turns.", Turns))  
                End If  
                GameOver()  
            End Sub  
    
        wfApp.Aborted = _  
            Sub(e As WorkflowApplicationAbortedEventArgs)  
                UpdateStatus(String.Format("Workflow Aborted. Exception: {0}" & vbCrLf & "{1}", _  
                    e.Reason.GetType().FullName, _  
                    e.Reason.Message))  
            End Sub  
    
        wfApp.OnUnhandledException = _  
            Function(e As WorkflowApplicationUnhandledExceptionEventArgs)  
                UpdateStatus(String.Format("Unhandled Exception: {0}" & vbCrLf & "{1}", _  
                    e.UnhandledException.GetType().FullName, _  
                    e.UnhandledException.Message))  
                GameOver()  
                Return UnhandledExceptionAction.Terminate  
            End Function  
    
        wfApp.PersistableIdle = _  
            Function(e As WorkflowApplicationIdleEventArgs)  
                'Send the current WriteLine outputs to the status window.  
                Dim writers = e.GetInstanceExtensions(Of StringWriter)()  
                For Each writer In writers  
                    UpdateStatus(writer.ToString())  
                Next  
                Return PersistableIdleAction.Unload  
            End Function  
    End Sub  
    
    private void ConfigureWorkflowApplication(WorkflowApplication wfApp)  
    {  
        // Configure the persistence store.  
        wfApp.InstanceStore = store;  
    
        // Add a StringWriter to the extensions. This captures the output  
        // from the WriteLine activities so we can display it in the form.  
        StringWriter sw = new StringWriter();  
        wfApp.Extensions.Add(sw);  
    
        wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)  
        {  
            if (e.CompletionState == ActivityInstanceState.Faulted)  
            {  
                UpdateStatus(string.Format("Workflow Terminated. Exception: {0}\r\n{1}",  
                    e.TerminationException.GetType().FullName,  
                    e.TerminationException.Message));  
            }  
            else if (e.CompletionState == ActivityInstanceState.Canceled)  
            {  
                UpdateStatus("Workflow Canceled.");  
            }  
            else  
            {  
                int Turns = Convert.ToInt32(e.Outputs["Turns"]);  
                UpdateStatus(string.Format("Congratulations, you guessed the number in {0} turns.", Turns));  
            }  
            GameOver();  
        };  
    
        wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)  
        {  
            UpdateStatus(string.Format("Workflow Aborted. Exception: {0}\r\n{1}",  
                    e.Reason.GetType().FullName,  
                    e.Reason.Message));  
        };  
    
        wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)  
        {  
            UpdateStatus(string.Format("Unhandled Exception: {0}\r\n{1}",  
                    e.UnhandledException.GetType().FullName,  
                    e.UnhandledException.Message));  
            GameOver();  
            return UnhandledExceptionAction.Terminate;  
        };  
    
        wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)  
        {  
            // Send the current WriteLine outputs to the status window.  
            var writers = e.GetInstanceExtensions<StringWriter>();  
            foreach (var writer in writers)  
            {  
                UpdateStatus(writer.ToString());  
            }  
            return PersistableIdleAction.Unload;  
        };  
    }  
    

To enable starting and resuming multiple workflow types

In order to resume a workflow instance, the host has to provide the workflow definition. In this tutorial there are three workflow types, and subsequent tutorial steps introduce multiple versions of these types. WorkflowIdentity provides a way for a host application to associate identifying information with a persisted workflow instance. The steps in this section demonstrate how to create a utility class to assist with mapping the workflow identity from a persisted workflow instance to the corresponding workflow definition. For more information aboutWorkflowIdentity and versioning, see Using WorkflowIdentity and Versioning.

  1. Right-click NumberGuessWorkflowHost in Solution Explorer and choose Add, Class. Type WorkflowVersionMap into the Name box and click Add.

  2. Add the following using or Imports statements at the top of the file with the other using or Imports statements.

    Imports NumberGuessWorkflowActivities  
    Imports System.Activities  
    
    using NumberGuessWorkflowActivities;  
    using System.Activities;  
    
  3. Replace the WorkflowVersionMap class declaration with the following declaration.

    Public Module WorkflowVersionMap  
        Dim map As Dictionary(Of WorkflowIdentity, Activity)  
    
        'Current version identities.  
        Public StateMachineNumberGuessIdentity As WorkflowIdentity  
        Public FlowchartNumberGuessIdentity As WorkflowIdentity  
        Public SequentialNumberGuessIdentity As WorkflowIdentity  
    
        Sub New()  
            map = New Dictionary(Of WorkflowIdentity, Activity)  
    
            'Add the current workflow version identities.  
            StateMachineNumberGuessIdentity = New WorkflowIdentity With  
            {  
                .Name = "StateMachineNumberGuessWorkflow",  
                .Version = New Version(1, 0, 0, 0)  
            }  
    
            FlowchartNumberGuessIdentity = New WorkflowIdentity With  
            {  
                .Name = "FlowchartNumberGuessWorkflow",  
                .Version = New Version(1, 0, 0, 0)  
            }  
    
            SequentialNumberGuessIdentity = New WorkflowIdentity With  
            {  
                .Name = "SequentialNumberGuessWorkflow",  
                .Version = New Version(1, 0, 0, 0)  
            }  
    
            map.Add(StateMachineNumberGuessIdentity, New StateMachineNumberGuessWorkflow())  
            map.Add(FlowchartNumberGuessIdentity, New FlowchartNumberGuessWorkflow())  
            map.Add(SequentialNumberGuessIdentity, New SequentialNumberGuessWorkflow())  
        End Sub  
    
        Public Function GetWorkflowDefinition(identity As WorkflowIdentity) As Activity  
            Return map(identity)  
        End Function  
    
        Public Function GetIdentityDescription(identity As WorkflowIdentity) As String  
            Return identity.ToString()  
        End Function  
    End Module  
    
    public static class WorkflowVersionMap  
    {  
        static Dictionary<WorkflowIdentity, Activity> map;  
    
        // Current version identities.  
        static public WorkflowIdentity StateMachineNumberGuessIdentity;  
        static public WorkflowIdentity FlowchartNumberGuessIdentity;  
        static public WorkflowIdentity SequentialNumberGuessIdentity;  
    
        static WorkflowVersionMap()  
        {  
            map = new Dictionary<WorkflowIdentity, Activity>();  
    
            // Add the current workflow version identities.  
            StateMachineNumberGuessIdentity = new WorkflowIdentity  
            {  
                Name = "StateMachineNumberGuessWorkflow",  
                Version = new Version(1, 0, 0, 0)  
            };  
    
            FlowchartNumberGuessIdentity = new WorkflowIdentity  
            {  
                Name = "FlowchartNumberGuessWorkflow",  
                Version = new Version(1, 0, 0, 0)  
            };  
    
            SequentialNumberGuessIdentity = new WorkflowIdentity  
            {  
                Name = "SequentialNumberGuessWorkflow",  
                Version = new Version(1, 0, 0, 0)  
            };  
    
            map.Add(StateMachineNumberGuessIdentity, new StateMachineNumberGuessWorkflow());  
            map.Add(FlowchartNumberGuessIdentity, new FlowchartNumberGuessWorkflow());  
            map.Add(SequentialNumberGuessIdentity, new SequentialNumberGuessWorkflow());  
        }  
    
        public static Activity GetWorkflowDefinition(WorkflowIdentity identity)  
        {  
            return map[identity];  
        }  
    
        public static string GetIdentityDescription(WorkflowIdentity identity)  
        {          
            return identity.ToString();  
       }  
    }  
    

    WorkflowVersionMap contains three workflow identities that map to the three workflow definitions from this tutorial and is used in the following sections when workflows are started and resumed.

To start a new workflow

  1. Add a Click handler for NewGame. To add the handler, switch to Design View for the form, and double-click NewGame. A NewGame_Click handler is added and the view switches to code view for the form. Whenever the user clicks this button a new workflow is started.

    Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click  
    
    End Sub  
    
    private void NewGame_Click(object sender, EventArgs e)  
    {  
    
    }  
    
  2. Add the following code to the click handler. This code creates a dictionary of input arguments for the workflow, keyed by argument name. This dictionary has one entry that contains the range of the randomly generated number retrieved from the range combo box.

    Dim inputs As New Dictionary(Of String, Object)()  
    inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem))  
    
    var inputs = new Dictionary<string, object>();  
    inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem));  
    
  3. Next, add the following code that starts the workflow. The WorkflowIdentity and workflow definition corresponding to the type of workflow selected are retrieved using the WorkflowVersionMap helper class. Next, a new WorkflowApplication instance is created using the workflow definition, WorkflowIdentity, and dictionary of input arguments.

    Dim identity As WorkflowIdentity = Nothing  
    Select Case WorkflowType.SelectedItem.ToString()  
        Case "SequentialNumberGuessWorkflow"  
            identity = WorkflowVersionMap.SequentialNumberGuessIdentity  
    
        Case "StateMachineNumberGuessWorkflow"  
            identity = WorkflowVersionMap.StateMachineNumberGuessIdentity  
    
        Case "FlowchartNumberGuessWorkflow"  
            identity = WorkflowVersionMap.FlowchartNumberGuessIdentity  
    End Select  
    
    Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity)  
    
    Dim wfApp = New WorkflowApplication(wf, inputs, identity)  
    
    WorkflowIdentity identity = null;  
    switch (WorkflowType.SelectedItem.ToString())  
    {  
        case "SequentialNumberGuessWorkflow":  
            identity = WorkflowVersionMap.SequentialNumberGuessIdentity;  
            break;  
    
        case "StateMachineNumberGuessWorkflow":  
            identity = WorkflowVersionMap.StateMachineNumberGuessIdentity;  
            break;  
    
        case "FlowchartNumberGuessWorkflow":  
            identity = WorkflowVersionMap.FlowchartNumberGuessIdentity;  
            break;  
    };  
    
    Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity);  
    
    WorkflowApplication wfApp = new WorkflowApplication(wf, inputs, identity);  
    
  4. Next, add the following code which adds the workflow to the workflow list and displays the workflow's version information on the form.

    'Add the workflow to the list and display the version information.  
    WorkflowStarting = True  
    InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id)  
    WorkflowVersion.Text = identity.ToString()  
    WorkflowStarting = False  
    
    // Add the workflow to the list and display the version information.  
    WorkflowStarting = true;  
    InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id);  
    WorkflowVersion.Text = identity.ToString();  
    WorkflowStarting = false;  
    
  5. Call ConfigureWorkflowApplication to configure the instance store, extensions, and workflow lifecycle handlers for this WorkflowApplication instance.

    'Configure the instance store, extensions, and   
    'workflow lifecycle handlers.  
    ConfigureWorkflowApplication(wfApp)  
    
    // Configure the instance store, extensions, and   
    // workflow lifecycle handlers.  
    ConfigureWorkflowApplication(wfApp);  
    
  6. Finally, call Run.

    'Start the workflow.  
    wfApp.Run()  
    
    // Start the workflow.  
    wfApp.Run();  
    

    The following example is the completed NewGame_Click handler.

    Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click  
        'Start a new workflow.  
        Dim inputs As New Dictionary(Of String, Object)()  
        inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem))  
    
        Dim identity As WorkflowIdentity = Nothing  
        Select Case WorkflowType.SelectedItem.ToString()  
            Case "SequentialNumberGuessWorkflow"  
                identity = WorkflowVersionMap.SequentialNumberGuessIdentity  
    
            Case "StateMachineNumberGuessWorkflow"  
                identity = WorkflowVersionMap.StateMachineNumberGuessIdentity  
    
            Case "FlowchartNumberGuessWorkflow"  
                identity = WorkflowVersionMap.FlowchartNumberGuessIdentity  
        End Select  
    
        Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity)  
    
        Dim wfApp = New WorkflowApplication(wf, inputs, identity)  
    
        'Add the workflow to the list and display the version information.  
        WorkflowStarting = True  
        InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id)  
        WorkflowVersion.Text = identity.ToString()  
        WorkflowStarting = False  
    
        'Configure the instance store, extensions, and   
        'workflow lifecycle handlers.  
        ConfigureWorkflowApplication(wfApp)  
    
        'Start the workflow.  
        wfApp.Run()  
    End Sub  
    
    private void NewGame_Click(object sender, EventArgs e)  
    {  
        var inputs = new Dictionary<string, object>();  
        inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem));  
    
        WorkflowIdentity identity = null;  
        switch (WorkflowType.SelectedItem.ToString())  
        {  
            case "SequentialNumberGuessWorkflow":  
                identity = WorkflowVersionMap.SequentialNumberGuessIdentity;  
                break;  
    
            case "StateMachineNumberGuessWorkflow":  
                identity = WorkflowVersionMap.StateMachineNumberGuessIdentity;  
                break;  
    
            case "FlowchartNumberGuessWorkflow":  
                identity = WorkflowVersionMap.FlowchartNumberGuessIdentity;  
                break;  
        };  
    
        Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity);  
    
        WorkflowApplication wfApp = new WorkflowApplication(wf, inputs, identity);  
    
        // Add the workflow to the list and display the version information.  
        WorkflowStarting = true;  
        InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id);  
        WorkflowVersion.Text = identity.ToString();  
        WorkflowStarting = false;  
    
        // Configure the instance store, extensions, and   
        // workflow lifecycle handlers.  
        ConfigureWorkflowApplication(wfApp);  
    
        // Start the workflow.  
        wfApp.Run();  
    }  
    

To resume a workflow

  1. Add a Click handler for EnterGuess. To add the handler, switch to Design View for the form, and double-click EnterGuess. Whenever the user clicks this button a workflow is resumed.

    Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click  
    
    End Sub  
    
    private void EnterGuess_Click(object sender, EventArgs e)  
    {  
    
    }  
    
  2. Add the following code to ensure that a workflow is selected in the workflow list, and that the user's guess is valid.

    If WorkflowInstanceId = Guid.Empty Then  
        MessageBox.Show("Please select a workflow.")  
        Return  
    End If  
    
    Dim userGuess As Integer  
    If Not Int32.TryParse(Guess.Text, userGuess) Then  
        MessageBox.Show("Please enter an integer.")  
        Guess.SelectAll()  
        Guess.Focus()  
        Return  
    End If  
    
    if (WorkflowInstanceId == Guid.Empty)  
    {  
        MessageBox.Show("Please select a workflow.");  
        return;  
    }  
    
    int guess;  
    if (!Int32.TryParse(Guess.Text, out guess))  
    {  
        MessageBox.Show("Please enter an integer.");  
        Guess.SelectAll();  
        Guess.Focus();  
        return;  
    }  
    
  3. Next, retrieve the WorkflowApplicationInstance of the persisted workflow instance. A WorkflowApplicationInstance represents a persisted workflow instance that has not yet been associated with a workflow definition. The DefinitionIdentity of the WorkflowApplicationInstance contains the WorkflowIdentity of the persisted workflow instance. In this tutorial, the WorkflowVersionMap utility class is used to map the WorkflowIdentity to the correct workflow definition. Once the workflow definition is retrieved, a WorkflowApplication is created, using the correct workflow definition.

    Dim instance As WorkflowApplicationInstance = _  
        WorkflowApplication.GetInstance(WorkflowInstanceId, store)  
    
    'Use the persisted WorkflowIdentity to retrieve the correct workflow  
    'definition from the dictionary.  
    Dim wf As Activity = _  
        WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity)  
    
    'Associate the WorkflowApplication with the correct definition  
    Dim wfApp As WorkflowApplication = _  
        New WorkflowApplication(wf, instance.DefinitionIdentity)  
    
    WorkflowApplicationInstance instance =  
        WorkflowApplication.GetInstance(WorkflowInstanceId, store);  
    
    // Use the persisted WorkflowIdentity to retrieve the correct workflow  
    // definition from the dictionary.  
    Activity wf =  
        WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity);  
    
    // Associate the WorkflowApplication with the correct definition  
    WorkflowApplication wfApp =  
        new WorkflowApplication(wf, instance.DefinitionIdentity);  
    
  4. Once the WorkflowApplication is created, configure the instance store, workflow lifecycle handlers, and extensions by calling ConfigureWorkflowApplication. These steps must be done every time a new WorkflowApplication is created, and they must be done before the workflow instance is loaded into the WorkflowApplication. After the workflow is loaded, it is resumed with the user's guess.

    'Configure the extensions and lifecycle handlers.  
    'Do this before the instance is loaded. Once the instance is  
    'loaded it is too late to add extensions.  
    ConfigureWorkflowApplication(wfApp)  
    
    'Load the workflow.  
    wfApp.Load(instance)  
    
    'Resume the workflow.  
    wfApp.ResumeBookmark("EnterGuess", userGuess)  
    
    // Configure the extensions and lifecycle handlers.  
    // Do this before the instance is loaded. Once the instance is  
    // loaded it is too late to add extensions.  
    ConfigureWorkflowApplication(wfApp);  
    
    // Load the workflow.  
    wfApp.Load(instance);  
    
    // Resume the workflow.  
    wfApp.ResumeBookmark("EnterGuess", guess);  
    
  5. Finally, clear the guess textbox and prepare the form to accept another guess.

    'Clear the Guess textbox.  
    Guess.Clear()  
    Guess.Focus()  
    
    // Clear the Guess textbox.  
    Guess.Clear();  
    Guess.Focus();  
    

    The following example is the completed EnterGuess_Click handler.

    Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click  
        If WorkflowInstanceId = Guid.Empty Then  
            MessageBox.Show("Please select a workflow.")  
            Return  
        End If  
    
        Dim userGuess As Integer  
        If Not Int32.TryParse(Guess.Text, userGuess) Then  
            MessageBox.Show("Please enter an integer.")  
            Guess.SelectAll()  
            Guess.Focus()  
            Return  
        End If  
    
        Dim instance As WorkflowApplicationInstance = _  
            WorkflowApplication.GetInstance(WorkflowInstanceId, store)  
    
        'Use the persisted WorkflowIdentity to retrieve the correct workflow  
        'definition from the dictionary.  
        Dim wf As Activity = _  
            WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity)  
    
        'Associate the WorkflowApplication with the correct definition  
        Dim wfApp As WorkflowApplication = _  
            New WorkflowApplication(wf, instance.DefinitionIdentity)  
    
        'Configure the extensions and lifecycle handlers.  
        'Do this before the instance is loaded. Once the instance is  
        'loaded it is too late to add extensions.  
        ConfigureWorkflowApplication(wfApp)  
    
        'Load the workflow.  
        wfApp.Load(instance)  
    
        'Resume the workflow.  
        wfApp.ResumeBookmark("EnterGuess", userGuess)  
    
        'Clear the Guess textbox.  
        Guess.Clear()  
        Guess.Focus()  
    End Sub  
    
    private void EnterGuess_Click(object sender, EventArgs e)  
    {  
        if (WorkflowInstanceId == Guid.Empty)  
        {  
            MessageBox.Show("Please select a workflow.");  
            return;  
        }  
    
        int guess;  
        if (!Int32.TryParse(Guess.Text, out guess))  
        {  
            MessageBox.Show("Please enter an integer.");  
            Guess.SelectAll();  
            Guess.Focus();  
            return;  
        }  
    
        WorkflowApplicationInstance instance =  
            WorkflowApplication.GetInstance(WorkflowInstanceId, store);  
    
        // Use the persisted WorkflowIdentity to retrieve the correct workflow  
        // definition from the dictionary.  
        Activity wf =  
            WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity);  
    
        // Associate the WorkflowApplication with the correct definition  
        WorkflowApplication wfApp =  
            new WorkflowApplication(wf, instance.DefinitionIdentity);  
    
        // Configure the extensions and lifecycle handlers.  
        // Do this before the instance is loaded. Once the instance is  
        // loaded it is too late to add extensions.  
        ConfigureWorkflowApplication(wfApp);  
    
        // Load the workflow.  
        wfApp.Load(instance);  
    
        // Resume the workflow.  
        wfApp.ResumeBookmark("EnterGuess", guess);  
    
        // Clear the Guess textbox.  
        Guess.Clear();  
        Guess.Focus();  
    }  
    

To terminate a workflow

  1. Add a Click handler for QuitGame. To add the handler, switch to Design View for the form, and double-click QuitGame. Whenever the user clicks this button the currently selected workflow is terminated.

    Private Sub QuitGame_Click(sender As Object, e As EventArgs) Handles QuitGame.Click  
    
    End Sub  
    
    private void QuitGame_Click(object sender, EventArgs e)  
    {  
    
    }  
    
  2. Add the following code to the QuitGame_Click handler. This code first checks to ensure that a workflow is selected in the workflow list. Then it loads the persisted instance into a WorkflowApplicationInstance, uses the DefinitionIdentity to determine the correct workflow definition, and then initializes the WorkflowApplication. Next the extensions and workflow lifecycle handlers are configured with a call to ConfigureWorkflowApplication. Once the WorkflowApplication is configured, it is loaded, and then Terminate is called.

    If WorkflowInstanceId = Guid.Empty Then  
        MessageBox.Show("Please select a workflow.")  
        Return  
    End If  
    
    Dim instance As WorkflowApplicationInstance = _  
        WorkflowApplication.GetInstance(WorkflowInstanceId, store)  
    
    'Use the persisted WorkflowIdentity to retrieve the correct workflow  
    'definition from the dictionary.  
    Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity)  
    
    'Associate the WorkflowApplication with the correct definition.  
    Dim wfApp As WorkflowApplication = _  
        New WorkflowApplication(wf, instance.DefinitionIdentity)  
    
    'Configure the extensions and lifecycle handlers.  
    ConfigureWorkflowApplication(wfApp)  
    
    'Load the workflow.  
    wfApp.Load(instance)  
    
    'Terminate the workflow.  
    wfApp.Terminate("User resigns.")  
    
    if (WorkflowInstanceId == Guid.Empty)  
    {  
        MessageBox.Show("Please select a workflow.");  
        return;  
    }  
    
    WorkflowApplicationInstance instance =  
        WorkflowApplication.GetInstance(WorkflowInstanceId, store);  
    
    // Use the persisted WorkflowIdentity to retrieve the correct workflow  
    // definition from the dictionary.  
    Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity);  
    
    // Associate the WorkflowApplication with the correct definition  
    WorkflowApplication wfApp =  
        new WorkflowApplication(wf, instance.DefinitionIdentity);  
    
    // Configure the extensions and lifecycle handlers  
    ConfigureWorkflowApplication(wfApp);  
    
    // Load the workflow.  
    wfApp.Load(instance);  
    
    // Terminate the workflow.  
    wfApp.Terminate("User resigns.");  
    

To build and run the application

  1. Double-click Program.cs (or Module1.vb) in Solution Explorer to display the code.

  2. Add the following using (or Imports) statement at the top of the file with the other using (or Imports) statements.

    Imports System.Windows.Forms  
    
    using System.Windows.Forms;  
    
  3. Remove or comment out the existing workflow hosting code from How to: Run a Workflow, and replace it with the following code.

    Sub Main()  
        Application.EnableVisualStyles()  
        Application.Run(New WorkflowHostForm())  
    End Sub  
    
    static void Main(string[] args)  
    {  
        Application.EnableVisualStyles();  
        Application.Run(new WorkflowHostForm());  
    }  
    
  4. Right-click NumberGuessWorkflowHost in Solution Explorer and choose Properties. In the Application tab, specify Windows Application for the Output type. This step is optional, but if it is not followed the console window is displayed in addition to the form.

  5. Press Ctrl+Shift+B to build the application.

  6. Ensure that NumberGuessWorkflowHost is set as the startup application, and press Ctrl+F5 to start the application.

  7. Select a range for the guessing game and the type of workflow to start, and click New Game. Enter a guess in the Guess box and click Go to submit your guess. Note that the output from the WriteLine activities is displayed on the form.

  8. Start several workflows using different workflow types and number ranges, enter some guesses, and switch between the workflows by selecting from the Workflow Instance Id list.

    Note that when you switch to a new workflow, the previous guesses and progress of the workflow are not displayed in the status window. The reason the status is not available is because it is not captured and saved anywhere. In the next step of the tutorial, How to: Create a Custom Tracking Participant, you create a custom tracking participant that saves this information.