程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 不引用服務而使用WCF,手動編寫客戶端代理類

不引用服務而使用WCF,手動編寫客戶端代理類

編輯:關於.NET

前面我寫過一個用WCF開發的聊天程序,大家可以翻看前面的博文。

在那個聊天程 序中,我是不引用服務而直接使用WCF。之前沒有跟大家說這一知識點,對於初學者朋友來說 ,可能不知道怎麼回事。

我們之所以說WCF比一般的Web Service要強大得多,是因為 它要比一般的Web服務要靈活得多,而且它不僅僅能在IIS服務器上運行,其實它可以用很多 種方法來運行,哪怕一個控制台應用程序。

現在,大家可以回憶一下前面我寫的《傳 說中的WCF》,我上面的例子絕大多數都是控制台應用程序類型的。我們應當把WCF理解為一 種通信技術,而不只是服務。前面的例子中我是告訴大家,完成服務器端後,就在客戶端項 目中添加服務引用,這樣就生成了客戶端代理類,我們就可以像平時使用一般類型一樣使用 了。

其實按照我們前面所講的方法,也足以完成許多實際任務了。大家是否還想拓展 一下呢? 有朋友肯定會問了:再拓展會不會變得很難? 放心吧,不會很難,相信我,老周從 來不會講大家都看不懂的東西的。

我們現在不妨嘗試一下,在客戶端不添加服務引用 ,而是由我們自己來編寫調用服務的代理類。要做到這一點,首先我們要明確的,其實我們 所編寫的服務協定,在服務器和客戶端都需要用到,如果大家查看過添加服務引用時由工具 生成的代碼,會發現其實它在客戶端也生成了服務協定的代碼。所以,在我們手動編寫調用 服務的代碼時,也需要這樣,因此有兩種方法可以在服務器和客戶端之間共用服務協定,一 是把代碼復制一下粘貼到客戶端中,另一種方法,我們可以新建一個類庫,然後把服務協定 寫到這個類庫中,最後在服務器端和客戶端都引用這個類庫即可。舉個例子,假如有以下定 義的協定:

[ServiceContract]  
public interface ITest  
{  
    [OperationContract]  
    int Add(int a, int b);  
      
    [OperationContract]  
    int GetRandmon();  
      
    [OperationContract]  
    int Multiply(int a, int b);  
}

然後,我們在服務器端實現協定,注意:接口在服務器端實現即可,客戶端不需 要。

// 實現服務  
public class MyService : CommonLib.ITest  
{  
    Random m_rand = null;  
      
    // 構造函數  
    public MyService()  
    {  
        m_rand = new Random();  
    }  
      
    public int Add(int a, int b)  
    {  
        return a + b;  
    }  
      
    public int GetRandmon()  
    {  
        return m_rand.Next();  
    }  
      
    public int Multiply(int a, int b)  
    {  
        return a * b;  
    }  
}

接著,和以前一樣,創建服務主機,並偵聽客戶端調用。

static void 

Main(string[] args)  
{  
    ServiceHost host = new ServiceHost(typeof(MyService));  
    // HTTP方式  
    WSHttpBinding httpBinding = new WSHttpBinding(SecurityMode.None);  
    host.AddServiceEndpoint(typeof(CommonLib.ITest), httpBinding, 

"http://localhost:8900/");  
    // TCP方式  
    NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.None);  
    host.AddServiceEndpoint(typeof(CommonLib.ITest), tcpBinding, 

"net.tcp://localhost:1700/");  
    // 打開服務  
    host.Open();  
    Console.WriteLine("服務已啟動。");  
    Console.Read();  
    host.Close();  
}

大家可以細心看一下,和以前的代碼有什麼不同? 不妨比較一下,看看。

1、以前,我們在創建ServiceHost時會指定一個HTTP基地址,但這裡不需要了,基址 是便於工具生成代理類的,我們既然要手動來寫了,就不用生成代碼了,也不用基址了。

