Hour 7 Creating Advanced State Machine Workflows

What You’ll Learn in This Hour:

Image   StateMachineWorkflowInstance introspection and manipulation capabilities

Image   Workflow queuing to retrieve current blocking activities

Image   Dim and activate form controls based on workflow state and workflow queues

This hour starts with a workflow identical to what you created in the previous hour. You will learn to use the workflow queuing system to retrieve information about what events are available at a given time. This information will then be used to control which buttons are enabled. These capabilities are important because they are one of the main factors that increases state machine workflow’s ability to be monitored and introspected. You will also use additional members of the StateMachineWorkflowInstance type.

The new form you will create this hour will look like Figure 7.1.

FIGURE 7.1 Order state machine.

Order state machine.

Updating the Form and Adding Member Variables

1.   Open the AdvancedStateMachineWorkflowSolution solution in the C:SamsWf24hrsHourHour07StateMachineWorkflowLab1AdvancedStateMAchineWorkflowSolution directory.

2.   Open the StateMachineOrderForm form in design mode.

3.   Add a combo box named comboBoxStates to the form and add a label above it and set its Text property to Set State.

4.   Add two command buttons. Name the first buttonPossibleStates and set its Text property to Possible States. Name the second buttonHistoryStates and set its Text property to History States.

5.   Open the form in code view and add the following variable declarations to the top of the class:

        private StateMachineWorkflowInstance stateMachineWorkflowInstance;
        private delegate void UpdateDelegate( );

6.   Add the following using directives:

       using System.Workflow.Runtime.Tracking;
       using System.Workflow.ComponentModel;
       using System.Collections.ObjectModel;

7.   Add the following to the end of the form load method:

            dimControls( );
            buttonCreate.Enabled = true;

Updating the Form Code-Behind Logic to Work with StateMachineWorkflowInstance Members

This is where the majority of the activity in this hour resides. It is where you will modify the host to interact with the workflow.

Extract “In Scope” Events

We will finally add logic to dim buttons that are not valid in the current workflow state. You will first extract the current events awaiting input and then retrieve their corollary event names. You will then compare the event names to the form buttons to see which ones should be active.

Most of the logic is similar to the work involved in determining which form controls should be dimmed or active in a UI application. The method to retrieve these events, however, calls for using WF’s underlying queuing technology. So far, HandleExternalEvent activities have been used to wait for external input, and CallExternalMethod activities have been used to send data from workflow to host. These and all other communication between WF and external applications are abstractions on top of WF’s queuing. The WorkflowQueueInfo and WorkflowQueue types are the two types that support WF’s queuing. There are times when the best—or only—way to accomplish a WF task is through its queues. One case when queues usage is necessary is to retrieve the current blocking activity (covered later in this hour); another is in place of higher-level host-workflow data exchange abstractions, which may be the appropriate or preferred method for some programmers and some scenarios.

The UpdateControls method you create in this section dims all the buttons, extracts the current active events, and activates the corollary buttons.

1.   Add a method to dim all the controls as shown, and place it in the state machine helper functions region:

        void dimControls( )
        {
            buttonCreate.Enabled = false;
            buttonApprove.Enabled = false;
            buttonCancel.Enabled = false;
            buttonL2Approve.Enabled = false;
            buttonL2Reject.Enabled = false;
            buttonProcess.Enabled = false;
            buttonReject.Enabled = false;
            buttonUpdate.Enabled = false;
            buttonHistoryStates.Enabled = false;
            buttonPossibleStates.Enabled = false;
            comboBoxStates.Enabled = false;
        }

2.   Add a method signature named UpdateControls below the dimControls method as shown:

    // Extracts the event names from the workflow queues
    // for the currently active blocking activities on the
    // workflow instance and matches them with the appropriate buttons

    void UpdateControls( )
    {
    }

3.   Add the following code to the UpdateControls method to dim all the controls before beginning to selectively activate the requisite controls:

        // dim all the controls before retreiving current ones
        dimControls( );

