关于Host与WorkflowInstance之间的通讯
工作流是将程序(实际上就是业务)流程独立出来,建立健壮的可伸缩的交互式的流程管理。其基本任务是保证每一个步骤必须严格地执行且仅可以执行一遍。
WF和其宿主(Host)之间必须能够进行通讯,不然就失去了“交互性”,这里介绍三种方法。
1. 使用参数
如在workflow1中有定义两个属性:
private string inputParam; private bool outputParam; public string InputParam { set { inputParam = value; } } public bool OutputParam { get { return outputParam; } } |
如何从Host传数据到WF:
使用Dictionary<string, object>对象可以传参数给WorkflowRuntime的CreateWorkflow方法,这种方法可以在WF初始化的时候,给其公开的属性赋值。
我们来看如何给workflow1.InputParam属性赋值:
Dictionary<string, object> myParams = new Dictionary<string, object>(); myParams.Add("InputParam", "tuyile006.cnblogs.com"); WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication2.Workflow1), myParams); instance.Start(); |
需要注意的是Dictionary集合中的Key名称必须要跟WF中公共属性的名称一致。
如何从WF中传数据到Host:
在WorkflowRuntime的WorkflowCompleted事件里面有一个WorkflowCompletedEventArgs参数,可以通过这个参数的OutputParameters属性获取WF中的公共属性的值。
我们来看如何获得workflow1.OutputParam的值:
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { if ((bool)(e.OutputParameters["OutputParam"])) { Console.WriteLine("OutputParam=true;"); } else { Console.WriteLine("OutputParam=false;"); } waitHandle.Set(); }; |
同前面的一样,OutputParameters集合中的键值名称必须是WF的公共属性名称。
2. 定义通信服务接口和类
这种方法是比较重要的,因为前面所讲传参数的方法只能在WF初始化的时候通信,而现在要介绍的方法主要用于与WF实时交互,也就是在WF运行过程中进行通信。
先建一个类库项目,添加对
System.Workflow.Runtime;
System.Workflow.Activities;
System.Workflow.ComponentModel;
的引用,如图:
修改Class1.cs的代码为:
using System; using System.Collections.Generic; using System.Workflow.Runtime; using System.Workflow.Activities; using System.Workflow.ComponentModel; using System.Text; namespace ClassLibrary1 { [ExternalDataExchange] public interface ICommService { //用于从WF中触发Host中的方法 void CallTheHost(string param); //用于从Host中触发WF中的事件,这里的ExternalDataEventArgs可以自定义 event EventHandler<ExternalDataEventArgs> NotigyTheWorkflow; } } |
其中的CallTheHost方法契约用于从WF中调用Host的方法,而事件则正好相反。接口必须使用ExternalDataExchange属性声明,这样WF架构才会找到本接口并生成Activity控件。
现在我们来增强一下实用性,派生ExternalDataEventArgs类来自定义自己的数据,新建如下类:
using System; using System.Collections.Generic; using System.Workflow.Runtime; using System.Workflow.Activities; using System.Workflow.ComponentModel; using System.Text; namespace ClassLibrary1 { [Serializable] public class MyEventArgs : ExternalDataEventArgs { private string msg; public string Msg { get { return msg; } } public MyEventArgs(Guid instanceid, string pmsg) : base(instanceid) { msg = pmsg; } } } |
建好之后IcommService接口中的NotigyTheWorkflow事件的参数可以改成MyEventArgs:
event EventHandler<MyEventArgs> NotigyTheWorkflow; |
这里需要注意两点:MyEventArgs必须有Serializable属性声明,因为工作流与宿主的通讯数据是使用串行化数据进行的;另外就是MyEventArgs构造函数中的instanceid参数,这个也是必须的,因为ExternalDataEventArgs的构造函数中当前工作流实例的ID是必须的参数。
下面介绍如何使用本通讯接口。
将刚刚做好的类库编译成ClassLibrary1.dll文件;
打开VS2008命令提示窗口,输入如下指令:
Wca.exe /l:cs /o:c:\ /n:MyComm yourdllpath |
其中/l:cs参数表示生成C#程序,/o:c:\表示输出到c盘根路径,/n:MyComm 表示修改命名空间为MyComm。
之后到输出盘(本例子为c:)会找到ICommService.Sinks.cs和ICommService.Invokes.cs两个文件,将其拷贝到你的工作流项目根目录下并包含在项目中。
添加对ClassLibrary1.dll的引用;
重新编译一下你的工作流Host项目(最好在添加这两个类之前先编译一次保证没有其他错误),这时你会在工具箱中看到两个新控件,CallTheHost和NotigyTheWorkflow如图:
接下来我们要画一个工作流,例子如下:
其中使用了我们用wca工具生成的CalltheHost和NotigyTheWorkflow两个控件。
选择callTheHost控件我们可以看见属性面板里面有一个param的属性,这正是在IcommService接口中定义的参数名称,点击“…”可以弹出属性绑定对话框,选择“绑定到新成员”—“创建属性”输入“MyParam”如下设置:
后台代码自动添加了如下属性:
public static DependencyProperty MyParamProperty = DependencyProperty.Register("MyParam", typeof(System.String), typeof(WorkflowConsoleApplication2.Workflow1)); [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)] [BrowsableAttribute(true)] [CategoryAttribute("杂项")] public String MyParam { get { return ((string)(base.GetValue(WorkflowConsoleApplication2.Workflow1.MyParamProperty))); } set { base.SetValue(WorkflowConsoleApplication2.Workflow1.MyParamProperty, value); } } |
当流程执行到CallTheHost1控件时,就会调用Host中的CallTheHost方法的实现了,工作流中只需在此之前设置好“MyParam”的值即可,如在mycheck1控件中设置:
MyParam = "我来自WorkFlowID="+this.WorkflowInstanceId.ToString(); |
而notigyTheWorkflow2控件的使用与CallTheHost1控件相似,在其属性中有一个“invoke”属性,这里输入一个方法名称后回车,如“WaitForHost”,程序会自动添加WaitForHost方法到代码中。notigyTheWorkflow2接口的参数MyEventArgs可以通过设置Msg属性来传值,操作与上面设置MyParam属性相同。
上面已经在工作流中安置好了通讯接口,现在在Host程序中实现该通讯接口并调用;
在工作流的Host程序中添加对ClassLibrary1.dll的引用,添加一个服务类,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using ClassLibrary1; namespace WorkflowConsoleApplication2 { public class CommService:ICommService { //用于从WF中触发Host中的方法 public void CallTheHost(string param) { Console.WriteLine("收到WF的消息:"+param); } //用于从Host中触发WF中的事件,这里的ExternalDataEventArgs可以自定义 public event EventHandler<MyEventArgs> NotigyTheWorkflow; public void FireTheNotifyMethod(MyEventArgs arg) { NotigyTheWorkflow(null, arg); } } } |
然后我们在Host程序调用此服务:
ExternalDataExchangeService dataservice = new ExternalDataExchangeService(); workflowRuntime.AddService(dataservice); CommService service = new CommService(); dataservice.AddService(service); ClassLibrary1.MyEventArgs arg = new ClassLibrary1.MyEventArgs(instance.InstanceId, "我来自Host。"); service.FireTheNotifyMethod(arg); |
如此便实现了Host和WF之间的实时通讯。
3. 使用WorkflowQueue来进行通讯
第2种方法中有一个NotigyTheWorkflow控件,它负责从Host里面触发事件并调用WF里面的一个方法,传递的数据放在EventArgs参数里面。现在介绍用WorkflowQueue对象来实现同样的功能。
我们自定义一个活动,代码如下:
using System; using System.Collections.Generic; using System.Workflow.Runtime; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Design; using System.ComponentModel; using System.Text; namespace WorkflowConsoleApplication2 { [ToolboxItem(typeof(ActivityToolboxItem))] [Description("用于读取数据的自定义活动")] public class MyRead:Activity { private string text; [Browsable(false)] public string Text { set { text = value; } get { return text; } } protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) { //获取WF队列服务 WorkflowQueuingService qService = executionContext.GetService<WorkflowQueuingService>(); //创建WF队列,第一个参数是队列的名称 WorkflowQueue queue = qService.CreateWorkflowQueue(this.Name, true); //当收到外部数据后触发的事件 queue.QueueItemAvailable += new EventHandler<QueueEventArgs>(queue_QueueItemAvailable); //重要!设置当前活动状态为“执行中” return ActivityExecutionStatus.Executing; } void queue_QueueItemAvailable(object sender, QueueEventArgs e) { ActivityExecutionContext context = sender as ActivityExecutionContext; WorkflowQueuingService qService = context.GetService<WorkflowQueuingService>(); WorkflowQueue queue = qService.GetWorkflowQueue(this.Name); //获取WF队列中的数据 text = (string)queue.Dequeue(); //删除WF队列,参数为队列名称 qService.DeleteWorkflowQueue(this.Name); //重要!此方法通知运行时本活动可以转移到closed状态 context.CloseActivity(); } } } |
介绍一下自定义活动的基本知识:只需要继承Activity或者其子类即可自定义活动控件,主要需要实现的方法是Execute,它有一个参数,可以获取当前运行的上下文,它需要返回一个ActivityExecutionStatus类型以便指示下一步怎么走,如果返回ActivityExecutionStatus.Executing,则表示本活动尚未结束不能转移到下一步。正常是返回ActivityExecutionStatus.Closed,表示当前活动结束,可以执行下一步活动。
在这段示例里面,我们创建了WorkflowQueue对象,并添加一个事件绑定,以便当WorkflowQueue收到消息的时候执行一个方法,此方法里面需要调用context.CloseActivity()将本活动状态标识为ActivityExecutionStatus.Closed。这样WorkflowRuntime就会将活动从队列中卸载并执行下一个活动。
然后看如何在Host里面传一个参数进来:
string password = Console.ReadLine(); instance.EnqueueItem("myRead1", password, null, null); |
只要在WorkflowInstance身上调用EnqueueItem方法就可以给指定队列传数据了,EnqueueItem方法第一个参数是队列的名字,必须跟CreateWorkflowQueue里面初始化的名字一样。这里可以多次调用EnqueueItem方法,以便传递多个数据进WF。
从WF里面往Host里面传参数,可以在WF里面使用WorkflowQueue.Enqueue()方法;在Host里面使用instance.GetWorkflowQueueData()方法。