Hour 4 Learning Host-Workflow Data Exchange

What You’ll Learn in This Hour:

Image   Overview of host-workflow data exchange

Image   Creating local service interface

Image   Creating an event argument payload

Image   Adding hosting logic to a Windows Form

Image   Creating a workflow with host-workflow communication activities

Host-Workflow Data Exchange

Although WF and therefore workflows run within the process space of the host, the host does not directly interact with a workflow instance. This may seem counterintuitive. After all, if the host application and workflow model are both .NET applications, why the need for an intermediary? The reason is that the WF runtime provides support services. The two main ones it provides are threading and reloading persisted workflows. When you call a workflow, the runtime accesses it for you and handles most threading details. Second, workflows sleep throughout most of their lifetime and are therefore persisted. The runtime receives the call and, if necessary, reloads the workflow and then transmits your request to it. For these reasons, and possibly others, host applications don’t, or at least shouldn’t, communicate directly with workflows.

Figure 4.1 illustrates the WF runtime brokering communication between hosts and workflows.

FIGURE 4.1 General hostworkflow data exchange.

General hostworkflow data exchange.

In this hour, we look at one type of host-workflow communication that is commonly referred to as external data exchange or local service. The reason is that this type of communication is conducted by a class that implements an interface that is, in turn, configured with an ExternalDataExchange attribute. There are other host-workflow communication options available in WF, including directly accessing its queuing system (discussed in Hour 22, “Creating Typed Queued and EventDriven-Enabled Activities,” on which all other host-workflow communication schemes are built.

Figure 4.2 offers another view of the local service-based host-workflow data exchange. It shows the host and workflow being mediated by the local service in the middle. The local service contains a contract with a specific set of events and methods to facilitate a particular data exchange. The host raises events to communicate with the workflow, and the workflow invokes methods to communicate with the host. These can be thought of as custom events and methods created for one particular data exchange, such as sending an order to a workflow and waiting for the workflow to announce approval or rejection. These events are in addition to the standard workflow events, such as completed and idled, that are available to all workflows.

FIGURE 4.2 Local service-based host-workflow data exchange.

Local service-based host-workflow data exchange.

A Windows Forms application will be used as the host because most developers are familiar with them and they are simple to use. The general concepts, however, apply to just about any other application that hosts a workflow.

There are four steps in host-workflow communication:

1.   Create a local service interface that acts as a host-workflow communication contract.

2.   Create a specialized payload to transmit data from the host to the workflow.

3.   Create a workflow with host-workflow-communication-centric activities.

4.   Implement the local service interface in the host.

A couple of other adjunct steps, such as creating the runtime host and creating dependency properties, are also covered. Some of the headings are prefixed with Step 1, Step 2, and so on to help you know which topics directly relate to implementing local service-based host-workflow communication.

Image

For those using the .NET 3.5, you should consider using the WCF Send and Receive activities in place of the method you learn to use in this hour. At the end of the book after using both, you can decide which method you prefer. You may also want to check the latest news at that time to see if Microsoft is indicating whether it will support both methods going forward equally, or one over the other.

Creating the Solution and Projects

There are three projects in this solution. One will hold the Windows Forms host. Another will hold the local service interface that permits host-workflow communication. The third will hold the workflow that holds the WF local service-based communication activities. Factoring the projects out makes reuse easier.

Creating the Solution

Follow the next set of steps to create the solution.

1.   Start Visual Studio 2008. Select File, New, Project.

2.   Click on the plus to the left of Other Project Types.

3.   Select the Visual Studio Solutions project template.

4.   Enter DataExchangeSolution as the Name.

5.   Enter or browse to C:SamsWf24hrsHoursHour04HostWorkflowDataExchange for the Location.

6.   Click OK.

Creating the Local Service Project

Follow the next set of steps to create a local service that controls communication between the host application and the workflow.

1.   Right-click DataExchangeSolution in the Solution Explorer, and select Add, New Project.

2.   Expand the Visual C# project templates and select Windows.

3.   Select the Class Library project template.

4.   Enter LocalService as the Name.

5.   Enter or browse to C:SamsWf24hrsHoursHour04HostWorkflowDataExchangeDataExchangeSolution for the Location.

6.   Click OK.

7.   Rename Class1.cs to BasicLocalService.cs in the Solution Explorer. (If you are asked to rename all references in the project to reflect the name change, click Yes.)

8.   Right-click the LocalService project in the Solution Explorer, select Add, Class, and name it LocalServiceEventArgs.cs.

9.   Add a reference to the workflow trio as well (System.Workflow.Activities, System.Workflow.Runtime, and System.Worfklow.Componentmodel).

Creating the Workflow Project

You add the workflow project to the solution in the next steps.

1.   Right-click DataExchangeSolution in the Solution Explorer, and select Add, New Project.

2.   Expand the Visual C# project templates and select Workflow.

3.   Select the Sequential Workflow Library project template.

4.   Enter LocalServiceWorkflows as the Name.

5.   Enter or browse to C:SamsWf24hrsHoursHour04HostWorkflowDataExchangeDataExchangeSolution for the Location.

6.   Click OK.

7.   Rename Workflow1.cs to LocalServiceWorkflow.cs. (Click Yes to rename all references.)

8.   Add a reference to the LocalService project.

Creating the Windows Forms Project

Add the Windows Forms host project in the next steps.

1.   Right-click DataExchangeSolution in the Solution Explorer, and select Add, New Project.

2.   Expand the Visual C# project templates and select Windows.

3.   Select the Windows Forms Application project template.

4.   Enter BasicWorkflowHost as the Name.

5.   Enter or browse to C:SamsWf24hrsHoursHour04HostWorkflowDataExchangeDataExchangeSolution for the Location.

6.   Click OK.

7.   Rename Form1.cs to BasicHostForm.cs in the Solution Explorer. Again, click Yes to rename all references.

8.   Right-click the BasicWorkflowHost project in the Solution Explorer, select Add, Add a Reference to both the LocalServiceWorkflows and the LocalService projects.

9.   Add a reference to the workflow trio as well (System.Workflow.Activities, System.Workflow.Runtime, and System.Worfklow.Componentmodel).

10.   Your Solution Explorer should now look like Figure 4.3:

FIGURE 4.3 The DataExchangeSolution in Solution Explorer.

The DataExchangeSolution in Solution Explorer.

Step 1: Creating the Local Service Interface

The local service interface contains the contract between the host and the workflow. The local service holds an ExternalDataExchange attribute, the events that will be raised against the workflow by the host, and the methods the workflow will invoke on the host. In the next steps you create a local service interface.

1.   Open BasicLocalService.cs.

2.   Replace the existing using directives with the ones shown next:

      using System;
      using System.Workflow.Activities;
      using System.Workflow.Runtime;

3.   Replace the class declaration with the following interface declaration that includes the ExternalDataExchange attribute. This is the attribute that tells the runtime that this interface performs host-workflow communication.

    [ExternalDataExchange]
    public interface ILocalService
    {
    }

4.   Fill in the interface body with these event and method signatures:

        void Approve(string status, string orderNumber);
        void Reject(string status, string orderNumber);

        event EventHandler<OrderInitEventArgs> OrderWorkflowInitialization;

5.   The LocalService.cs class should look like Code Listing 4.1:

LISTING 4.1 LocalService.cs


using System;
using System.Workflow.Activities;
using System.Workflow.Runtime;

namespace LocalService
{
    [ExternalDataExchange]
    public interface ILocalService
    {
        void Approve(string status, string orderNumber);
        void Reject(string status, string orderNumber);

        event EventHandler<OrderInitEventArgs> OrderWorkflowInitialization;
    }
}


You have now created a local service interface that contains two methods for workflow-to-host communication and one event for host-to-workflow communication. The events raised on the workflow are asynchronous, and the methods from the workflow are synchronous. Communicating with the workflow asynchronously is mandated so that the WF runtime’s threading and reloading services do not take place while the host is waiting for a response to its method call.

Step 2: Creating the Payload (EventArgs) Class

All local service-based event payloads are delivered by a class that is derived from ExternalDataEventArgs. Because WF may persist workflows, all event args payload classes (those derived from ExternalDataEventArgs) must be marked as serializable. The workflow instance id must be part of the payload because the WF runtime will need it to determine which workflow instance to route the event to. To ensure this the host must pass the workflow instance id in all events it raises on the workflow, and the event args class must contain the following statement in its constructor: : base(instanceId)

Follow the next steps to create the EventArgs class.

Image

The standard WF runtime event payloads are also derived from ExternalDataEventArgs. They carry event-specific payloads. For instance, the completed event contains all public workflow variables and the terminated class contains exception information.

1.   Open LocalServiceEventArgs.cs.

2.   Replace the current using directives with the following:

using System;
using System.Workflow.Activities;

3.   Replace the class declaration in the LocalServiceEventArgs.cs class with the following:

    [Serializable]
    public class OrderInitEventArgs : ExternalDataEventArgs
    {
    }

4.   Add the following code within the OrderInitEventArgs class that defines a property to carry the order number:

        private string orderNumber;
        public string OrderNumber
        {
            get { return orderNumber; }
            set { orderNumber = value; }
        }

5.   Add the constructor below the ending bracket of the OrderNumber property:

        public OrderInitEventArgs(Guid instanceId, string number)
            : base(instanceId)
        {
            orderNumber = number;
        }

6.   Your completed OrderInitEventArgs class should look like Listing 4.2.

LISTING 4.2 OrderInitEventArgs


using System;
using System.Workflow.Activities;

namespace LocalService
{
    [Serializable]
    public class OrderInitEventArgs : ExternalDataEventArgs
    {
        private string orderNumber;
        public string OrderNumber
        {

            get { return orderNumber; }
            set { orderNumber = value; }

        }
        public OrderInitEventArgs(Guid instanceId, string number)
            : base(instanceId)
        {

            orderNumber = number;
        }
    }
}


7.   Right-click the LocalService project and select Build. Fix errors, if any.

Step 3: Creating the Workflow

Now it is time to connect the local service interface to the workflow. This is accomplished by using the HandleExternalEvent and CallExternalMethod activities. The HandleExternalEvent activity wraps to the events coming from the host to the workflow. The CallExternalMethod serves the same purpose for workflow-invoked methods on the host. These activities can be wired to any ExternalDataExchange-attributed interface accessible by the workflow project. This means the local service may be within the workflow project itself or in a project referenced by the workflow project. Before adding the communication activities, you will add a couple of properties as described in the next section.

Adding the DependencyProperty Type

The DependencyProperty is a .NET type that is heavily used in WF and WPF. They are essentially properties that contain a few advantages over standard .NET properties, such as data binding and event handling, which will be covered in Hour 20, “Creating Basic Custom Activities.” The one advantage relevant now is that they are stored more effectively because they do not allocate memory for blank values. Dependency properties are generally strongly typed to the type they are contained in and the type of property they are. For instance, the Customer DependencyProperty of type string in the Customer class is typed to both the Customer type and as a string member. There is a shortcut to add them so it takes no longer to add them than adding a standard .NET property. They are, however, quite verbose, which can make reading them and the classes they are contained in more difficult.

Let’s now create DependencyProperty variables that carry the approval information tracked by the workflow in the next steps:

1.   Open the workflow code-beside file by right-clicking LocalServiceWorkflow.cs in the Solution Explorer and selecting View Code.

2.   Create a DependencyProperty named OrderNo as follows:

A.   Right-click below the constructor.

B.   Select Insert Snippet.

C.   Double-click the Other choice (skip to step D if using Visual Studio 2005).

D.   Double-click the Workflow choice.

E.   Double-click the Dependency Property—Property choice.

F.   Replace the highlighted MyProperty text with OrderNo (leave the nonhighlighted Property in the suffix) and press Tab.

G.   Press Enter to accept the string default and to leave the wizard (if the first Enter does not exit you from the wizard, press Enter again). You should see the code generated in Listing 4.3.

LISTING 4.3 OrderNo DependencyProperty


        public static DependencyProperty OrderNoProperty =
            DependencyProperty.Register("OrderNo",
            typeof(string),
            typeof(LocalServiceWorkflow));

        [DescriptionAttribute("OrderNo")]
        [CategoryAttribute("OrderNo Category")]
        [BrowsableAttribute(true)]

[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]

        public string OrderNo
        {

            get
            {

                return
((string)(base.GetValue(LocalServiceWorkflow.OrderNoProperty)));
            }
            set
            {
                base.SetValue(LocalServiceWorkflow.OrderNoProperty, value);
            }
        }

3.   Create another DependencyProperty named Status as follows:

A.   Right-click below the OrderNo DependencyProperty you just created (the entire DependencyProperty).

B.   Select Insert Snippet.

C.   Double-click the Other choice.

D.   Double-click the Workflow choice.

E.   Double-click the Dependency Property—Property choice.

F.   Replace the highlighted MyProperty text with Status (leave the nonhighlighted Property in the suffix) and press Tab.

G.   Press Enter to accept the string default and to leave the wizard. (If the first Enter does not exit you from the wizard, press Enter again.)

Image

If you look at the generated code, you may notice that the DependencyProperty contains two types. One binds it to a class—the workflow in this case. The other tells if it is a string or double. It is by binding the property to a class that data binding and other features are enabled.

Step 3A: Creating a HandleExternalMethod Activity

Now back to data exchange. Let’s add the HandleExternalEvent activities that receive the events in the next set of steps.

1.   Double click on LocalServiceWorkflow in the solution explorer to open it in design mode.

2.   Drag and drop a HandleExternalEvent activity onto the workflow and name it ReceiveOrder.

3.   Click the InterfaceType property, then click the ellipsis, select LocalService, and then select ILocalService in the middle of the form.

Image

If ILocalService does not show up, make sure the LocalService project is referenced and built. The easiest way is to add a new reference to it from the LocalServiceWorkflows project and build it again.

      Your screen should look like Figure 4.4.

FIGURE 4.4 Local Service selection dialog.

Local Service selection dialog.

4.   Click OK.

5.   Click the drop-down in the EventName property and select the OrderWorkflowInitialization event.

6.   Enter OrderInvoked in the Invoked property.

7.   Enter the following code in the OrderInvoked handler to retrieve the OrderNumber from the event args.

  // Retrieve the order number from the eventArgs
  OrderNo = (e as LocalService.OrderInitEventArgs).OrderNumber;

8.   Switch back to the workflow designer, and the ReceiveOrder activity property window should look like Figure 4.5.

FIGURE 4.5 The ReceiveOrder activity’s properties.

The ReceiveOrder activity’s properties.

Handle External Event Activity Property Window

This activity is now wired to the OrderWorkflowInitialization event and will execute when the event is raised. In addition, the OrderInvoked method will be invoked after the event is raised and will be used to retrieve the OrderNo from the OrderInitEventArgs class.

Adding an IfElse Activity and Rules

In the next steps you will add an IfElse activity and configure its rules.

1.   Drag and drop an IfElse activity onto the workflow below the ReceiveOrder activity.

2.   Name the IfElse activity CheckCredit, name the left branch IsApproved, and name the right branch IsRejected.

3.   Click the red exclamation mark in the left branch, then click Property ‘Condition’ Is Not Set. You will now be into the Condition property ready to input a rule to determine whether this IfElseBranch should execute.

4.   Click the drop-down in the Condition property and select Code Condition.

5.   Click the + at the left of the Condition property, enter CheckOrderCondition in the Condition property, and press Enter.

6.   You are placed in the CheckOrderCondition handler.

7.   Enter the following code in the CheckOrderCondition handler to approve orders ending in 1:

        if (OrderNo.EndsWith("1"))
            e.Result = true;
        else
            e.Result = false;

8.   Switch back to the workflow designer, and your workflow should now look like Figure 4.6.

FIGURE 4.6 Workflow with HandleExternalEvent and IfElse activities.

Workflow with HandleExternalEvent and IfElse activities.

Adding CallExternalMethod Activities

Step 3B: Add the First CallExternalMethod Activity

This is the activity that will invoke the Approve method that is part of the interface and will be added to the host. From a process standpoint, it will send approval to the host. In the next steps, you add the CallExternalMethod activity to approve the order.

1.   Drag and drop a CallExternalMethod activity into the left IfElse activity branch.

2.   Click the InterfaceType property, then click the ellipsis, select the LocalService, and then select ILocalService in the middle of the form.

3.   Click OK.

4.   Click the drop-down in the MethodName property and select the Approve method.

5.   Enter ApproveIt into the MethodInvoking property (called before the approve method is called; thus it is a good place to perform preparatory work, such as initializing variables).

6.   Enter the following in its code handler:

        Status = "Approved";

7.   Switch back to the workflow designer view.

8.   Bind the OrderNo DependencyProperty to the orderNumber parameter by clicking the orderNumber property, then clicking its ellipsis, selecting OrderNo, and clicking OK.

9.   Bind the Status DependencyProperty to the status parameter by clicking the status property, then clicking its ellipsis, selecting Status, and clicking OK.

10.   Your CallExternalMethod activity property window should now look like Figure 4.7:

FIGURE 4.7 CallExternalMethod activity property window.

CallExternalMethod activity property window.

Step 3C: Add the Second CallExternalMethod Activity

This is the activity that will invoke the Reject method that is part of the interface and will be added to the host. From a process standpoint it will send rejection to the host. In the next steps you add the CallExternalMethod activity that rejects the order.

1.   Drag and drop a CallExternalMethod activity into the right IfElse activity branch.

2.   Click the InterfaceType property, then click the ellipsis, select the LocalService, and then select ILocalService in the middle of the form.

3.   Click OK.

4.   Click the drop-down in the MethodName property and select the Reject method.

5.   Enter RejectIt in the MethodInvoking property (called before the reject method is called; thus it is a good place to perform preparatory work, such as initializing variables).

6.   Enter the following in its code handler:

         Status = "Rejected";

7.   Switch back to the workflow designer view.

8.   Bind the OrderNo DependencyProperty to the orderNumber parameter by clicking on the orderNumber property, then clicking on its ellipsis, selecting OrderNo, and clicking OK.

9.   Bind the Status DependencyProperty to the status parameter by clicking on the status property, then clicking on its ellipsis, selecting Status, and clicking OK.

10.   Your workflow should now look like Figure 4.8:

FIGURE 4.8 Workflow configured with HandleExternalEvent and CallExternalMethod activities.

Workflow configured with HandleExternalEvent and CallExternalMethod activities.

11.   Build your LocalServiceWorkflows project and fix compilation errors, if any.

12.   The code in your workflow code-beside file should look like Listing 4.4:

LISTING 4.4 LocalServiceWorkflow Code-Beside File


using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace LocalServiceWorkflows
{
     public sealed partial class LocalServiceWorkflow: SequentialWorkflowActivity
     {
          public LocalServiceWorkflow( )

          {
               InitializeComponent( );
          }

        public static DependencyProperty OrderNoProperty =
            DependencyProperty.Register("OrderNo",
            typeof(string),
            typeof(LocalServiceWorkflow));

        [DescriptionAttribute("OrderNo")]
        [CategoryAttribute("OrderNo Category")]
        [BrowsableAttribute(true)]

[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible
)]

        public string OrderNo
        {
            get
            {
                return
((string)(base.GetValue(LocalServiceWorkflow.OrderNoProperty)));
            }
            set
            {
                base.SetValue(LocalServiceWorkflow.OrderNoProperty, value);
            }
        }

        public static DependencyProperty StatusProperty =
            DependencyProperty.Register("Status",
            typeof(string),
            typeof(LocalServiceWorkflow));

        [DescriptionAttribute("Status")]
        [CategoryAttribute("Status Category")]
        [BrowsableAttribute(true)]

[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible
)]

        public string Status
        {
            get
            {
                return
((string)(base.GetValue(LocalServiceWorkflow.StatusProperty)));
            }
            set
            {
                base.SetValue(LocalServiceWorkflow.StatusProperty, value);
            }
        }

        private void OrderInvoked(object sender, ExternalDataEventArgs e)
        {
            // Retreive the order number from the eventArgs
            OrderNo = (e as LocalService.OrderInitEventArgs).OrderNumber;
        }

        private void CheckOrderCondition(object sender, ConditionalEventArgs e)
        {
            if (OrderNo.EndsWith("1"))
                e.Result = true;
            else
                e.Result = false;

        }

        private void ApproveIt(object sender, EventArgs e)
        {
            Status = "Approved";
        }

        private void RejectIt(object sender, EventArgs e)
        {
            Status = "Rejected";
        }
        }
}