2、以前,我們會在ServiceHost.Description.Behaviors集合中加一個 ServiceMetadataBehavior對象,以提供WSDL,幫肋工具生成代碼。現在我們都自己手動寫了 ,當然就不用提供WSDL了。

認真想想,看是不是這樣? 如果你有興趣,也可以為 ServiceHost弄一個基址,但不添加ServiceMetadataBehavior,然後在客戶端項目中添加引 用,你會發現……呵呵,你懂的。

那現在在客戶端怎麼調用服務呢? 使用通道,可能 有朋友會看到IChannel接口,又派生出很多接口,但貌似沒有一個是類的,是不是要自己來 寫通道啊? 不用,當然你要擴展通道層是另一回事,通常我們無需擴展通道,因為現有的已 經足夠牛逼了。我們在“對象浏覽器”中是看不到與通道相關的可用的類,因為.NET內部是 有實現的,只是沒有定義為public而已,是internal。

我們根本可以不必理會如何找 通道的問題,就好像我們坐在一輛全自動導航或者有專業司機駕駛的車上,司機知道怎麼走 ,我們不必要擔心不知道怎麼走這段路。同理,我們可以不直接操作通道,為什麼呢?因為我 們定義的每一個服務協定都可以認為是一個通道。

上面我們定義的那麼ITest就是一 個通道,WCF內部已經幫我們把它變成一個通道了,不信的話,你往後看例子。

我們 已經知道,編寫的服務協定可以當成一個通道來操作,所以,在客戶端中,我們要手動寫代 碼來調用服務,要可以遵循以下步驟,有興趣的話你可以背下來,但告訴你,背了沒用。

1、創建與服務器匹配的Binding,這個就不用懷疑的了,你跟別人簽合同,那肯定是 一式兩份,對方持一份,你拿一份,你肯定不會拿一張白紙回家保存吧。

2、創建通 道,使用ChannelFactory<TChannel>類可以創建通道,因為它是“工廠”嘛,工廠當 然是用來生產的,但ChannelFactory工廠不是用來生產老鼠藥也不是生產地雷的,它是專門 生產Channel(通道)的。這個TChannel就可以寫上你定義的服務協定的接口,如上面的 ITest。

3、得到的通道就是ITest,然後就可以調用服務了,比如要兩個數相加,就 調用ITest.Add。

4、關閉通道,把ITest強制轉換為IClientChannel就可以調用Close 方法關閉通道。

可能你對這些步驟還有疑問,沒關系,你不妨先疑一下。我們繼續往 下操作。

前面定義服務主機的時候,我們使用了兩個終結點,一個是HTTP的,另一個 是TCP調用。所以我們這裡也要分別用這兩種方法調用。我可沒說一定要用這兩種方法調用, 我只是多寫了一個作演示。

在客戶端,先聲明這兩個終結點地址,就是我們在服務器 定義的兩個地址。

EndpointAddress edpHttp = new EndpointAddress

("http://localhost:8900/");  
EndpointAddress edpTcp = new EndpointAddress

("net.tcp://localhost:1700/");

然後,分別用兩種Binding調有服務。

private void btnHTTP_Click(object sender, EventArgs e)  
{  
    // 創建Binding  
    WSHttpBinding httpBinding = new WSHttpBinding(SecurityMode.None);  
    // 創建通道  
    ChannelFactory<CommonLib.ITest> factory = new ChannelFactory<CommonLib.ITest>(httpBinding);  
    CommonLib.ITest channel = factory.CreateChannel(edpHttp);  
    // 調用  
    int resAdd = channel.Add(int.Parse(txtNum11.Text), int.Parse(txtNum12.Text)); 

 
    txtResAdd.Text = resAdd.ToString();  
      
    int resMult = channel.Multiply(int.Parse(txtNum21.Text), int.Parse(txtNum22.Text));  
    txtResMulti.Text = resMult.ToString();  
      
    int rand = channel.GetRandmon();  
    txtRand.Text = rand.ToString();  
      
    // 關閉通道  
    ((IClientChannel)channel).Close();  
}  
      
