工作流编程循序渐进(9:使用本地服务在宿主和工作流之间通信)

作者  朱先忠

[摘要]

      在本篇中,首先详细分析本地服务有关概念,探讨本地服务在工作流运行时、工作流实例及工作流宿主间的地位及作用。然后,通过一个简单的例子来说明使用本地服务在宿主和工作流之间的具体通信方法。

一、简介

      WWF中的服务可分为核心服务和本地服务。核心服务由WWF定义,而本地服务(也称为“数据交换服务”)则是开发人员自定义的。本地服务可以是任何想在WWF中实现的服务,通常的用处是:使用本地服务在工作流实例与宿主之间进行通信。比如,数据最初从宿主应用程序的某个运行结果中传入工作流,工作流实例在处理完数据后返还给宿主。
      在本篇中,我想首先详细分析本地服务有关概念,探讨本地服务在工作流运行时、工作流实例及工作流宿主间的地位及作用。然后,通过一个简单的例子来说明使用本地服务在宿主和工作流之间的具体通信方法。

二、与本地服务相关的两个重要活动-HandleExternalEventActivityCallExternalMethodActivity

首先,让我们看一下MSDN中有关解释。

Windows Workflow Foundation支持工作流的承载环境中的本地通信和使用 Web 服务在工作流之间进行的通信。
有关在工作流中使用 Web 服务的实例,将在后面文章中陆续给出。
WWF工作流通信服务向工作流开发人员公开了一个用户自定义服务类,通过在此服务类中定义一系列的方法和事件处理程序,以达到简化工作流与宿主间数据通信的建模目的。
      实际开发中,实现本地服务的目的要根据当前应用程序的需求来定,比如需要查询或设置应用程序的状态,从数据库获取或更新数据,或者是与其他不属于工作流的对象组件进行通信,等等。当将功能实现为本地服务后,本地服务对多个工作流实例都可以有效。
下图演示本地(通信)服务如何与其主机(也称“宿主”,即Host)应用程序之间进行通信(图片改编自MSDN)。

      从上图中可以看出,工作流实例上的定义的两个活动HandleExternalEventActivityCallExternalMethodActivity活动(也就是说,典型情况下,在创建我们的工作流时需要加入这两个“特殊”的活动)的任务是:与在自定义接口(请看本文后面的介绍)中声明并在自定义本地服务中实现的事件和方法交互。
      具体来看,[1]HandleExternalEventActivity 活动响应由主机应用程序引发且由本地服务实现的特定事件。 [2]CallExternalMethodActivity负责调用本地服务中定义的方法。
      有关两个活动HandleExternalEventActivityCallExternalMethodActivity活动,请继续阅读下面来自于MSDN的重要解释。
来自MSDN:
[1]HandleExternalEventActivity 活动与  CallExternalMethodActivity 活动结合使用,可输入或输出与本地服务的通信。 可以直接对一般通信使用这些活动。 或者,可以创建 HandleExternalEventActivity 和 CallExternalMethodActivity 类的子类,以创建严格绑定到某个接口上的特定事件和方法的活动,并具有  ExternalDataExchangeAttribute 属性。
     
      HandleExternalEventActivity 基类阻止工作流,直到通过 WorkflowRuntime 注册的相应本地服务引发由 InterfaceType 和 EventName 属性指定的事件。 引发该事件后,或者如果该事件在活动开始执行前引发,则将传入数据分配给在 ParameterBindings 集合中定义的绑定位置。
 
       [2]CallExternalMethodActivity 活动和 HandleExternalEventActivity 活动可用于与本地服务进行输入和输出通信。 您可以直接使用这些活动进行一般通信,也可以创建 CallExternalMethodActivity 和 HandleExternalEventActivity类的子类以创建一些活动,这些活动严格绑定到具有 ExternalDataExchangeAttribute属性的接口上的特定事件和方法。
 
      CallExternalMethodActivity 基类调用由向 WorkflowRuntime 注册的相应本地服务的 InterfaceType 和 MethodName 属性指定的方法。 此调用是使用从绑定位置的 ParameterBindings 集合中收集的参数以同步方式执行的。 如果该方法具有返回值,则会在活动执行完毕前将这些值设置为绑定位置。

三、局部步骤归纳

    为了让工作流运行时与本地服务进行交互,需要完成如下步骤:
    (1)使用标准的C#接口定义一个服务契约(你必须先定义符合这一特征的一个接口),在该接口中定义工作流实例中可以使用的方法和事件。
    (2)在接口前面修饰以ExternalDataExchangeAttribute特性,使该接口能被识别为一个本地服务接口。