Creating the Windows Forms Host

You will create a Windows Forms application that implements the local service interface you created. It will create the event args payload used by the local service and raise events to the host. Likewise it will act on the methods invoked by the workflow. It will also instantiate the WorkflowRuntime and create and start a WorkflowInstance as you saw in Hour 3, “Learning Basic Hosting.”

Whereas a Console Host application runs sequentially from beginning to end and then terminates, a Windows Forms application remains running until explicitly exited. The main implication of this is that you need to plan where you will instantiate the WorkflowRuntime. You do not want to do this each time an order is submitted. There needs to be only one WF runtime per application that will, in turn, run all the individual workflow instances.

Therefore, we are adding the runtime instantiation to the form load method, which is called only once in our application. For good measure and practice for other hosting scenarios, you first check to see if the runtime has already been instantiated. If so, you return the existing runtime. If not, you create a new runtime.

You also learn to use a new runtime service called the ExternalDataExchangeService. It is a container for one or more local services. You first add the ExternalDataExchangeService to the runtime and then add the local service you created to the ExternalDataExchangeService, as you will see shortly. We will add this service in code, but it can be added though configuration files as you did with the persistence service in Hour 3.

Adding Controls to the Form

In the next steps you add controls to the form.

1.   Create the form in the BasicHostForm.cs designer, as shown in Figure 4.9. (Control names are conveyed in the next task.)

FIGURE 4.9 Basic approval form.

Basic approval form.

2.   Name the text boxes textBoxOrderNumber and textBoxStatus. Name the command button buttonSubmitOrder. The labels can retain their default names.

Adding the WF Runtime to the Form/Host

Over the next few subsections, you add the WF runtime to the Windows Forms host.

Prepare for Runtime Hosting

You need to prepare this form to be a host. It needs to implement the local service so it can exchange data with the workflow. It also needs WorkflowRuntime and WorkflowInstance variables and the other code to host the runtime and create and run a workflow. In many ways it will be similar to the host created from scratch earlier in Hour 3.

Follow the next steps to prepare the form host for runtime hosting.

1.   Open BasicHostForm.cs in code view.

2.   Add the following using directives below the existing ones:

using System.Workflow.Runtime;
using System.Workflow.Activities;

3.   Change the class signature to incorporate the local service interface as shown:

    public partial class BasicHostForm : Form,
       LocalService.ILocalService

4.   Add the following variable declarations below the constructor (the UpdateDelegate delegate will be explained shortly):

   private WorkflowRuntime workflowRuntime;
   private WorkflowInstance workflowInstance;
   private delegate void UpdateDelegate( );

Setup Method to Create WorkflowRuntime

In the next steps, you add a method to and begin to instantiate the WF runtime.

