然后我們在<Client>中添加一個終結點,這個是客戶端的終結點,我們前面曾經提過,通信實際上發生在兩個終結點間,客戶端也有個終結點,然而請求總是從客戶端首先發起,所以終結點地址應該填寫為服務端終結點的地址讓客戶端來尋址,但是服務協定要客戶端本地是有的,所以這個要寫本地定義的那個協定的完全限定名:
?
引用http://blog.csdn.net/songyefei/article/details/7389186
第五篇 再探通信--ClientBase
?
在上一篇中,我們拋開了服務引用和元數據交換,在客戶端中手動添加了元數據代碼,并利用通道工廠ChannelFactory<>類創建了通道,實現了和服務端的通信。然而,與服務端通信的編程模型不只一種,今天我們來學習利用另外一個服務類ClientBase<>來完成同樣的工作,了解了這個類的使用方法,我們對服務引用中的關鍵部分就能夠理解了。
?
?
?
ClientBase<>類也是一個泛型類,接受服務協定作為泛型參數,與ChannelFactory<>不同的是,這個類是一個基類,即抽象類,是不能實例化成對象直接使用的,我們需要自己寫一個類來繼承這個類,我們新寫的類實例化出來就是客戶端代理了,這個對象可以調用基類的一些受保護的方法來實現通信。ClientBase<>為我們封裝的很好,我們只需要寫個新類來繼承他就可以了,通信的很多部分他都替我們做好了,比如我們不用進行去創建通道和打開通道的操作,直接調用協定方法就可以了。這也是服務引用和其他元數據生成工具(如svcutil)使用這個類來構造客戶端代理的原因。
?
?
?
我們一邊動手一邊學習
?
?
?
1. 建立客戶端程序
?
我們依然不用服務引用,完全手寫,這次還是建立一個控制臺應用程序作為客戶端。
?
2. 添加必要的引用
?
我們用到的ClientBase<>類在System.ServiceModel程序集中,因此,要添加這個程序集的引用,同時在program.cs中添加using語句
- using?System.ServiceModel;?
- using?System.ServiceModel;?
這一步應該很熟了,如果有疑問,返回前幾篇溫習一下。
因為沒有使用服務引用,客戶端現在沒有任何元數據的信息,我們要來手寫。首先把服務協定寫進去。這個再熟悉不過了(寫在Program類后面):
[ServiceContract] public interface IHelloWCF { [OperationContract] string HelloWCF(); }
4. 編寫客戶端代理類
上面已經提到,我們要自己寫一個新類來繼承ClientBase<>基類,這樣這個新類就是代理類了,同時,為了能夠用代理類直接調用服務協定的方法,我們還要讓代理類實現服務協定的接口,注意,繼承要寫在前面,實現接口要寫在后面。我們把這個類起名為HelloWCFClient。
public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF { }
ClientBase<>有許多的構造函數,接受不同種類的參數來創建代理類對象,其實這些參數都是元數據信息,剛才我們已經通過泛型參數傳遞給基類服務協定這個元數據了,現在基類還需要綁定和終結點地址這兩個元數據才能正確創建連接,所以我們繼承的新類應該把這個構造函數給覆載一下接受這兩種元數據參數并傳遞給基類。
public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF { public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { } }
我們看到這個新建的構造函數什么也沒做,只是接受了兩個參數,一個是綁定,一個是終結點地址,然后直接調用基類(也就是ClientBase<>)的構造函數,把這個兩個參數傳遞了上去。其實工作都是ClientBase<>做的,我們新建的類就是個傳話的,要不然怎么叫代理呢,他什么活都不干。
既然我們實現了服務協定接口,當然要實現接口的方法了。下面我們把方法的實現寫下來:
public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF { public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { } public string HelloWCF() { return base.Channel.HelloWCF(); } }
別忘了你的處境,凡人!我們這是在客戶端啊,怎么可能有服務協定呢?這個可以有,但是這個實現不是我們在做,而是要和服務端通信讓服務端做,這里可以看到代理鮮明的特點了,代理類雖然實現了服務協定的方法,但是在方法中,他調用了基類(就是ClientBase<>)上的通道,并通過通道調用了了協定方法。此時,ClientBase<>已經為我們建立好與服務端的通道了,而且是用服務協定建立的,我們當然可以在通道上調用服務協定的方法。所以調用代理類對象的HelloWCF()的過程是代理類委托基類在已經建立好的服務協定通道上調用協定方法,并從服務端獲得返回值,然后再返回給代理類對象的調用者。狗腿啊狗腿。
5. 編寫程序主體
代理類已經寫完了,我們現在開始寫程序的主體,讓我們來到Program的Main函數中。
還是有一些準備要做,還差兩個元數據呢,對了,就是綁定和地址。和上一篇一樣,我們先建立這個兩個元數據的對象備用:
WSHttpBinding binding = new WSHttpBinding(); EndpointAddress remoteAddress = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");
接下來就要建立我們的代理類對象了,new一個出來吧:
HelloWCFClient client = new HelloWCFClient(binding, remoteAddress);
把我們剛剛建立的兩個元數據對象作為參數傳遞進去。
?
接下來就可以調用服務協定方法了:
string result = client.HelloWCF();
別忘了關閉通道,ClientBase<>很貼心的為我們準備了這個方法,不用再做強制類型轉換什么的了(見前一篇)。
client.Close();?
以下是Program.cs的全部代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; namespace ConsoleClient { class Program { static void Main(string[] args) { WSHttpBinding binding = new WSHttpBinding(); EndpointAddress remoteAddress = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc"); HelloWCFClient client = new HelloWCFClient(binding, remoteAddress); string result = client.HelloWCF(); client.Close(); Console.WriteLine(result); Console.ReadLine(); } } [ServiceContract] public interface IHelloWCF { [OperationContract] string HelloWCF(); } public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF { public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { } public string HelloWCF() { return base.Channel.HelloWCF(); } } }
6. 再展開一點點
這樣看上去已經挺成功了,我們現在再打開服務引用的reference.cs代碼,看看是不是大部分都看得懂了?監管有些地方他寫的可能有些復雜,比如描述協定的屬性參數啊,代理類更多的構造函數啊,但是核心的就是我們剛剛寫的部分,那一大堆wsdl什么的其實都不是核心,不信的話你把他們都刪掉,就留一個reference.cs看看還好不好用?那一堆東西也不是沒用就是現在自己看起來還蠻復雜的,我們到后面一點點學習。
?
我們仔細看服務引用的reference.cs代碼,有一樣東西是我們有而他沒有的,那就是對終結點地址和綁定的建立,而且在使用服務引用的時候我們也沒有提供之兩樣東西,直接掉服務協定方法就行了(見第一篇),那么服務引用是從哪里找到這兩個關鍵的元數據元素呢?
?
就在配置文件里,我們做的客戶端還沒有配置文件,我們可以把這兩個元素都放在配置文件里,這樣就可以避免硬編碼了。
?
為我們的控制臺應用程序添加一個應用程序配置文件。方法是右鍵點擊項目->添加->新建項->應用程序配置文件。保持默認名稱app.config。
打開來看,里面沒寫什么實際的內容:
<?xml version="1.0" encoding="utf-8" ?> <configuration> </configuration>
我們對配置服務應該不陌生,如果忘記了,翻回到第二篇去回顧一下。
首先還是要添加<System.ServiceModel>節,無論是服務端還是客戶端,只要是WCF的服務配置都要在這個節里面:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> </system.serviceModel> </configuration>
在這里我們要配置的是客戶端,所以我們不添加<Services>節了,而改成<Client>:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> </client> </system.serviceModel> </configuration>
?
如果你忘記了我們在第三篇中配置的IIS服務的內容,可能稍有迷惑,這里的地址看上去是個服務地址而不是終結點地址,這是因為我們在IIS中把終結點地址設置為了空字符串,此時服務地址就是終結點地址了。注意看后面的contract,他的完全限定名的命名空間是客戶端程序的命名空間ConsoleClient,這也表示這個類型是定義在本地的,不要搞錯了。
?
保存,配置已經寫完了,你如果看服務引用為我們生成的配置會看到一堆東西,實際上核心的就是這些。
?
既然我們已經在配置中聲明了綁定和終結點地址,在代碼中就不再需要了。
首先我們修改一下代理類,為他提供一個沒有參數的構造函數,否則在new他的時候他會非管我們要兩個參數。
public HelloWCFClient() : base()
{ }
還是什么也不做,直接調基類的無參構造函數。
?
然后修改一下Main函數,去掉終結點對象和地址對象的聲明,并用無參構造函數來new 代理類的實例:
static void Main(string[] args) { HelloWCFClient client = new HelloWCFClient(); string result = client.HelloWCF(); client.Close(); Console.WriteLine(result); Console.ReadLine(); }
F5運行一下,結果如一吧!
到這里已經和服務引用的感覺基本一樣了,我們已經寫出了服務引用的核心部分。
以下是修改后的Program.cs完整代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; namespace ConsoleClient { class Program { static void Main(string[] args) { HelloWCFClient client = new HelloWCFClient(); string result = client.HelloWCF(); client.Close(); Console.WriteLine(result); Console.ReadLine(); } } [ServiceContract] public interface IHelloWCF { [OperationContract] string HelloWCF(); } public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF { public HelloWCFClient() : base() { } public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { } public string HelloWCF() { return base.Channel.HelloWCF(); } } }
7. 總結
通過本篇的學習,我們熟悉了另一種與服務端通信的辦法,即通過ClientBase<>派生代理類的方法,這種方法實際上就是服務引用使用的方法,可以說我們已經在最簡單級別上手寫了一個服務引用的實現。
?
使用ChannelFactory<>和ClientBase<>都可以實現與服務端的通信,這是類庫支持的最高層次的通訊方法了,其實還有更底層的通道通信方法,我們現在就不再深入了。而選擇這兩種方法完全取決于開發人員,有人喜歡工廠,有人喜歡代理類。既然我們都已經掌握了,就隨意選擇吧。
?
至此,我們對通信有了一個基本的了解,只能算一個初探吧。然而我相信這一次的小小深入會對我們今后的學習帶來大大的幫助的。
?