Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Host and Workflow: Two Worlds to Communicate: Part III

4.70/5 (8 votes)
3 Oct 2008CPOL11 min read 1   385  
Part III: Host -> Workflow intercommunication through HandleExternalEvent activities.

Introduction

This is the third of five articles about Host Workflow communication.

In this series of articles, I try to show the different possibilities to implement communication, from the simplest case to the more complex. I am not planning to be exhaustive, but I will try to give a general panoramic in order to get us a good idea about the thematic. Because I also don’t like large articles, I have divided the series in to the followings parts:

Background

In Part II, we understood how to pass information to a Workflow when it is launched and how the workflow can return information to the host. But, you can have a more complex situation, one that you must send information to the Workflow, not in the initialization phase but in an intermediate point, or you have to send information more often in the workflow's life time. Also, you need to be able to send information in a determinate point of the Workflow flux. Those entire requirements can be fulfilled with the Activity HandleExternalEvent.

With HandleExternalEvent, you can stop the workflow and wait for a determinate external event, and when the event is received, the Workflow can continue the execution of the next activity.

HandleExternalEvent gets information from the Host in the Event Arguments parameter defined in the Event.

If we compare CallExternalMethod with HandleExternalEvent, we can see that both call a piece of software that is defined in another thread in another application. The first calls a method, and the second waits for an Event.

Image 1

As you can see, you must supply the command or the number to the workflow each time the user clicks the Add button in the Host application. Obviously, you can not supply these information in the initialization of the Workflow.

…OK, you can run the Workflow each time you need the calculation; give the number, and the total is returned as a parameter, but I want to show you how to use the Workflow to control the process….

The HandleExternalEvent Activity waits for the defined external event and you are sure that the data is received at the right moment. The data is returned by using the CallExternalMethod that was explained in Part II of this article.

To implement the communication, we need to fulfill a specific pattern that is similar to CallExternalMethod, but now, we need to define an external event. In short, we need to do the following steps:

Create the following interface and classes:

  1. An interface with the method definition called by the CallExternalMethod activity and the External Event handled by the HandleExternalEvent activity.
  2. A class that implements the interface. In this class, you implement the method that is called by CallExternalMethod. In this method, you add the code to send the information to the host, and the event to be handled by the HandleExternalEvent activity.
  3. You should create a special Event Arguments class to send the information to the Workflow. Arguments for External Events need to inherit from a special Event Argument class: ExternalDataEventArgs. This special event argument comes with a constructor that needs the GUID from the instanced workflow that we want to communicate with. In our example, that is not very important because we only have an instance of the Workflow, but that plays an important role if we are working with different Workflows in the same time.
  4. You should also implement the Event Argument class to receive the information from the method called by CallExternalMethod in the host.

In the host application, do the following steps:

  1. You must create an instance of the ExternalDataExchangeService class and register it in the Workflow runtime.
  2. You must create an instance of CommunicationCalculator and add it to the ExternalDataExchangeService instance (created in point 1).
  3. If you use an event to drive the communication WF –> Host, you must also handle the communication event in your host class.
  4. To communicate with the Workflow, you must raise the created External Event and pass the information through the created Event arguments.

In the Workflow:

  1. You must insert a CallExternalMethod activity.
  2. In it, register the communication interface and the method to call.
  3. You must have a HandleExternalEvent activity for each event that you want to handle to receive information from the host application. In this case, you need only one.
  4. You should configure this HandleExternalEvent activity with the declared communication interface and the event to call. Eventually, you can also define an event to be called after the external event is processed.
  5. You should declare a public CalculadorEventArguments to receive the information for the external event.

In the following example, we have more code that we will explain here. That is because we don’t explain in depth what we need to for the functional logic for these examples, we concentrate only in the passing of the information.

You can see the result class structure for our example in the following diagram:

Image 2

What is new here? The CalculadorEventArguments, as you see, makes a relation between two threads and it is all done through ICommunicationCalculator. The event to communicate data from the Workflow is raised in the host and is received from the HandleExternalEvent activity.

Now, we explain in detail how the program works.

Using the code

To use the companion code, create an empty VS2008 solution and unzip the companion ZIP file in to the created project directory. In Visual Studio, add the three unzipped projects to the solutions. Build your solution.

The best part is downloading the code that is attached to this article. You can also developer your own ideas and copy and paste with creativity the code about the communication explained in the following paragraph.

The example project is a Windows project that adds integer numbers to a total field. You can add numbers, reset it, or exit from the program. You have three buttons to launch these functions.

In this project, we use an individual project to group all the classes that we need to implement the communication between the host and the workflow.

A. WFCommunication Interface Project

Create a project to hold the classes that you need to communicate the host with the workflow. Select a class project and name it WFCommunicationInterface. You can also revise the project in the attached code.

Open to add a file and select in New, the Item Interface item, then name it to ICommunicationCalculator. This file must have all the methods and events that are called or consumed by the workflow.

Add the following code to the class:

C#
/// <summary>
/// This interfaces must have all method to communicate
/// between WF to Host and all Events to communicate Host to WF
/// </summary>
[ExternalDataExchange]
public interface ICommunicationCalculator
{
  #region Communication WF - > Host (explained in Part II)
  /// <summary>
  /// External Method to return result to host
  /// </summary>
  /// <param name="""total""" />calculated total</param />
  void SendTotalToHost(Int64 total);
  #endregion

  #region Communication Host -> WF
  /// <summary>
  /// External Event to send information to workflow (number)
  /// </summary>
  event EventHandler<CalculadorEventArguments>SendCommandAndDataToWF;

  #endregion
}

Here, you must define an event to do the communication between the host and the workflow. The EventHandler has a special EventArguments: CalculatorEventArguments.

Now, create a new class file and add to the project. Name it as CalculatorEventArguments and create the following class:

C#
/// <summary>
/// See the attribute "Serializable" This argument must be
/// passed fromHost to Workflow 
/// through a underline Intercomunication Process. 
/// You must mark as serializable!!
/// </summary>
[Serializable]
public class CalculadorEventArguments: ExternalDataEventArgs
{
   #region Create properties to hold the values to send to Workflow
   string _command = "RESET";
   int _valueToAdd = 0;

   public string Command 
   {
       get { return _command; }
       set { _command = value; } 
   }

   public int ValueToAdd
   {
       get { return _valueToAdd; }
       set { _valueToAdd = value; }
   }
   #endregion

   #region Create a Constructor with the InstanceId to pass to base class

    public CalculadorEventArguments(Guid instanceID)
           :base(instanceID)
    {

    }
   #endregion

The event makes space for the information that we need to pass to the workflow: the command string and the integer to be added to the total sum.

Here, the two important points are the [serializable] attribute and the base class for the class. You must decorate the class with the [serializable] attribute. This event argument must be used between threads in an inter communication process, and so it must be serializable.

The second point is the inheritance from ExternalEventArgs. This special event argument has a constructor that take as parameter the GUID or the identifier for the workflow instance. You must use this identifier to guarantee that the information goed to the right instance of the Workflow. The framework uses this parameter to derive the event to the correct instance of the workflow. You know that you can have multiples instances of the same workflow that run concurrently. We will see this in detail in another article.

You should also create an event to send to the host when the external method SendCommandAndDataToWp is executed. That is a normal event as you can see in the following code:

C#
/// <summary >
/// Argument to be used when the WF send the result to 
/// Host. Note that you dont need to use the serializable attribute
/// neither derived the class from ExternalEventArguments
/// That is because the event dont need to pass any Thread frontier.
/// </summary >
public class SummeEventArgs: EventArgs
{
    Int64 _summe = 0;

    public Int64 Summe
    {
        get {return _summe;}
        set {_summe = value;}
    }
}

As you see, we use a normal argument to return the sum total from the workflow to the host. In the last step, we need to implement the ICommunicationCalculator interface. Create a new file in the project, create a new class to create the file, and write the following code:

C#
public class CommunicationCalculator: ICommunicationCalculator
{
  #region Zone Workflow to Host communication
  /// <summary >
  /// Watch that this Event is not in the communication Interface.
  /// because is internal of the implementation of SendTotalToHost 
  /// and internal to the Host programmation.
  /// </summary >
  public event EventHandler<summeeventargs > ReturnSummeEvent;

  public void SendTotalToHost(Int64 total)
  {
       SummeEventArgs e = new SummeEventArgs();
       e.Summe = total;
       EventHandler<SummeEventArgs> sendData = this.ReturnSummeEvent;
       if (sendData != null)
       {
           sendData(this, e);
       }

  }
  #endregion

  #region Zone Host to Workflow comunnication
  /// <summary >
  /// This is the external Even to should handled by the WF to receive the information
  /// </summary >
  public event EventHandler<CalculadorEventArguments> SendCommandAndDataToWF;

  public void ReiseEventSendCommandAndDataToWF(Guid instanceID, 
              string command, int numberToAdd)
  {
      if (SendCommandAndDataToWF != null)
      {
          CalculadorEventArguments e = new CalculadorEventArguments(instanceID);
          e.ValueToAdd = numberToAdd;
          e.Command = command;
          SendCommandAndDataToWF(null, e);
      }
   }
        
   #endregion
 }

We can analyze the code in parts. The first part has the communication between the workflow and the host that we showed in the second part of this series of articles. The second is new, but as you see is very simple to implement. You only need to define an instance for the external argument to be used. The workflow can automatically get this event and handle it.

The second procedure is a helper procedure to raise the event in the host. The use of this helper procedure encapsulates the call to the SendCommandAndDataToWF event.

B. Host application

As we see in the second part of this series of articles, you must register the communication service that we have created in step A as an ExternalDataService.

The code is quasi the same and you can see it here:

C#
#region Add support to the Communication Service
//Declare a ExternalDataExchangeService class
ExternalDataExchangeService dataservice = new ExternalDataExchangeService();
//Add to workflow runtime
_workflowRuntime.AddService(dataservice);
//Declare our CommunicationService instance
cservice = new CommunicationCalculator();
//Add to the ExternalDataService
dataservice.AddService(cservice);
//Add a handler to Service Event (retrieve Summe from Workflow)
cservice.ReturnSummeEvent += 
  new EventHandler<summeeventargs> (cservice_ReturnSummeEvent);

#endregion end of support to Communication Service.....

As you can see, the code is the same as in the example in the previous article.

Here, in the click events for each command, each button fires a command in the workflow that we must transport to the workflow. To do this operation, we must raise the external event. See the following code in the buttons' Click events:

C#
 #region Send data and commands to Workflow 
/// <summary>
/// Send to workflow the operation ADD and the number to add.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void bAdd_Click(object sender, EventArgs e)
{
   if(IsValidNumber(tbNumber.Text))
   {
      //Raise the event...to add a number
      cservice.ReiseEventSendCommandAndDataToWF(instance.InstanceId, 
               "ADD", int.Parse(tbNumber.Text));
   }
} 
/// <summary>
/// Send to Workflow that reset the accumulate summe
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void bReset_Click(object sender, EventArgs e)
{
     //Raise the event...to add a number
      cservice.ReiseEventSendCommandAndDataToWF(instance.InstanceId, "RESET", 0);
} /// <summary>
  /// Close the application
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void bExit_Click(object sender, EventArgs e)
  {
     //Raise the event...to exit form workflow
    cservice.ReiseEventSendCommandAndDataToWF(instance.InstanceId, "EXIT", 0);
    this.Close();
  }

#endregion

We can see that it is simple calling the ReiseEventSendCommandAndDataToWF method from the communication interface. This method raises an external event in the workflow. The next section explain how to use this event.

C. The workflow application

Now, we need to configure the workflow application. In the following figure, we can see the implementation of the workflow to resolve the example task:

Image 3

As you can see here, the sequence is simple, and we use the simple IfElse activities to select the right command and do the job.

Also, we use a While activity to wait for the command from the host and then execute it. The sequence ends when the Exit command is received. You can see the details of the implementation in the code.

You can see that the HandleExternalEvent Activity is directly after the beginning of the while loop. The HandleExternalEvent activity stops the flow of the workflow until the event is received. Then, we can be secure that the command and data are received at the right moment in the flow of the workflow. To configure the HandleExternalEvent activity is simple. The following figure shows how to configure it for our example.

Image 4

As you can see, you need to declare the InterfaceType, the name of the event, and a variable that holds the information that the event from the Host gives us. You only need to declare a variable type CalculadorEventArguments, as you can see in the following code:

C#
/// <summary>
/// The HandleExternalEvent use this property to store the data from the Host
/// </summary>

public jagg.ClassCommunication.CalculadorEventArguments _returnedArguments = 
       default(CalculadorEventArguments);

Here, you can also see the property Invoked. You can use this property to invoke a method that is executed after the event is received. You can use this method to accommodate the received parameter in the right position in your workflow, or to do another task that you need. In our situation, we pass the received information to two internal variables:

C#
/// <summary>
/// This method is call after the handlerExternalEvent activities finish.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>

private void SetResponseInVariables(object sender, ExternalDataEventArgs e)
{
   _numberToAdd = _returnedArguments.ValueToAdd;
   _command = _returnedArguments.Command;
}

And that is all!

You can see the complete application in the attached code.

Resume

The basic steps to pass information from a Host to a Workflow using the HandleExternalEvent activity are:

Create the followings interface and classes:

  • An interface with the event definition consumed by the HandleExternalEvent activity and decorated with the header: [ExternalDataExchange].
  • A class that implements the interface. In this class, you implement the event to be consumed by HandleExternalEvent in the Workflow, and you can also implement the method that raises the event in the host application.

In the Host application, do the following:

  • Register an instance of the ExternalDataExchangeService class in the Workflow runtime.
  • Add the instance of the communication class to the ExternalDataExchangeService instance.
  • At the point of the application that you can send information to the Workflow, call the routine created to raise the external event in the Workflow and pass as parameters the information that you want to pass to the Workflow.

In the Workflow:

  • Drop a HandleExternalEvent Activity at the point of the Workflow that you want to get the information from the Host.
  • In the HandleExternalEvent Activity properties, register the Communication Interface, the event to be consumed, and the variable or the property to hold the information returned by the event.

Now, we have the basic tool to deal with the communication between a workflow and a host. We can resolve many problems that need to send information in a specific point of the workflow and return the information to the host. But all it is a little complex. You must register the service in one part of the program, and configure the HandleExternalEvent and CallExternalMethod classes. In the next part, we will cover the tool and a method to automatically generate a custom communication class and better organize the communication process.

History

  • First version: 4.10.2008.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)