1.   Double-click the form to create the Form_Load handler and populate it with the code shown in the next snippet that calls a method that either instantiates a new WorkflowRuntime or returns an existing WorkflowRuntime:

  // Get an existing workflow runtime or create a new one
  workflowRuntime = GetWorkflowRuntime(workflowRuntime);

2.   Add the GetWorkflowRuntime method signature below the Form_Load handler. The GetWorkflowRuntime method creates the WorkflowRuntime, registers runtime events with it, and adds runtime services to it:

   private WorkflowRuntime GetWorkflowRuntime
      (WorkflowRuntime WorkflowRuntime)
   {
   }

3.   Add the logic to check whether the workflow runtime has already been instantiated inside the GetWorkflowRuntime method:

      if (WorkflowRuntime == null)
         {
         }

4.   Instantiate the runtime inside the if statement you just added:

        workflowRuntime = new WorkflowRuntime( );

Register Events and Local Service with the WorkflowRuntime

In the steps in this section, you register events and the local service with the WF runtime.

1.   Below the code you just added, register the terminated event by entering "workflowRuntime.", then select WorkflowTerminated from the menu, press the spacebar, enter += and press Tab twice.

2.   Add the ExternalDataExchangeService to the runtime below the event you just registered in the GetWorkflowRuntime method:

        // Add the local service
        ExternalDataExchangeService des = new
