代碼
https://yunpan.cn/cPns5DkGnRGNs 密碼:3913
ServiceContractAttribute 與 OperationContractAttribute
ServiceContractAttribute:將一個接口轉換成一個契約,每個服務契約都有一個確定的名稱,當在一個接口上應用了該屬性以后,默認的名稱就是接口的名稱。我們可以通過Name屬性顯示地指定需要的名稱。
NameSpace:服務契約的命名空間,其作用是解決命名沖突的問題,提倡將你所在的公司名稱或項目名稱的URN作為命名空間。WCF默認的命名空間是:http://tempuri.org/
OperationContractAttribute:服務契約是一組相關服務操作(Operation)的集合,當我們在一個接口上應用了ServiceContractAttribute,便賦予了服務契約的屬性。但是對於這樣一個類型,它的成員並不會自動成為契約的服務操作,只有應用了OperationContractAttribute特性后,相應的方法成員才能成為能夠通過服務調用方式訪問的服務操作.
上面這一塊 在前面介紹的 時候 我們已經很熟悉了 ,就是 之前定義契約的 代碼中可以看到 這兩個特性 ,而 NameSpace 是 改變元數據 的 命名名稱 和 命名空間
同一個服務契約的所有服務操作都有一個名稱,並作為其唯一的標識,該名稱通過OperationContractAttribute的Name屬性指定。在默認的情況下,該名稱為操作對應的方法名稱。
例如:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Contracts 8 { 9 [ServiceContract(Name = "CalCulator", Namespace = "http://www.hulkxu.com")] 10 public interface ICalculator 11 { 12 double Add(double x, double y); 13 } 14 }
就等於 代碼:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Contracts 8 { 9 [ServiceContract(Name = "CalCulator", Namespace = "http://www.hulkxu.com")] 10 public interface ICalculator 11 { 12 13 [OperationContract(Name = "Add")] 14 double Add(double x, double y); 15 } 16 }
因為 在默認的情況下,該名稱為操作對應的方法名稱。 ( 我只是這么說,讓大家理解,實際操作中可不會這么寫 再重復 定義 name 等於 自己的操作名稱 )
注意:由於名稱是操作的唯一標識,所以在同一個操作契約中,不允許多個操作共享同一個名稱。另一方面,由於在默認的情況下,方法名稱即為操作名稱,對於重載的兩個服務操作,如果沒有顯示指定操作名稱,也是不允許的。
比如下面兩種服務契約的定義都是不合法的。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Contracts 8 { 9 [ServiceContract] 10 public interface ICalculator 11 { 12 [OperationContract(Name = "Add")] 13 double AddDouble(double x, double y); 不合法 14 [OperationContract(Name = "Add")] 15 double AddInt(int x, int y); 16 } 17 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Contracts 8 { 9 [ServiceContract] 10 public interface ICalculator 11 { 12 [OperationContract] 13 double Add(double x, double y); 14 [OperationContract] 不合法 15 double Add(int x, int y); 16 } 17 }
下面這個 雖然是 重載 但 指定的操作名稱 不同 是合法的
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Contracts 8 { 9 [ServiceContract(Name="CalCulator",Namespace="http://www.HulkXu.com")] 10 public interface ICalculator 11 { 12 13 [OperationContract(Name = "AddDouble")] 合法 14 double Add(double x, double y); 15 16 [OperationContract(Name = "AddInt")] 17 double Add(int x, int y); 18 19 } 20 }
消息交換模式(MEP)與服務操作
WCF是一個完全基於消息的通信框架,服務的調用通過消息交換來實現。
1:請求-回復模式下的服務契約與操作
該模式是最常用的消息交換模式,服務的消費者向服務的提供者發送請求,並等待,接受對方的回復。服務操作在默認的情況下采用這種消息交換模式。從消息格式的角度來說,對於一個普通的操作方法,輸入參數列表決定了請求消息的格式,而回復消息的格式則
由返回值類型決定。
注意:
如果我們定義的契約的返回值為Void
我們說對於請求——回復消息交換模式,輸入參數列表決定輸入消息的格式,而輸出消息的格式通過返回值的類型決定,那么對於void這種特殊的返回類型,情況是怎樣的呢?Void表示沒有返回值,是否意味着通過基於void的服務操作只有請求消息,沒有回復消息呢?
答案是否定的,既然采用了請求—回復的模式,就意味着請求方在發送請求后會受到回復消息,只不過回復消息的主體為空。
如:
1 [OperationContract] 2 Void Add(double x, double y);
從消息傳遞的角度來理解基於ref和out參數的服務操作,ref參數的值將包含在輸入(請求)和輸出(回復)消息中,而out參數和返回值並沒有本質的不同,參數值最終作為操作結果返回到客戶端。從消息結構的角度來說,ref參數的類型同時是輸入消息結構的決定因素之一,而輸出消息的結構除了決定於返回類型外,out和ref參數的類型是決定輸出消息結構的因素之一。
如:
1 [OperationContract] 2 Void Add(double x, double y, ref double result);
2.單向(one-way)模式下的服務契約與操作
雖然在大多數情況下,我們采用請求——回復的消息交換模式調用服務並獲得結果,但是,在某些情況下,我們調用某個服務並不一定有相應的結果返回,在一些極端的情況下,我們甚至不要求所有的服務調用都能成功執行。采用單向模式的服務調用,一旦消息進入網絡傳輸層,就馬上返回,此后的過程則完全和客戶端無關了。客戶端不但不會收到任何回復消息即使服務端拋出異常也不會被客戶端感知。在定義服務契約的時候,通過OperationContractAttribute的IsOneWay屬性將服務操作賦予單向消息交換的特性。由於單向消息交換是不具有任何回復消息的,單向的操作方法沒有返回值,所以只有返回類型為void的方法才能將IsOneWay屬性設為true.
如:
1 [OperationContract(IsOneWay = true)] 2 void WriteLog(string message);
3.雙工模式下的服務契約與操作
雙工模式模式的消息交換方式體現在消息交換過程中,參與的雙方均可以向對方發送消息。客戶端在進行服務調用的時候,附加上一個回調對象;服務在執行服務操作的過程中,通過客戶端附加的回調對象(實際上是調用回調服務的代理對象)回調客戶端的操作(該操作在客戶端執行)。整個消息交換的過程實際上由兩個基本的消息交換構成,其一是客戶端正常的服務請求,其二則是服務端的客戶端的回調。兩者可以采用請求——回復模式,也可以采用單向模式進行消息的交換。
[ 4-01 ]
這里我們重新做一個Demo
創建基於雙工通信的WCF應用的Dome
為簡單起見,我們沿用計算服務的例子。在這之前,我們都是調用CalculatorService直接得到計算結果,並將計算結果通過控制台輸出。該例子是:通過單向的模式調用CalculatorService(也就是客戶端不可能通過回復消息得到計算結果),服務端在完成運算結果后,通過回調的方式在客戶端將計算結果打印出來。
這個Demo 我就不 一步一步 教着搭建了,去雲盤 上找 代碼。
先看服務端的 契約
[ 4-02 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Contracts 8 { 9 /// <summary> 10 /// 客戶端調用CalculatorService進行正常的服務調用,那么在服務執行過程中借助於客戶端在 11 /// 服務調用時提供的回調對象對客戶端的操作進行回調。從本質上講是另外一種形式的服務調用 12 /// 。WCF采用基於服務契約的調用方式,客戶端正常的服務調用需要服務契約,同理服務端回調 13 /// 客戶端依然需要通過描述回調操作的服務契約,我們把這種服務契約稱為回調契約。回調契約 14 /// 的類型通過CallbackContract屬性指定. 15 /// </summary> 16 [ServiceContract(Namespace = "http://www.HulkXu.com/", CallbackContract = typeof(ICallback))] 17 public interface ICalculator 18 { 19 [OperationContract(IsOneWay = true)] 20 void Add(double x, double y); 21 } 22 }
上面這一步是 服務端的 契約 注意 定義 接口為 服務契約的時候 打上了 特性 ServiceContract ,並且 特性中的屬性 CallbackContract = typeof( 客戶端的 回調契約 ) 即 4-03 圖片中的接口名
**************************************************************************************************************************
[ 4-03 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Contracts 8 { 9 public interface ICallback 10 { 11 /// <summary> 12 /// 由於回調契約本質上也是一個服務契約,所以定義方式和一般意義上的服務契約基本上 13 /// 一樣。有一點不同的是,由於定義ICalculator的時候已經通過 [ServiceContract(CallbackContract = typeof(ICallback))] 14 /// 指明ICallback是一個服務契約了,所以ICallback不再需要添加ServiceContractAttribute特性 15 /// DisplayResult:用於顯示運算結果,由於服務端不需要回調的返回值,所以回調操作也設為 16 /// 單向的模式. 17 /// </summary> 18 /// <param name="x"></param> 19 /// <param name="y"></param> 20 /// <param name="result"></param> 21 [OperationContract(IsOneWay = true)] 22 void DisplayResult(double x, double y, double result); 23 } 24 }
然后 到這里 是 服務端調用客戶端的回調契約, 記得這里做的 雙方都不需要返回值
**************************************************************************************************************************
再看我們的 服務端的實現:
[ 4-04 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Contracts; 6 using System.ServiceModel; 7 8 namespace Services 9 { 10 public class CalculatorService : ICalculator 11 { 12 /// <summary> 13 /// 實現了服務契約ICalculator中的Add方法。結果顯示是通過回調的方式實現的, 14 /// 所以要借助於客戶端提供的回調對象。在WCF中,回調對象通過當前OperationContext對象獲取 15 /// </summary> 16 /// <param name="x"></param> 17 /// <param name="y"></param> 18 public void Add(double x, double y) 19 { 20 double result = x + y; 21 //得到客戶端的回調契約 22 ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>(); 23 //調用客戶端方法 24 callback.DisplayResult(x, y, result); 25 } 26 } 27 }
在上一步,在服務端的 回調契約中 定義的 DisplayResult 方法,但是那僅僅是一個接口,這個接口是由客戶端 去實現的,所以服務器端的 只去實現 ICalculator 這個接口就可以了。
**************************************************************************************************************************
再看服務器寄宿
代碼:
[ 4-05 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using Services; 7 8 namespace Hosting 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 /*在WCF綁定類型中,WsDualHttpBinding和NetTcpBinding均提供了對雙工通信的支持 15 * ,但是兩者在對雙工通信的實現機制上卻有本質的區別。WsDualHttpBinding是基於 16 * HTTP傳輸協議的;而HTTP協議本身是基於請求--回復的傳輸協議,基於HTTP的通道本質 17 * 上都是單向的,WsDualHttpBinding實際上創建了兩個通道,一個用於客戶端向服務端 18 * 的通信,而另一個則用於服務端到客戶端的通信,從而間接地提供了雙工通信的實現。 19 * WsDualHttpBinding通過創建兩個單向信道的方式提供雙工通信的實現 20 * NetTcpBinding完全基於支持雙工通信的TCP協議. 21 */ 22 using(ServiceHost host = new ServiceHost(typeof(CalculatorService))) 23 { 24 host.Open(); 25 Console.Read(); 26 } 27 } 28 } 29 }
注意:我們這里的服務端 采用的 是 NetTcpBinding(TCP) 方式的 通信,也就是說 當打通 客戶端到服務端的通信 就 創建了 兩條通信(一條進一條出)。而如果使用的是WsDualHttpBinding ( HTTP ),則只會創建一條通信,進出的消息都在本通信內完成。
配置:
[ 4-06 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <services> 5 <service name="Services.CalculatorService"> 6 <endpoint address="net.tcp://127.0.0.1:9988/CalculatorService" binding="netTcpBinding" contract="Contracts.ICalculator"></endpoint> 7 </service> 8 </services> 9 </system.serviceModel> 10 </configuration>
可以看到我們的服務器寄宿 沒有什么變化
**************************************************************************************************************************
最后看我們的 客戶端 :
首先去實現 我們在 契約層定義的 客戶端 回調契約 ICallback
[ 4-07 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Contracts; 6 7 namespace Client 8 { 9 /// <summary> 10 /// 實現回調契約 11 /// 客戶端程序為回調契約提供實現。 12 /// </summary> 13 class CalculateCallback:ICallback 14 { 15 public void DisplayResult(double x, double y, double result) 16 { 17 Console.WriteLine("x+y={2} when x={0} and y={1}",x,y,result); 18 } 19 } 20 }
這樣我們的回調契約就實現了,可以看出,服務端 的 契約 在 Services層的CalculatorService類去實現,而 客戶端的 回調契約 在 客戶端 Client 層的CalculateCallback 類去實現。
[ 4-08 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using Contracts; 7 8 namespace Client 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 /* 在服務端調用的程序中,通過DuplexChannelFactory<TChannel>創建服務代理對象 15 * DuplexChannelFactory<TChannel>和ChannelFactory<TChannel>的功能都是一個服務代理對象 16 * 不過DuplexChannelFactory<TChannel>專門用於基於雙工通信的服務代理創建 17 * 在創建DuplexChannelFactory<TChannel>之前,先創建回調對象,並通過InstanceContext對回調對象進行包裝. 18 */ 19 20 //完成對回調對象的包裝 21 InstanceContext instanceContext = new InstanceContext(new CalculateCallback()); 22 //創建基於雙工通信的服務代理 23 using (DuplexChannelFactory<ICalculator> channelFactory = new DuplexChannelFactory<ICalculator>(instanceContext, "CalculatorService")) 24 { 25 ICalculator proxy = channelFactory.CreateChannel(); 26 using (proxy as IDisposable) 27 { 28 proxy.Add(1,2); 29 Console.Read(); 30 } 31 } 32 } 33 } 34 }
[ 4-09 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <client> 5 <endpoint name="CalculatorService" address="net.tcp://127.0.0.1:9988/CalculatorService" binding="netTcpBinding" contract="Contracts.ICalculator"></endpoint> 6 </client> 7 </system.serviceModel> 8 </configuration>
客戶端的配置也沒有什么變化。
這個Demo 的 整體流程就是:
[ 4-10 ]
我們這個Demo已經定義了 服務契約以及回調契約 都沒有返回值(Void) 的 方法
所以 客戶端請求 服務端 就沒有下文了,而服務端接收到了客戶端的 請求 之后 處理結果,再根據客戶端的 回調對象 調用客戶端的 方法 就沒有下文了。
注意 : 這 服務契約 與 回調契約 中 都是 void 並且 特性上打了標簽 IsOneWay = true 。
那如果我們將 ICallback 契約中DisplayResult方法上的特性IsOneWay = true 去掉,程序運行 將 Hosting設為 啟動項 運行,然后使用 客戶端 去調用服務端 :
[ 4-11 ]
這里報錯了。 產生了 死鎖。 想想,這是為什么?
下一章 說!