WinVista新技術 WCF開發指南之構建服務
作者:朱先忠編譯
一. 引言Windows通訊基礎(簡稱為WCF)是一種SDK,用于讓你使用典型的CLR編程結構(例如用于發布和消費服務的類和接口等)來構建Windows面向服務的應用程序。WCF的編程模型是聲明性的并且大部分是屬性驅動的。WCF為通訊服務提供了一種運行時刻環境,使你能夠把CLR類型暴露為服務并且以CLR類型來消費服務。盡管在理論上你可以不用WCF來構建服務;但是,在實踐中,WCF能夠顯著地簡化這一任務。WCF是微軟的一組工業標準的實現,該標準定義了服務交互、類型轉化、編排和多種協議的管理。因此,WCF提供了服務間的互操作性并且提高了開發效率(包括幾乎任何應用程序所要求實現的基本的常規的繁重任務)。本文將描述WCF塊及其架構的基本概念和構建,從而使你能夠構建簡單的服務。二. 什么是WCF服務?一個服務是一個暴露給外界的功能單元。從編程模型的發展歷史來說,它經歷了從函數到對象再到組件最后到服務的過程;而WCF服務正代表了下一代的革命性的Windows編程模型。面向服務(SO)是一組原則的抽象集和針對于構建SO應用程序的最好實踐,但這其中的一大部分已經超出了本文的范圍。一個面向服務的應用程序(SOA)把服務聚合成單個邏輯的應用程序(見圖1),這類似于一個面向組件的應用程序聚合組件或一個面向對象的應用程序聚合對象的方式。服務可以是本地的也可以是遠程的,可以由多種團隊使用任何技術開發而成,它們可以被獨立地進行版本化管理,甚至可以在不同的時間進度上執行。在一個服務內部,你可以使用例如語言,技術,平臺,版本和框架等概念;然而,在服務之間,只允許使用規定的通訊模式。 圖1:一個面向服務的應用程序的框架??蛻舳送ㄟ^發送和接收消息與服務進行交互。消息可以從客戶端直接或經中介傳輸到服務。在WCF中,所有的消息都是SOAP消息。注意,這些消息獨立于傳輸協議——不象Web服務,WCF服務可以通過多種傳輸協議進行通訊,而不僅是HTTP。在WCF中,客戶端從不直接與服務進行交互,即使在當處理一個本地的內存中服務時。而是,客戶端總是使用一個代理來把該調用轉發給服務。WCF允許客戶端跨越所有執行邊界與服務進行通訊。在同一臺計算機上(見圖2),客戶端可以跨越同一進程中的應用程序域或進程來消費同一個應用程序域中的服務。通過跨越計算機邊界(圖3),客戶端能夠在企業內網或跨越因特網與服務進行交互。圖3.跨機器通訊:這里是一個跨機器使用WCF通訊的例子。圖2.使用WCF在同一臺機器上通訊。因為所有的交互是經由一個代理實現的,所以對于本地和遠程情況下,WCF保持相同的編程模型,這樣以來不僅能夠使你進行位置切換而不影響客戶端,而且顯著地簡化應用程序編程模型。大多數WCF功能被包括到位于System.ServiceModel命名空間的單個的程序集System.ServiceModel.dll中。三. 服務地址在WCF中,每一個服務都與唯一一個地址相聯系。該地址提供了兩個重要的元素:服務的位置和用于與服務進行通訊的傳輸協議。地址的位置部分指出目標計算機名,站點或網絡,一個通訊端口,管道或隊列,還有一個可選的特定的路徑或URI。至于傳輸,WCF 1.0支持下列:· HTTP· TCP· 端對端網絡· IPC(通過命名管道進行的進程間通訊)· MSMQ地址總是使用如下格式:[base address]/[optional URI]其中,基地址總是使用如下格式:[transport]://[Machine or domain][:optional port]下面是一些可能的服務地址:http://localhost:8001http://localhost:8001/MyServicenet.tcp://localhost:8002/MyServicenet.pipe://localhost/MyPipenet.msmq://localhost/private/MyService
四. 服務合同在WCF中,所有的服務都暴露合同。合同是一種描述服務所實現功能的平臺中立的標準的方式。WCF定義了四種類型的合同:· 服務合同描述你可以在服務上執行哪些操作?!?數據合同定義哪些數據類型被傳入和傳出服務。WCF為內置類型定義隱式合同,例如int和string,但是你可以容易地為定制類型定義顯式的選入式數據合同?!?錯誤合同定義哪些錯誤將被該服務所激發,以及該服務怎樣處理錯誤信息和把如何把它們傳播到客戶端?!?消息合同允許服務直接與消息進行交互。消息合同可以被類型化或非類型化,并且有點類似于CLR中的遲綁定調用。不過,消息合同很少為SOA開發者所用。在這4種類型的合同中,本文將集中討論服務合同。你可以使用ServiceContractAttribute來定義一個服務合同,并且你可以把該屬性應用于一個接口或一個類,如列表1(見本文相應下載源碼)所示。服務合同獨立于接口或類可見性-公共或內部可見性是一個CLR概念,而不是WCF概念。在一個內部接口上應用ServiceContractAttribute將把該接口暴露為一個公共服務合同(可以跨越服務邊界進行消費)。沒有ServiceContractAttribute的話,該接口對WCF客戶端是不可見的,這與面向服務的宗旨一致(服務邊界是顯式的)。為了強制實現這一點,所有的合同必須是嚴格選入的。OperationContractAttribute僅能被應用到方法(而不是屬性,索引器或事件,這都是一些CLR概念)中。OperationContractAttribute把一個合同方法暴露為在服務合同上執行的一種邏輯操作。該接口上的其它不具有OperationContractAttribute屬性的方法不會成為合同的一部分。這可以強制實現顯式的服務邊界,并且,對于操作本身來說,保持一種選入模型。注意,合同操作獨立于方法可見性。列表1展示了通過定義一個合同接口把服務合同與其實現分離開來的最好應用。另外,你還可以直接把ServiceContractAttribute和OperationContractAttribute應用于類,在這種情況下,WCF使用OperationContractAttribute從類中推斷出一個服務合同和方法。這是一種應該盡量避免使用的技術: //盡量避免使用[ServiceContract]class MyService{ [OperationContract] //可見性并不要緊 string MyMethod(string text) {return 'Hello ' + text; } public string MyOtherMethod(string text) {return 'Cannot call this method over WCF'; }}這個ServiceContractAttribute把CLR接口(或推斷的接口)映射到一個技術中立的WCF合同上。通過派生和實現多個帶有ServiceContractAttribute的接口,單個類可以支持多個合同。類能夠通過隱式或顯式方式實現這個接口,因為該方法可見性對WCF沒有任何影響。然而,存在許多實現約束:避免使用參數化的構造器,因為WCF僅使用默認的構造器。盡管該類能夠使用內部屬性,索引器和靜態成員,但是沒有WCF客戶端能夠存取它們。五. 宿主每個WCF服務必須宿主在一個Windows進程中(稱為宿主進程)。單個宿主進程可以宿主多個服務,而相同的服務類型可以宿主在多個進程中。WCF并不要求是否該宿主進程也是客戶端進程。顯然,應該有一個獨立的進程支持錯誤和安全的隔離。另外,誰提供進程或調用哪種類型的進程都不是實質性的問題。這個宿主可以由IIS或Windows Vista中的Widows活動服務(WAS)或由開發者作為應用程序的一部分來提供。六. IIS宿主在IIS中宿主一個服務的主要優點是,在發生客戶端請求時宿主進程會被自動啟動,并且你可以依靠IIS來管理宿主進程的生命周期。IIS宿主的主要不利在于,你僅僅可以在IIS5和IIS6上使用HTTP傳輸數據;而且當使用IIS5時,你僅可以使用80端口。在IIS上宿主非常類似于宿主一個典型的ASMX Web服務。你需要在IIS下創建一個虛擬的目錄并且提供一個.svc文件。這個.svc文件的功能就象一個被用來標識服務的code-behind文件和類的.asmx文件一樣。<%@ ServiceHost Language = 'C#' Debug = 'true'CodeBehind = '~/App_Code/MyService.cs'Service = 'MyService'%>你甚至可以把服務代碼以內聯方式注入到.svc文件中,但是不建議這樣用(就象對于ASMX的情形一樣)。一旦你準備好了.svc文件,你就可以使用一個瀏覽器來觀看它。如果一切順利,那么你將得到一個確認頁面。Visual Studio 2005能夠為你生成一個新的IIS宿主的服務。這只要從File菜單下選擇'New Website',然后從'New Web Site'對話框中選擇WinFX服務。這使得Visual Studio 2005創建一個新的Web站點,服務代碼和匹配的.svc文件。另外,Web站點配置文件必須列舉出你想要暴露的服務類型。你需要使用完全限定類型名(包括程序集名),如果類型來自于一個未引用的程序集的話。<system.serviceModel><services><service name='MyNamespace.MyService'>...</service></services></system.serviceModel>七. 自宿主自宿主是當開發者負責提供和管理宿主進程的生命周期時使用的技術名詞。自宿主被應用在位于客戶端和服務之間的一個進程(或計算機)邊界環境中,以及當使用進程中服務的情況下(也就是說,與客戶端處于相同的進程中)。你需要提供的進程可能是任何Windows進程,例如,一個Windows表單應用程序,一個控制臺應用程序或一個Windows NT服務。注意,該進程必須在客戶端調用服務之前先運行起來;典型情況下,這意味著,你必須預先啟動它。對于NT進程中服務來說這并不是一個問題。類似于IIS宿主,宿主應用程序配置文件必須列出你想宿主的服務的類型并且暴露給外界。而且,該宿主進程必須在運行時刻顯式地注冊服務類型并且打開該宿主以便于客戶端調用。典型地,這是在Main()方法中使用如下定義的助理類ServiceHost實現的:public interface ICommunicationObject : IDisposable{void Open();void Close();//更多成員}public abstract class CommunicationObject : ICommunicationObject{...}public class ServiceHostBase : CommunicationObject,...{...}public class ServiceHost : ServiceHostBase,...{public ServiceHost(Type serviceType,params Uri[]baseAddresses);//更多成員}提供給ServiceHost的構造函數的信息有:服務類型和(可選)默認的基地址。該基地址集可以是一個空集(以后,你可以配置不同的基地址)。擁有一組基地址能夠使服務接受在多個地址和協議上的調用。注意,每個ServiceHost實例都關聯與一個特定的服務類型,并且如果宿主進程需要宿主多個類型的服務的話,你需要一些匹配的ServiceHost實例。通過調用宿主中的ServiceHost.Open()方法,你允許調入(call-in);并且通過調用ServiceHost.Close()方法,你可以體面地退出宿主實例并完成到當前客戶端的數據發送,并且還要拒絕未來的客戶端調用-即使宿主進程仍在運行中。典型地,關閉操作是在宿主進程關閉時實現的。例如,為了把這個服務宿主在一個Windows表單應用程序中:[ServiceContract]interface IMyContract{...}class MyService : IMyContract{...}你可以編寫:public static void Main(){Uri baseAddress = new Uri('http://localhost:8000/');ServiceHost serviceHost;serviceHost = new ServiceHost(typeof(MyService),baseAddress);serviceHost.Open();//能夠攔截調用:Application.Run(new MyForm());serviceHost.Close();}注意,你可以在調用ServiceHost.Open()之后攔截調用,因為該宿主接收在工作者線程上的所有調用。對ServiceHost.Open()的調用將加載WCF運行時刻并且支持接收客戶端調用。該宿主能注冊多個基地址,只要它們至少在傳輸方面存在不同:Uri tcpBaseAddress = new Uri('net.tcp://localhost:8001/');Uri httpBaseAddress = new Uri('http://localhost:8002/');ServiceHost serviceHost = new ServiceHost(typeof(MyService),tcpBaseAddress,httpBaseAddress);通過從'Add New Item'對話框中選擇WCF服務,Visual Studio 2005允許你把一個WCF服務添加到任何應用程序工程。以此方式添加的服務當然是進程中服務(相對于宿主進程來說),但是也可以由外部客戶端存取。
八. WAS宿主Windows活動服務(WAS)是可用于Windows Vista中的一種系統服務。WAS是IIS7的一部分,但是可以被獨立地配置。為了使用WAS來宿主通訊WCF服務,你需要提供一個.svc文件。該WAS提供與IIS和自宿主相比更多的優點,包括空閑時間管理、標識管理、應用程序池、隔離等等,并且是選擇的宿主進程(在可用的情況下)。而且,這種自宿主的進程為進程內宿主提供獨特的優點:處理未知的客戶環境、依賴于TCP或IPC(當只有IIS可用時)、利用HTTP上的多個端口(當只有IIS 6可用時)以及通過編程方式存取一些高級宿主特征。 九. 綁定與任何給定的服務進行通訊都存在多方面的問題。首先,存在許多可能的通訊模式:消息可能是同步請求/響應或異步式的'激活-忘記'模式(fire-and-forget);消息也可能是雙向的;消息能夠被立即傳輸或隊列化操作而該隊列可能是持久性的或易破壞性的。還存在許多可能的消息傳輸協議:例如HTTP(或HTTPS),TCP,P2P(端對端網絡),IPC(命名管道)或MSMQ。也存在一些可能的消息編碼選項:你可以選擇普通文本以支持互操作性,二進制編碼以便于優化性能,或MTOM(消息傳輸優化機制)以便處理巨大載荷。也存在一些消息保護選項:你可以選擇不對之進行保護,你也可以使用它們來僅提供傳輸級安全或提供消息級隱私和安全,并且當然,也存在很多種方式用于對客戶端實現認證和授權。消息傳輸在跨越中間媒體和中斷連接時可能是不可靠的或可靠的端到端式,并且消息可能是以其發送的方式傳輸的或是以其接收方式傳輸的。 通訊服務可能需要與其它服務或僅能夠使用基本Web服務協議的客戶端互操作,或者它們能夠使用WS-*現代協議的核心(例如WS-安全和WS-原子事務)。通訊服務可能需要與舊式的客戶端通過原始的MSMQ消息進行互操作,或你可能想限制通訊服務以便僅與另一個WCF服務或客戶端互操作。 簡言之,通訊存在許多方面的內容,包括大量的參數和決策點。其中,一些選擇可能是互斥的,而另一些選擇可能要求必須使用另外的相應選擇。很明顯,客戶端和服務必須在所有這些選項上相吻合,以達到正確交流的目的。為了簡化并使其更具可管理性,WCF小組共同在綁定中提供了一個這樣的通訊方面集合。個綁定僅僅是對于相協調的傳輸協議、消息編碼、通訊模式、可靠性、安全性、事務傳播和互操作性的預封裝。理想情況下,你能夠從通訊服務代碼中'提取'所有的這些繁重的任務方面并且允許它專注于實現業務邏輯。這樣做可以使你在相當不同的繁重任務方面使用相同的服務邏輯,而綁定正好使你能夠實現這一目的。你可以使用WCF提供的綁定,也就是說,你或者可以'濃縮'它們的屬性,或者是從頭編寫通訊自己的定制綁定。一個服務在它的元數據中出版它的綁定選擇,這使得客戶端能夠查詢這種類型和綁定的特定屬性,因為客戶端必須使用與服務完全一樣的綁定。單個服務能夠支持在獨立的地址上的多個綁定。通常,服務并不指定關于綁定本身。WCF定義了列舉于表格1中的共9種標準綁定。基于文本的編碼使一個WCF服務(或客戶端)能夠通過HTTP與任何其它服務(或客戶端)進行交流而不考慮它的技術;然而,通過TCP或IPC的二進制編碼傳輸能夠產生最優的性能,但是卻以失去極廣泛的互操作性為代價(因為它必須使用WCF到WCF的通訊)。表格1:WCF標準綁定名稱傳輸編碼InteropBasicHttpBindingHTTP/HTTPSText+ NetTcpBindingTCPBinary-NetPeerTcpBindingP2PBinary-NetNamedPipeBindingIPCBinary-WSHttpBindingHTTP/HTTPSText,MTOM+WSFederationBindingHTTP/HTTPSText,MTOM+WSDualHttpBindingHTTPText,MTOM+NetMsmqBindingMSMQBinary-MsmqIntegrationBindingMSMQBinary+為一個傳輸協議選擇MSMQ能夠強制實現WCF到WCF或WCF到MSQM的通訊,但是,這僅是針對非連接的離線工作情況提供的。典型情況下,為通訊服務選擇一個綁定應該遵循如圖4所示的策略活動圖。圖4.策略活動圖:該圖展示了選擇一個綁定的過程。你應該問自己的第一個問題是,是否通訊服務需要與非WCF客戶進行交互。如果回答'是',并且如果客戶端是一個舊的MSMQ客戶端,那么應該選擇NetMsmqBinding-它可以使通訊服務與這樣的一個客戶端通過MSMQ進行互操作。如果你需要與一非WCF客戶端進行互操作并且該客戶端期望使用基本的Web服務協議(ASMX Web服務),那么,你可以選擇BasicHttpBinding-它能夠把通訊WCF服務暴露到外界,就好象它是一個ASMX Web服務一樣。缺點是你不能利用任何現代WS-*協議。然而,如果非WCF客戶端能理解這些標準,那么,你可以選擇WS綁定之一,例如WSHttpBinding,WSFederationBinding或WSDualHttpBinding。如果你可以假定客戶端是一個WCF客戶端,但它要求離線或非連接性交互,那么你可以選擇使用MSMQ的NetMsmqBinding來傳輸消息。如果客戶端需要連接的通訊但能夠跨越計算機邊界被調用,那么你可以選擇通過TCP進行通訊的NetTcpBinding。如果客戶端位于與服務同一臺計算機上,那么你可以選擇使用命名管道的NetNamedPipeBinding來(IPC)最優化性能。注意,一個使用NetNamedPipeBinding的服務不能接受除它自己以外的來自任何其它計算機的調用,并且這樣也會更為安全。你可以基于其它標準(例如,回調需要(WSDualHttpBinding),端對端網絡(NetPeerTcpBinding)或聯盟安全(WSFederationBinding))來詳細地調整綁定選擇。十. 端點每一個服務都關聯于一個定義了該服務所在位置的地址,一個定義了如何與服務進行通訊的綁定和一個定義了該服務所實現功能的合同。事實上,WCF用端點的形式來形式化描述這種關系。該端點是地址、合同和綁定的一個結合(見圖5)。每一個服務必須具有三個端點,而且由服務暴露該端點。從邏輯上講,端點是服務的接口,并且類似于一個CLR或COM接口。圖5.該端點是地址、合同和綁定的結合每一個服務必須暴露至少一個業務端點,并且每一個端點都具有一個相同的合同。在一個服務上的所有的端點都具有唯一的地址,而單個服務可以暴露多個端點。這些端點能夠使用相同的或不同的綁定并能暴露相同的或不同的合同。你可以使用一個配置文件來以管理方式配置端點或以編程方式來實現端點配置。
十一. 管理端點配置請考慮下列服務定義:namespace MyNamespace{[ServiceContract]interface IMyContract{...}Class MyService : IMyContract{...}} 列表2(見本文相應下載源碼)展示了在宿主進程配置文件中要求的入口。管理配置是在大多數情況下的配置,因為它提供靈活性來實現改變服務地址、綁定甚至暴露合同而不必重新構建和重新發布服務。源碼中的列表3展示了一個配置文件-它定義暴露多個端點的單個服務。注意,這些端點必須提供一個與綁定相一致的基地址(例如,對于HTTP使用WSHttpBinding綁定)。每一個不匹配都會導致在服務加載時刻拋出一個異常。只要URI是不同的,那么你可以使用相同的基地址來配置多個端點:<service name='MyNamespace.MyService'><endpoint Address ='net.tcp://localhost:8001/Service1/' .../><endpointaddress='net.tcp://localhost:8001/Service2/' .../></service>還可以省略地址-在這種情況下,該服務使用與宿主一起注冊的基地址(宿主必須提供一個匹配的基地址):<endpointbinding='wsHttpBinding'contract='MyNamespace.IMyContract' /> 可以僅提供一個URI-在這種情況下,地址是在基地址下的相對地址(并且宿主必須提供一個匹配的基地址):<endpointaddress='SubAddress' .../>當提供一個基地址時,該端點覆蓋宿主所提供的任何基地址:<endpointaddress='http://localhost:8000/MyService/' .../>注意,當使用IIS進行宿主時,服務必須使用IIS基地址(在HTTP中使用計算機名+虛擬目錄)。十二. 端點配置編程以編程方式實現端點配置完全等價于管理配置;然而,它不必依賴于一個配置文件而是可以通過編程調用來把端點添加到ServiceHost實例。再次強調的是,這些調用總是位于服務代碼的范圍之外。ServiceHost提供AddServiceEndpoint()方法的重載版本:public class ServiceHost : ServiceHostBase { public ServiceEndpoint AddServiceEndpoint(Type implementedContract, Binding binding,String address); //另外的成員}列表4展示了與在列表3中的端點一樣的可編程的配置。為了依賴宿主基地址,只需要提供如地址一樣的URI即可:Uri tcpBaseAddress = new Uri('http://localhost:8000/');ServiceHost serviceHost = new ServiceHost(typeof(MyService),tcpBaseAddress);Binding tcpBinding = new NetTcpBinding();//使用基地址作為地址serviceHost.AddServiceEndpoint(typeof(IMyContract) ,tcpBinding,'');//添加相對地址serviceHost.AddServiceEndpoint(typeof(IMyContract), tcpBinding,'MyService');//忽略基地址serviceHost.AddServiceEndpoint(typeof(IMyContract), tcpBinding,'net.tcp://localhost:8001/MyService');serviceHost.Open();十三. 小結在本篇中,我們全面介紹了構建一個WCF服務所需要的基本概念,有關完整的WCF服務的例子請參考本文相應源碼。在下篇中,我們將給出一個使用WCF進行Windows開發的客戶端案例分析。