接口定义举例(来自MSDN):
[ExternalDataExchange]
public interface ICommunicationService
{

void HelloHost(string message);
event EventHandler<ExternalDataEventArgs> HelloWorkflow;

}

    (3)编写一个实现了第2步定义的接口的标准C#类(即自定义本地服务类)。
自定义本地服务类举例(来自MSDN)
public class CommunicationService : ICommunicationService
{

public event EventHandler<ExternalDataEventArgs> HelloWorkflow;

public void HelloHost(string message)
{

Console.WriteLine("This is the message: {0}", message);
//引发HelloWorkflow事件
HelloWorkflow(null, new

ExternalDataEventArgs(WorkflowEnvironment.WorkflowInstanceId));

}

}

    (4)创建一个系统提供的本地服务类ExternalDataExchangeService的实例,再创建上面自定义服务类的实例,把本地服务类ExternalDataExchangeService的实例添加到工作流运行时引擎中,然后把自定义服务类的实例加载到本地服务类ExternalDataExchangeService的实例中。这个过程需要在工作流运行时引擎初始化期间(在正式启动工作流之前)进行创建。
在启动工作流之前需要完成的任务示例:
ExternalDataExchangeService externalService = new ExternalDataExchangeService();
workflowRuntime.AddService(externalService);
externalService.AddService(new CommunicationService());
      注意:所有本地服务必须经由接口类型进行唯一的识别。也就是说,每个服务接口仅允许有一个实例。但是可以在工作流运行时引擎中注册多个本地服务,只要每个服务实现了不同的接口。
注意:因为每个服务只能具有单个实例,因而有可能有多个工作流实例同时调用服务中的方法和事件。因此在设计时需要考虑线程安全的数据访问问题。
    当本地服务向工作流运行时引擎注册后,便可以被任何工作流引擎使用。有两种方法可以调用本地服务中的方法。
  [1]使用GetService()方法获取服务的引用,并调用定义在服务接口中的方法。
  [2]使用CallExtemalMethodActivity活动以声明性方式调用一个方法,使之作为工作流的一个步骤。当使用工作流时,不需要工作流或者是活动的代码。

四、案例分析

(一)创建控制台顺序工作流示例框架

说明:本文创建的LocalServiceDemo示例演示了如何在一个状态机工作流内部调用另外的一个工作SubWorkflow,并且定义了本地服务接口实现,使用HandleExternalEvent活动调用外部事件以等待被调用的工作流实例执行完成。该活动需要等待一个事件的触发才能够继续工作流的运行,而在Program.cs中,设置了只有当指定非宿主工作流执行完毕后,才触发事件。因此这实现了一种等待被调用工作流执行完成才继续执行的效果。
请遵循如下步骤创建一个控制台状态机工作流示例程序:
1. 启动VS2008,单击菜单”文件“|”新建“|”项目“,选择“Sequential Workflow Console Application”模板创建一个名字为LocalServiceDemo的控制台状态机工作流示例程序。
2.之后,系统自动打开工作流设计器界面。
3. 从工具箱中拖动几个活动到工作流设计器中,得到如图所示的情形。
在上图中,我们依次把三个活动:Code,CallExternalMethod和Listen拖动到工作流设计器中,其他没有作任何更改,具体的修改操作将在后面步骤中给出。

(二)创建自定义事件参数类

创建自定义事件参数类(用于在宿主与工作流间传递参数之用):

class CustomServiceEventArgs:ExternalDataEventArgs
{
    private string name;
    //这个公共属性用于在宿主与工作流间传递参数之用,可以是复杂的类,也可以是简单的字符串
    public string Name
    {
        get { return this.name; }
    }

public CustomServiceEventArgs(Guid instanceID, string name)
        : base(instanceID)
    {
        this.name = name;
    }
}

(三)定义本地服务接口

本地服务接口定义:
[ExternalDataExchange]
internal interface ICustService
{
    event EventHandler<CustomServiceEventArgs> Approved;
    event EventHandler<CustomServiceEventArgs> Rejected;

void CreateBallot(string name);//产生一次新的投票

}

(四)定义本地服务类

IVotingService接口的类。该类将实现CreateBallot(),并触发这两个事件,代码如下所示。