ExternalDataExchangeService( );

3.   Add the local service to the ExternalDataExchangeService:

        workflowRuntime.AddService(des);
        // this implements the local service
        des.AddService(this);

Start and Return the Runtime

In this set of steps, you start the WF runtime and return it to the calling method.

1.   Add code to start the runtime:

        workflowRuntime.StartRuntime( );

2.   Add code to return the runtime outside of the if statement scope as shown:

        return workflowRuntime;

The GetWorkflowRuntime method should look like Listing 4.5.

LISTING 4.5 GetWorkflowRuntime Hosting Method


        private WorkflowRuntime GetWorkflowRuntime
            (WorkflowRuntime WorkflowRuntime)
        {
            if (WorkflowRuntime == null)
            {
                workflowRuntime = new WorkflowRuntime( );
                // Set workflow event

                workflowRuntime.WorkflowTerminated +=
                   new EventHandler<WorkflowTerminatedEventArgs>
                   (workflowRuntime_WorkflowTerminated);

                //Add the local service
                ExternalDataExchangeService des = new
ExternalDataExchangeService( );

                workflowRuntime.AddService(des);
                // this implements the local service
                des.AddService(this);

                workflowRuntime.StartRuntime( );

           }
           return workflowRuntime;
        }


Update the Terminated Event Handler

