65.9K
CodeProject 正在变化。 阅读更多。
Home

宿主和工作流:通信的两个世界:第三部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (8投票s)

2008年10月3日

CPOL

11分钟阅读

viewsIcon

54653

downloadIcon

401

第三部分:通过 HandleExternalEvent 活动实现主机 -> 工作流的相互通信。

引言

这是关于主机工作流通信的五篇文章中的第三篇。

在这一系列文章中,我将展示实现通信的各种可能性,从最简单的情况到更复杂的情况。我并不打算详尽,但我会尝试给出一个普遍性的概述,以便我们对这个主题有一个好的认识。因为我也不喜欢长篇大论的文章,所以我将系列文章分成了以下几部分:

背景

在第二部分中,我们了解了如何在启动工作流时传递信息以及工作流如何将信息返回给主机。但是,您可能会遇到更复杂的情况,即您必须在工作流的中间点而不是初始化阶段将信息发送到工作流,或者您需要更频繁地在工作流的生命周期中发送信息。此外,您还需要能够在工作流流程的特定点发送信息。所有这些要求都可以通过 `HandleExternalEvent` 活动来实现。

使用 `HandleExternalEvent`,您可以停止工作流并等待一个特定的外部事件,当事件接收到时,工作流可以继续执行下一个活动。

`HandleExternalEvent` 在事件中定义的 Event Arguments 参数中获取来自主机的信息。

如果我们比较 `CallExternalMethod` 和 `HandleExternalEvent`,我们可以看到它们都调用在另一个应用程序的另一个线程中定义的软件片段。前者调用一个方法,后者等待一个事件。

正如您所见,每次用户在主机应用程序中单击“添加”按钮时,您都必须向工作流提供命令或数字。显然,您无法在工作流初始化时提供这些信息。

…好的,您可以每次需要计算时运行工作流;提供数字,总和将作为参数返回,但我希望向您展示如何使用工作流来控制流程……。

`HandleExternalEvent` 活动等待定义的外部事件,并且您可以确保数据在正确的时间被接收。数据通过本文第二部分中解释的 `CallExternalMethod` 返回。

为了实现通信,我们需要遵循一个特定的模式,该模式类似于 `CallExternalMethod`,但现在我们需要定义一个外部事件。简而言之,我们需要执行以下步骤:

创建以下接口和类

  1. 一个接口,其中包含 `CallExternalMethod` 活动调用的方法定义以及 `HandleExternalEvent` 活动处理的外部事件。
  2. 一个实现该接口的类。在此类中,您将实现 `CallExternalMethod` 调用的方法。在此方法中,您将添加将信息发送到主机以及 `HandleExternalEvent` 活动要处理的事件的代码。
  3. 您应该创建一个特殊的 Event Arguments 类来将信息发送到工作流。外部事件的参数需要继承自一个特殊的 Event Argument 类:`ExternalDataEventArgs`。这个特殊的事件参数带有一个构造函数,该构造函数需要我们想要通信的工作流实例的 GUID。在我们的例子中,这一点不是很重要,因为我们只有一个工作流实例,但如果我们同时处理不同的工作流,它将发挥重要作用。
  4. 您还应该实现 Event Argument 类来接收来自主机中 `CallExternalMethod` 调用的方法的信息。

在主机应用程序中,执行以下步骤:

  1. 您必须创建 `ExternalDataExchangeService` 类的实例并将其注册到工作流运行时。
  2. 您必须创建 `CommunicationCalculator` 的实例并将其添加到 `ExternalDataExchangeService` 实例(在第 1 点创建)。
  3. 如果您使用事件来驱动工作流 -> 主机的通信,您还必须在主机类中处理通信事件。
  4. 要与工作流通信,您必须引发创建的外部事件并通过创建的 Event arguments 传递信息。

在工作流中:

  1. 您必须插入一个 `CallExternalMethod` 活动。
  2. 在其中,注册通信接口和要调用的方法。
  3. 您必须有一个 `HandleExternalEvent` 活动来处理您想从主机应用程序接收信息的每个事件。在这种情况下,您只需要一个。
  4. 您应该使用声明的通信接口和要调用的事件来配置此 `HandleExternalEvent` 活动。最终,您还可以定义一个在外部事件处理后要调用的事件。
  5. 您应该声明一个公共 `CalculadorEventArguments` 来接收外部事件的信息。

在下面的示例中,我们有更多的代码将在其中解释。这是因为我们不深入解释我们对这些示例的功能逻辑所需要的内容,我们只关注信息的传递。

您可以在下面的图表中看到我们示例的结果类结构。

这里有什么新的?`CalculadorEventArguments`,正如您所见,它在两个线程之间建立了关联,所有这些都通过 `ICommunicationCalculator` 完成。从工作流传递数据的事件在主机中引发,并由 `HandleExternalEvent` 活动接收。

现在,我们将详细解释程序的工作原理。

使用代码

要使用配套代码,请创建一个空的 VS2008 解决方案,并将配套的 ZIP 文件解压缩到创建的项目目录中。在 Visual Studio 中,将解压缩的三个项目添加到解决方案中。构建您的解决方案。

最好的部分是下载随本文附带的代码。您也可以根据自己的想法,创造性地复制粘贴下面段落中解释的通信代码。

示例项目是一个 Windows 项目,它将整数加到一个总计字段中。您可以添加数字、重置它或退出程序。您有三个按钮来启动这些功能。

在此项目中,我们使用一个单独的项目来集中实现主机和工作流之间通信所需的所有类。

A. WFCommunication Interface 项目

创建一个项目来存放您需要用于主机与工作流通信的类。选择一个类项目并命名为 `WFCommunicationInterface`。您也可以在附带的代码中查看该项目。

打开以添加一个文件,并在“新建”中选择“项接口”项,然后将其命名为 `ICommunicationCalculator`。此文件必须包含工作流调用或使用的所有方法和事件。

将以下代码添加到中:

/// <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
}

在这里,您必须定义一个事件来进行主机和工作流之间的通信。`EventHandler` 有一个特殊的 `EventArguments`:`CalculatorEventArguments`。

现在,创建一个新的类文件并添加到项目中。将其命名为 `CalculatorEventArguments` 并创建以下类:

/// <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

该事件为我们需要传递给工作流的信息留出了空间:命令字符串和要添加到总和的整数。

这里有两个要点是 `[serializable]` 属性和类的基类。您必须用 `[serializable]` 属性修饰该类。此事件参数必须在进程间通信的线程之间使用,因此它必须是可序列化的。

第二点是从 `ExternalEventArgs` 继承。这个特殊的事件参数带有一个构造函数,该构造函数将工作流实例的 GUID 或标识符作为参数。您必须使用此标识符来确保信息能够发送到正确的工作流实例。框架使用此参数将事件路由到正确的工作流实例。您知道可以同时运行同一个工作流的多个实例。我们将在另一篇文章中详细介绍这一点。

您还应该创建一个事件,在执行外部方法 `SendCommandAndDataToWp` 时发送到主机。这是一个正常的事件,正如您在以下代码中看到的:

/// <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;}
    }
}

如您所见,我们使用一个普通参数将总和从工作流返回给主机。在最后一步,我们需要实现 `ICommunicationCalculator` 接口。在项目中创建一个新文件,创建一个新类来创建文件,并编写以下代码:

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
 }

我们可以分部分分析代码。第一部分是我们在本系列文章第二部分中展示的工作流和主机之间的通信。第二部分是新的,但正如您所见,它非常容易实现。您只需要定义一个用于外部参数的实例。工作流可以自动获取此事件并处理它。

第二个过程是用于引发主机中事件的辅助过程。使用此辅助过程可以封装对 `SendCommandAndDataToWF` 事件的调用。

B. 主机应用程序

正如我们在本系列文章的第二部分中看到的,您必须将我们在 A 节中创建的通信服务注册为 `ExternalDataService`。

代码基本相同,您可以在此处看到它:

#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.....

正如您所见,代码与上一篇文章中的示例相同。

在这里,在每个命令的点击事件中,每个按钮都会触发一个工作流中的命令,我们必须将其传输到工作流。要执行此操作,我们必须引发外部事件。请参见以下按钮 `Click` 事件中的代码:

 #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

我们可以看到,调用 `ReiseEventSendCommandAndDataToWF` 方法很简单,该方法从通信接口调用。此方法会在工作流中引发一个外部事件。下一节将解释如何使用此事件。

C. 工作流应用程序

现在,我们需要配置工作流应用程序。在下面的图中,我们可以看到工作流的实现,用于解决示例任务。

正如您在此处看到的,顺序很简单,我们使用简单的 `IfElse` 活动来选择正确的命令并完成工作。

此外,我们使用 `While` 活动来等待来自主机的命令,然后执行它。当收到 `Exit` 命令时,序列结束。您可以在代码中看到实现的细节。

您可以看到 `HandleExternalEvent` 活动直接位于 `while` 循环的开始之后。`HandleExternalEvent` 活动会停止工作流的流程,直到事件被接收。然后,我们可以确保命令和数据在工作流流程的正确时间被接收。配置 `HandleExternalEvent` 活动很简单。下图显示了如何为此示例进行配置。

正如您所见,您需要声明 `InterfaceType`、事件的名称以及一个用于保存主机事件提供的信息的变量。您只需要声明一个类型为 `CalculadorEventArguments` 的变量,如以下代码所示:

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

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

在这里,您还可以看到 `Invoked` 属性。您可以使用此属性调用一个在事件接收后执行的方法。您可以使用此方法将接收到的参数安排在工作流中的正确位置,或者执行您需要的其他任务。在我们的情况下,我们将接收到的信息传递给两个内部变量。

/// <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;
}

就是这样!

您可以在附带的代码中看到完整的应用程序。

恢复

使用 `HandleExternalEvent` 活动从主机向工作流传递信息的关键步骤是:

创建以下接口和类:

  • 一个接口,其中包含 `HandleExternalEvent` 活动使用的事件定义,并用 `[ExternalDataExchange]` 标头进行修饰。
  • 一个实现该接口的类。在此类中,您将实现 `HandleExternalEvent` 在工作流中使用的事件,并且您还可以实现引发主机应用程序中事件的方法。

在主机应用程序中,执行以下操作:

  • 在工作流运行时注册 `ExternalDataExchangeService` 类的实例。
  • 将通信类的实例添加到 `ExternalDataExchangeService` 实例。
  • 在应用程序中,您可以在可以向工作流发送信息的点调用创建的例程来引发工作流中的外部事件,并将您想传递给工作流的信息作为参数传递。

在工作流中:

  • 将 `HandleExternalEvent` 活动放置在工作流中您想要从主机获取信息的点。
  • 在 `HandleExternalEvent` 活动的属性中,注册通信接口、要使用的事件以及用于保存事件返回信息的变量或属性。

现在,我们有了处理工作流和主机之间通信的基本工具。我们可以解决许多需要在工作流特定点发送信息并将信息返回给主机的问题。但这一切都有些复杂。您必须在程序的某个部分注册服务,并配置 `HandleExternalEvent` 和 `CallExternalMethod` 类。在下一部分中,我们将介绍用于自动生成自定义通信类并更好地组织通信过程的工具和方法。

历史

  • 初版:2008.4.10。
© . All rights reserved.