摘要:本文簡要介紹如何在通過 Windows Communication Foundation (WCF) 創(chuàng)建的服務(wù)內(nèi)托管用 Windows Workflow Foundation (WF) 所構(gòu)建的工作流。本文還將介紹如何利用 WCF 所提供的眾多功能中的幾個(gè),來通過雙工信道簡化客戶端事件回調(diào)。(本文還包含指向英文網(wǎng)頁的鏈接。) ![]() 本頁內(nèi)容
單擊此處下載“Windows Workflow Foundation 示例:WF 與 WCF 集成”。 簡介隨著 Windows Workflow Foundation (WF) 的問世,Microsoft 逐步將各種工作流功能引入了 .NET 開發(fā)人員平臺(tái)。這些功能使開發(fā)人員能夠構(gòu)建用于滿足各種應(yīng)用需求的工作流,從簡單的順序工作流到需要復(fù)雜的人員交互的復(fù)雜狀態(tài)機(jī)工作流。 與此同時(shí),業(yè)務(wù)能力越來越多地通過封裝的服務(wù)端點(diǎn)展現(xiàn)出來,這樣就可以重用和組合業(yè)務(wù)功能和業(yè)務(wù)流程,使面向服務(wù)的體系架構(gòu)更加完善。Windows Communication Foundation (WCF) 提供了統(tǒng)一的開發(fā)人員 API、穩(wěn)健的托管運(yùn)行時(shí)和靈活的配置驅(qū)動(dòng)解決方案來幫助進(jìn)行部署,進(jìn)而幫助開發(fā)人員通過各種功能來輕松開發(fā)互聯(lián)系統(tǒng)。 本文在結(jié)尾處列出了一些可用來進(jìn)一步了解 WF 和 WCF 的附加資源。 開支報(bào)告示例本文的代碼示例基于為員工報(bào)銷申請(qǐng)?zhí)峤慌c審批標(biāo)準(zhǔn)業(yè)務(wù)流程建立模型的“開支報(bào)告”工作流示例。我們?cè)谠纠A(chǔ)上進(jìn)行了更新,用以說明如何利用 WCF 和 .NET 3.0 Framework 來更有效地托管這一業(yè)務(wù)環(huán)節(jié)。 在發(fā)行的第一版“開支報(bào)告”示例中,使用 .NET 遠(yuǎn)程調(diào)用來提供客戶端應(yīng)用程序與包含工作流運(yùn)行時(shí)實(shí)例的宿主應(yīng)用程序之間的通信。 而經(jīng)我們重構(gòu)的開支報(bào)告示例在實(shí)現(xiàn)時(shí)使用 WCF 來執(zhí)行客戶端和服務(wù)端之間的通信。此外,該解決方案還實(shí)現(xiàn)了邏輯上的結(jié)構(gòu)化,從而將其中的各種問題分離出來。 ![]() 圖 1. 重構(gòu)后解決方案的結(jié)構(gòu) 了解消息在業(yè)務(wù)流程上下文中的使用方式非常重要,只有這樣您才能將它們?nèi)谌朐O(shè)計(jì)之中。在“開支報(bào)告”的生命周期中,存在幾個(gè)交互點(diǎn)。我們來簡單分析一下:
通過 WF,我們可以利用該框架所提供的標(biāo)準(zhǔn)活動(dòng)來為這一流程建模。我們可以使用 DelayActivity 來管理在一段時(shí)間后觸發(fā)的事件,可以使用規(guī)則引擎和 PolicyActivity 來管理一套靈活的規(guī)則,通過詢問這些規(guī)則可以獲得結(jié)果。 由于這是一個(gè)面向人員的流程,因此我們必須與最終用戶進(jìn)行交互,并將該交互交回到工作流中。WF 提供了“本地服務(wù)”、HandleExternalEventActivity 和 CallExternalMethodActivity,從而為實(shí)現(xiàn)宿主和工作流之間的通信提供了全面的編程模型。 由于這對(duì)于構(gòu)建交互式工作流而言是一個(gè)重要的概念,因此讓我們快來了解一下它是如何被設(shè)計(jì)到 WF 中的。 為了在 WF 中建立用戶交互的模型,我們必須設(shè)計(jì)用于提供許多事件和方法的約定。工作流和宿主進(jìn)程都應(yīng)理解這一約定。所構(gòu)建的約定/接口必須以 [ExternalDataExchange()] 屬性來標(biāo)記,這一屬性會(huì)將其標(biāo)識(shí)為專用于進(jìn)行工作流數(shù)據(jù)交換。在我們所列舉的示例中,工作流使用 IExpenseLocalService 接口。 然后我們訂閱一個(gè)與工作流運(yùn)行時(shí)實(shí)現(xiàn)該接口的類(稱為本地服務(wù))。工作流活動(dòng)可以注冊(cè)到事件或者使用接口類型上定義的方法,并將綁定到我們已經(jīng)注冊(cè)的本地服務(wù)。這采用的是一種稱為“控制反轉(zhuǎn)”的模式,這種模式消除了工作流與具體類型本地服務(wù)之間的緊耦合。在我們所列舉的示例中,ExpenseLocalService 類實(shí)現(xiàn)了 IExpenseLocalService 約定。 工作流在第一次運(yùn)行時(shí),可以獲得一個(gè)用于執(zhí)行操作的初始數(shù)據(jù)包。當(dāng)工作流到達(dá)需要外部交互的點(diǎn)后,我們可以引發(fā)一個(gè)能夠在工作流中綁定到 HandleExternalEventActivity 的事件。此活動(dòng)將接口類型和事件作為參數(shù),當(dāng)該事件被引發(fā)后,工作流會(huì)被喚醒,使操作繼續(xù)下去。 如果工作流必須對(duì)本地服務(wù)進(jìn)行回調(diào),可以使用 CallExternalMethodActivity 并以接口和方法名為參數(shù)來實(shí)現(xiàn)。 通過這些活動(dòng),可以在宿主進(jìn)程中與運(yùn)行中的工作流實(shí)現(xiàn)雙向通信,而通過在 WF 中使用“控制反轉(zhuǎn)”模式,避開了工作流和本地服務(wù)之間的緊耦合。 然而,一旦跨出宿主進(jìn)程這一范圍,我們就必須允許交互由其他系統(tǒng)甚至是人來驅(qū)動(dòng)。為實(shí)現(xiàn)這一級(jí)別的交互,我們可以通過服務(wù)來分配交互操作,而這些服務(wù)進(jìn)而再由其他服務(wù)或用戶驅(qū)動(dòng)的應(yīng)用程序來調(diào)用。而 WCF 就是能夠靈活構(gòu)建這一消息傳遞功能的框架。 在這個(gè)與 WCF 集成的方案中,主要的優(yōu)點(diǎn)在于:
集成工作檢查表為了實(shí)現(xiàn) WF 與 WCF 的集成,必須有一個(gè)將為使用者提供眾多接口點(diǎn)的服務(wù)接口,使用者可以在這些接口點(diǎn)啟動(dòng)運(yùn)行中的工作流或與之交互。應(yīng)圍繞業(yè)務(wù)流程與外部實(shí)體(例如這一流程所涉及的人員)進(jìn)行交互的這些接口點(diǎn)來建立服務(wù)模型。 ![]() 圖 2.“開支報(bào)告”方案中的交互點(diǎn) 為此,我們必須:
除了簡單地托管工作流之外,我們還可以利用 WCF 雙工信道將事件從工作流交回給作為使用方的客戶端。就“開支報(bào)告”而言,這是非常有益的,因?yàn)樵摻鉀Q方案依靠客戶端進(jìn)行服務(wù)輪詢來實(shí)現(xiàn)定期的數(shù)據(jù)更新。但這些客戶端也可以直接從服務(wù)處獲得通知。 為此,我們必須:
定義服務(wù)約定Windows Communication Foundation (WCF) 要求聲明一份正式的約定來抽象地定義服務(wù)功能和數(shù)據(jù)交換。這份約定通過聲明接口以代碼的形式定義。 在設(shè)計(jì)業(yè)務(wù)服務(wù)時(shí),通常會(huì)使用“請(qǐng)求/響應(yīng)”協(xié)作模式。使用這種模式時(shí),在所提供的約定中必須考慮三方面因素,即:
服務(wù)約定使用基于屬性的標(biāo)記來定義,它首先定義提供操作的約定,然后再定義通過網(wǎng)絡(luò)發(fā)布的具體操作。 每個(gè)服務(wù)約定都以 [ServiceContract] 屬性明確標(biāo)記。此屬性可通過下列參數(shù)進(jìn)行聲明:
聲明操作在約定定義之后,服務(wù)再由許多發(fā)布的操作構(gòu)成。操作通過 [OperationContract] 屬性標(biāo)記被明確地加入約定之中。與 ServiceContract 一樣,OperationContract 也有許多用于控制與端點(diǎn)的綁定方式的參數(shù)。這些參數(shù)包括:
以下是代碼形式服務(wù)約定的示例。 [ServiceContract] public interface IExpenseService { [OperationContract] GetExpenseReportsResponse GetExpenseReports(); [OperationContract] GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest getExpenseReportRequest); } 聲明消息和數(shù)據(jù)實(shí)體在構(gòu)建消息的模型時(shí),您可能希望采用類的形式,來為每個(gè)將發(fā)送的消息定義負(fù)載或正文。這與在以 ASP.NET 構(gòu)建 Web 服務(wù)時(shí)使用 WS Contract First (WSCF) 等工具來構(gòu)建消息模型的方式十分相似。 在默認(rèn)情況下,WCF 使用稱為 DataContractSerializer 的序列化引擎來實(shí)現(xiàn)數(shù)據(jù)的序列化和反序列化(即與 XML 的來回轉(zhuǎn)換)。我們通過添加對(duì) System.Runtime.Serialization 命名空間的引用來利用 DataContractSerializer,然后以 [DataContract] 屬性標(biāo)記類,以 [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í)體。就像消息約定一樣,我們可以通過 DataContractSerializer 和標(biāo)記屬性來明確加入所分配的成員;或者,如果我們只想構(gòu)建數(shù)據(jù)模型,可以使用公共字段的方法,并將類標(biāo)記為可序列化類。 在本示例中,我們使用了數(shù)據(jù)約定的方法來標(biāo)記消息傳遞。在實(shí)際的工作中,您常常會(huì)需要面對(duì)更為復(fù)雜的架構(gòu),需要在架構(gòu)中使用屬性,以及需要使用 SOAP 標(biāo)頭。WCF 支持定義以 [MessageContract] 屬性(可描述整個(gè) SOAP 封裝,而不僅僅是正文)標(biāo)記的類,從而解決了這些尖銳的問題。 有關(guān)數(shù)據(jù)約定和消息約定的詳細(xì)信息,請(qǐng)參閱本文結(jié)尾處更多信息部分所列的相應(yīng) MSDN 庫文章。 托管工作流運(yùn)行時(shí)服務(wù)通常會(huì)支持將創(chuàng)建新服務(wù)類型實(shí)例并在整個(gè)會(huì)話生命周期內(nèi)維護(hù)該服務(wù)類型實(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ò)展類。此擴(kuò)展類會(huì)創(chuàng)建和維護(hù)工作流運(yùn)行時(shí)的全局實(shí)例,使每個(gè)單獨(dú)的服務(wù)實(shí)例都能夠訪問它。 為在 ServiceHost 上實(shí)現(xiàn)擴(kuò)展,應(yīng)創(chuàng)建一個(gè)實(shí)現(xiàn) IExtension<ServiceHostBase> 的類。在此解決方案中,可以在 WcfExtensions 代碼項(xiàng)目下的 WfWcfExtension 類中找到這樣一個(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(); } 如您所見,工作流運(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 類中 SetUpWorkflowEnvironment 方法的一部分,我們?cè)?WorkflowRuntime 啟動(dòng)后將 ExpenseLocalService 實(shí)例添加到 ExternalDataExchangeService 中。 以下所示的 Detach 方法通過調(diào)用 StopRuntime 來關(guān)閉運(yùn)行時(shí)。 void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner) { workflowRuntime.StopRuntime(); } 由于 WorkflowRuntime 在服務(wù)宿主啟動(dòng)過程中創(chuàng)建并初始化,因此在服務(wù)調(diào)用執(zhí)行前,任何現(xiàn)有的工作流都可以繼續(xù)工作。當(dāng)服務(wù)宿主終止時(shí),工作流運(yùn)行時(shí)會(huì)隨之徹底關(guān)閉。 注意 在托管工作流和構(gòu)建長期工作流模型時(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ù)行為的類,必須實(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 來實(shí)例化所需工作流類型的一個(gè)新實(shí)例。我們已在 ServiceHost 擴(kuò)展類中創(chuàng)建了一個(gè)這樣的實(shí)例。為了獲取對(duì)該實(shí)例的引用,我們必須使用 OperationContext 來找到自定義的擴(kuò)展。 WfWcfExtension extension = OperationContext.Current.Host.Extensions.Find<WfWcfExtension>(); workflowRuntime = extension.WorkflowRuntime; OperationContext 是供我們?cè)L問服務(wù)方法擴(kuò)展上下文的類。正如您在之前的代碼中所看到的,它提供一個(gè)名為 Current 的單例,來為我們展示當(dāng)前服務(wù)方法的上下文。我們調(diào)用 Host 屬性來將實(shí)例返回給所屬的 ServiceHost,然后根據(jù)其類型找到擴(kuò)展。 在獲得擴(kuò)展實(shí)例的引用后,可以通過公共屬性返回 WorkflowRuntime,并用它來創(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ù)定義的類型創(chuàng)建了新的工作流實(shí)例。雖然這也可以通過直接的類型實(shí)例化來實(shí)現(xiàn),但上述的方式告訴我們,其實(shí)可以在運(yùn)行時(shí)基于動(dòng)態(tài)規(guī)則(而不是通過強(qiáng)類型化綁定)來靈活地創(chuàng)建工作流。 最后一行代碼會(huì)引發(fā)由工作流中第一個(gè) HandleExternalEventActivity 處理的事件,標(biāo)志著工作流的開始。我們通過 ExpenseLocalService 類的實(shí)例來引發(fā)這一事件。在本示例中,ExpenseLocalService 會(huì)啟動(dòng)新的工作流或?qū)⑹录唤o現(xiàn)有工作流,從而與工作流進(jìn)行交互。我們將此類用作封裝業(yè)務(wù)流程的機(jī)制。在內(nèi)部,我們使用 WF 來實(shí)現(xiàn)這一機(jī)制。 ![]() 圖 3. 工作流始于 HandleExternalEventActivity。 我們將要應(yīng)對(duì)的另一類情況是如何回調(diào)到現(xiàn)有工作流中并引發(fā)事件。我們必須將事件交給將促使現(xiàn)有工作流接收事件并繼續(xù)進(jìn)行處理的工作流引擎。 在“開支報(bào)告”流程中引發(fā)事件的一個(gè)示例就是在需要管理人員進(jìn)行審批之時(shí)發(fā)生的。工作流將對(duì) RequestManagerApproval 調(diào)用外部方法,用以向管理人員發(fā)出警報(bào),告訴他們必須批準(zhǔn)或拒絕新的開支報(bào)告。 工作流中包含在一個(gè)可能事件發(fā)生前一直處于阻塞狀態(tài)的 ListenActivity。在本例中,我們會(huì)接收到一個(gè)事件,指示管理人員已經(jīng)審核該報(bào)告,或者已經(jīng)超過 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),來調(diào)用引發(fā) ExpenseReportReviewed 事件的 SubmitReviewedExpenseReport 方法。 引發(fā)交給工作流中 HandleExternalEventActivity 的事件時(shí),必須知道所處理工作流實(shí)例的 GUID,使事件能夠得以路由。 每個(gè)事件都以 EventArgs 引發(fā),使我們能夠通過事件模型將數(shù)據(jù)傳遞回工作流。在本例中,我們可以傳遞詳細(xì)的當(dāng)前報(bào)表狀態(tài)信息和審核活動(dòng)上下文數(shù)據(jù)信息。 在工作流中,事件通過 HandleExternalEventActivity 的屬性自動(dòng)綁定到工作流。 ![]() 圖 5. 將 HandleExternalEventActivity 綁定到 IExpenseLocalService 接口。 首先指定必須以 [ExternalDataExchange] 屬性標(biāo)記的接口類型,然后指定該接口上 HandleExternalEventActivity 將訂閱的事件。 事件參數(shù)必須從 ExternalDataEventArgs 類派生而來。這至少意味著每個(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)托管,首先讓我們來了解一下幾種可用的備選方案:
構(gòu)建 Web 服務(wù)時(shí),您通常會(huì)選擇使用 Internet Information Services 進(jìn)行托管。而在構(gòu)建將用作后臺(tái)程序的單個(gè)實(shí)例端口時(shí),您通常會(huì)選擇通過 Windows 服務(wù)進(jìn)行托管。 在本示例中,我們?cè)谝粋€(gè) Windows 控制臺(tái)應(yīng)用程序中托管主要的服務(wù)實(shí)例,托管方式與 Windows 服務(wù)的托管方式類似。 為了部署服務(wù),我們必須創(chuàng)建 ServiceHost 類的一個(gè)實(shí)例,并針對(duì)要發(fā)布的每個(gè)服務(wù)類型開放其端點(diǎn)。ServiceHost 將許多參數(shù)作為其構(gòu)造函數(shù)的一部分;但主要參數(shù)是 Type 參數(shù)或?qū)崿F(xiàn) ServiceContract 的類的實(shí)例。
有關(guān) WCF 中實(shí)例化與并發(fā)性的詳細(xì)信息,請(qǐng)參閱 MSDN 庫中的 Sessions, Instancing, and Concurrency。 宿主建立起來后,會(huì)分析所有可用的配置(在下面的配置部署部分做詳細(xì)介紹)并將其與任何明確添加的配置合并,來確定可用的端點(diǎn)并針對(duì)發(fā)布開放那些端點(diǎn)。宿主接收到從客戶端發(fā)來的調(diào)用后,會(huì)在新的后臺(tái)工作線程上處理請(qǐng)求,并按消息中 SOAP 約定名稱和動(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í),必須首先為連接開放端點(diǎn)。為此,您可以在調(diào)用 .Open() 之前與宿主對(duì)象進(jìn)行交互,如前面代碼所示。建議您利用作用域在使用前對(duì) ServiceHost 進(jìn)行處置,并建議您在該作用域結(jié)束時(shí)明確地調(diào)用 Close() 來徹底關(guān)閉所有活動(dòng)的連接和端點(diǎn)。 配置部署WCF 提供了一種機(jī)制,允許通過 XML 配置來配置端點(diǎn),從而將部署問題從實(shí)現(xiàn)工作中分離出來。這使得管理員無需重新部署代碼即可修改服務(wù)策略。 每個(gè)服務(wù)在一個(gè)或多個(gè)端點(diǎn)上發(fā)布。一個(gè)端點(diǎn)就是一個(gè)可尋址的連接點(diǎn),客戶端可對(duì)其使用服務(wù)。在 WCF 中,每個(gè)端點(diǎn)都以三個(gè)屬性聲明,這三個(gè)屬性就是人們所熟知的 WCF 的 ABC。 它們是“地址”(A)、“綁定”(B) 和“約定”(C)。 地址:此端點(diǎn)的唯一可尋址位置。通常情況下,地址是為您提供絕對(duì)地址的 URI,服務(wù)在該地址處偵聽請(qǐng)求,例如:http://myhost/myservice 或 net.tcp://myhost:400/myservice 綁定:用于規(guī)定服務(wù)與其使用者之間通信協(xié)議的策略。綁定指定了幾個(gè)方面的要素,例如所使用的傳輸類型、消息的編碼方式以及數(shù)據(jù)的序列化方式。WCF 附帶了許多用于支持最常見情況的現(xiàn)成綁定。 約定:通過接口以代碼形式定義的、要發(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 類型的服務(wù)聲明配置。這樣,運(yùn)行時(shí)就可以在我們根據(jù)此類型實(shí)例化新的 ServiceHost 時(shí)找到該配置。有關(guān)綁定的詳細(xì)信息,請(qǐng)參閱 MSDN 庫中的 WCF Bindings。 使用服務(wù)通過 ChannelFactory 類,可以利用 WCF 來使用服務(wù)。ChannelFactory 通過工廠模式為我們提供連接到配置中指定端點(diǎn)的服務(wù)約定代理實(shí)例。我們可以使用運(yùn)行時(shí)信息(例如用于消息加密的安全憑據(jù)和證書)配置工廠,或者動(dòng)態(tài)地確定端點(diǎn)信息。 private IExpenseServiceManager CreateChannelExpenseServiceManager() { ChannelFactory<IExpenseServiceManager> factory = new ChannelFactory<IExpenseServiceManager>("ExpenseServiceManager"); IExpenseServiceManager proxy = factory.CreateChannel(); return proxy; } 如您所見,我們首先創(chuàng)建了一個(gè)工廠實(shí)例,該實(shí)例對(duì)服務(wù)約定使用泛型參數(shù),以構(gòu)造出將僅返回所需約定實(shí)例的更精確的工廠。我們還指定了用于確定端點(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ò)配置而造成客戶端與服務(wù)器的地址不同時(shí),或者在實(shí)現(xiàn)自定義行為時(shí),才會(huì)出現(xiàn)配置的差異。 如果安裝了 Windows SDK,您會(huì)發(fā)現(xiàn)一個(gè)用于自動(dòng)創(chuàng)建代理類和端點(diǎn)配置的工具,您可以將其集成到您的解決方案中。目標(biāo)服務(wù)必須通過 WSDL 或 WS-MetadataExchange 發(fā)布其元數(shù)據(jù)的說明,才能利用此工具。 配置雙工信道到現(xiàn)在為止,我們一直在假設(shè)我們的通信流采用的是請(qǐng)求/響應(yīng)協(xié)作模式,消息由使用者發(fā)出并由服務(wù)做出應(yīng)答。WCF 支持許多備選的消息流,例如單向(“即發(fā)即棄”模式)或雙向雙工通信。如果處理每一方都可以啟動(dòng)會(huì)話的消息流,則需要使用雙工或雙向信道。對(duì)于那些連接更為緊密且支持沿任一方向發(fā)送數(shù)據(jù)的系統(tǒng)而言,雙工信道非常有效。例如,如果要提供始于事件處理過程的回調(diào),雙工信道將非常有用。 實(shí)現(xiàn)客戶端回調(diào)在 WCF 中,客戶端回調(diào)通過一個(gè)稱為 CallbackContracts 的概念來實(shí)現(xiàn)。對(duì)于所發(fā)布的約定,我們可以指定另一個(gè)約定來定義客戶端將發(fā)布的、可由服務(wù)上運(yùn)行的代碼回調(diào)的操作。 要聲明 CallbackContract,應(yīng)在發(fā)出回調(diào)的服務(wù)約定中指定接口類型。 [ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))] 您還需要使用支持雙工信道的綁定,如 netTcpBinding 或 wsDualHttpBinding?;?TCP 的雙工通信通過在整個(gè)消息交換過程中所建立和維護(hù)的雙向連接來實(shí)現(xiàn)。而基于 HTTP 的雙工通信則通過對(duì)客戶端偵聽程序的回調(diào)來實(shí)現(xiàn)。由于客戶端可能不知道其返回路徑,或者您可能希望通過配置對(duì)其進(jìn)行強(qiáng)定義,因此我們可以使用自定義綁定配置來聲明一個(gè)替代的 clientBaseAddress。 <endpoint binding="wsDualHttpBinding" bindingConfiguration="AlternativeClientCallback"/> <bindings> <wsDualHttpBinding> <binding name="AlternativeClientCallback" clientBaseAddress="http://localhost:8082/ExpenseService/ClientCallback"/> </wsDualHttpBinding> </bindings> 在客戶端實(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)客戶端邏輯以對(duì)回調(diào)作出響應(yīng)。 } } 為了使宿主回調(diào)到 CallbackHandler 類的實(shí)例,建立的客戶端信道必須能夠知曉連接的雙工特性。 如之前所述,首先使用支持雙工信道的綁定。其次,在初始化與服務(wù)端點(diǎn)的連接時(shí),使用名為 DuplexChannelFactory 的 ChannelFactory 子類版本,它將創(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 類的實(shí)例,并將其傳遞給工廠構(gòu)造函數(shù),以便初始化回調(diào)所使用的上下文。 實(shí)現(xiàn)宿主的回調(diào)從宿主的角度來看,我們可以通過在 IExpenseServiceClient 約定中定義的回調(diào)信道來獲取對(duì)面向客戶端的回調(diào)的一個(gè)引用。 [ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))] public interface IExpenseServiceClient : IExpenseService CallbackContract 屬性聲明用于定義宿主回調(diào)約定的接口。 為了進(jìn)行回調(diào),我們通過調(diào)用 OperationContext.Current.GetCallbackChannel 來獲取回調(diào)約定的引用,如下所示。 IExpenseServiceClientCallback callback = OperationContext.Current.GetCallbackChannel <IExpenseServiceClientCallback>(); callback.ExpenseReportReviewed(new ExpenseReportReviewedRequest(e.Report)); 在獲得對(duì)回調(diào)信道的引用后,我們即可正常地進(jìn)行調(diào)用。 結(jié)束語Windows Workflow Foundation 提供了一個(gè)用于定義工作流的通用框架,以及一個(gè)用于托管運(yùn)行中工作流并與之進(jìn)行交互的、穩(wěn)健的運(yùn)行時(shí)引擎。 Windows Communication Foundation 提供了用于構(gòu)建互聯(lián)系統(tǒng)的通用框架,并為開發(fā)人員提供了一致的 API 和豐富的功能集,來定義通信方式。 您可以將這兩種框架結(jié)合起來,為在所處環(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í)需要牢記的幾條原則:
更多信息
致謝特此對(duì)以下付出過辛勤勞動(dòng)的貢獻(xiàn)者和審核人員表示萬分的感謝:
作者簡介 Jeremy Boyd 是 Intergen 公司(總部位于新西蘭的解決方案供應(yīng)商,是 Microsoft 金牌認(rèn)證合作伙伴)的高級(jí)技術(shù)顧問,也是 MSDN 新西蘭區(qū)區(qū)域總監(jiān)。在過去的 12 個(gè)月里,Jeremy 與客戶積極合作,致力于幫助實(shí)現(xiàn)基于 WF 和 WCF 的解決方案,并通過他的博客幫助其他開發(fā)人員了解這些技術(shù)所帶來的益處。 |
|