4.   Add the following code to retrieve a collection of WorkflowQueueInfo objects:

        // collection of in scope workflow queues. In scope means
        // that if in the Approval Level 2State, the
        // L2 Approved and L2 Rejected
        // events will be in scope (as well as the workflow-wide canceled)
        ReadOnlyCollection<WorkflowQueueInfo> wqi =
            workflowInstance.GetWorkflowQueueData( );

5.   Iterate through each WorkflowQueuInfo object in the returned WorkflowQueueInfo collection by adding the following code:

        // iterate through each of the in scope queues and match
        // them with the proper form buttons
        foreach (WorkflowQueueInfo q in wqi)
        {
        }

6.   Cast the WorkflowQueueInfo.QueueName property as an EventQueueName type, which contains the method name property needed. Add this code between the foreach beginning and ending brackets.

        // Get the event name associated with the queue
        EventQueueName eq = q.QueueName as EventQueueName;

7.   After ensuring the queue is not null, compare the MethodName property of the EventQueueName type to the list of values in the switch statement to determine which buttons should be activated. Add this code following the code entered in the prior step, and again within the foreach brackets.

        if (eq != null)
        {
            switch (eq.MethodName)
            {
                case "OrderWorkflowApproved":
                    buttonApprove.Enabled = true;
                    break;
                case "OrderWorkflowRejected":
                    buttonReject.Enabled = true;
                    break;
                case "OrderWorkflowCanceled":
                    buttonCancel.Enabled = true;
                    break;

                case "OrderWorkflowProcessed":
                    buttonProcess.Enabled = true;
                    break;
                case "OrderWorkflowApproved2":
                    buttonL2Approve.Enabled = true;
                     break;
                case "OrderWorkflowRejected2":
                    buttonL2Reject.Enabled = true;
                    break;
            }
       }

8.   If the collection of WorkflowQueueInfo objects returned at the top of the method is true, activate the create button, because there are no blocking activities and therefore there is no workflow instance. If, on the other hand, it is not null, activate the helper controls. This code should be inserted below the closing bracket of the foreach statement.

        // if none are in scope then there is no workflow instance
        // loaded and therefore the create button should be undimmed.
        if (wqi.Count == 0)
            buttonCreate.Enabled = true;
        else
        {
            // If there is a workflow instance undim the additional
            // "nonevent" buttons
            buttonHistoryStates.Enabled = true;
            buttonPossibleStates.Enabled = true;
            comboBoxStates.Enabled = true;
        }

9.   Build the AdvancedWindowsForm project and fix errors, if any.

The UpdateControls method should look like Listing 7.1.

LISTING 7.1 Update Controls Method with Direct Workflow Queue Access


        // Extracts the event names from the workflow queues
        // for the currently active blocking activities on the
        // workflow instance and matches them with the appropriate buttons
        void UpdateControls( )
        {

            // dim all the controls before retreiving current ones
            dimControls( );

            // collection of in scope workflow queues. In scope means
            // that if in the Approval Level 2State, the
            // L2 Approved and L2 Rejected
            // events will be in scope (as well as the workflow-wide canceled)
            ReadOnlyCollection<WorkflowQueueInfo> wqi =
                workflowInstance.GetWorkflowQueueData( );

            // iterate through each of the in scope queues and match

            // them with the proper form buttons
            foreach (WorkflowQueueInfo q in wqi)
            {

                // Get the event name associated with the queue
                EventQueueName eq = q.QueueName as EventQueueName;

                if (eq != null)
                {

                    switch (eq.MethodName)
                    {
                        case "OrderWorkflowApproved":
                            buttonApprove.Enabled = true;
                            break;
                        case "OrderWorkflowRejected":
                            buttonReject.Enabled = true;
                            break;
                        case "OrderWorkflowCanceled":
                            buttonCancel.Enabled = true;
                            break;
                        case "OrderWorkflowProcessed":
                            buttonProcess.Enabled = true;
                            break;
                        case "OrderWorkflowApproved2":
                            buttonL2Approve.Enabled = true;
                            break;
                        case "OrderWorkflowRejected2":
                            buttonL2Reject.Enabled = true;
                            break;
                    }
                }
            }
            // if none are in scope then there is no workflow instance
            // loaded and therefore the create button should be undimmed.
            if (wqi.Count == 0)
                buttonCreate.Enabled = true;
            else
            {
                // If there is a workflow instance undim the additional
                // "nonevent" buttons
                buttonHistoryStates.Enabled = true;
                buttonPossibleStates.Enabled = true;
                comboBoxStates.Enabled = true;
            }
        }


The current queues are retrieved and the proper form controls are activated.

SetState Combo Box

We will now look through the built-in members in the StateMachineWorkflowInstance class that interact with workflow queue info on our behalf. The first one of these methods retrieves all the states on the state machine workflow.

The states will be loaded when the workflow instance is created. They are retrieved from the StateMachineWorkflowInstance, thereby requiring it to be created first. A good place to do this is right after the workflow instance is started.

1.   Add the following code to the buttonCreate_click handler below the line that starts the workflow instance (workflowInstance.Start). This instantiates the StateMachineWorkflowInstance and calls a method to update the comboBoxStates combo box:

        // Initiate a state machine workflow instance
        stateMachineWorkflowInstance =
            new StateMachineWorkflowInstance(
            workflowRuntime, workflowInstance.InstanceId);

        // Load all the states on the state machine into a combo box.
        loadStatesComboBox( );

2.   Create the loadStateComboBox method to load all workflow states via the StateMachineWorkflowInstance.States property that is iterated through and added to the combo box. Add this method to the end of the state machine helper functions region.

        // Loads all the states in the workflow into the combo box
        // where they can simply be viewed or the current state can
        // be overridden
        private void loadStatesComboBox( )
        {

            comboBoxStates.Items.Clear( );

            foreach (Activity a in stateMachineWorkflowInstance.States)
            {
                comboBoxStates.Items.Add(a.Name);
            }
        }

3.   Build the AdvancedWindowsForm project and fix errors, if any.

Historical States

A collection of all states the workflow has transitioned to can be retrieved through the StateMachineWorkflowInstance.StateHistory property if and only if you are using the SqlTrackingService. This can be useful for auditing or other purposes.

1.   Add the SqlTrackingService to the host by inserting the following code into the GetWorkflowRuntime method below the code where the external data exchange service is added (des.AddService(this);) as shown:

        // Add the sql tracking service to the runtime
        SqlTrackingService sts =

            new SqlTrackingService(connectionString);
        workflowRuntime.AddService(sts);

2.   Double-click the History States button and add the following code to return a collection of the states that were previously transitioned to and display them in a message box:

            string historyStates = "";
            foreach (string s in stateMachineWorkflowInstance.StateHistory)
            {
                historyStates = historyStates + s + " ";

            }
            MessageBox.Show(historyStates);

Possible State Transitions

The possible transitions can also be obtained from the StateMachineWorkflowInstance. You can use this to monitor the possible transitions during runtime.

1.   Double-click the Possible States button and add the following code to return a collection of valid states to transition to and then display them in a message box:

        // Use the StateMachineWorkflowInstance.PossibleStateTransitions method
        // to retrieve all states that may be transitioned to (directly or
        // indirectly) from the current state
        string possibleTransitions = "";
        foreach (string stateName in
            stateMachineWorkflowInstance.PossibleStateTransitions)
        {
            possibleTransitions = possibleTransitions + stateName + " ";

        }
        MessageBox.Show("Possible transitions: " +

stateMachineWorkflowInstance.PossibleStateTransitions.ToString( ));

2.   Build the AdvancedWindowsForm project and fix errors, if any.

Override the Current State

So far you have simply introspected the state machine workflow. You can manipulate it as well using the StateMachineWorkflowInstance.SetState method. This should obviously be used sparingly, but there may be times when it is necessary to manually override the process and switch the workflow to a different state. It is consistent with the state machine autonomy premise that you can move forward and backward through the states without consequence in many scenarios.

When selecting state in the comboBoxState combo box (created earlier), its changed handler is called where the state is switched.

1.   Add the following code to the comboBoxState's on change event handler by double-clicking the SetState combo box to manually set a state on a state machine workflow.

        // Sets the state machine to the new state selected in
        // the combo box.
        stateMachineWorkflowInstance.SetState(comboBoxStates.Text);

2.   For readability purposes, cut and paste the methods you have created in the last few steps to the bottom of the state machine helper functions region.

Update the UI When Workflow Idles

The UI is updated when the workflow idles to reflect the state change that may occur after an event fires and the workflow then idles. This will generally happen after the workflow acts on an event, so it is a good place to update the newly available events (buttons).

1.   Add the following code to the workflowRuntime_WorkflowIdled handler:

        UpdateDelegate ud = delegate( )
        {
            if (stateMachineWorkflowInstance.CurrentState != null)
                textBoxCurrentState.Text =
                    stateMachineWorkflowInstance.CurrentStateName;
            else
                textBoxCurrentState.Text = "";

            UpdateControls( );
        };
        this.Invoke(ud);

Update the UI when Workflow Completes

It is also a good place to update the available buttons on a UI when the workflow completes.

1.   Replace the code in the workflowRuntime_WorkflowCompleted method with the following:

            MessageBox.Show("Workflow completed.");
            UpdateDelegate ud = delegate( )
            {
                UpdateControls( );
            };
            this.Invoke(ud);

Update the UI When Workflow Terminates

Finally, update the available buttons and UI when the workflow terminates.

1.   Replace the code in the workflowRuntime_WorkflowTerminated method with the following:

         MessageBox.Show("Workflow terminated: " + e.Exception.Message);

        UpdateDelegate ud = delegate( )
        {
            UpdateControls( );
        };
        this.Invoke(ud);

Running the Workflow

The buttons should now dim and activate as you use the form. Use the state combo box to see all workflow states and to override the current state, the PossibleStates button to see the possible transitions, and the HistoryStates to see past workflow transitions.

1.   Set the AdvancedWindowsForm project as the startup project.

2.   Press F5 to run the workflow.

3.   Enter any order number and an amount less than 1,000 (so that second-level approval will not be required).

4.   Click the Create button to create the order, then click the Approve button, and finally the Process button. The order should now be approved.

5.   Enter any new order number and an amount larger than 1,000.

6.   Click the Create button to create the order, then click the Approve button, the L2Approved button, and finally the Process button. The order that required second-level approval should now be approved.

7.   Enter any new order number and an amount larger than 1,000.

8.   When the workflow reaches the Distribution state (shown in Figure 7.2), change it to the approval state. Notice that the manual override of state changes which buttons are activated, and the Current State text box also reflects the state change (shown in Figure 7.3).

FIGURE 7.2 Distribution state.

Distribution state.

FIGURE 7.3 State machine overridden.

State machine overridden.

9.   Continue submitting orders; try the Cancel button and other buttons and overrides until you are comfortable with the form’s operations.

Summary

This demonstrated WF’s sate machine workflow introspection through enhanced use of the StateMachineWorkflowInstance type. Workflow queues—the underlying WF communication technology—were used to access information for the currently scoped events. Finally, the state machine workflow’s ability to override the current state was used. The manual override of states is particularly powerful and dangerous if used incorrectly.

Workshop

Quiz

1.

What activities used throughout this book so far are abstractions on top of WF’s queuing system?

2.

How do you retrieve all workflow states?

3.

How do you override the current state?

4.

Do you have to have the SqlTrackingService registered with the workflow runtime to retrieve state history?

5.

How do you return a collection of WorkflowQueueInfo objects?

6.

What do you cast the WorkflowQueueInfo object as to retrieve the method name of the blocking activity?

Answers

1.

HandleExternalEvent and CallExternalMethod.

2.

StateMachineWorkflowInstance.States.

3.

StateMachineWorkflowInstance.SetState.

4.

Yes.

5.

WorkflowInstance.GetWorkflowQueueData( ).

6.

EventQueueName

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.108.175