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.
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:
- An interface with the method definition called by the
CallExternalMethod
activity and the External Event handled by the HandleExternalEvent
activity. - 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. - 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. - 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:
- You must create an instance of the
ExternalDataExchangeService
class and register it in the Workflow runtime. - You must create an instance of
CommunicationCalculator
and add it to the ExternalDataExchangeService instance (created in point 1). - If you use an event to drive the communication WF –> Host, you must also handle the communication event in your host class.
- To communicate with the Workflow, you must raise the created External Event and pass the information through the created Event arguments.
In the Workflow:
- You must insert a
CallExternalMethod
activity. - In it, register the communication interface and the method to call.
- 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. - 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. - 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:
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:
[ExternalDataExchange]
public interface ICommunicationCalculator
{
#region Communication WF - > Host (explained in Part II)
void SendTotalToHost(Int64 total);
#endregion
#region Communication Host -> WF
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:
[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:
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:
public class CommunicationCalculator: ICommunicationCalculator
{
#region Zone Workflow to Host communication
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
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:
#region Add support to the Communication Service
ExternalDataExchangeService dataservice = new ExternalDataExchangeService();
_workflowRuntime.AddService(dataservice);
cservice = new CommunicationCalculator();
dataservice.AddService(cservice);
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:
#region Send data and commands to Workflow
private void bAdd_Click(object sender, EventArgs e)
{
if(IsValidNumber(tbNumber.Text))
{
cservice.ReiseEventSendCommandAndDataToWF(instance.InstanceId,
"ADD", int.Parse(tbNumber.Text));
}
}
private void bReset_Click(object sender, EventArgs e)
{
cservice.ReiseEventSendCommandAndDataToWF(instance.InstanceId, "RESET", 0);
} private void bExit_Click(object sender, EventArgs e)
{
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:
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.
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:
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:
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.