學習完本章,你將掌握:
1.創建並調用你的工作流外部的本地數據服務
2.理解怎樣使用接口來為宿主進程和你的工作流之間進行通信。
3.使用設計的外部方法在你的工作流和宿主應用程序之間傳輸數據。
4.在一個正執行的工作流中調用其它工作流
在寫前面的章節時,我自己不斷地思考,“我不能再等了,我要弄清楚在哪裡可把(工作流中的)真實數據返回到宿主應用程序中!”為什麼?做了這麼多的活動和工作流的演示,但都沒有實際返回某些感興趣的東西給宿主應用程序。我不知寫過多少我們感興趣的工作流的實例和演示,但至多只是僅僅處理過數據的初始化(就像第一章-WF簡介中你看過的郵政編碼的例子)。但事情變得更加有趣,坦率地說,當我們啟動工作流,然後從外部源中尋找並處理數據、返回處理後的數據給我們的主應用程序要更加接近現實。
為什麼不這樣呢?公開一個對象,來從執行的工作流中傳給宿主應用程序,或者從宿主應用程序傳給工作流不就行了嗎?其實,使用現有的串行化技術,如.NET Remoting或者XML Web服務,就可完成這些事。串行化,也叫序列化,它可把數據從原有的形式轉換成合適的形式,以在不同進程甚至不同計算機之間進行傳輸。
為什麼談到序列化呢?因為你的工作流是在你的宿主進程中的不同線程上執行,不同線程之間傳送數據,如不進行適當的序列化,將會引發災難,具體原因超出了本書的討論范圍。其實,你的工作流能在一個持久化的狀態下發送它的數據。這並沒有在不同線程上,甚至它不在執行中。
但我們想在我們的工作流和正控制該工作流的宿主進程間傳送數據時,使用.NET Remoting或者XML Web服務這樣的技術為什麼並沒有認為是多余的呢?其實這絕對有必要!我們將創建local通信,本章將以此出發。我們將搭建必須的體系來滿足線程數據序列化,以進行計算機之間或進程之間的數據傳輸。
創建ExternalDataService服務
當工作流和它的宿主進行通信時,在它發送和接收數據的時候,工作流要使用隊列和消息。WF為我們做的越多,我們就可把重點更多的放到應用中特定任務的解決上。
工作流內部進程通信
對於簡單的通信任務,WF使用“abstraction layer”來在工作流和宿主之間進行緩沖。抽象層像一個黑盒,你為它提供輸入,它會執行一些神奇的任務,然後信息流出到另一邊。但我們不用知道它是如何工作的。
在這種情形下,該黑盒就是一個知名的“local communication”服務。和WF術語中的任何一種服務一樣,它也是另一種可插拔服務。區別是它不像WF中的那些已預先創建好的服務,你需要寫出這個服務的一部分。為什麼呢?因為你在宿主應用程序和你的工作流之間傳遞的數據有一定的特殊性。更進一步說,你可創建各種各樣的數據傳輸方法,你可使用你設計的各種方法從宿主應用程序發送數據,然後在工作流中接收數據。
備注:這裡有些事情你需要進行關注,那就是對象或集合的共享問題。因為宿主應用程序和工作流運行時在同一個應用程序域執行,因此引用類型的對象和集合就是通過引用而不是值進行傳遞。這意味著宿主應用程序和工作流實例在同一時間會訪問和使用同一個對象,多線程環境下這會產生bug,出現數據並發訪問錯誤。因此,對於可能要進行並發訪問的對象或集合,你可考慮傳遞一個對象或集合的副本,或許這可通過實現ICloneable接口,或者考慮親自序列化該對象或集合並傳遞序列化後的版本。
你可寫這種local service,把它插進工作流,然後打開連接,發送數據。這些數據可以是字符串,DataSet對象,甚至可以是你設計的任何可被序列化的自定義對象。通信可以是雙向的,盡管在本章我沒有演示它。(這裡,我僅僅是把數據從工作流中傳回給宿主應用程序。)從工作流的角度來說,我們使用工具生成活動的目的是發送和接收數據。從宿主應用程序的角度來說,接收數據等同於一個事件,而發送數據就是在一個服務對象上的方法的簡單調用。
備注:我們在後面幾章看到更多的活動後還會重溫該雙向數據傳輸的概念。工作流活動從宿主應用程序中接收數據基於一個HandleExternalEvent活動,我們將在第10章“Event活動”中看到。我們也需要更深入地了解這些概念間的相互關系,這在第17章“宿主通信”中將進行介紹。對於當前,我們只是在工作流實例完成它的任務後,簡單地返回復合數據給宿主。
我們需要做的還不僅僅是這一點,我們最終需要添加ExternalDataService服務到我們的工作流運行時中。ExternalDataService是一個可插拔的服務,它方便了工作流實例和宿主應用程序之間進行序列化數據的傳輸。在緊接下來的一節我們將寫出的該服務的代碼將做很多事(包括序列化數據的傳輸)。讓我們來看看大體的開發過程。
設計並實現工作流內部進程通信
我們先決定將傳送些什麼數據。它是一個DataSet嗎?是一個像整形數字或字符串之類的系統直接支持的對象嗎?或者是一個由我們自己設計的自定義對象嗎?無論它是什麼,我們都將設計一個ExternalDataService能夠綁定的接口。這個接口將包含我們設計的一些方法,這些方法能分別從工作流實例的角度上及宿主的角度上來發送數據和接收數據。使用該接口中的方法,我們就可來回傳送數據。
我們然後需要寫一些代碼:外部數據服務的一部分。它表述了連接或者稱作橋接代碼,宿主和工作流將使用它來和WF提供的ExternalDataService進行交互。假如我們正涉及一個XML Web服務,Visual Studio會為我們自動地創建代理代碼。但對於工作流來說沒有這樣的工具,因此我們需要親自設計這個橋接代碼。我們這裡使用的“橋”實際上由兩個類組成:一個connector類和一個service類。你可用你喜歡的名稱來命名它們,但我推薦使用這樣的名字來命名它們。connector類管理數據管道(狀態維護),而service類被宿主和工作流用來直接進行數據交換。
在創建好接口後,我們將使用一個工具:wca.exe,它的位置通常是在你的“Program Files\Microsoft SDKs\Windows\v6.0A\Bin”目錄下。該工具叫做Workflow communications Activity generator utility,該工具的作用是,給出一個接口,它將生成兩個活動,你能使用它們去把該接口和你的工作流實例進行綁定。一個用來發送數據,為invoker,另一個用來接收數據,為sink。一旦它們創建好後,你就能從Visual Studio工具箱中把它們拖拽到工作流視圖設計器上,它們也和任何其它工作流活動一樣進行工作。但前面我已經提到過,我們沒有一個工具創建連接橋代碼,這樣的工具在工作流方面一定很有用。
提示:從項目的角度考慮,我傾向於為宿主應用程序創建一個或一組項目,為前面提到的接口和連接橋創建另一個項目,為工作流代碼再單獨創建一個項目。這可讓我方便地從宿主應用程序和工作流中添加對該接口和橋接類的引用,做到了在程序集之間進行簡潔的功能隔離。
我們有了這些程序集後,我們需要連通我們的工作流和宿主應用程序之間的通信。在執行時,通過使用ExternalDataService整個過程被簡化了。我們先快速看看本章中的最基本的應用程序實例(就它而言,它比我們目前看到過的例子都有復雜),然後開使創建我們需要的工作流外部數據通信代碼。
機動車數據檢查應用程序
本示例應用程序是一個Windows Forms應用程序,它提供了一個用戶界面,上面集中了指定駕駛員的機動車數據。該應用程序本身已是很有意義的,我不想再重復創建它的每一個細節。相反,你將使用這個已經提供好了的樣本代碼來作為本章的起點。但是,我將展示怎樣把它們綁進工作流組件中。
主用戶界面窗體見圖8-1。下拉列表框控件包含了三個駕駛員的姓名,選擇其中一個的姓名都會生成一個新的設計好的工作流的實例來對該駕駛員的機動車信息進行檢索,並返回一個完整的DataSet。該DataSet然後被綁定到兩個ListView控件,一個是違規信息。
圖8-1 MVDataChecker窗體的主用戶界面
當你點擊“Recrieve MV Data”按鈕時,你就會初始化一個新的工作流實例,用戶界面會禁用該檢索按鈕及駕駛員下拉列表框控件並顯示一個“searching”通知,如圖8-2所示。你在該窗體底部看到的picture box控件是一個動畫圖片文件。該應用程序根據情況對其中的label控件和picture box控件進行隱藏或顯示。
圖8-2 MVDataChecker窗體的“searching”用戶界面
當工作流實例來完成了它的工作後,它會使用我們將要創建的一個活動來激發一個事件,宿主應用程序會截獲該事件,該事件把數據已准備好的消息通告該宿主應用程序。因為Windows窗體的ListView控件不能直接綁定到DataTable對象,因此我們從工作流中檢索到數據後將一行一行地把數據插入到該控件中,如圖8-3所示。
圖8-3 MVDataChecker窗體檢索數據後的用戶界面
在應用程序執行到此時,你可選擇是檢索另一個駕駛員的信息還是退出程序。假如你在查詢過程中退出該應用程序,正執行的工作流實例會被異常終止。
然後我們來看看需要添寫完成所有這些任務的代碼,首先我們需要為WF提供一個接口,以便它能激發我提過的“數據已准備好”的事件。
創建服務接口
該服務接口完全要由你創建,它應基於你想在你的工作流實例和你的宿主應用程序之間進行通信的數據之上。對於本示例,想像你需要設計一個工作流來從各個源數據中檢索駕駛員的信息,然後你想把這些信息整理為一個單一的數據結構:帶多個表的DataSet,一個表是車輛標識信息,一個表是駕駛員違規信息。我們將簡單地使用虛擬的數據,以便更側重於把焦點放到工作流自身上。在宿主應用程序中,我們將在兩個ListView控件中顯示這些(偽造的)數據。
你要把駕駛員的名字傳入工作流實例中,該工作流實例使用它來查找駕駛員和車輛的信息。在獲取了這些數據後,工作流實例通知宿主應用程序數據已經准備好了,然後宿主應用程序讀取並顯示這些信息。
因此實際上在我們的接口只需要一個單一的方法:MVDataUpdate。我們知道需要發送一個DataSet,因此我們把這個DataSet作為方法的參數傳入到MVDataUpdate中。
創建一個工作流數據通信接口
1.該MVDataChecker示例應用程序,同前面的例子一樣,包含兩個版本:練習版本(MVDataChecker目錄中)和完整版本(MVDataChecker Completed目錄中),它們可在本章的源代碼中進行下載。我們現在就使用Visual Studio打開練習項目中的解決方案。
2.在該解決方案中包含三個項目。在Visual Studio解決方案浏覽器中展開MVDataServic項目,然後打開IMVDataService.cs文件。
3.在MVDataService名稱空間中添加下面的代碼並進行保存。
public interface IMVDataService
{
void MVDataUpdate (DataSet mvData);
}
這樣就大功告成了!這就是所有你需要為創建一個接口所要做的工作。不過,我們需要添加一個屬性,以使這個接口適合於WF的使用,我們將在下面的一節介紹。
使用ExternalDataExchange特性
盡管有了接口:IMVDataService,但我們仍不能把該接口提供給WF,以讓WF真正使用它來進行數據通信。為此,我們需要添加ExternalDataExchange特性。
ExternalDataExchange特性是一個簡單的標記,WF使用它來指明接口可適合於本地通信服務使用。記得我提到的wca.exe工具嗎?它和Visual Studio都使用這個特性來指明接口可被你的工作流實例使用。我們就來添加ExternalDataExchange特性。
備注:不要讓詞語“特性標記”所欺騙,你不要認為該ExternalDataExchage特性不是一個關鍵組成部分。它相當重要。當工作流運行時試圖進行外部數據傳送時會尋找該特性。沒有它,工作流和宿主之間進行數據傳輸就不可能。
創建一個工作流數據通信接口
在Visual Studio中打開IMVDataService.cs文件,為前面定義的接口添加下面的代碼:
[ExternalDataExchange]
IMVDataService接口的完整代碼在下面的清單8-1中。此時不要擔心該應用程序編譯出錯。在編譯無錯之前,我們還需要添加更多的代碼。
清單8-1 IMVDataService.cs完整代碼
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace MVDataService
{
[ExternalDataExchange]
public interface IMVDataService
{
void MVDataUpdate(DataSet mvData);
}
}
使用ExternalDataEventArgs
我在前面提到過,宿主應用程序和正執行的工作流之間使用事件進行通信。宿主應用程序無法事先准確地知道工作流實例准備好數據的時間,對該數據進行輪詢效率又低得可怕。因此WF使用異步模式,當數據准備好了的時候激發一些事件。宿主應用程序捕獲這些事件然後讀出數據。
因為我們想把信息發送給事件的接收者,因此我們需要創建一個自定義事件參數的類。假如你在前面的工作中已創建過一個自定義事件類,你或許就是使用System.EventArgs作為基類。
但是,WF外部數據事件需要帶一個(和上述)不同的參數作為基類,以便該事件能承載工作流實例的實例ID。我們應使用的基類是ExternalDataEventArgs,它從System.EventArgs類派生,這樣我們就熟悉了它的背景。另外,還有兩點要求:我們需要提供一個以該實例ID(一個Guid)作為參數的基本的構造器,該構造器又把實例ID傳給基類構造器,第二點是我們必須使用Serializable特性來標記我們的類,以表明我們的類是可序列化的。
我們現在就來創建我們所需要的外部數據事件參數類。
創建工作流數據事件參數類
1.使用Visual Studio打開MVDataService項目,定位在MVDataAvailableArgs.cs文件上,打開該文件准備進行編輯。
2.在該文件所定義的名稱空間中,添加下面的代碼:
[Serializable]
public class MVDataAvailableArgs : ExternalDataEventArgs
{
}
3.最後我們需要添加一個構造器,以便把工作流實例ID傳給基類:
public MVDataAvailableArgs(Guid instanceId)
: base(instanceId)
{
}
完整的事件參數類如清單8-2所示。
清單8-2 完整的MVDataAvailableArgs.cs源文件 MVDataService
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
namespace MVDataService
{
[Serializable]
public class MVDataAvailableArgs : ExternalDataEventArgs
{
public MVDataAvailableArgs(Guid instanceId)
: base(instanceId)
{
}
}
}
本文配套源碼