定义本地服务类:
internal class CustServiceImpl : ICustService
{

#region ICustService 成员

public event EventHandler<CustomServiceEventArgs> Approved;

public event EventHandler<CustomServiceEventArgs> Rejected;

public void CreateBallot(string name)
    {
        Console.WriteLine("现在为{0}投票。", name);
        ShowDlg(new CustomServiceEventArgs(WorkflowEnvironment.WorkflowInstanceId, name));
    }
    #endregion

public void ShowDlg(CustomServiceEventArgs args)
    {
        DialogResult result;
        string name = args.Name;

result = MessageBox.Show(string.Format("是否同意,{0}", name),
            string.Format("当前为{0}投票", name), MessageBoxButtons.YesNo);

if (DialogResult.Yes == result)
        {
            EventHandler<CustomServiceEventArgs> approved = this.Approved;
            if (approved != null)
                approved(null, args);
        }
        else
        {
            EventHandler<CustomServiceEventArgs> rejected = this.Rejected;
            if (rejected != null)
                rejected(null, args);
        }
    }
}

[注意]为了使用MessageBox和DialogResult,需要在项目上右击“引用”菜单,添加对System.Windows.Forms的引用。

(五)工作流编程

1. 添加CallExtemalMethodActivity活动
进入工作流设计视图,首先添加一个CallExtemalMethodActivity。该活动的InterfaceType被设置为ICustService,指定方法名为CreateBallot,可以在属性窗口中使用弹出式窗口选择InterfaceType,如图所示。此外,MethodName属性列出可用的方法名称供应用进行选择,选择接口中声明的方法“CreateBallot”。
[注意]后面还要进一步这个CallExtemalMethodActivity修改活动。
2. 添加ListenActivity活动
然后,在CallExtemalMethodActivity的下面添加一个ListenActivity。有关ListenActivity活动,我们后面还会专门撰文探讨。在此只需了解,ListenActivity是一个组合活动,该活动将在活动继续前使工作流等待多个可能事件中的任何一个事件发生。
然后,在ListenActivity活动中,添加2个HandleExternalEventActivity活动,分别使之负责监听接口ICustService中的Approved和Rejected两个事件。
当然,HandleExternalEventActivity也需要指定InterfaceType属性,如下图所示。
然后,分别指定2个HandleExternalEventActivity活动的EventName属性为ICustService中定义的Approved和Rejected两个事件,如下面二图所示。
到目前为止,工作流设计视图如下图所示。
3. 在工作流类中定义公共属性
目前为止,我们必须强调:在工作流类中需要定义了一个公共属性,以便于接收从宿主传入的投票人信息,代码如下所示。
///定义了一个公共根属性,以便于接收从宿主传入的投票人信息。
//定义一个表示投票人姓名信息的属性
private string votername;
public string VoterName
{
    set { this.votername = value; }
    get { return this.votername; }
}
4. 进一步修改CallExternalMethodActivity活动
要想使外部方法获取到工作流实例中传入的参数信息,需要进一步修改CallExternalMethodActivity活动的属性值,单击CallExternalMethodActivity活动的属性对话框中的name属性(而不是(Name)属性!!!),弹出如下图所示的对话框:
从图中选择前面定义的属性VoterName,单击“确定”按钮,得到如下图所示的属性对话框。
最后,我们来看一下控制台宿主的编程内容。

(六)控制台宿主编程

最后,需要在工作流运行时引擎中注册本地服务,并且为工作流实例传递参数。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;

using System.Workflow.Activities;//ExternalDataExehangeService
namespace LocalServiceDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {

//加载本地服务
                ExternalDataExchangeService dataService = new ExternalDataExchangeService();
                workflowRuntime.AddService(dataService);

//将自定义的本地通信服务加载到本地服务中
                CustServiceImpl ls = new CustServiceImpl();
                dataService.AddService(ls);

AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
                {
                    waitHandle.Set();
                };
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

//向工作流实例传递参数,注意格式
                Dictionary<string, object> paras = new Dictionary<string, object>();
                paras.Add("VoterName", "爱因斯坦");

WorkflowInstance instance = workflowRuntime.CreateWorkflow(
                    typeof(LocalServiceDemo.Workflow1),paras );
                instance.Start();

waitHandle.WaitOne();

Console.Read();
            }
        }
    }
}

注意,在上面控制台程序中代码没有特殊的内容,开始时的加载本地服务编码在以前的文章中已经作过介绍。接下来,后面的代码基本是系统自动生成的。
最后,请注意向工作流实例传递参数的格式。
总体来看,上面的代码关键之处还在于前面的围绕本地服务的编程,以及工作流中相应活动的关联设置方面。而本篇编程逻辑的根本在于深入理解工作流运行时与本地服务进行交互的原理,以及理解工作流编程中的HandleExternalEventActivity和 CallExternalMethodActivity两个活动的重要作用。

(七)观察运行结果

按F5运行控制台程序,一般顺利的话,将得到如下图所示运行时快照。

单击“是”按钮后,得到如下结果:

最后一句:本篇是实战环境下比较重要的一篇,必须理解透彻,烂熟于心。

五、部分参考资料

