WinVista新技術(shù) WCF開(kāi)發(fā)指南之客戶端開(kāi)發(fā)
作者:朱先忠編譯
一. 客戶端編程為了調(diào)用服務(wù)上的操作,客戶端首先需要把服務(wù)合同導(dǎo)入到客戶端的本地描述中。這意味著,該服務(wù)需要客戶端暴露一個(gè)標(biāo)準(zhǔn)方法以檢索它的元數(shù)據(jù)-這是通過(guò)讓服務(wù)暴露一個(gè)元數(shù)據(jù)交換(MEX)端點(diǎn)來(lái)實(shí)現(xiàn)的。只要服務(wù)在宿主上注冊(cè)至少一個(gè)TCP、HTTP或IPC基地址,那么,這個(gè)基于WCF的服務(wù)就能夠自動(dòng)地暴露一個(gè)MEX端點(diǎn)。如果客戶端使用WCF,那么調(diào)用操作的普通方法是使用一個(gè)代理。這個(gè)代理是一個(gè)CLR類,它暴露單個(gè)描述服務(wù)合同的CLR接口。注意,如果該服務(wù)支持若干合同,那么客戶端需要對(duì)于每一種合同類型都創(chuàng)建一個(gè)相應(yīng)代理。代理負(fù)責(zé)提供與服務(wù)的合同相同的操作,而且還提供其它方法來(lái)管理代理的生命周期和到服務(wù)的連接。該代理完整地封裝該服務(wù)的每一個(gè)方面:服務(wù)的位置,服務(wù)的實(shí)現(xiàn)技術(shù)及其運(yùn)行時(shí)刻平臺(tái)和通訊傳輸。 你可以使用Visual Studio 2005來(lái)導(dǎo)入服務(wù)元數(shù)據(jù)并且生成一個(gè)代理。如果服務(wù)是自宿主的,那么首先要啟動(dòng)該服務(wù),然后從客戶端工程的上下文菜單中選擇'Add Service Reference…'。如果服務(wù)宿主在IIS或WAS上,那么就不需要預(yù)啟動(dòng)該服務(wù)了。有趣的是,如果服務(wù)是自宿主在與客戶端工程同一個(gè)方案中的另一個(gè)工程中,那么你可以在Visual Studio 2005中啟動(dòng)宿主并且仍然添加參考,因?yàn)椴煌诖蠖鄶?shù)工程設(shè)置,這個(gè)選項(xiàng)是無(wú)法禁止的(見(jiàn)圖6)。這會(huì)導(dǎo)致調(diào)出'Add Service Reference'對(duì)話框,你需要在其中提供服務(wù)的基地址(或一個(gè)基地址和一個(gè)MEX URI)以及包含代理的命名空間。圖6.你可以使用Visual Studio 2005生成一個(gè)代理。Visual Studio 2005使用SvcUtil.exe命令行工具,并且你可以自己使用它。主要理由是,你可以使用SvcUtil開(kāi)關(guān)所提供的眾多的選項(xiàng)。為了直接使用SvcUtil,你可以提供給它MEX地址并且,作為選擇,還可以提供一個(gè)代理文件名。默認(rèn)的代理文件名是實(shí)現(xiàn)該服務(wù)的服務(wù)端類的名字。例如,當(dāng)把服務(wù)MyService宿主在IIS中時(shí),簡(jiǎn)單地運(yùn)行下列命令行: SvcUtil http://localhost/MyService/MyService.svc /out:Proxy.cs借助于自宿主,你可以不受限于僅使用HTTP基地址。假定自宿主服務(wù)注冊(cè)這些基地址:http://localhost:8002net.tcp://localhost:8003net.pipe://localhost/MyPipe然后,啟動(dòng)宿主,則你可以使用任何下列命令之一來(lái)生成該代理:SvcUtil http://localhost:8002/MEX /out:Proxy.csSvcUtil http://localhost:8002/ /out:Proxy.csSvcUtil net.tcp://localhost:8003/MEX /out:Proxy.csSvcUtil net.pipe://localhost/MyPipe /MEX/out:Proxy.cs該代理類沒(méi)有對(duì)服務(wù)實(shí)現(xiàn)類的參考,而只參考服務(wù)暴露的合同。這個(gè)代理可以與一個(gè)提供地址和綁定的客戶端配置文件一起使用,也可以在沒(méi)有配置文件的情況下使用。注意,每一個(gè)代理實(shí)例都準(zhǔn)確地指向一個(gè)端點(diǎn),與之交互的端點(diǎn)在構(gòu)造時(shí)刻提供給代理。二. 管理客戶端配置客戶端需要知道服務(wù)所在地并使用與它的服務(wù)相同的綁定;當(dāng)然,也要以代理的形式導(dǎo)入服務(wù)合同。實(shí)質(zhì)上,這與在服務(wù)的端點(diǎn)處捕獲的信息完全一致。為了反映這一信息,客戶端配置文件包含關(guān)于目標(biāo)端點(diǎn)的信息并且甚至使用與宿主相同的模式。 例如,列表6(見(jiàn)本文相應(yīng)下載源碼)顯示了與一個(gè)服務(wù)(其宿主是根據(jù)列表2進(jìn)行配置的)進(jìn)行交互需要的客戶端配置文件。注意,在這個(gè)客戶端配置文件中的合同類型(和命名空間)是由SvcUtil生成的導(dǎo)入的類型(和命名空間,如果有的話),而不是服務(wù)類型和命名空間。該客戶端配置文件可以列出與服務(wù)支持一樣多的端點(diǎn),并且該客戶端可以使用任何其中之一來(lái)與該服務(wù)交互。列表7(見(jiàn)本文相應(yīng)下載源碼)展示了客戶端配置文件-它匹配顯示于列表3中的宿主配置文件。默認(rèn)地,SvcUtil還自動(dòng)生成一個(gè)客戶端配置文件output.config。你可以使用/config開(kāi)關(guān)來(lái)指定一個(gè)配置文件名:SvcUtil http://localhost:8002/MyService/MEX/out:Proxy.cs /config:App.Config并且,你可以使用/noconfig開(kāi)關(guān)來(lái)壓縮生成的配置文件:SvcUtil http://localhost:8002/MyService/MEX/out:Proxy.cs /noconfig為了支持進(jìn)程內(nèi)宿主,應(yīng)用程序配置文件應(yīng)該列出服務(wù)和客戶端節(jié),見(jiàn)列表8(見(jiàn)本文相應(yīng)下載源碼)。注意,NetNamedPipeBinding被用于進(jìn)程內(nèi)調(diào)用。WCF提供一個(gè)能夠編輯宿主和客戶端配置文件的配置文件編輯器SvcConfigEditor.exe(見(jiàn)圖7)。在寫(xiě)本文時(shí),SvcConfigEditor只是生成一些不可讀的配置文件,因此,在糾正這一問(wèn)題之前,你應(yīng)該手工地編輯這個(gè)文件。圖7:SvcConfigEditor用于編輯宿主和客戶端配置文件。
三. 創(chuàng)建和使用代理SvcUtil生成的代理類派生自類ClIEntBase<T>,定義為:
public class ClientBase<T> : IDisposable{ protected ClientBase(string endpointConfigurationName); protected ClientBase(Binding binding,EndpointAddress remoteAddress); public void Close(); public void Dispose(); protected T InnerProxy{get;} //其它成員}這個(gè)InnerProxy屬性是客戶端需要消費(fèi)的合同類型,并且SvcUtil生成的ClientBase<T>的子類簡(jiǎn)單地把它代理到方法調(diào)用(見(jiàn)列表5)。客戶端需要實(shí)例化一個(gè)代理對(duì)象并且提供給其構(gòu)造器端點(diǎn)信息-或者是來(lái)自配置文件的端點(diǎn)節(jié)名(見(jiàn)列表6)或者是在不使用一個(gè)配置文件時(shí)的端點(diǎn)地址和綁定對(duì)象。然后,該客戶端可以使用代理方法來(lái)調(diào)用該服務(wù),并且客戶端完成后,它需要關(guān)閉該代理實(shí)例:MyContractProxy proxy = new MyContractProxy('MyEndpoint');proxy.MyMethod();proxy.Close();關(guān)閉代理將終止與服務(wù)的會(huì)話并且關(guān)閉連接。作為選擇,你可以使用代理的Dispose()方法來(lái)關(guān)閉它。Dispose()方法的優(yōu)點(diǎn)在于,你可以使用using語(yǔ)句來(lái)調(diào)用它,即使在面臨異常處理時(shí):using(MyContractProxy proxy = new MyContractProxy('MyEndpoint')){ proxy.MyMethod();}在客戶端配置文件中的每個(gè)合同類型的一個(gè)端點(diǎn)可以被指派為一個(gè)默認(rèn)端點(diǎn)。默認(rèn)端點(diǎn)是一個(gè)沒(méi)有名字標(biāo)志或只有一個(gè)空名('')的端點(diǎn)節(jié):<system.serviceModel><client><endpoint...contract='IMyContract' /><endpoint name='OtherEndpoint'...contract='IMyContract' /></client></system.serviceModel>一個(gè)默認(rèn)的端點(diǎn)僅僅是一種理想情況;當(dāng)創(chuàng)建一個(gè)針對(duì)默認(rèn)端點(diǎn)的代理時(shí),你可以使用代理的默認(rèn)構(gòu)造器來(lái)讓它使用默認(rèn)端點(diǎn):MyContractProxy proxy = new MyContractProxy();proxy.MyMethod();proxy.Close();四. 可編程的客戶端配置不依賴于配置文件,客戶端也可以通過(guò)編程方式來(lái)構(gòu)建端點(diǎn)并且把它提供給代理構(gòu)造器。列表9(見(jiàn)本文相應(yīng)下載源碼)展示了這一技術(shù),其中展示了等價(jià)于列表6中的代碼(針對(duì)列表2中的服務(wù))。編程配置是很有用的:當(dāng)端點(diǎn)決策或者是完全動(dòng)態(tài)的-基于當(dāng)前輸入或在運(yùn)行時(shí)刻使用;或當(dāng)決策是靜態(tài)的且從不更改時(shí),你最好采用硬編碼之。
五. WCF架構(gòu)到目前為止,本文討論了建立和消費(fèi)簡(jiǎn)單WCF服務(wù)所有要求的內(nèi)容。然而,WCF還為可靠性、事務(wù)、安全和實(shí)例激活等提供了極其寶貴的支持,所有這些都依賴于WCF基于攔截的架構(gòu)。讓客戶端與代理交互意味著,WCF總是介于服務(wù)和客戶端之間來(lái)攔截調(diào)用并且執(zhí)行預(yù)調(diào)用和調(diào)用后處理。當(dāng)代理把調(diào)用堆棧幀串行化為一條消息并且沿著一個(gè)通道鏈發(fā)送消息時(shí),該解釋即開(kāi)始。 每一個(gè)客戶端通道都要做消息的預(yù)調(diào)用處理。這個(gè)鏈的正確結(jié)構(gòu)和組成在很大程度上依賴于綁定。例如,其中一個(gè)通道負(fù)責(zé)編碼該消息(二進(jìn)制,文本或MTOM),一個(gè)通道負(fù)責(zé)傳遞安全調(diào)用上下文,一個(gè)通道負(fù)責(zé)傳播客戶端事務(wù),一個(gè)通道負(fù)責(zé)管理可靠的會(huì)話,一個(gè)通道負(fù)責(zé)加密消息正文(如果這樣配置的話),等等。客戶端的最后一個(gè)通道是傳輸通道,它負(fù)責(zé)把消息由經(jīng)配置的傳輸傳送到宿主。在宿主端,該消息也經(jīng)過(guò)一個(gè)通道鏈,從而實(shí)現(xiàn)宿主端消息的預(yù)調(diào)用處理。在宿主端的第一個(gè)通道是傳輸通道,它接收來(lái)自傳輸?shù)南ⅰkS后的通道執(zhí)行各種任務(wù),例如解密消息正文、譯解消息、把傳播的事務(wù)設(shè)置到執(zhí)行線程、設(shè)置安全主管、管理會(huì)話和激活服務(wù)實(shí)例。最后一個(gè)通道在宿主端把該消息傳遞到調(diào)度器。由調(diào)度器把該消息值的轉(zhuǎn)換成一個(gè)堆棧幀并且調(diào)用服務(wù)實(shí)例。圖8描述了這一順序。圖8:WCF架構(gòu)看上去的樣子。在客戶端和服務(wù)端的攔截都要確保客戶端和服務(wù)得到它們所要求的運(yùn)行時(shí)刻環(huán)境以便正確運(yùn)行。由服務(wù)實(shí)例執(zhí)行該調(diào)用并且把控制返回到調(diào)度器,由它把返回值和錯(cuò)誤信息(如果有的話)轉(zhuǎn)換成一條返回消息。現(xiàn)在,整個(gè)過(guò)程被顛倒:調(diào)度器通過(guò)宿主端通道傳送消息以執(zhí)行調(diào)用后處理,例如管理事務(wù)、撤銷實(shí)例、編碼應(yīng)答信息、加密它,等等。返回的消息轉(zhuǎn)到傳輸通道,在此把消息發(fā)送到客戶端通道以便客戶端調(diào)用后處理:解密,編碼,提交或取消事務(wù),等等。由代理把返回的消息轉(zhuǎn)換成一個(gè)堆棧幀并且把控制返回到客戶端。最值得注意的是,在這個(gè)架構(gòu)中的幾乎所有的點(diǎn)都提供了可擴(kuò)展性鉤子-你可以為專利性行為提供定制通道,定制實(shí)例管理或定制安全。事實(shí)上,WCF提供的這些標(biāo)準(zhǔn)工具都是使用相同的可擴(kuò)展性模型實(shí)現(xiàn)的。六. 使用通道你可以直接使用通道來(lái)調(diào)用服務(wù)上的操作而甚至不必依賴于一個(gè)SvcUtil生成的代理。顯示于列表10(見(jiàn)本文相應(yīng)下載源碼)中的ChannelFactory<T>類能夠使你任意地創(chuàng)建一個(gè)代理。你需要提供給它的構(gòu)造器端點(diǎn)信息-或者是來(lái)自配置文件的端點(diǎn)名,或者是綁定和地址對(duì)象,或者是一個(gè)端點(diǎn)對(duì)象。然后,使用CreateChannel()方法以獲得一個(gè)到代理的參考(頂級(jí)通道)并且使用它的方法。最后,關(guān)閉該代理或者通過(guò)把它強(qiáng)制轉(zhuǎn)換為一個(gè)IDisposable接口并且調(diào)用其Dispose()方法或強(qiáng)制轉(zhuǎn)換為一個(gè)IClIEntChannel接口并且調(diào)用其Close()方法:ChannelFactory<IMyContract> factory;//使用默認(rèn)的站點(diǎn)factory = new ChannelFactory<IMyContract>('');IMyContract proxy1 = factory.CreateChannel();using(proxy1 as IDisposable){proxy1.MyMethod();}IMyContract proxy2 = factory.CreateChannel();proxy2.MyMethod();IClientChannel clientChannel = proxy2 as IClientChannel;Debug.Assert(clientChannel != null);clientChannel.Close();七. 總結(jié)WCF是一種用于構(gòu)建Windows面向服務(wù)的應(yīng)用程序的SDK。它能夠讓你使用杰出的CLR編程結(jié)構(gòu)(例如類和接口)來(lái)發(fā)布和消費(fèi)服務(wù)。這種編程模型是聲明性的并且大部分是屬性驅(qū)動(dòng)的。WCF基于攔截的架構(gòu)提供了內(nèi)置的工具用于管理服務(wù)的許多運(yùn)行時(shí)刻方面;并且,從其應(yīng)用前景來(lái)看,它也是構(gòu)建Windows分布式應(yīng)用程序的最具生產(chǎn)效率的方式。WCF的第一個(gè)發(fā)行版本為開(kāi)發(fā)服務(wù)(例如宿主,服務(wù)實(shí)例管理,異步調(diào)用,可靠性,事務(wù)管理,非連接性隊(duì)列調(diào)用和安全性)提供了許多有用的工具。在WCF的第二個(gè)發(fā)行版本中將添加服務(wù)發(fā)現(xiàn)以及事件出版和訂閱。WCF要求.NET 2.0環(huán)境并將同Windows Vista一同發(fā)行。另外,你可以在Windows XP SP2和Windows Server 2003 SP1環(huán)境下使用它。
