Click here to Skip to main content
Click here to Skip to main content

Host and Workflow: Two Worlds to Communicate IV

By , 3 Oct 2008
Rate this:
Please Sign up or sign in to vote.

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 I: Simplest communication case: Communication Host -> Workflow by parameters.
Part II: Intercommunications Workflow -> Host through CallExternalMethod Activity
Part III: Intercommunications Host -> Workflow through HandleExternalEvent Activity.
Part IV: Organisation of the communication classes: Communication manager, wca.exe utility and Wwca.exe windows front-end for wca.exe
Part V: Intercommunications with a Workflow instance using Correlation parameter
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.

  • From Workflow to Host:

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
  /// <span class="code-SummaryComment"><summary></span>
  /// Used by CallExternalEvent Argument to pass the valve
  /// status to Host
  /// <span class="code-SummaryComment"></summary></span>
  /// <span class="code-SummaryComment"><param name="status">Actual statis for valve</param></span>
  void StatusValve(Guid wfGuid, string status);
#endregion

#region Communication Host -> WF
  /// <span class="code-SummaryComment"><summary>Use to pass the valve operation to workflow</span>
  ///<span class="code-SummaryComment"></summary></span>
  event EventHandler<ExternalDataEventArgs> CloseValve;
  /// <span class="code-SummaryComment"><summary>Use to pass the valve operation to workflow</span>
  ///<span class="code-SummaryComment"></summary></span>
  event EventHandler<ExternalDataEventArgs> Exit;
  /// <span class="code-SummaryComment"><summary>Use to pass the valve operation to workflow</span>
  ///<span class="code-SummaryComment"></summary></span>
  event EventHandler<ExternalDataEventArgs> OpenValve;

#endregion

}//end ICommunicationValve

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;

  /// <span class="code-SummaryComment"><summary></span>
  /// Used by CallExternalEvent Argument to pass the valve 
  ///status to Host
  /// <span class="code-SummaryComment"></summary></span>
  /// <span class="code-SummaryComment"><param name="status">Valve status OPEN / CLOSE / EXIT</param></span>
  public void StatusValve(Guid wfGuid, string status)
  {
     if (EventValveStatus != null)
     {
        ExternalValveEventArgs e = new ExternalValveEventArgs(wfGuid);
        e.Status = status;
        EventValveStatus(this, e); //Raise the event
  }

}

#endregion

#region Host -> WF
  /// <span class="code-SummaryComment"><summary></span>
  /// Use to pass the valve operation to workflow
  /// <span class="code-SummaryComment"></summary></span>
  public event EventHandler<ExternalDataEventArgs> CloseValve;

  /// <span class="code-SummaryComment"><summary></span>
  /// Use to pass the valve operation to workflow
  /// <span class="code-SummaryComment"></summary></span>
  public event EventHandler<ExternalDataEventArgs> Exit;

  /// <span class="code-SummaryComment"><summary></span>
  /// Use to pass the valve operation to workflow
  /// <span class="code-SummaryComment"></summary></span>
  public event EventHandler<ExternalDataEventArgs> OpenValve;

#endregion

#region Auxiliar procedures

  /// <span class="code-SummaryComment"><summary></span>
  /// Raise the event Exit
  /// <span class="code-SummaryComment"></summary></span>
  /// <span class="code-SummaryComment"><param name="instanceId">workflow instance</param></span>

  public void RaiseExit(Guid instanceId)
  {

     if (Exit != null)
     {
        ExternalDataEventArgs e = new ExternalDataEventArgs(instanceId);
        e.WaitForIdle = true;
        Exit(null, e);
     }
  }

  /// <span class="code-SummaryComment"><summary></span>
  /// Raise the event Open
  /// <span class="code-SummaryComment"></summary></span>
  /// <span class="code-SummaryComment"><param name="instanceId">workflow instance</param></span>
  public void RaiseOpen(Guid instanceId)
  {
    if (OpenValve != null)
    {
       ExternalDataEventArgs e = new ExternalDataEventArgs(instanceId);
      OpenValve(null, e);
    }
  }
  /// <span class="code-SummaryComment"><summary></span>
  /// Raise the event Close
  /// <span class="code-SummaryComment"></summary></span>
  /// <span class="code-SummaryComment"><param name="instanceId">workflow instance</param></span>
  public void RaiseClose(Guid instanceId)
  {
    if (CloseValve != null)
    {
       ExternalDataEventArgs e = new ExternalDataEventArgs(instanceId);
       CloseValve(null, e);
    }
  }