When you registered the terminated event, you also created an event handler for it. The reason you registered this one event is so that errors will be reported to you. There are quite a few moving parts between the workflow, local service, and host, and it is pretty easy to introduce an error that is hard to debug. Trapping this event makes it easier to know a problem exists and to identify what the problem is.

In the next step, you add code to the terminated event handler.

1.   Add the following code to the workflow Runtime_WorkflowTerminated handler you generated when registering the terminated event:

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

Step 4A: Creating the Workflow Instance and Raising the Initial Event

We finally go back to the host-workflow data exchange now that the host is set up.

Creating the workflow instance in the Windows Forms application is similar to doing so in a console host. Our workflow, though, differs in communication topology. You are no longer passing a dictionary object to the CreateWorkflow method. You are using local service-based communication.

Therefore, you have to raise an event to send the data to the workflow. The workflow contains a HandleExternalEvent activity as its first activity that is awaiting the event, although there will be one workflow runtime for the application. The user may create multiple workflow instances, which are created each time the user clicks the Submit button.

In the next steps, you add the logic to start the workflow instance when the Submit button is clicked.

1.   Double-click the Submit button (buttonSubmitOrder) and add the following code to its handler:

        // Create and start a workflow instance
        workflowInstance = workflowRuntime.CreateWorkflow(typeof
           (LocalServiceWorkflows.LocalServiceWorkflow));

       workflowInstance.Start( );

