在2005年9月,微軟在它的一年兩次的專業(yè)開發(fā)者會(huì)議上公開了Windows Workflow Foundation(WWF,Windows工作流基礎(chǔ))。作為WinFX API的支柱之一,WWF提供給開發(fā)者一個(gè)普通框架-在其上開發(fā)過程驅(qū)動(dòng)的和以工作流為中心的應(yīng)用程序。
當(dāng)前,有些組織力圖把整個(gè)商業(yè)過程自動(dòng)化;他們的標(biāo)準(zhǔn)答案就是集合一隊(duì)開發(fā)者來開發(fā)相應(yīng)的代碼。盡管這種方式對(duì)于這些組織帶來良好的作用,然而也有一些固有的問題。為了深入理解這一問題,你需要理解一個(gè)工作流的基本特征。
一個(gè)工作流本質(zhì)是一種方法-用來歸檔包含在完成一個(gè)單元的工作中的活動(dòng)。典型地,在處理過程中,工作"流"流過一項(xiàng)或更多活動(dòng)。這些活動(dòng)可以通過機(jī)器或人工來實(shí)現(xiàn),并且有可能象在一個(gè)互聯(lián)網(wǎng)應(yīng)用程序定義頁面順序一樣得簡(jiǎn)單,也有可能象管理必須為任何數(shù)目的人都要看到、更改并同意的文件或產(chǎn)品一樣得復(fù)雜。
因?yàn)槿绱硕嗟墓ぷ髁鞅仨毧紤]到人工參預(yù),所以可能需要花費(fèi)很長(zhǎng)工期才能完成,時(shí)間可能為幾小時(shí)到數(shù)月或更長(zhǎng)。例如,參預(yù)在該過程中的人可能無法找到,不在本地或忙于另外的任務(wù);因此,工作流必須在所有非活動(dòng)期間能夠把自身持續(xù)性存儲(chǔ)。而且,通過編碼獨(dú)立實(shí)現(xiàn)的過程可能對(duì)非技術(shù)人員難于理解而對(duì)開發(fā)者卻難于更改。這一點(diǎn)和其它一些因素正是例如WindowsWF等通用工作流框架的目標(biāo)-其目的就在于使創(chuàng)建、改變和管理工作流更容易-這是通過向它們提供一個(gè)可視化接口或通過定義一組普通API來實(shí)現(xiàn)的。
你可以把WWF工作流放置在任何類型的.NET應(yīng)用程序中-包括Windows表單程序,控制臺(tái)應(yīng)用程序,Windows服務(wù)和ASP.NET Web應(yīng)用程序。每種類型都需要專門的考慮。盡管一些現(xiàn)有示例已經(jīng)足夠說明如何把工作流宿主到Windows表單程序和控制臺(tái)應(yīng)用程序中,但是本文將集中于討論ASP.NET開發(fā)者的問題-他們希望把工作流集成到自己的應(yīng)用程序中。
作者注:本文所提供的代碼是以Windows WF Beta 1和Visual Studio 2005 Beta 2 為工具創(chuàng)建的。你可以在www.windowsworkflow.net找到有關(guān)安裝Windows WF的信息。盡管本文討論了Windows WF的一些基礎(chǔ)問題,但是還有其它一些這方面的可用資源。我假定讀者至少了解一點(diǎn)Windows WF。本文的目的是深度分析Windows WF和ASP.NET,而不是從一個(gè)高層次上討論Windows WF。
一、 Windows WF和MVC模式
在開發(fā)一個(gè)ASP.NET應(yīng)用程序時(shí),你可能使用WWF的一個(gè)普通的方法是實(shí)現(xiàn)一種模型-視圖-控制器(MVC)方法。實(shí)質(zhì)上,MVC的目標(biāo)是把描述層、應(yīng)用程序邏輯和應(yīng)用程序流邏輯分離開來。
搞清楚這個(gè)將十分有益于一個(gè)ASP.NET應(yīng)用程序的開發(fā),請(qǐng)考慮一個(gè)幫助桌面票工作流的場(chǎng)所。假定有一個(gè)商業(yè)用戶通過填寫一個(gè)ASP.NET Web表單并點(diǎn)擊一個(gè)提交按鈕來啟動(dòng)該工作流。接下來,服務(wù)器就會(huì)通知一個(gè)使用Windows表單應(yīng)用程序和幫助桌面的雇員--"有新票可用了"。該幫助桌面雇員然后將在這一問題上工作,并在最后關(guān)閉該票。如果使用Windows WF來開發(fā)這個(gè)工作流情形,那么所有的處理邏輯和流程可以被包含在工作流本身,而該ASP.NET應(yīng)用程序?qū)⑼耆恍枰私膺@一邏輯。
這種場(chǎng)所提供了一些穩(wěn)固的證據(jù)-把描述與邏輯相分離是一件好事情。因?yàn)檫@個(gè)處理幫助桌面請(qǐng)求的過程是非常普通的,如果使用C#或VB.NET代碼在若干不同的.NET應(yīng)用程序中實(shí)現(xiàn)這一邏輯,那么你將會(huì)冒著重復(fù)編碼的危險(xiǎn)甚至更壞的情形--用完全不同的代碼導(dǎo)致同樣的商業(yè)處理過程的不同實(shí)現(xiàn)。但是如果你使用WWF來實(shí)現(xiàn)這一過程,那么需要這一過程的應(yīng)用程序開發(fā)者將僅需在一處修改這些步驟-工作流本身-而不必?fù)?dān)心這樣會(huì)改變應(yīng)用程序邏輯。代碼復(fù)制和在哪里實(shí)現(xiàn)該過程可以通過Windows WF的使用來加以緩和。
當(dāng)使用Windows WF在ASP.NET中實(shí)現(xiàn)MVC架構(gòu)時(shí),開發(fā)者應(yīng)該嘗試構(gòu)建獨(dú)立于應(yīng)用程序的工作流-而該工作流仍然宿主于該應(yīng)用程序中。這將有助于保持邏輯獨(dú)立于描述并且保持在該Web應(yīng)用程序中的工作步驟順序和頁面流之間的高度獨(dú)立性。
一個(gè)WWF開發(fā)新手可能試圖用一固定數(shù)目的活動(dòng)以某種順序去開發(fā)一個(gè)工作流,然后開發(fā)一組ASP.NET Web表單--這些表單以與之相同的順序從一個(gè)表單流向另一個(gè)表單。很遺憾,盡管這看上去挺符合邏輯,但是實(shí)際上這是非常不具有生產(chǎn)效率的,因?yàn)槟銓?huì)再次實(shí)現(xiàn)這個(gè)工作流邏輯。Web頁面X不需要知道是否它需要轉(zhuǎn)到頁面Y或頁面Z來正確地實(shí)現(xiàn)該工作流步驟。代之的是,該工作流(模型)應(yīng)該告訴ASP.NET(控制器)下一步該干什么;然后ASP.NET應(yīng)該決定要顯示哪個(gè)頁面。這樣,每個(gè)頁面幾乎不需要了解整個(gè)過程;它僅需要知道怎樣完成一個(gè)不同的活動(dòng)并且讓該工作流來關(guān)心頁面是如何從一處流向另一處的。這種分離在開發(fā)者處理頁面流時(shí)帶來了一種極大的靈活性。例如,如果你決定改變?cè)擁撁骘@示順序,那么你可以從工作流中容易地實(shí)現(xiàn)這一點(diǎn),而不需要改變?cè)揂SP.NET應(yīng)用程序中的一行代碼。
二、 一個(gè)簡(jiǎn)單的工作流MVC實(shí)例
為了說明這一思想,我將向你展示一個(gè)簡(jiǎn)單ASP.NET應(yīng)用程序和工作流。這個(gè)過度簡(jiǎn)化的工作流描述了一個(gè)進(jìn)度-收集一些來自于一外部應(yīng)用程序的私人信息,然后顯示它。步驟如下:
1. 調(diào)用一個(gè)方法--這意味著請(qǐng)求一個(gè)人的名字;該工作流使用了InvokeMethod活動(dòng)(見圖1)。
2. 等待直到一個(gè)事件被激發(fā)--這意味著收到一個(gè)名字;在這一步中,該工作流使用了EventSink活動(dòng)。
3. 使用一類似調(diào)用,從宿主獲得一個(gè)電子郵件地址。
4. 等待一個(gè)事件意味著收到一個(gè)地址。
5. 在收到名字和電子郵件以后,該工作流啟動(dòng)一個(gè)InvokeMethod活動(dòng)來發(fā)送個(gè)人資料到調(diào)用者應(yīng)用程序。在一種真實(shí)世界情形,這最后一步并不很重要。更可能的是,你將調(diào)用一個(gè)Web服務(wù)來發(fā)送數(shù)據(jù)到另外的系統(tǒng),或把它放進(jìn)一數(shù)據(jù)庫。
![]() 圖1.示例工作流:這個(gè)工作流描述了隱含在示例ASP.NET應(yīng)用程序中的過程 |
為了在ASP.NET中實(shí)現(xiàn)這個(gè)工作流,你需要一個(gè)頁面來收集人名,一個(gè)頁面來收集電子郵件地址和一個(gè)頁面來顯示個(gè)人資料。記住,數(shù)據(jù)登錄表單應(yīng)該絲毫不知道之前或之后所發(fā)生的一切。對(duì)于顯示頁面也是如此。然而,該ASP.NET應(yīng)用程序必須了解要把哪個(gè)頁面顯示給用戶;這正是引入控制器的目的之所在。這個(gè)示例使用一個(gè)Http處理器來實(shí)現(xiàn)該解決方案。這個(gè)稱為WorkflowController的定制的處理器負(fù)責(zé)下列任務(wù):
·獲得到工作流運(yùn)行時(shí)刻的一個(gè)參考。
·獲得一個(gè)到已有的或啟動(dòng)一個(gè)新的工作流實(shí)例的參考(這依賴于是否已啟動(dòng)一個(gè)工作流實(shí)例)。
·建立控制器和工作流之間的通訊。
·處理來自該工作流的事件。
·告訴ASP.NET需要顯示哪個(gè)頁面,這依賴于現(xiàn)在正執(zhí)行該工作流中的哪一層。
你已看到,這個(gè)定制的處理器實(shí)質(zhì)上負(fù)責(zé)處理所有的與WWF和頁面控制相關(guān)的工作--讓單個(gè)的ASP.NET頁面對(duì)在后臺(tái)正在進(jìn)行的動(dòng)作保持"緘默"。Web表單需要擔(dān)心的唯一的事情是執(zhí)行手頭特定的任務(wù)并且把必要的數(shù)據(jù)傳遞到控制器。
默認(rèn)地,WWF以一個(gè)異步的模型工作。這意味著,當(dāng)一個(gè)應(yīng)用程序宿主啟動(dòng)一個(gè)工作流實(shí)例時(shí),控制立即返回到該宿主,而該工作流繼續(xù)在另一個(gè)線程上執(zhí)行。這在一個(gè)Windows表單應(yīng)用程序中可能是很有用的-其中十分期盼用戶接口的連續(xù)響應(yīng)性。通過使用這個(gè)異步的模型,工作流可以在后臺(tái)執(zhí)行而該用戶可以繼續(xù)操作該應(yīng)用程序。然而,在一個(gè)Web應(yīng)用程序中,可能不期望這種類型的行為,因?yàn)樵诜?wù)器完成一個(gè)單元的工作后控制通常將只返回到用戶。這正是Windows WF的可擴(kuò)展性的體現(xiàn)。在Windows WF中,開發(fā)者可以利用或創(chuàng)建"運(yùn)行時(shí)刻服務(wù)"來監(jiān)控甚至修改該工作流運(yùn)行時(shí)刻。該示例包括:
·持續(xù)性服務(wù)-存儲(chǔ)執(zhí)行和空閑時(shí)間之間的工作流狀態(tài)
·追蹤服務(wù)-輸出有關(guān)工作流執(zhí)行的信息到某種媒體
·事務(wù)服務(wù)-幫助維持工作流執(zhí)行過程中的數(shù)據(jù)完整性
另外,線程服務(wù)讓開發(fā)者控制工作流實(shí)例的執(zhí)行方式。如前面所討論的,工作流運(yùn)行時(shí)刻默認(rèn)地將在一個(gè)獨(dú)立于宿主的線程上異步地運(yùn)行實(shí)例。但是由于這很可能不是ASP.NET所期望的,所以你需要交換默認(rèn)工作流線程服務(wù)。幸運(yùn)的是,微軟已經(jīng)為此提供了一種解決方案--ASPNetThreadingService。為了實(shí)現(xiàn)這一變化,你或者可以手工編碼方式把ASPNetThreadingService添加到工作流運(yùn)行時(shí)刻服務(wù),或在web.config文件中完成這一任務(wù)。本文中的示例應(yīng)用程序使用了配置方式。在web.config(見列表1)的工作流運(yùn)行時(shí)刻/服務(wù)節(jié)中,添加類似下列的這一行:
<add type= "System.Workflow.Runtime.Hosting.ASPNetThreadingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> |
三、 實(shí)現(xiàn)控制器邏輯
接下來,你需要以一個(gè)Http處理器來實(shí)現(xiàn)控制器邏輯。為了構(gòu)建該控制器,所有你需要做的是創(chuàng)建一個(gè)稱為WorkflowController處理器的類-它實(shí)現(xiàn)IHttp處理器接口。到目前為止,你還沒有看到有關(guān)Windows WF的任何特別的東西-這是特別針對(duì)于ASP.NET的功能(請(qǐng)繼續(xù)往下讀)。
在這個(gè)WorkflowController處理器類中,名為ProcessRequest的IHttp處理器接口方法處理一個(gè)來自于該ASP.NET應(yīng)用程序的Web請(qǐng)求。這里,你需要獲得到一個(gè)靜態(tài)的工作流運(yùn)行時(shí)刻實(shí)例的一個(gè)參考,為該工作流建立事件處理器,并且啟動(dòng)工作流的執(zhí)行。在啟動(dòng)一個(gè)工作流實(shí)例之前,你需要檢查是否該請(qǐng)求的查詢串值包含一個(gè)代表一個(gè)工作流實(shí)例ID的GUID。如果存在這個(gè)ID,你就知道已經(jīng)有一個(gè)實(shí)例正在運(yùn)行,這樣你可以得到一個(gè)到該實(shí)例的參考并繼續(xù)執(zhí)行。如果不存在這個(gè)ID,你需要通過調(diào)用工作流運(yùn)行時(shí)刻Start Workflow方法來創(chuàng)建一個(gè)新的實(shí)例并且開始執(zhí)行過程。
在啟動(dòng)一個(gè)實(shí)例后,事件處理器將管理進(jìn)出工作流的通訊。因?yàn)楸疚牡哪康牟皇怯懻摫镜赝ㄓ嵎?wù),所以在此我不會(huì)詳細(xì)討論這個(gè)主題,而是分析其高級(jí)的實(shí)現(xiàn)技術(shù),并再次討論這在一個(gè)ASP.NET應(yīng)用程序中是如果工作的。為了便利通訊處理,你將需要若干.NET接口--用于描述出/入該工作流和宿主的信息。你會(huì)在本文所附WorkflowClassLibrary工程源碼中找到這一些。你還會(huì)找到一些實(shí)現(xiàn)這些接口的類以及實(shí)現(xiàn)工作流機(jī)制所必須的相應(yīng)功能。
讓我們?cè)俸?jiǎn)單地看一下web.config文件。注意,在早些時(shí)候討論的ASPNetThreadingService元素附近,我們使用了三個(gè)元素來描述通訊服務(wù)類:
<add type="Workflow.RuntimeServices.GetNameService, Workflow.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c4620ae819b5257e"/> <add type="Workflow.RuntimeServices.GetEmailService, Workflow.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c4620ae819b5257e"/> <add type="Workflow.RuntimeServices.SendDataService, Workflow.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c4620ae819b5257e"/> |
這里還有一個(gè)元素,它指導(dǎo)工作流運(yùn)行時(shí)刻庫來使用SqlStatePersistenceService。這種另外的服務(wù)把一個(gè)工作流的狀態(tài)持續(xù)性存儲(chǔ)到一個(gè)在頁面請(qǐng)求之間的SQL服務(wù)器數(shù)據(jù)庫之中。你必須提前手工地創(chuàng)建這個(gè)數(shù)據(jù)庫,但是微軟提供了SQL腳本來做這件事情。你將會(huì)在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215\Windows Workflow Foundation\SQL文件夾下找到它們。就象模型服務(wù)一樣,你可以編程地添加這些服務(wù),但是你也可以在配置中實(shí)現(xiàn)它,這將會(huì)大大降低代碼的編寫量并且提供靈活性-甚至在代碼生產(chǎn)之后。而且在web.config中有一行,它添加一個(gè)HttpModule-它支持在ASP.NET中的Windows WF運(yùn)行時(shí)刻庫;還有一行用于設(shè)置更早些時(shí)候討論的Http處理器控制器。如你所見,在這個(gè)配置中存在許多的東西。
作為結(jié)論,Windows WF為開發(fā)者在其上開發(fā)基于工作流的應(yīng)用程序提供了一個(gè)極其易用和可擴(kuò)展的框架。實(shí)現(xiàn)商業(yè)過程已經(jīng)并將繼續(xù)成為一種重要的應(yīng)用程序技術(shù)。除非你是一個(gè)技術(shù)服務(wù)公司或ISV,否則軟件是一定要提供商業(yè)及其運(yùn)行過程的。通過使用如Windows WF這樣的工具,開發(fā)者可以使得開發(fā)過程容易且靈活。