#endregion
}//end CommunicationValve

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 
{

  /// <span class="code-SummaryComment"><summary></span>
  /// Single onject of the communicationValve class.
  /// <span class="code-SummaryComment"></summary></span>
  private static CommunicationValve Comvalve = null;

  /// <span class="code-SummaryComment"><summary></span>
  /// Reference to the workflow runtime.
  /// <span class="code-SummaryComment"></summary></span>
  private WorkflowRuntime runtime = null;

  /// <span class="code-SummaryComment"><summary></span>
  /// Return the CommunicationValve instance.
  /// <span class="code-SummaryComment"></summary></span>
  public CommunicationValve Valve
  {
    get
      {
        return Comvalve;
      }
  }

  /// <span class="code-SummaryComment"><summary>Constructor Communication manager</summary></span>
  /// <span class="code-SummaryComment"><param name="wfRuntime">Runtime instance</param></span>
  public CommunicationManager(WorkflowRuntime wfRuntime)
  {
    runtime = wfRuntime;
    if (Comvalve == null)
    {
      Comvalve = new CommunicationValve();
    }
  }

  /// <span class="code-SummaryComment"><summary></span>
  /// Procedure to register the communication service.
  /// <span class="code-SummaryComment"></summary></span>
  public void RegisterCommunicationService()
  {
    //Declare a ExternalDataExchangeService class
    ExternalDataExchangeService dataservice = new
    ExternalDataExchangeService();
    //Add to workflow runtime
    runtime.AddService(dataservice);
    //Add to the ExternalDataService
    dataservice.AddService(Comvalve);
  }
}//end CommunicationManager

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:

/// <span class="code-SummaryComment"><summary></span>
/// Constructor
/// <span class="code-SummaryComment"></summary></span>
public Valve()
{
  InitializeComponent();
  lName.Text = "-";
}

/// <span class="code-SummaryComment"><summary></span>
/// Initialization of activities
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="name">Decorative name for the</span>
/// valve<span class="code-SummaryComment"></param></span>
/// <span class="code-SummaryComment"><param name="wr">runtime reference</param></span>
/// <span class="code-SummaryComment"><param name="cm">communication manager reference</param></span>

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:

/// <span class="code-SummaryComment"><summary></span>
/// Send order to Workflow
/// <span class="code-SummaryComment"></summary></span>
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();
  // initialize form...
  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 

License

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

About the Author

freedeveloper
Software Developer (Senior) G-SoftSolutions
United States United States
Jose A. Garcia Guirado, Electronic Engineer, graduated in Havana/Cuba 1982, MCTS, MCSD.NET, MCAD.NET, MCSE. Worked in the Institute for Cybernetics and Mathematics of Academy of Science of Cuba for 8 years; since 1995 working as free software architect, developer and advisor, first in Argentina and from 2003 to 2010, in Germany as External consultant in DWS Luxemburg, AIXTRON AG and Shell Deutschland GmbH and from 2010 to 2012 in in Mexico in Twenty Century Fox, and Mexico Stock Exchange (BMV). Actually as freelance in Florida, USA.
Follow on   Google+

Comments and Discussions

 
GeneralThanks for some good articles PinmemberCafechess10-Apr-09 8:15 
GeneralRe: Thanks for some good articles Pinmemberfreedeveloper10-Apr-09 23:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 4 Oct 2008
Article Copyright 2008 by freedeveloper
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid