WF 与 WCF 集成
Demo : ?單擊此處下載“Windows Workflow Foundation 示例:WF 與 WCF 集成”。
隨著 Windows Workflow Foundation (WF) 的問(wèn)世,Microsoft 逐步將各種工作流功能引入了 .NET 開(kāi)發(fā)人員平臺(tái)。這些功能使開(kāi)發(fā)人員能夠構(gòu)建用于滿(mǎn)足各種應(yīng)用需求的工作流,從簡(jiǎn)單的順序工作流到需要復(fù)雜的人員交互的復(fù)雜狀態(tài)機(jī)工作流。
與此同時(shí),業(yè)務(wù)能力越來(lái)越多地通過(guò)封裝的服務(wù)端點(diǎn)展現(xiàn)出來(lái),這樣就可以重用和組合業(yè)務(wù)功能和業(yè)務(wù)流程,使面向服務(wù)的體系架構(gòu)更加完善。Windows Communication Foundation (WCF) 提供了統(tǒng)一的開(kāi)發(fā)人員 API、穩(wěn)健的托管運(yùn)行時(shí)和靈活的配置驅(qū)動(dòng)解決方案來(lái)幫助進(jìn)行部署,進(jìn)而幫助開(kāi)發(fā)人員通過(guò)各種功能來(lái)輕松開(kāi)發(fā)互聯(lián)系統(tǒng)。
本文在結(jié)尾處列出了一些可用來(lái)進(jìn)一步了解 WF 和 WCF 的附加資源。
開(kāi)支報(bào)告示例
本文的代碼示例基于為員工報(bào)銷(xiāo)申請(qǐng)?zhí)峤慌c審批標(biāo)準(zhǔn)業(yè)務(wù)流程建立模型的“開(kāi)支報(bào)告”工作流示例。我們?cè)谠纠A(chǔ)上進(jìn)行了更新,用以說(shuō)明如何利用 WCF 和 .NET 3.0 Framework 來(lái)更有效地托管這一業(yè)務(wù)環(huán)節(jié)。
在發(fā)行的第一版“開(kāi)支報(bào)告”示例中,使用 .NET 遠(yuǎn)程調(diào)用來(lái)提供客戶(hù)端應(yīng)用程序與包含工作流運(yùn)行時(shí)實(shí)例的宿主應(yīng)用程序之間的通信。
而經(jīng)我們重構(gòu)的開(kāi)支報(bào)告示例在實(shí)現(xiàn)時(shí)使用 WCF 來(lái)執(zhí)行客戶(hù)端和服務(wù)端之間的通信。此外,該解決方案還實(shí)現(xiàn)了邏輯上的結(jié)構(gòu)化,從而將其中的各種問(wèn)題分離出來(lái)。
圖 1. 重構(gòu)后解決方案的結(jié)構(gòu)
了解消息在業(yè)務(wù)流程上下文中的使用方式非常重要,只有這樣您才能將它們?nèi)谌朐O(shè)計(jì)之中。在“開(kāi)支報(bào)告”的生命周期中,存在幾個(gè)交互點(diǎn)。我們來(lái)簡(jiǎn)單分析一下:
| ? | 這一流程涉及到三方:客戶(hù)端、管理人員和開(kāi)支報(bào)告宿主系統(tǒng)。 |
| ? | 這一流程從客戶(hù)端提交新的報(bào)銷(xiāo)申請(qǐng)開(kāi)始。 |
| ? | 我們使用一種規(guī)則策略來(lái)確定報(bào)銷(xiāo)申請(qǐng)是否可以自動(dòng)審批。 |
| ? | 如果報(bào)銷(xiāo)申請(qǐng)不能自動(dòng)審批,則需要管理人員審批該報(bào)告。管理人員要么需要自行檢查是否有待審批的新報(bào)告,要么需要在有待審批的新報(bào)告時(shí)獲得通知。 |
| ? | 如果管理人員在靈活的延遲時(shí)間內(nèi)未進(jìn)行審批,該流程會(huì)自動(dòng)拒絕此報(bào)銷(xiāo)申請(qǐng)。 |
| ? | 報(bào)銷(xiāo)申請(qǐng)被復(fù)審?fù)戤吅?#xff0c;客戶(hù)端和管理人員必須被及時(shí)告知結(jié)果。 |
通過(guò) WF,我們可以利用該框架所提供的標(biāo)準(zhǔn)活動(dòng)來(lái)為這一流程建模。我們可以使用 DelayActivity 來(lái)管理在一段時(shí)間后觸發(fā)的事件,可以使用規(guī)則引擎和 PolicyActivity 來(lái)管理一套靈活的規(guī)則,通過(guò)詢(xún)問(wèn)這些規(guī)則可以獲得結(jié)果。
由于這是一個(gè)面向人員的流程,因此我們必須與最終用戶(hù)進(jìn)行交互,并將該交互交回到工作流中。WF 提供了“本地服務(wù)”、HandleExternalEventActivity 和 CallExternalMethodActivity,從而為實(shí)現(xiàn)宿主和工作流之間的通信提供了全面的編程模型。
由于這對(duì)于構(gòu)建交互式工作流而言是一個(gè)重要的概念,因此讓我們快來(lái)了解一下它是如何被設(shè)計(jì)到 WF 中的。
為了在 WF 中建立用戶(hù)交互的模型,我們必須設(shè)計(jì)用于提供許多事件和方法的約定。工作流和宿主進(jìn)程都應(yīng)理解這一約定。所構(gòu)建的約定/接口必須以 [ExternalDataExchange()] 屬性來(lái)標(biāo)記,這一屬性會(huì)將其標(biāo)識(shí)為專(zhuān)用于進(jìn)行工作流數(shù)據(jù)交換。在我們所列舉的示例中,工作流使用 IExpenseLocalService 接口。
然后我們訂閱一個(gè)與工作流運(yùn)行時(shí)實(shí)現(xiàn)該接口的類(lèi)(稱(chēng)為本地服務(wù))。工作流活動(dòng)可以注冊(cè)到事件或者使用接口類(lèi)型上定義的方法,并將綁定到我們已經(jīng)注冊(cè)的本地服務(wù)。這采用的是一種稱(chēng)為“控制反轉(zhuǎn)”的模式,這種模式消除了工作流與具體類(lèi)型本地服務(wù)之間的緊耦合。在我們所列舉的示例中,ExpenseLocalService 類(lèi)實(shí)現(xiàn)了 IExpenseLocalService 約定。
工作流在第一次運(yùn)行時(shí),可以獲得一個(gè)用于執(zhí)行操作的初始數(shù)據(jù)包。當(dāng)工作流到達(dá)需要外部交互的點(diǎn)后,我們可以引發(fā)一個(gè)能夠在工作流中綁定到 HandleExternalEventActivity 的事件。此活動(dòng)將接口類(lèi)型和事件作為參數(shù),當(dāng)該事件被引發(fā)后,工作流會(huì)被喚醒,使操作繼續(xù)下去。
如果工作流必須對(duì)本地服務(wù)進(jìn)行回調(diào),可以使用 CallExternalMethodActivity 并以接口和方法名為參數(shù)來(lái)實(shí)現(xiàn)。
通過(guò)這些活動(dòng),可以在宿主進(jìn)程中與運(yùn)行中的工作流實(shí)現(xiàn)雙向通信,而通過(guò)在 WF 中使用“控制反轉(zhuǎn)”模式,避開(kāi)了工作流和本地服務(wù)之間的緊耦合。
然而,一旦跨出宿主進(jìn)程這一范圍,我們就必須允許交互由其他系統(tǒng)甚至是人來(lái)驅(qū)動(dòng)。為實(shí)現(xiàn)這一級(jí)別的交互,我們可以通過(guò)服務(wù)來(lái)分配交互操作,而這些服務(wù)進(jìn)而再由其他服務(wù)或用戶(hù)驅(qū)動(dòng)的應(yīng)用程序來(lái)調(diào)用。而 WCF 就是能夠靈活構(gòu)建這一消息傳遞功能的框架。
在這個(gè)與 WCF 集成的方案中,主要的優(yōu)點(diǎn)在于:
| ? | 可以將服務(wù)實(shí)現(xiàn)與消息傳遞管道代碼相分離。 |
| ? | 大大減少了與系統(tǒng)連接有關(guān)的代碼量和復(fù)雜程度。 |
| ? | 增加了部署的靈活性。 |
| ? | 可以利用從主機(jī)到客戶(hù)端的直接回調(diào),使信息更新的速度更快、開(kāi)銷(xiāo)更低。 |
集成工作檢查表
為了實(shí)現(xiàn) WF 與 WCF 的集成,必須有一個(gè)將為使用者提供眾多接口點(diǎn)的服務(wù)接口,使用者可以在這些接口點(diǎn)啟動(dòng)運(yùn)行中的工作流或與之交互。應(yīng)圍繞業(yè)務(wù)流程與外部實(shí)體(例如這一流程所涉及的人員)進(jìn)行交互的這些接口點(diǎn)來(lái)建立服務(wù)模型。
圖 2.“開(kāi)支報(bào)告”方案中的交互點(diǎn)
為此,我們必須:
| ? | 定義服務(wù)約定。 |
| ? | 實(shí)現(xiàn)通過(guò)事件創(chuàng)建新工作流(或與現(xiàn)有工作流進(jìn)行交互)的服務(wù)操作。 |
| ? | 將工作流運(yùn)行時(shí)實(shí)例托管到服務(wù)宿主中。 |
除了簡(jiǎn)單地托管工作流之外,我們還可以利用 WCF 雙工信道將事件從工作流交回給作為使用方的客戶(hù)端。就“開(kāi)支報(bào)告”而言,這是非常有益的,因?yàn)樵摻鉀Q方案依靠客戶(hù)端進(jìn)行服務(wù)輪詢(xún)來(lái)實(shí)現(xiàn)定期的數(shù)據(jù)更新。但這些客戶(hù)端也可以直接從服務(wù)處獲得通知。
為此,我們必須:
| ? | 定義一個(gè)回調(diào)約定。 |
| ? | 使用將支持雙工信道的綁定。 |
定義服務(wù)約定
Windows Communication Foundation (WCF) 要求聲明一份正式的約定來(lái)抽象地定義服務(wù)功能和數(shù)據(jù)交換。這份約定通過(guò)聲明接口以代碼的形式定義。
在設(shè)計(jì)業(yè)務(wù)服務(wù)時(shí),通常會(huì)使用“請(qǐng)求/響應(yīng)”協(xié)作模式。使用這種模式時(shí),在所提供的約定中必須考慮三方面因素,即:
| ? | 所發(fā)布的操作。這些操作是服務(wù)發(fā)布給其使用者的功能,是接口上的方法。 |
| ? | 為每個(gè)請(qǐng)求和響應(yīng)封裝結(jié)構(gòu)化數(shù)據(jù)的消息。這些消息是每個(gè)方法的參數(shù)和返回類(lèi)型。在 WCF 術(shù)語(yǔ)中,它們通常是消息約定,而在更簡(jiǎn)單的情況中,它們是數(shù)據(jù)約定。 |
| ? | 可通過(guò)服務(wù)進(jìn)行交換的核心業(yè)務(wù)實(shí)體的數(shù)據(jù)定義。這些定義構(gòu)成消息的一部分。在 WCF 術(shù)語(yǔ)中,它們就是數(shù)據(jù)約定。 |
服務(wù)約定使用基于屬性的標(biāo)記來(lái)定義,它首先定義提供操作的約定,然后再定義通過(guò)網(wǎng)絡(luò)發(fā)布的具體操作。
每個(gè)服務(wù)約定都以 [ServiceContract] 屬性明確標(biāo)記。此屬性可通過(guò)下列參數(shù)進(jìn)行聲明:
| ? | Name。控制在 WSDL <portType> 元素中聲明的約定名稱(chēng) |
| ? | Namespace。控制在 WSDL <portType> 元素中聲明的約定名稱(chēng) |
| ? | SessionMode。指定約定是否需要支持會(huì)話(huà)的綁定 |
| ? | CallbackContract。指定用于客戶(hù)端回調(diào)的約定 |
| ? | ProtectionLevel。指定約定是否需要支持 ProtectionLevel 屬性的綁定,該屬性用于聲明加密和數(shù)字簽名需求 |
聲明操作
在約定定義之后,服務(wù)再由許多發(fā)布的操作構(gòu)成。操作通過(guò) [OperationContract] 屬性標(biāo)記被明確地加入約定之中。與 ServiceContract 一樣,OperationContract 也有許多用于控制與端點(diǎn)的綁定方式的參數(shù)。這些參數(shù)包括:
| ? | Action。控制用于唯一標(biāo)識(shí)此操作的名稱(chēng)。當(dāng)某個(gè)端點(diǎn)接收到消息時(shí),調(diào)度程序會(huì)使用該控件和動(dòng)作來(lái)確定所要調(diào)用的方法。 |
| ? | IsOneWay。指示該操作將接受一個(gè)“請(qǐng)求”消息,但不會(huì)產(chǎn)生任何響應(yīng)。這不同于簡(jiǎn)單地返回一個(gè)還將生成“結(jié)果”消息的 void 返回類(lèi)型。 |
| ? | ProtectionLevel。指定該操作的加密和簽名需求。 |
以下是代碼形式服務(wù)約定的示例。
[ServiceContract] public interface IExpenseService { [OperationContract] GetExpenseReportsResponse GetExpenseReports(); [OperationContract] GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest getExpenseReportRequest); }聲明消息和數(shù)據(jù)實(shí)體
在構(gòu)建消息的模型時(shí),您可能希望采用類(lèi)的形式,來(lái)為每個(gè)將發(fā)送的消息定義負(fù)載或正文。這與在以 ASP.NET 構(gòu)建 Web 服務(wù)時(shí)使用 WS Contract First (WSCF) 等工具來(lái)構(gòu)建消息模型的方式十分相似。
在默認(rèn)情況下,WCF 使用稱(chēng)為 DataContractSerializer 的序列化引擎來(lái)實(shí)現(xiàn)數(shù)據(jù)的序列化和反序列化(即與 XML 的來(lái)回轉(zhuǎn)換)。我們通過(guò)添加對(duì) System.Runtime.Serialization 命名空間的引用來(lái)利用 DataContractSerializer,然后以 [DataContract] 屬性標(biāo)記類(lèi),以 [DataMember] 標(biāo)記要發(fā)布的成員。
[DataContract] public class GetExpenseReportsResponse { private List<ExpenseReport> reports; [DataMember] public List<ExpenseReport> Reports { get { return reports; } set { reports = value; } } }消息中所使用的數(shù)據(jù)實(shí)體代表您業(yè)務(wù)領(lǐng)域中的實(shí)體。就像消息約定一樣,我們可以通過(guò) DataContractSerializer 和標(biāo)記屬性來(lái)明確加入所分配的成員;或者,如果我們只想構(gòu)建數(shù)據(jù)模型,可以使用公共字段的方法,并將類(lèi)標(biāo)記為可序列化類(lèi)。
在本示例中,我們使用了數(shù)據(jù)約定的方法來(lái)標(biāo)記消息傳遞。在實(shí)際的工作中,您常常會(huì)需要面對(duì)更為復(fù)雜的架構(gòu),需要在架構(gòu)中使用屬性,以及需要使用 SOAP 標(biāo)頭。WCF 支持定義以 [MessageContract] 屬性(可描述整個(gè) SOAP 封裝,而不僅僅是正文)標(biāo)記的類(lèi),從而解決了這些尖銳的問(wèn)題。
有關(guān)數(shù)據(jù)約定和消息約定的詳細(xì)信息,請(qǐng)參閱本文結(jié)尾處更多信息部分所列的相應(yīng) MSDN 庫(kù)文章。
托管工作流運(yùn)行時(shí)
服務(wù)通常會(huì)支持將創(chuàng)建新服務(wù)類(lèi)型實(shí)例并在整個(gè)會(huì)話(huà)生命周期內(nèi)維護(hù)該服務(wù)類(lèi)型實(shí)例的并行行為。要在這種情況下使用工作流,必須創(chuàng)建工作流運(yùn)行時(shí)的一個(gè)實(shí)例,并在服務(wù)宿主實(shí)例的生命周期內(nèi)(而不是基于每次調(diào)用)對(duì)其進(jìn)行維護(hù)。
對(duì)此,我們建議使用一個(gè)會(huì)在創(chuàng)建服務(wù)宿主后被激活的擴(kuò)展類(lèi)。此擴(kuò)展類(lèi)會(huì)創(chuàng)建和維護(hù)工作流運(yùn)行時(shí)的全局實(shí)例,使每個(gè)單獨(dú)的服務(wù)實(shí)例都能夠訪(fǎng)問(wèn)它。
為在 ServiceHost 上實(shí)現(xiàn)擴(kuò)展,應(yīng)創(chuàng)建一個(gè)實(shí)現(xiàn) IExtension<ServiceHostBase> 的類(lèi)。在此解決方案中,可以在 WcfExtensions 代碼項(xiàng)目下的 WfWcfExtension 類(lèi)中找到這樣一個(gè)示例。
我們需要實(shí)現(xiàn)的兩個(gè)方法是:Attach(當(dāng)擴(kuò)展附加到其父對(duì)象上時(shí)調(diào)用此方法)和 Detach(當(dāng)卸載父對(duì)象時(shí)調(diào)用此方法)。
如下所示的 Attach 方法會(huì)創(chuàng)建一個(gè)新的 WorkflowRuntime 實(shí)例,并就所需服務(wù)將其進(jìn)行實(shí)例化。我們將此存儲(chǔ)在名為 workflowRuntime 的本地私有字段中。
void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner) { workflowRuntime = new WorkflowRuntime(workflowServicesConfig); ExternalDataExchangeService exSvc = new ExternalDataExchangeService(); workflowRuntime.AddService(exSvc); workflowRuntime.StartRuntime(); }如您所見(jiàn),工作流運(yùn)行時(shí)的初始化還涉及在啟動(dòng)運(yùn)行時(shí)之前將服務(wù)實(shí)例添加到其中。在構(gòu)建解決方案時(shí),通常建議您在啟動(dòng)運(yùn)行時(shí)之前添加所有的服務(wù)。但如果耦合是一個(gè)重要因素,您會(huì)發(fā)現(xiàn)采用后期綁定的方法更為明智。
在本示例中,作為 ExpenseService 類(lèi)中 SetUpWorkflowEnvironment 方法的一部分,我們?cè)?WorkflowRuntime 啟動(dòng)后將 ExpenseLocalService 實(shí)例添加到 ExternalDataExchangeService 中。
以下所示的 Detach 方法通過(guò)調(diào)用 StopRuntime 來(lái)關(guān)閉運(yùn)行時(shí)。
void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner) { workflowRuntime.StopRuntime(); }由于 WorkflowRuntime 在服務(wù)宿主啟動(dòng)過(guò)程中創(chuàng)建并初始化,因此在服務(wù)調(diào)用執(zhí)行前,任何現(xiàn)有的工作流都可以繼續(xù)工作。當(dāng)服務(wù)宿主終止時(shí),工作流運(yùn)行時(shí)會(huì)隨之徹底關(guān)閉。
注意 在托管工作流和構(gòu)建長(zhǎng)期工作流模型時(shí),建議您使用工作流持久性服務(wù)(如 SqlWorkflowPersistenceService),這是一條基本的準(zhǔn)則。這將提供一個(gè)實(shí)現(xiàn)狀態(tài)持久性的機(jī)制,即使重新啟動(dòng)應(yīng)用程序或進(jìn)程也不會(huì)受到影響。
創(chuàng)建服務(wù)操作
要?jiǎng)?chuàng)建包含服務(wù)行為的類(lèi),必須實(shí)現(xiàn)一個(gè)或多個(gè)用于定義服務(wù)約定的接口。
public class ExpenseService : IExpenseService, IExpenseServiceClient, IExpenseServiceManager為了與工作流相集成,我們的服務(wù)方法將不包含業(yè)務(wù)邏輯,而是包含一些代碼,用以控制事件或?qū)⑹录唤o將封裝業(yè)務(wù)流程的運(yùn)行中工作流。
對(duì)于處理工作流的操作,我們將啟動(dòng)新的工作流,或者與現(xiàn)有的運(yùn)行中工作流進(jìn)行交互。
要?jiǎng)?chuàng)建新的工作流實(shí)例,需要使用 WorkflowRuntime 來(lái)實(shí)例化所需工作流類(lèi)型的一個(gè)新實(shí)例。我們已在 ServiceHost 擴(kuò)展類(lèi)中創(chuàng)建了一個(gè)這樣的實(shí)例。為了獲取對(duì)該實(shí)例的引用,我們必須使用 OperationContext 來(lái)找到自定義的擴(kuò)展。
WfWcfExtension extension = OperationContext.Current.Host.Extensions.Find<WfWcfExtension>(); workflowRuntime = extension.WorkflowRuntime;OperationContext 是供我們?cè)L問(wèn)服務(wù)方法擴(kuò)展上下文的類(lèi)。正如您在之前的代碼中所看到的,它提供一個(gè)名為 Current 的單例,來(lái)為我們展示當(dāng)前服務(wù)方法的上下文。我們調(diào)用 Host 屬性來(lái)將實(shí)例返回給所屬的 ServiceHost,然后根據(jù)其類(lèi)型找到擴(kuò)展。
在獲得擴(kuò)展實(shí)例的引用后,可以通過(guò)公共屬性返回 WorkflowRuntime,并用它來(lái)創(chuàng)建 SequentialWorkflow 的新實(shí)例。
Guid workflowInstanceId = submitExpenseReportRequest.Report.ExpenseReportId; Assembly asm = Assembly.Load("ExpenseWorkflows"); Type workflowType = asm.GetType("ExpenseWorkflows.SequentialWorkflow"); WorkflowInstance workflowInstance = workflowRuntime.CreateWorkflow(workflowType, null, workflowInstanceId); workflowInstance.Start(); expenseLocalService.RaiseExpenseReportSubmittedEvent( workflowInstanceId, submitExpenseReportRequest.Report);在上面的代碼中,我們基于預(yù)定義的類(lèi)型創(chuàng)建了新的工作流實(shí)例。雖然這也可以通過(guò)直接的類(lèi)型實(shí)例化來(lái)實(shí)現(xiàn),但上述的方式告訴我們,其實(shí)可以在運(yùn)行時(shí)基于動(dòng)態(tài)規(guī)則(而不是通過(guò)強(qiáng)類(lèi)型化綁定)來(lái)靈活地創(chuàng)建工作流。
最后一行代碼會(huì)引發(fā)由工作流中第一個(gè) HandleExternalEventActivity 處理的事件,標(biāo)志著工作流的開(kāi)始。我們通過(guò) ExpenseLocalService 類(lèi)的實(shí)例來(lái)引發(fā)這一事件。在本示例中,ExpenseLocalService 會(huì)啟動(dòng)新的工作流或?qū)⑹录唤o現(xiàn)有工作流,從而與工作流進(jìn)行交互。我們將此類(lèi)用作封裝業(yè)務(wù)流程的機(jī)制。在內(nèi)部,我們使用 WF 來(lái)實(shí)現(xiàn)這一機(jī)制。
圖 3. 工作流始于 HandleExternalEventActivity。
我們將要應(yīng)對(duì)的另一類(lèi)情況是如何回調(diào)到現(xiàn)有工作流中并引發(fā)事件。我們必須將事件交給將促使現(xiàn)有工作流接收事件并繼續(xù)進(jìn)行處理的工作流引擎。
在“開(kāi)支報(bào)告”流程中引發(fā)事件的一個(gè)示例就是在需要管理人員進(jìn)行審批之時(shí)發(fā)生的。工作流將對(duì) RequestManagerApproval 調(diào)用外部方法,用以向管理人員發(fā)出警報(bào),告訴他們必須批準(zhǔn)或拒絕新的開(kāi)支報(bào)告。
工作流中包含在一個(gè)可能事件發(fā)生前一直處于阻塞狀態(tài)的 ListenActivity。在本例中,我們會(huì)接收到一個(gè)事件,指示管理人員已經(jīng)審核該報(bào)告,或者已經(jīng)超過(guò) DelayActivity 的時(shí)間限制。
圖 4. ManagerApproval 自定義活動(dòng)流程
Guid workflowInstanceId = submitReviewedExpenseReportRequest.Report.ExpenseReportId; ExpenseReportReviewedEventArgs e = new ExpenseReportReviewedEventArgs(workflowInstanceId, report, review); if (ExpenseReportReviewed != null) { ExpenseReportReviewed(null, e); }管理人員使用 ManagerApplication 審核報(bào)告時(shí),會(huì)對(duì)宿主進(jìn)行服務(wù)回調(diào),來(lái)調(diào)用引發(fā) ExpenseReportReviewed 事件的 SubmitReviewedExpenseReport 方法。
引發(fā)交給工作流中 HandleExternalEventActivity 的事件時(shí),必須知道所處理工作流實(shí)例的 GUID,使事件能夠得以路由。
每個(gè)事件都以 EventArgs 引發(fā),使我們能夠通過(guò)事件模型將數(shù)據(jù)傳遞回工作流。在本例中,我們可以傳遞詳細(xì)的當(dāng)前報(bào)表狀態(tài)信息和審核活動(dòng)上下文數(shù)據(jù)信息。
在工作流中,事件通過(guò) HandleExternalEventActivity 的屬性自動(dòng)綁定到工作流。
圖 5. 將 HandleExternalEventActivity 綁定到 IExpenseLocalService 接口。
首先指定必須以 [ExternalDataExchange] 屬性標(biāo)記的接口類(lèi)型,然后指定該接口上 HandleExternalEventActivity 將訂閱的事件。
事件參數(shù)必須從 ExternalDataEventArgs 類(lèi)派生而來(lái)。這至少意味著每個(gè)事件都會(huì)包含上下文(例如工作流的 InstanceId)。然后,工作流運(yùn)行時(shí)負(fù)責(zé)將事件路由給正確的工作流實(shí)例,以使工作流繼續(xù)進(jìn)行。如果使用持久性服務(wù),運(yùn)行時(shí)還會(huì)在整個(gè)執(zhí)行期間管理工作流所有運(yùn)行狀態(tài)的水化和再水化。
托管服務(wù)
要托管 WCF 服務(wù),必須在 ServiceHost 容器內(nèi)運(yùn)行。
為了了解如何使用 WCF 實(shí)現(xiàn)托管,首先讓我們來(lái)了解一下幾種可用的備選方案:
| ? | 對(duì)于標(biāo)準(zhǔn)的 Windows 進(jìn)程,可以手動(dòng)創(chuàng)建并打開(kāi) ServiceHost 實(shí)例。 |
| ? | 在通過(guò) Microsoft Internet Information Services (IIS) 6.0 托管 Web 端點(diǎn)(Web 服務(wù))時(shí),使用 System.ServiceModel 命名空間下提供的自定義 HttpHandler。 |
| ? | 在 IIS 7 下進(jìn)行托管時(shí),可以使用 Windows Activation Service (WAS) 來(lái)托管端點(diǎn)。 |
構(gòu)建 Web 服務(wù)時(shí),您通常會(huì)選擇使用 Internet Information Services 進(jìn)行托管。而在構(gòu)建將用作后臺(tái)程序的單個(gè)實(shí)例端口時(shí),您通常會(huì)選擇通過(guò) Windows 服務(wù)進(jìn)行托管。
在本示例中,我們?cè)谝粋€(gè) Windows 控制臺(tái)應(yīng)用程序中托管主要的服務(wù)實(shí)例,托管方式與 Windows 服務(wù)的托管方式類(lèi)似。
為了部署服務(wù),我們必須創(chuàng)建 ServiceHost 類(lèi)的一個(gè)實(shí)例,并針對(duì)要發(fā)布的每個(gè)服務(wù)類(lèi)型開(kāi)放其端點(diǎn)。ServiceHost 將許多參數(shù)作為其構(gòu)造函數(shù)的一部分;但主要參數(shù)是 Type 參數(shù)或?qū)崿F(xiàn) ServiceContract 的類(lèi)的實(shí)例。
| ? | 如果要采用 PerCall 或 PerSession 實(shí)例化,請(qǐng)使用 Type。 |
| ? | 如果采用 Single 實(shí)例化,請(qǐng)使用單個(gè)實(shí)例。 |
有關(guān) WCF 中實(shí)例化與并發(fā)性的詳細(xì)信息,請(qǐng)參閱 MSDN 庫(kù)中的 Sessions, Instancing, and Concurrency。
宿主建立起來(lái)后,會(huì)分析所有可用的配置(在下面的配置部署部分做詳細(xì)介紹)并將其與任何明確添加的配置合并,來(lái)確定可用的端點(diǎn)并針對(duì)發(fā)布開(kāi)放那些端點(diǎn)。宿主接收到從客戶(hù)端發(fā)來(lái)的調(diào)用后,會(huì)在新的后臺(tái)工作線(xiàn)程上處理請(qǐng)求,并按消息中 SOAP 約定名稱(chēng)和動(dòng)作的指示,將其路由給相應(yīng)的服務(wù)操作。
using (ServiceHost serviceHost = new ServiceHost(new ExpenseService())) { WfWcfExtension wfWcfExtension = new WfWcfExtension("WorkflowRuntimeConfig"); serviceHost.Extensions.Add(wfWcfExtension); serviceHost.Open(); // 在此處阻止該進(jìn)程,例如 Console.ReadLine(); serviceHost.Close(); }配置 ServiceHost 時(shí),必須首先為連接開(kāi)放端點(diǎn)。為此,您可以在調(diào)用 .Open() 之前與宿主對(duì)象進(jìn)行交互,如前面代碼所示。建議您利用作用域在使用前對(duì) ServiceHost 進(jìn)行處置,并建議您在該作用域結(jié)束時(shí)明確地調(diào)用 Close() 來(lái)徹底關(guān)閉所有活動(dòng)的連接和端點(diǎn)。
配置部署
WCF 提供了一種機(jī)制,允許通過(guò) XML 配置來(lái)配置端點(diǎn),從而將部署問(wèn)題從實(shí)現(xiàn)工作中分離出來(lái)。這使得管理員無(wú)需重新部署代碼即可修改服務(wù)策略。
每個(gè)服務(wù)在一個(gè)或多個(gè)端點(diǎn)上發(fā)布。一個(gè)端點(diǎn)就是一個(gè)可尋址的連接點(diǎn),客戶(hù)端可對(duì)其使用服務(wù)。在 WCF 中,每個(gè)端點(diǎn)都以三個(gè)屬性聲明,這三個(gè)屬性就是人們所熟知的 WCF 的 ABC。
它們是“地址”(A)、“綁定”(B) 和“約定”(C)。
地址:此端點(diǎn)的唯一可尋址位置。通常情況下,地址是為您提供絕對(duì)地址的 URI,服務(wù)在該地址處偵聽(tīng)請(qǐng)求,例如:http://myhost/myservice 或 net.tcp://myhost:400/myservice
綁定:用于規(guī)定服務(wù)與其使用者之間通信協(xié)議的策略。綁定指定了幾個(gè)方面的要素,例如所使用的傳輸類(lèi)型、消息的編碼方式以及數(shù)據(jù)的序列化方式。WCF 附帶了許多用于支持最常見(jiàn)情況的現(xiàn)成綁定。
約定:通過(guò)接口以代碼形式定義的、要發(fā)布的操作和數(shù)據(jù)。
要配置服務(wù),我們必須對(duì)聲明服務(wù)的配置進(jìn)行聲明,并為服務(wù)配置任意數(shù)量的端點(diǎn)。由于服務(wù)可能正在實(shí)現(xiàn)任意多個(gè)約定,因此這還將影響到需要發(fā)布的端點(diǎn)數(shù)。
以下是一個(gè)配置示例。
<services> <service name="ExpenseServices.ExpenseService"> <endpoint address="http://localhost:8081/ExpenseService/Manager" binding="wsHttpBinding" contract="ExpenseContracts.IExpenseServiceManager" /> <endpoint address="http://localhost:8081/ExpenseService/Client" binding="wsDualHttpBinding" contract="ExpenseContracts.IExpenseServiceClient" /> </service> </services>在此配置示例中,我們?yōu)?ExpenseServices.ExpenseService 類(lèi)型的服務(wù)聲明配置。這樣,運(yùn)行時(shí)就可以在我們根據(jù)此類(lèi)型實(shí)例化新的 ServiceHost 時(shí)找到該配置。有關(guān)綁定的詳細(xì)信息,請(qǐng)參閱 MSDN 庫(kù)中的 WCF Bindings。
使用服務(wù)
通過(guò) ChannelFactory 類(lèi),可以利用 WCF 來(lái)使用服務(wù)。ChannelFactory 通過(guò)工廠(chǎng)模式為我們提供連接到配置中指定端點(diǎn)的服務(wù)約定代理實(shí)例。我們可以使用運(yùn)行時(shí)信息(例如用于消息加密的安全憑據(jù)和證書(shū))配置工廠(chǎng),或者動(dòng)態(tài)地確定端點(diǎn)信息。
private IExpenseServiceManager CreateChannelExpenseServiceManager() { ChannelFactory<IExpenseServiceManager> factory = new ChannelFactory<IExpenseServiceManager>("ExpenseServiceManager"); IExpenseServiceManager proxy = factory.CreateChannel(); return proxy; }如您所見(jiàn),我們首先創(chuàng)建了一個(gè)工廠(chǎng)實(shí)例,該實(shí)例對(duì)服務(wù)約定使用泛型參數(shù),以構(gòu)造出將僅返回所需約定實(shí)例的更精確的工廠(chǎng)。我們還指定了用于確定端點(diǎn)所用配置的參數(shù)。在本例中,我們使用名為 ExpenseServiceManager 的端點(diǎn)配置,它就是我們應(yīng)用程序配置文件中的配置。
<system.serviceModel> <client> <endpoint name="ExpenseServiceManager" address="http://localhost:8081/ExpenseService/Manager" binding="wsHttpBinding" contract="ExpenseContracts.IExpenseServiceManager" /> </client> </system.serviceModel>您會(huì)發(fā)現(xiàn)端點(diǎn)定義與在宿主配置中聲明的定義完全相符。通常,只有在因網(wǎng)絡(luò)配置而造成客戶(hù)端與服務(wù)器的地址不同時(shí),或者在實(shí)現(xiàn)自定義行為時(shí),才會(huì)出現(xiàn)配置的差異。
如果安裝了 Windows SDK,您會(huì)發(fā)現(xiàn)一個(gè)用于自動(dòng)創(chuàng)建代理類(lèi)和端點(diǎn)配置的工具,您可以將其集成到您的解決方案中。目標(biāo)服務(wù)必須通過(guò) WSDL 或 WS-MetadataExchange 發(fā)布其元數(shù)據(jù)的說(shuō)明,才能利用此工具。
配置雙工信道到現(xiàn)在為止,我們一直在假設(shè)我們的通信流采用的是請(qǐng)求/響應(yīng)協(xié)作模式,消息由使用者發(fā)出并由服務(wù)做出應(yīng)答。WCF 支持許多備選的消息流,例如單向(“即發(fā)即棄”模式)或雙向雙工通信。如果處理每一方都可以啟動(dòng)會(huì)話(huà)的消息流,則需要使用雙工或雙向信道。對(duì)于那些連接更為緊密且支持沿任一方向發(fā)送數(shù)據(jù)的系統(tǒng)而言,雙工信道非常有效。例如,如果要提供始于事件處理過(guò)程的回調(diào),雙工信道將非常有用。
實(shí)現(xiàn)客戶(hù)端回調(diào)
在 WCF 中,客戶(hù)端回調(diào)通過(guò)一個(gè)稱(chēng)為 CallbackContracts 的概念來(lái)實(shí)現(xiàn)。對(duì)于所發(fā)布的約定,我們可以指定另一個(gè)約定來(lái)定義客戶(hù)端將發(fā)布的、可由服務(wù)上運(yùn)行的代碼回調(diào)的操作。
要聲明 CallbackContract,應(yīng)在發(fā)出回調(diào)的服務(wù)約定中指定接口類(lèi)型。
[ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))]您還需要使用支持雙工信道的綁定,如 netTcpBinding 或 wsDualHttpBinding。基于 TCP 的雙工通信通過(guò)在整個(gè)消息交換過(guò)程中所建立和維護(hù)的雙向連接來(lái)實(shí)現(xiàn)。而基于 HTTP 的雙工通信則通過(guò)對(duì)客戶(hù)端偵聽(tīng)程序的回調(diào)來(lái)實(shí)現(xiàn)。由于客戶(hù)端可能不知道其返回路徑,或者您可能希望通過(guò)配置對(duì)其進(jìn)行強(qiáng)定義,因此我們可以使用自定義綁定配置來(lái)聲明一個(gè)替代的 clientBaseAddress。
<endpoint binding="wsDualHttpBinding" bindingConfiguration="AlternativeClientCallback"/> <bindings> <wsDualHttpBinding> <binding name="AlternativeClientCallback" clientBaseAddress="http://localhost:8082/ExpenseService/ClientCallback"/> </wsDualHttpBinding> </bindings>在客戶(hù)端實(shí)現(xiàn)回調(diào)
實(shí)現(xiàn)回調(diào)約定的方式與實(shí)現(xiàn)服務(wù)約定的方式是完全相同的。我們必須提供所定義接口的實(shí)現(xiàn)。
class CallbackHandler : IExpenseServiceClientCallback { public void ExpenseReportReviewed( ExpenseReportReviewedRequest expenseReportReviewedRequest) { // 在此實(shí)現(xiàn)客戶(hù)端邏輯以對(duì)回調(diào)作出響應(yīng)。 } }為了使宿主回調(diào)到 CallbackHandler 類(lèi)的實(shí)例,建立的客戶(hù)端信道必須能夠知曉連接的雙工特性。
如之前所述,首先使用支持雙工信道的綁定。其次,在初始化與服務(wù)端點(diǎn)的連接時(shí),使用名為 DuplexChannelFactory 的 ChannelFactory 子類(lèi)版本,它將創(chuàng)建與服務(wù)的雙工連接。
private IExpenseServiceClient CreateChannelExpenseServiceClient() { InstanceContext context = new InstanceContext(new CallbackHandler()); DuplexChannelFactory<IExpenseServiceClient> factory = new DuplexChannelFactory<IExpenseServiceClient>(context, "ExpenseServiceClient"); IExpenseServiceClient proxy = factory.CreateChannel(); return proxy; }使用 DuplexChannelFactory 的主要不同之處在于,要先初始化 CallbackHandler 類(lèi)的實(shí)例,并將其傳遞給工廠(chǎng)構(gòu)造函數(shù),以便初始化回調(diào)所使用的上下文。
實(shí)現(xiàn)宿主的回調(diào)
從宿主的角度來(lái)看,我們可以通過(guò)在 IExpenseServiceClient 約定中定義的回調(diào)信道來(lái)獲取對(duì)面向客戶(hù)端的回調(diào)的一個(gè)引用。
[ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))] public interface IExpenseServiceClient : IExpenseServiceCallbackContract 屬性聲明用于定義宿主回調(diào)約定的接口。
為了進(jìn)行回調(diào),我們通過(guò)調(diào)用 OperationContext.Current.GetCallbackChannel 來(lái)獲取回調(diào)約定的引用,如下所示。
IExpenseServiceClientCallback callback = OperationContext.Current.GetCallbackChannel <IExpenseServiceClientCallback>(); callback.ExpenseReportReviewed(new ExpenseReportReviewedRequest(e.Report));在獲得對(duì)回調(diào)信道的引用后,我們即可正常地進(jìn)行調(diào)用。
結(jié)束語(yǔ)
Windows Workflow Foundation 提供了一個(gè)用于定義工作流的通用框架,以及一個(gè)用于托管運(yùn)行中工作流并與之進(jìn)行交互的、穩(wěn)健的運(yùn)行時(shí)引擎。
Windows Communication Foundation 提供了用于構(gòu)建互聯(lián)系統(tǒng)的通用框架,并為開(kāi)發(fā)人員提供了一致的 API 和豐富的功能集,來(lái)定義通信方式。
您可以將這兩種框架結(jié)合起來(lái),為在所處環(huán)境中構(gòu)建和部署分布式業(yè)務(wù)流程提供靈活而全面的應(yīng)用程序平臺(tái)。WF 允許您為業(yè)務(wù)邏輯和流程建模并進(jìn)行封裝,而 WCF 又為您提供了具有多種系統(tǒng)分布方式選擇的消息傳遞基礎(chǔ)結(jié)構(gòu)。
以下是您在設(shè)計(jì)服務(wù)時(shí)需要牢記的幾條原則:
| ? | 應(yīng)通過(guò)使用持久性服務(wù)來(lái)支持長(zhǎng)時(shí)間運(yùn)行的工作流。 |
| ? | 服務(wù)操作可以通過(guò)運(yùn)行工作流、通過(guò)引發(fā)事件進(jìn)行交互。應(yīng)將工作流設(shè)計(jì)為在需要介入時(shí)引發(fā)事件,在與外部(例如,外部服務(wù)或人員)進(jìn)行交互時(shí)響應(yīng)事件。 |
| ? | 工作流將異步執(zhí)行服務(wù)調(diào)用,因此考慮自服務(wù)返回的數(shù)據(jù)及當(dāng)時(shí)數(shù)據(jù)所處的狀態(tài),可有針對(duì)性地進(jìn)行設(shè)計(jì)。如果要使用同步方法,可以利用 ManualWorkflowSchedulerService 類(lèi)對(duì)工作流的執(zhí)行進(jìn)行手動(dòng)調(diào)度。 |
轉(zhuǎn)載于:https://www.cnblogs.com/vcool/archive/2008/04/18/1159945.html
總結(jié)
以上是生活随笔為你收集整理的WF 与 WCF 集成的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【操作系统】分区分配算法(首次适应算法、
- 下一篇: 渣打称中国房市出现泡沫