Introduction
This is the fourth of five articles about Host Workflow communication. This series of articles try to show the different possibilities to implement communication between Host and Workflow, from the simplest case to complexes. I am not planning to be exhaustive, but I 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 them in the followings parts:
Part IV: Organisation of the communication classes: Communication manager, wca.exe utility and Wwca.exe windows front-end for wca.exe
Background
Until now we made the emphasis in how the workflow and host making the communication, now we working a little in what tool we haven to make our live easy and also how to organize our work more object orientated
It is possible as we are saw in the previous articles parts using directly the activities CallExternalMethod and HandleExternalEvent, but when you have a Workflow a little complex the direct use of these activities can be confuse and not reflect what the activity doing.
The Workflow SDK comes with a tool to create automatically all needed custom CallExternalMethod and HandleExternalEvent. That can make our work more easy and the workflow clearer to understand.
The wca.exe creates the custom activities taking the necessary information direct from the .dll that have our defined external communication interface. Then the tool needs a pair of option to generate the custom action. Because the console program always are a little complicate to running, because the path for the data or the program itself are more and more complicate I wrote a small windows front- end that you can obtain free here: WWCA.ZIP
You can see the graphic interface for WWCA in the following figure:
Before use the program you must store the path and filename of wca.exe in the configuration Menu of WWCA.exe.
As you see the program only catch the path from input file, give you the possibility to check the different line parameter options easy and quick.
In the previous example we are developed the code to insert the workflows and the communication service in the host without any object orientation. That was doing because the idea was explain the communication essence without the encapsulation level that the object gives.
In this part, we make a Communication Manager class to driver all that is related with the communication.
Ok, we use as example a program to control a simple valve.
The valve can be power by a button and another button drives the open or close valve state.
As simple business rules; the valve can be power off only if the valve is closed. If the valve is open you should not power-off.
If the valve is closed you can power-off it.
We will use a state workflow to control the valve status and the user interface must a windows program.
In the next figure you can see a general schema about the application:
Using the code
1. - The Communication Manager
From the previous figure you can get how the communication between the host and the workflow is doing.
You need an External Method to return the status information to host. You can define the returned status as a string: CLOSE OPEN EXIT INIT.
- Host to Workflow communication
Here you have two possibilities, A Event for each status changes or one Event with a parameter with the information of the new state.
I opted here for one event for each state. With this solution I don’t need to create a special external event argument to use the event, I can use direct the ExternalDataEventArgs because I don’t need to pass information in the arguments.
Then we haven Three Events to declare Open, Close, Exit. You don’t need to declare init because you can make the init when you create the workflow.
In the next code segment we have the code to create:
[ExternalDataExchange]
public interface ICommunicationValve
{
#region Communication WF -> Host
void StatusValve(Guid wfGuid, string status);
#endregion
#region Communication Host -> WF
event EventHandler<ExternalDataEventArgs> CloseValve;
event EventHandler<ExternalDataEventArgs> Exit;
event EventHandler<ExternalDataEventArgs> OpenValve;
#endregion
}
The next action is implement this Communication interface. Because we use a windows application we need in the StatusValve procedure implement a a internal event to pass the information to the form through a event.
public class CommunicationValve : ICommunicationValve
{
#region WF -> Host Communication
public event EventHandler<ExternalValveEventArgs>
EventValveStatus;
public void StatusValve(Guid wfGuid, string status)
{
if (EventValveStatus != null)
{
ExternalValveEventArgs e = new ExternalValveEventArgs(wfGuid);
e.Status = status;
EventValveStatus(this, e);
}
}
#endregion
#region Host -> WF
public event EventHandler<ExternalDataEventArgs> CloseValve;
public event EventHandler<ExternalDataEventArgs> Exit;
public event EventHandler<ExternalDataEventArgs> OpenValve;
#endregion
#region Auxiliar procedures
public void RaiseExit(Guid instanceId)
{
if (Exit != null)
{
ExternalDataEventArgs e = new ExternalDataEventArgs(instanceId);
e.WaitForIdle = true;
Exit(null, e);
}
}
public void RaiseOpen(Guid instanceId)
{
if (OpenValve != null)
{
ExternalDataEventArgs e = new ExternalDataEventArgs(instanceId);
OpenValve(null, e);
}
}
public void RaiseClose(Guid instanceId)
{
if (CloseValve != null)
{
ExternalDataEventArgs e = new ExternalDataEventArgs(instanceId);
CloseValve(null, e);
}
}
#endregion
}
As you see here, we encapsulate the raise event method direct in the implementation of the interface, and then you don’t need to raise the event external.
As you are seeing in the previous part, you must register the service in the workflow runtime as communication service . Then it is a good idea creates an object to encapsulate in one object all these activities. Then here we create the CommunicationManager class. This class must content a single instance of the CommunicationValve class, a reference to the workflow runtime.
See the following code:
public class CommunicationManager
{
private static CommunicationValve Comvalve = null;
private WorkflowRuntime runtime = null;
public CommunicationValve Valve
{
get
{
return Comvalve;
}
}
public CommunicationManager(WorkflowRuntime wfRuntime)
{
runtime = wfRuntime;
if (Comvalve == null)
{
Comvalve = new CommunicationValve();
}
}
public void RegisterCommunicationService()
{
ExternalDataExchangeService dataservice = new
ExternalDataExchangeService();
runtime.AddService(dataservice);
dataservice.AddService(Comvalve);
}
}
In code is all included that it is with the communication to do.
We can also apply a façade concept and declare in this manager the raise event procedures that are in the ValveCommunication class, and does not call direct the ComValve instance.
That is all, we haven our Communication Manager ready. You can see the complete class diagram in the following figure:
2. – The Workflow Application:
Create a new State Workflow library project. The first action that we should do is creating the custom actions.
Open the WWCA.EXE program and enter the .dll created in the previous step, when we build the CommunicationManager. Click the ellipsis button right in the first textbox, then select as output directory the directory of the workflow create project. See Figure 1: Select include sender option and the name of the namespace that you use in your project.
Then select action -> Execute wca and see if the result of the operation was correct in the result textbox. (The textbox capture the console information send by the wca.exe utility.
That is! You get the custom activities in two files in the workflow application. Select the workflow project in VS solution explorer select: See All Files, You see the created files: ICommunicationValve.Invokes.cs and ICommunicationValve.Sinks.cs, include both in project, and compile the project. (You can get an error if the namespace that you use are not including in the workflow project. Insert the reference to CommunicationManager project or correct the entered namespace and build the project).
Open the workflow designer, and open the toolbox. You can see the created custom activities in the toolbox with the names of the methods and event from the IValveCommunication.
The new created our state workflow with 4 states that resolve our problem. See the following figure:
You can see the detail of the implementation in the attached code. We only illustrate the use of the custom activities to send a status and receive an event, as you can see in the following figures.
Here you use the StatusValve as a CallExternalMethod activity. Well StatusValve inherit from CallExternalMethod. And it is personalised for our application. If you want to see the code used, take a look to the generated files.
The next figure illustrates the use of a custom HandleExternalEvent activity:
As you see the use of the custom activities is the same as the original activities, only you don’t need to declare the interface names and used method (See the properties windows in the previous photos)
The workflow is only for demonstrative purposes, the only code action is updating the status variable when the states change. The state changes when receive the determinate event from host to change the state.
3. – The Host Application.
The user musses interacting with the application and watches the valve status and changes it when it is necessary. The following user interface is implemented to cover the requirements.
In initial state the power from valve is off and the valve is inoperative. If you click the powers ON the valve is active and initialize the status as close
If you click over valve Open the valve show “illuminated” the button, if the valve is open you can hot power off the system, then the ON button is disable. Build and run the code for more detail about the program function.
We are the valve code encapsulated in a user control. And in the control encapsulate the interaction with the workflow, as you can see in the following code:
The code is large to include complete here, then I only included the part related to the communication:
public Valve()
{
InitializeComponent();
lName.Text = "-";
}
public void InitializeControl(string name, WorkflowRuntime wr, CommunicationManager cm)
{
wi = null;
wi = wr.CreateWorkflow(typeof(WFValveControl));
this.wr = wr;
this.cm = cm;
lName.Text = name;
cm.Valve.EventValveStatus += new EventHandler<ExternalValveEventArgs>
(Valve_EventValveStatus);
wi.Start();
}
The initializeControl method is used when the valve is activate (power on) and in this moment the workflow is instantiated, the receive event status from workflow is wired and the workflow is started.
The command to trigger OPEN, CLOSE is triggered by the internal on-click event as you can see in the following code snipped:
private void bControl_Click(object sender, EventArgs e)
{
string dg = Color.DarkGreen.Name;
switch (bControl.BackColor.Name)
{
case "DarkGreen": {cm.Valve.RaiseOpen (wi.InstanceId); break; }
case "LightGreen":{cm.Valve.RaiseClose(wi.InstanceId); break; }
}
}
The rest of the project is a normal logic to control the power button and the rest that is related to the communication is doing in the class constructor to first create the workflow runtime and then register the communication service in the workflow runtime.
public Form1()
{
InitializeComponent();
runtime = WFRuntime.GetRuntime();
manager = new CommunicationManager(runtime);
manager.RegisterCommunicationService();
bActive.BackColor = Color.DarkRed;
gbValvePanel.Enabled = false;
valve1.Name = "VALVE I";
}
You can see that the register service communication operation is encapsulated in the communication manager.
Multiple workflow instance example:
This method that we described here also works for multiple instance of the same workflow or multiple workflows. You can see in the attached code a complete example for a program to control 4 valves.
Resume
The use of the wca.exe tool or the developed windows front-end WWCA.exe gives you the possibility to create custom activities for manages the input output operation between the host and workflow. The generated custom actions haven the advantages that the configuration is simpler and you get a program simpler to understand.
Encapsulate the communication service in a manager class give us a better program simpler of understand and with minus possibility of error. You can also reuse the code with minimum change in another code.
Yes we are a good panoramic about the communication within host and workflow. Rest only a very specific situation to driver.
The workflow has not problem to localize the correct instance of the workflow because use the idInstance in the eventArguments. You can see one example in the companion code.
The only situation that we are not cover is the situation where the same event in consume in two or more part of the workflow in parallel. We go over this point in the last part of this article.
History
First Version 04.10.2008