1.《如何:发布符合 .NET Framework 准则的事件(C# 编程指南)》(http://msdn.microsoft.com/zh-cn/library/w369ty8x.aspx)。

工作流编程循序渐进(9:使用本地服务在宿主和工作流之间通信)相关推荐

  1. 工作流编程循序渐进(3:While活动)

    工作流编程循序渐进(3:While活动) 作者  朱先忠 一.引言 本文中,我们来学习另一个简单活动--While活动.While活动将会持续循环的执行,直至指定判断为假. 下面,我们来构建一个简单的 ...

  2. Qt:Windows编程—Qt实现本地服务管理

    Qt实现本地服务管理 前言 本节将使用Windows的几个API,使用qt实现 本地服务 简单管理.这里简单介绍下服务. 几乎每一种操作系统都有一种在系统启动时启动的进程机制,这种机制不会依赖于用户的 ...

  3. WF(9):本地服务之事件处理

    一:先来介绍两个活动 EventDrivenActivity和 ListenActivity. EventDrivenActivity是一个等侍事件触发的容器,EventDrivenActivity第 ...

  4. 坚持学习WF(8):本地服务之调用外部方法

    WF提供了一组核心服务,例如在SQL 数据库中存储工作流实例的执行详细信息的持久性服务,计划服务,事务服务和跟踪服务.除了这些WF也提供了另外一种服务,叫做Local Service也可以叫做Data ...

  5. java linux 管理系统_用Java开发一个本地服务管理软件

    使用Java开发一个本机服务管理程序,能够控制本机Tomcat.Apache服务的开启和关闭,图形界面控制.用户可以自己扩展其他服务,用来学习图形界面编程.多线程.事件响应等都不错. 一.最终界面 先 ...

  6. centos7 搭建本地git_本地服务调用K8S环境中的SpringCloud微服务实战

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:原创文章分类汇总及配套源码,涉及Java.Docker.K8S.Devops等 下图是典型的微 ...

  7. windows的服务中的登录身份本地系统账户、本地服务账户和网络服务账户修改

    以一个redis服务为例: 一个redis注册服务后一般是网络服务账户,但是当系统不存在网络服务账户时,就会导致redis服务无法正常启动.接下来修改redis服务的登录身份. cmd下输入如下命令: ...

  8. Android Service学习之本地服务

    Service是在一段不定的时间运行在后台,不和用户交互应用组件.每个Service必须在manifest中 通过<service>来声明.可以通过contect.startservice ...

  9. 增加 oracle服务名,oracle本地服务名配置说明

    本地oracle客户端连接远程oracle数据库服务器配置说明如下: 1.找到本地oracle客户端安装目录,如:%path%\Oracle 2.再找配置文件tnsnames.ora,其目录为 %pa ...

最新文章

  1. ORACLE设置用户密码不过期
  2. wave格式分析,wave音频文件格式分析配程序
  3. quartz源码解析--转
  4. 深入分析 iBATIS 框架之系统架构与映射原理--转载
  5. python基础(part8)--容器类型之元组和字典
  6. LeetCode 849. 到最近的人的最大距离
  7. 谷歌离开游览器不触发_谷歌游览器
  8. 【Flink】Flink Not all required tasks are currently running
  9. 华为全球分析师大会:HMS Core全球开发者应用集成的数量加速增长,打造全场景智慧体验...
  10. [HNOI2015]开店(树剖+主席树+标记永久化)
  11. [云计算]VXLAN的网关划分
  12. C语言科学计数法介绍和示例
  13. Java工程师的职业规划,上个月成功拿到阿里P7offer
  14. html怎么改变网页整体的大小,html设置浏览器大小
  15. 研华IO板卡驱动安装与PICE-1753(PCIE-1751)接线说明
  16. 数据库-进阶6-连接查询
  17. 拯救BUG 10五笔输入法Shift键切换中英文问题
  18. 错过这次,再等一年!视频云CDN全线折扣Hi购启动...
  19. 新浪博客和微博已经成为黄色网站的…
  20. 简单的数据结构介绍(栈、队列、数组、链表、红黑树)

热门文章

  1. Java项目:在线商城系统(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)
  2. python创建mysql数据库_python 怎么创建create mysql的数据库
  3. 基于SSM实现二手交易系统
  4. CocoaPods私有库搭建的记录
  5. H5使用百度地图SDK获取用户当前位置并且标记显示在地图
  6. js 微信小程序日期 时间转时间戳
  7. 【微信小程序之画布】四:手指触摸绘波浪线
  8. ScratchCardView:刮刮卡视图组件
  9. Linux Mint 19 安装Gnome Boxes 新建失败
  10. pfSense book之2.4安装指南