2.   Add the following code below the code you just added to create the event args payload to be delivered to the WF runtime:

        // Pass the order number entered and raise the event specifed in
the local service
        // that carries the payload defined in the order init event
argument class:
        LocalService.OrderInitEventArgs eventArgs =
           new LocalService.OrderInitEventArgs
           (workflowInstance.InstanceId, textBoxOrderNumber.Text);

3.   Raise the event to be captured by the initial HandleExternalEvent activity on the workflow and package the event args with it.

        OrderWorkflowInitialization(null, eventArgs);

Step 4B: Implementing the Local Service

You need to implement the two methods and one event defined in the local service interface. The methods return either approved or rejected, which is passed to the form. Windows Forms controls, however, are not thread safe. Your workflow runs on a different thread than the Windows Form.

This book uses anonymous delegates (see MSDN or other sources if you want to learn more about anonymous delegates) to update the Windows Forms controls from the workflow threads. This is why you declared the UpdateDelegate at the top of this class. You enclose the changes to controls you want to make inside of the UpdateDelegate( ). This will become clearer when you see the code for the Approve and Reject methods shortly.

Image

Threading is covered in Hour 17, “Learning Advanced Hosting,” and an option exists to run workflows on the same thread synchronously. This eliminates the need to account for cross-thread communication via delegate-based communication or other methods. I think you could make a fair argument that the synchronous threading is best suited for many UI scenarios.

1.   Right before the end of the class declaration, implement the workflow event:

       public event EventHandler<LocalService.OrderInitEventArgs>
          OrderWorkflowInitialization;

2.   Implement the methods that each update a textbox with the order status returned from the workflow:

        public void Approve(string status, string orderNumber)
        {
            UpdateDelegate ud = delegate( )
            {
                textBoxStatus.Text = "Order No: " + orderNumber + " is " +
 status;
            };
            this.Invoke(ud);
        }
        public void Reject(string status, string orderNumber)
        {
            UpdateDelegate ud = delegate( )
            {
                textBoxStatus.Text = "Order No: " + orderNumber + " is " +
status;
            };
            this.Invoke(ud);
        }

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

4.   The code in your form class should look like Listing 4.6.

LISTING 4.6 BasicHostForm Windows Forms Host


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Workflow.Runtime;
using System.Workflow.Activities;


namespace BasicWorkflowHost
{
    public partial class BasicHostForm : Form, LocalService.ILocalService
    {
        public BasicHostForm( )
        {
            InitializeComponent( );
        }

        private WorkflowRuntime workflowRuntime;

        private WorkflowInstance workflowInstance;
        private delegate void UpdateDelegate( );

        private void BasicHostForm_Load(object sender, EventArgs e)
        {
            // Get an existing workflow runtime or create a new one
            workflowRuntime = GetWorkflowRuntime(workflowRuntime);

        }

        private WorkflowRuntime GetWorkflowRuntime
            (WorkflowRuntime WorkflowRuntime)
        {
            if (WorkflowRuntime == null)
            {
                workflowRuntime = new WorkflowRuntime( );

                workflowRuntime.WorkflowTerminated += new

EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

                // Add the local service
                ExternalDataExchangeService des = new
ExternalDataExchangeService( );


                workflowRuntime.AddService(des);
                // this implements the local service
                des.AddService(this);

                workflowRuntime.StartRuntime( );
            }

            return workflowRuntime;

        }

        void workflowRuntime_WorkflowTerminated(object sender,
WorkflowTerminatedEventArgs e)
        {
            MessageBox.Show("Workflow terminated: " + e.Exception.Message);
        }

        private void buttonSubmitOrder_Click(object sender, EventArgs e)
        {
            // Create and start a workflow instance
            workflowInstance = workflowRuntime.CreateWorkflow(typeof
               (LocalServiceWorkflows.LocalServiceWorkflow));

            workflowInstance.Start( );

            // Pass the order number entered and raise the event specifed in the
local service
            // that carries the payload defined in the order init event
argument class:
            LocalService.OrderInitEventArgs eventArgs =
               new LocalService.OrderInitEventArgs
               (workflowInstance.InstanceId, textBoxOrderNumber.Text);

            OrderWorkflowInitialization(null, eventArgs);


        }

        public event EventHandler<LocalService.OrderInitEventArgs>
   OrderWorkflowInitialization;
        public void Approve(string status, string orderNumber)
        {
            UpdateDelegate ud = delegate( )
            {
                textBoxStatus.Text = "Order No: " + orderNumber + " is " +
status;
            };
            this.Invoke(ud);
        }
        public void Reject(string status, string orderNumber)
        {
            UpdateDelegate ud = delegate( )
            {
                textBoxStatus.Text = "Order No: " + orderNumber + " is " +
status;
            };
            this.Invoke(ud);
        }
    }
}


Building and Running the Project

The project is rather simple. Entering an order number ending in 1 will result in approval. Any other ending number will conversely result in rejection.

In the next steps, you run the project.

1.   Set the BasicWorkflowHost project as the startup project.

2.   Run the project, enter an order number ending in 1, and you should see results similar to Figure 4.10.

FIGURE 4.10 Approved approval form.

Approved approval form.

Summary

You can now create a simple workflow, build a basic host, and perform mid-flight host-workflow communication. You used local services, event args classes, HandleExternalEvent activities, and CallExternalMethod activities to conduct mid-flight host-workflow communication. You also learned about the DependencyProperty type and hosting the WF runtime in a Windows Forms application. In the next hour you will build on what you learned in an application that supports escalation across multiple forms.

Workshop

Quiz

1.

Why can’t a .NET application communicate directly with a workflow?

2.

Why are delegates necessary when communicating between a workflow and a Windows Forms application?

3.

What is the purpose of a local service interface?

4.

What are HandleExternalEvent activities used for?

5.

What are CallExternalMethod activities used for?

6.

Does a workflow communicate with a host synchronously or asynchronously?

7.

Why must the local service interface payloads (the event argument classes) be marked as serializable?

8.

How does WF know which classes can be connected to HandleExternalEvent and CallExternalMethod activities?

Answers

1.

Because the workflow runtime handles threading and workflow persistence issues for you.

2.

Because Windows Forms controls are not thread safe.

3.

It contains the signature for classes that perform host-workflow communication.

4.

They are used to receive events raised by the host to the workflow.

5.

They are used to call methods on the host from the workflow.

6.

Asynchronously.

7.

Because WF may need to persist the workflow; therefore, all workflow members must be serializable.

8.

They are marked with ExternalDataExchange attributes.

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

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