private void btnTCP_Click(object sender, EventArgs e)  
{  
    // 創建Binding  
    NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.None);  
    // 創建通道  
    ChannelFactory<CommonLib.ITest> factory = new ChannelFactory<CommonLib.ITest>(tcpBinding);  
    CommonLib.ITest channel = factory.CreateChannel(edpTcp);  
    // 調用  
    txtResAdd.Text = channel.Add(int.Parse(txtNum11.Text),int.Parse(txtNum12.Text)).ToString();  
    txtResMulti.Text = channel.Multiply(int.Parse(txtNum21.Text),int.Parse(txtNum22.Text)).ToString();  
    txtRand.Text = channel.GetRandmon().ToString();  
      
    // 關閉通道  
    ((IClientChannel)channel).Close();  
}

你也許會問,ITest不是接口來的嗎,怎麼可以調用? 別忘了,我們在服務器端已 經實現過了,WCF內部會幫我們找到關聯的類。

現在,你就興奮地看看結果吧。記著 ,運行服務器端需要管理員身份運行,這個我說了三千五百遍了。

嘿嘿,乍一看,好像可以了,已經能調用了,但是,這樣是不是不太簡潔呢? 而且我們不 能將其當成一人類來用,每次調用要通過ChannelFactory來生產,比較麻煩,更重要的是, 如果有服務器回調協定,就不好弄了。

因此,對於上面的客戶端代碼我們是否考慮進 一個封裝呢? 這裡我們完全可以考慮使用ClientBase<TChannel>類,它對於通道和相 關操作作了進一步封裝,當然它是抽象類,不能直接拿來玩,要先派生出一個類。

/// <summary>  
/// 用於調用服務的類  
/// </summary>  
public class MyClient : ClientBase<CommonLib.ITest>,CommonLib.ITest  
{  
    public MyClient(System.ServiceModel.Channels.Binding binding, EndpointAddress 

edpAddr)  
        : base(binding, edpAddr) { }  
      
    public int Add(int a, int b)  
    {  
        return base.Channel.Add(a, b);  
    }  
      
    public int GetRandmon()  
    {  
        return base.Channel.GetRandmon();  
    }  
      
    public int Multiply(int a, int b)  
    {  
        return base.Channel.Multiply(a, b);  
    }  
}

有人會問,為什麼從ClientBase<CommonLib.ITest>派生,又要實現一次 CommonLib.ITest接口呢? 當然,你不實現也無所謂,再實現一次CommonLib.ITest接口是為 了讓這個類的公共方法和ITest的方法一樣,這樣方便調用。

通過訪問base.Channel 就可以得到一個對ITest的引用,無需要我們自己創建通道,因為基類中已經帶了默認實現。

現在,把前面的調用代碼改一下,是不是覺得簡潔了?

private void 

btnHTTP_Click(object sender, EventArgs e)  
{  
    MyClient client = new MyClient(new WSHttpBinding(SecurityMode.None), edpHttp);  
    txtResAdd.Text = client.Add(int.Parse(txtNum11.Text), int.Parse(txtNum12.Text)).ToString();  
    txtResMulti.Text = client.Multiply(int.Parse(txtNum21.Text), int.Parse(txtNum22.Text)).ToString();  
    txtRand.Text = client.GetRandmon().ToString();  
}  
      
private void btnTCP_Click(object sender, EventArgs e)  
{  
    MyClient client = new MyClient(new NetTcpBinding(SecurityMode.None), edpTcp); 

 
    txtResAdd.Text = client.Add(int.Parse(txtNum11.Text), int.Parse(txtNum12.Text)).ToString();  
    txtResMulti.Text = client.Multiply(int.Parse(txtNum21.Text), int.Parse(txtNum22.Text)).ToString();  
    txtRand.Text = client.GetRandmon().ToString();  
}

現在看看我們自己寫的這段代碼,是不是與VS生成的代碼比較接近了? 而且連配 置文件也省了。

查看本欄目

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved