WWF系列之----关于Host与WorkflowInstance之间的通讯

关于HostWorkflowInstance之间的通讯

工作流是将程序(实际上就是业务)流程独立出来,建立健壮的可伸缩的交互式的流程管理。其基本任务是保证每一个步骤必须严格地执行且仅可以执行一遍。

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>对象可以传参数给WorkflowRuntimeCreateWorkflow方法,这种方法可以在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

WorkflowRuntimeWorkflowCompleted事件里面有一个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.csICommService.Invokes.cs两个文件,将其拷贝到你的工作流项目根目录下并包含在项目中。

添加对ClassLibrary1.dll的引用;

重新编译一下你的工作流Host项目(最好在添加这两个类之前先编译一次保证没有其他错误),这时你会在工具箱中看到两个新控件,CallTheHostNotigyTheWorkflow如图:

接下来我们要画一个工作流,例子如下:

其中使用了我们用wca工具生成的CalltheHostNotigyTheWorkflow两个控件。

选择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);

 

如此便实现了HostWF之间的实时通讯。

 

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()方法。

posted @ 2009-09-10 17:46  小y  阅读(2483)  评论(4编辑  收藏  举报