WF从入门到精通(第八章):调用外部方法及工作流(二)

 

 

 

  创建外部数据服务

  我们现在来到了更加复杂的一节,我们的任务是为外部数据服务创建桥接代码。宿主必须有这些代码,它才能访问到工作流实例试图传递过来的数据。我们将使用工具来为工作流创建活动(这在下一节介绍),但对于宿主这边的通信连接来说,却没有现成的工具。

  在这里,我们将创建一个稍微简化的连接桥版本(这是对于完整的连接桥架构来说)。该版本仅仅支持工作流到宿主的通信。(当我们学到17章时,我们将会创建一个可重用的通用双向连接桥。)我们在此将创建的连接桥被分成了两个部分:一是connector,它实现了我们前面已经开发好了的接口;二是service,除了别的事情外,它有一个职责是激发“data available”事件以及提供一个“read”方法,使用该方法来把数据从工作流中取出。

  提示:该代码应由你而不是WF来提供。我在写本地数据交换服务时提供了该代码,但你要写的代码可以有所不同。唯一要求是本地数据交换服务实现了通信接口并提供一种机制,用于检索需要交换的数据。

  为什么如此复杂?和传统的.NET对象不同,工作流实例在工作流运行时的范围内执行。因此进出工作流实例的事件都由工作流运行时进行代理。工作流运行时必须做这些工作,因为你的宿主应用程序不能把数据发送给已经被持久化或不处在执行状态下的工作流实例。

  回到我们的连接桥上,该连接类包含一个字段,工作流将使用要被传回的数据来填充该字段。对于我们正在创建的本示例应用程序来说,我们不允许并发执行工作流实例,但这仅仅是出于方便。通常情况下,并没有阻止我们执行并发执行的工作流实例,这些我们将在第17章看到。

  当然,每一个工作流实例可能会返回不同的数据,至少它传递的驾驶员会和另一个工作流实例不同。连接类的职责是实现我们开发的在宿主这边接口,以及不间断地保持这些数据。当宿主请求该数据时,连接类根据工作流实例ID来确定应正确返回的DataSet是否已经到达。

该服务类为你处理一些任务。首先,它使用工作流运行时注册该ExternalDataService,以便我们可在宿主和工作流实例间进行通信。它维护一个连接类的单例副本,并把它自己作为服务提供者绑定到该连接类。该服务类也充当了工厂(设计模式)的角色,确保我们有一个且仅有一个连接类(实例)。(假如我们实现了双向的接口,该服务类也会提供一个“write”方法。)我们现在就来创建这些类。

  创建桥接器(bridge connector)类

  1.在Visual Studio中打开MVDataService项目,定位到MVDataCnnector.cs文件,最后打开该文件。

  2.在所定义的名称空间中添加下面的代码:

  

  字段_dataValue用来容纳工作流实例产生的数据。字段_service用来容纳数据服务对象的单一实例。_syncLock对象仅仅用来进行线程的同步。

  3.下面,我们添加一个static属性来访问该服务对象的单一实例。代码如下:

4.我们需要添加一个属性来访问该DataSet,代码如下:

 

 

 

  1.再次在Visual Studio中打开MVDataService项目,定位到WorkflowMVDataService.cs文件,打开该文件准备进行编辑。

 

  2.我们创建好了MVDataConnector类,我们还要把下面的代码复制到WorkflowMVDataService.cs文件中:

 

  3.我们需要具有从类的外部访问_instanceID的能力,因此添加下面的属性:

 

 

  ----------------------------------在 文章摘要-----------

5.在前面一节(“创建桥接器类”)我们创建的连接器对象中保存有我们在第4步中创建的该桥接器对象。我们现在将添加一个静态方法,使用该方法可返回该桥接服务实例。尽管这些现在看来没有太大必要,但稍后会讲讲我们这样做的理由。代码如下:

  6.下面我们将添加我们(私有属性)的构造器和析构器。有了桥接器类后,我们需要确保在桥接器对象和桥接服务对象间不会造成循环的引用。你需要添加下面的代码:

 

  7.尽管我们为桥接服务类添加了一些重要的东西,但还没有把ExternalDataService引入工作流运行时中,我们仍然要添加一些代码,以使工作流运行时具有读取数据并返回给宿主应用程序的能力。桥接器对象实际上是维持该连接状态,但宿主使用这个服务来获得要访问的数据。下面是我们要添加的read方法:

 

  8.要为我们的桥接服务添加的最后的功能块是一个方法,它激发“机动车数据更新(motor vehicle data update)”事件。工作流使用这个方法来为宿主发送一个通知,告知要挑选的数据已经获取完了。代码如下:

  

  完整的桥接服务代码参见清单8-4:

  清单8-4 完整的WorkflowMVDataService.cs源文件 WorkflowMVDataService

CallExternalMethod活动

  你目前在本章看到过的所有代码都已支持一个特殊的WF活动:CallExternalMethod活动。CallExternalMethod活动的作用是可以接受一个接口及该接口所支持的方法,并来调用这个方法。现在的问题是,由谁来实现这个方法?

  你可能会考虑由你的宿主应用程序来完成,但这不太正确。假如你向前看看前面的一节“创建桥接器类”,你实际上在那里会找到这个方法。数据连接器由ExternalDataService捆住实现了该方法。该数据服务依次把该方法的调用转换成一个宿主应用程序能识别的事件。

  直接使用CallExternalMethod活动是允许的,你甚至可以绕开一些服务代码就可把它插入到你的应用程序中。但是绕开这些服务代码对你来说还有一组难题。你的宿主应用程序和你的工作流实例是一对一地联系在一起的。在这里使用数据服务来完成这件事要更适合一些,当你把该数据服务结合起来后,你就能使许多的应用程序实例从许多的工作流实例中进行数据访问,而绕过你创建好的那些数据服务后则不能做到这些。

  对于直接使用CallExternalMethod活动,它通常更适合于创建自定义活动来为你调用外部方法。你可使用一个工具来自定义你的数据交换接口和创建派生自CallExternalMethod的活动,更恰当地对其命名,对它们的属性(接口和方法名称)进行配置。接下来我们就来看看该工具的使用方法。

  创建和使用自定义外部数据服务活动

  回头看看,我们刚刚写下的代码比目前整本书已写过的代码还要多。原因是WF事先不知道我们的工作流将和我们的宿主应用程序之间交换些什么信息。因此在二者之间毫无疑问必须做一些工作,以便对它们之间的差距进行填充。

  但是,WF知悉所有的工作流活动,我们可愉快地使用一个工具来对我们的数据传送接口进行解释,使用ExternalDataExchange特性(attribute)来进行标记,自动地生成WF活动。

我们本章正生成的应用程序把数据从工作流中发送到宿主应用程序中,也就是说数据传送是单向的。我故意这样做是因为,我们只有积累了足够的知识,才能更好地学习并充分理解双向数据传送。我们将使用的Workflow Communication Activity生成器完全有能力创建那些发送和接受宿主数据的活动。对于本应用程序的特殊性,我们将“扔掉”它的输出部分,因为我们不需要它。(其实,将生成的活动是畸形的,因为我们的接口没有指定宿主到工作流的通信,这些我们将保留到第10章讲解。)

  为此,我们就来执行wca.exe并创建一个可用来发送数据到我们的宿主应用程序的活动。

  创建通信活动

  1.为使wca.exe能生成符合我们要求的代码,我们需要确保有一个接口。因此,确保MVDataService项目生成时无错误(如生成时有错误,请纠正所有的错误)并已生成了MVDataService程序集。

  2.点击操作系统的开始按钮,然后点击运行菜单项打开运行对话框。

  3.在打开的组合框控件中输入“cmd”,然后点击确定进入命令提示符窗口。

  4.更改起始目录以便我们能直接访问到“MVDataService”程序集。通常使用的是“cd”命令。

  5.wca.exe文件默认情况下被安装到Program Files目录下的Windows SDK子目录中。(当然,假如你没有使用默认目录进行安装,你在此需要使用你安装Windws SDK的目录。)在命令行提示符下输入下面的命令来执行该工具(包含双引号):

“C:Program FilesMicrosoft SDKsWindowsv6.0ABinWca.exe”  

 MVDataService.dll

  按下回车键,该工具的输出结果和下面的类似:

  WF从入门到精通(第八章):调用外部方法及工作流(二)

6.在命令提示符中键入dir你可看到wca.exe创建的文件。

  WF从入门到精通(第八章):调用外部方法及工作流(二)

  7.IMVDataService.Sinks.cs文件不是必须要的,可忽略它甚至是删除它,因为该文件只是包含了一些指示,没有代码。(在我们的通信接口中没有定义事件。)我们在第十章将再使用这个文件。对于另一个生成的文件:IMVDataService.Invokes.cs文件,是我们要保留的文件。它包含我们能使用的一个新活动的源代码,该活动可把数据从工作流中发送给宿主应用程序。因此,我们将重命名该文件以便更加好用。在命令提示符下输入“ren IMVDataService.Invokes.cs MVDataUpdate.cs”,然后按下回车键重命名该文件。

  8.因为我们刚刚重命名的这个文件是一个工作流活动,因此我们需要把它从当前目录下移动到MVWorkflow目录下以便编译和使用。在命令提示符下输入“move MVDataUpdate.cs ......MVWorkflow”,然后按下回车键。

  9.回到Visual Studio,我们需要把这个新创建的MVDataUpdate.cs文件添加我们的工作流项目中。

  WF从入门到精通(第八章):调用外部方法及工作流(二)

  10.编译并生成MVWorkflow项目,假如出现编译错误的话,修正所有的错误。在你成功编译后,在视图设计器界面模式下打开Workflow1.cs文件将会在Visual Studio的工具箱中看到这个MVDataUpdate活动。

  备注:假如MVDataUpdate因为某些原因没有添加进Visual Studio工具箱中,请关闭该解决方案,然后再重新打开它。

我们现在就准备好了一个活动,我们可使用它来把数据发送到我们的宿主应用程序中。该活动的基类是CallExternalMethod,它的作用是触发对工作流执行环境的外部调用。

  添加并配置该工作流通信活动

  1.在Visual Studio中以视图设计器的模式打开MVWorkflow项目中的Workflow1.cs文件。该工作流预先加载了两个活动:一个是Delay活动,用来模拟处理数据的等待时间;一个是Code活动,它创建并填充一个基于驾驶员姓名的DataSet。

  2.打开Visual Studio工具箱,定位到MVDataUpdate活动。

  WF从入门到精通(第八章):调用外部方法及工作流(二)

  3.把该活动拖拽到工作流视图设计器界面上,放到Code活动的下面使它在Code活动执行后执行。

  WF从入门到精通(第八章):调用外部方法及工作流(二)

  4.我们的工作流在视图设计器上的设计工作这就完成了。现在该写少量代码来进行衔接。因此以代码视图模式打开Workflow1.cs文件。在Workflow1类中找到GenerateMVDData方法。这个方法是codeActivity1所执行的方法,在里面你会看到对GenerateVehicleTable和GenerateViolationTable两个方法的调用,它们创建并填充所要返回的DataSet。(其实,你可用一些外部服务来为驾驶员的信息进行一个调用,但我们在此对这些进行了模拟)。在生成了DataSet后,我们需要添加下面的代码以把DataSet返回给宿主:

// Assign the DataSet we just created as the host data
mvDataUpdate1.mvData = ds;

指定了我们所返回的DataSet后,我们就完成了工作流的开发,并且使用了工具来把该DataSet传给宿主应用程序。但我们的宿主应用程序需要做些什么才能接收到该数据了?

  在宿主应用程序中检索工作流数据

  现在让我们返回到我们的主应用程序中。我们现在要做的是修改应用程序,以使用我们在本章的“创建外部数据访问”这一节中创建的桥接类。

  备注:尽管这是一个简化的例子,但这个应用程序仍然是一个完全意义上的Windows Form应用程序,它演示了怎样处理工作流及其怎样进行多线程操作(比如updating控制的时候)。

  为了让我们的接口可使用工作流返回的数据集,我们需要使用桥接代码中的connector类来对我们的工作流实例进行注册(为了使我们能正确的接收DataSet)。我们也需要勾住(hook)MVDataUpdate事件,以便我们的应用程序知道接收数据的时间。为方便做这些事。我们将为“Retrieve MV Data”按钮的event handler添加一点代码,并为MVDataUpdate添加一个新的event handler。

  备注:假如你不熟悉匿名方法(anonymous methods)的话,现在就是简要学习它的一个好机会!

  为我们的宿主应用程序添加工作流外部数据服务

  1.在Visual Studio解决方案资源管理器中打开Form1.cs文件,并切换到代码视图界面。

  2.找到cmdRetrieve_Click方法。在该响应按钮点击的事件方法中已经存在了初始化工作流实例的代码,但我们还需要在创建工作流实例和启动该实例之间的地方插入一些代码,也就是在调用“_workflowRuntime.CreateWorkflow”的下面添加如下的代码(为让Visual Studio为我们自动生成事件处理程序的代码,请尽量不要使用复制粘贴的方式,应在=号后使用连续两个Tab键):

 

  3.在Form1类中,为Visual Studio刚刚创建的dataService_MVDataUpdate事件处理程序添加下面的事件处理代码,并去掉存在的“not implemented”异常。

就这样!我们的应用程序就完成了,编译并执行该应用程序。当你点击“Retrieve MV Data”按钮时,选中的驾驶员姓名就会被传给工作流实例。当DataSet创建好后,该工作流实例就会激发MVDataUpdate事件。宿主应用程序代码会截获该事件进行数据的接收,然后把它绑定到ListView控件。

  在最后一步我们需注意一个关键的地方,就是在我们调用WorkflowMVDataService的静态方法GetRegisteredWorkflowDataService来检索数据服务包含的DataSet后,我们使用数据服务的Read方法来把该DataSet拉进我们的宿主应用程序执行环境中以便我们进行数据绑定。

  用InvokeWorkflow调用外部工作流

  这儿要问你一个问题:假如你有一个正在执行的工作流,该工作流能执行第二个工作流吗?

  答案是Yes!有这样一个活动,InvokeWorkflow活动,可用它来启动第二个工作流。我们通过一个例子来简要地看看这个活动。我们将创建一个新的控制台应用程序示例来启动一个工作流,该工作流只是向控制台输出一条信息。在输出该信息后,该工作流实例启动第二个工作流实例,被启动的工作流实例也输出一条信息,这样就生动地为我们展示了两个工作流都执行了。

  调用第二个工作流

  1.和前面一样,本章的源代码中包含了完整版和练习版两种版本的WorkflowInvoker应用程序。我们现在打开练习版的WorkflowInvoker解决方案。

  2.在Visual Studio加载WorkflowInvoker解决方案后,在WorkflowInvoker解决方案中添加一个新的基于顺序工作流库的项目,工作流的名称命名为:Workflow1,保存该项目。

  3.下一步,从工具箱中拖拽一个Code活动到工作流视图设计器界面上。在该活动的ExecuteCode属性中键入“SayHello”,然后按下回车键。

  4.Visual Studio会自动切换到代码编辑界面。定位到Visual Studio刚刚添加的SayHello方法,在该方法内输入下面的代码:

// Output text to the console.
Console.WriteLine("Hello from Workflow1!");

  5.我们现在需要添加第二个要执行的工作流,因此重复步骤2,但工作流的名称命名为:Workflow2。重复步骤3和4,但把信息“Hello from Workflow1!”替换为“Hello from Workflow2!”,当然工作流源文件的名称也要重命名为workflow2.cs,以避免冲突。

  6.我们想在第一个工作流中调用第二个工作流,但这样做,我们还需要添加对第二个工作流的引用。在这之前,我们需要编译并生成Workflow1。

  7.回到Visual Studio解决方案资源管理器,为Workflow1项目添加对项目Workflow2的项目级引用。

  8.回到Workflow1的工作流视图设计器界面上。这次,拖拽一个InvokeWorkflow活动到你的顺序工作流视图设计器界面上。

  WF从入门到精通(第八章):调用外部方法及工作流(二)

  9.看看这个新活动的属性,我们会看到它有一个“TargetWorkflow”属性需要我们去设置。点击以激活它的TargetWorkflow属性,然后点击它的浏览(...)按钮(该按钮带三个点)。

  WF从入门到精通(第八章):调用外部方法及工作流(二)

  10.这将打开一个“浏览和选择一个.NET类型”对话框。在左边面板中选择Workflow2,这将在右边的面板中显示Workflow2类型。在右边的面板中选择Workflow2类型(Workflow2.Workflow2是它的完全限定名称),然后点击确定。

WF从入门到精通(第八章):调用外部方法及工作流(二)

  11.然后Visual Studio会检查该Workflow2工作流,并在工作流视图设计器的InvokeWorkflow活动内部展示它的图形界面。

  WF从入门到精通(第八章):调用外部方法及工作流(二)

  12.工作流现在就完整地实现了,我们现在需要为WorkflowInvoker项目添加对Workflow1和Workflow2的项目引用。

  13.接下来在Program.cs文件中定位到下面的代码上:

  Console.WriteLine("Waiting for workflow completion.");

  14.在上面的代码下添加如下代码:

  15.我们现在将为宿主应用程序添加少量的代码,以便每个工作流完成后通知我们。在WorkflowCompleted的事件处理程序中插入下面的代码:

  第一个完成的工作流设置AutoResetEvent,以便强制应用程序等待工作流完成。我们可添加代码以使应用程序等待所有的工作流,但出于展示的目的这已足够。假如你编译并执行该WorkflowInvoker应用程序,你将在控制台中看到下面图8-4中所展示的输出结果。假如输出信息的顺序有些许的不同,不用吃惊,这是多线程程序的特征。

WF从入门到精通(第八章):调用外部方法及工作流(二)

  图8-4 WorkflowInvoker应用程序的控制台输出

 

 

 

 

  

 

  5.因为连接器类从IMVDataService派生,因此我们必须实现MVDataUpdate方法:

 

  工作流使用这个方法来把DataSet保存到_dataValue字段中。它激发事件以通知宿主数据已经准备好了。该桥接器类的完整代码参见清单8-3。注意我们这时并没准备去编译整个应用程序,我们还有更多的代码需要添加。

  清单8-3 完整的MVDataconnector.cs源文件 MVDataConnector

创建桥接服务(bridge service)类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值