WCF進階學習ing...
在熟練掌握了ABC的使用以后,就開始想着去了解WCF是怎么通信的了。首先是服務描述語言wsdl,它定義了服務的描述等等,用於讓外界知道這個服務的ABC是什么。另外一個比較重要的就是消息。
WCF是通過消息進行通訊的,一般是使用SOAP形式。服務端的信道監聽器接收到消息之后,對消息進行反序列化,解碼,然后通過激活對象,再去invoke相應的操作,操作的結果(返回值)再通過編碼,序列化,傳送給調用者,調用者再對消息進行反序列化,解碼,最后拿到結果。所以在這個過程中,對消息的理解和熟悉對於我們理解WCF的操作流程是很大的幫助的。
然后我們就開始攔截這個消息來看看這個消息到底是什么。。。廢話不多說,上code。。
首先創建一個提供復數計算的服務,使用共享C的方式,項目結構如下,服務端和客戶端都是一個控制台程序
服務契約代碼 IComplexCalculate.cs:
1 namespace Cookiezhi.WcfStudy.Contracts.ServiceContracts 2 { 3 [ServiceContract(Namespace="http://www.cookiezhi.com/service/complex")] 4 public interface IComplexCalculate 5 { 6 /// <summary> 7 /// 加 8 /// </summary> 9 [OperationContract] 10 Complex Add(Complex a, Complex b); 11 12 /// <summary> 13 /// 減 14 /// </summary> 15 [OperationContract] 16 Complex Subtract(Complex a, Complex b); 17 18 /// <summary> 19 /// 乘 20 /// </summary> 21 [OperationContract] 22 Complex Multiply(Complex a, Complex b); 23 24 /// <summary> 25 /// 取模 26 /// </summary> 27 [OperationContract] 28 double Modulus(Complex a); 29 } 30 }
數據契約 Complex.cs:
1 namespace Cookiezhi.WcfStudy.Contracts.DataContracts 2 { 3 [DataContract(Namespace = "http://www.cookiezhi.com/data/complex")] 4 public class Complex 5 { 6 /// <summary> 7 /// 實數 8 /// </summary> 9 [DataMember] 10 public double A { get; set; } 11 12 /// <summary> 13 /// 虛數 14 /// </summary> 15 [DataMember] 16 public double B { get; set; } 17 } 18 }
服務契約實現 ComplexCalculateService:
1 namespace Cookiezhi.WcfStudy.Services 2 { 3 public class ComplexCalculateService : IComplexCalculate 4 { 5 public Complex Add(Complex a, Complex b) 6 { 7 return new Complex() 8 { 9 A = a.A + b.A, 10 B = a.B + b.B 11 }; 12 } 13 14 public Complex Subtract(Complex a, Complex b) 15 { 16 return new Complex() 17 { 18 A = a.A - b.A, 19 B = a.B - b.B 20 }; 21 } 22 23 public Complex Multiply(Complex a, Complex b) 24 { 25 return new Complex() 26 { 27 A = a.A * b.A - a.B * b.B, 28 B = a.A * b.B + a.B * b.A 29 }; 30 } 31 32 public double Modulus(Complex a) 33 { 34 return Math.Sqrt(a.A * a.A + a.B * a.B); 35 } 36 } 37 }
采用配置文件方式去設置服務 app.config:
1 <system.serviceModel> 2 <behaviors> 3 <serviceBehaviors> 4 <behavior name="mexBehavior"> 5 <serviceMetadata httpGetEnabled="true"/> 6 </behavior> 7 </serviceBehaviors> 8 </behaviors> 9 10 <services> 11 <service name="Cookiezhi.WcfStudy.Services.ComplexCalculateService" behaviorConfiguration="mexBehavior"> 12 <endpoint address="" binding="basicHttpBinding" contract="Cookiezhi.WcfStudy.Contracts.ServiceContracts.IComplexCalculate" /> 13 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> 14 <host> 15 <baseAddresses> 16 <add baseAddress="http://127.0.0.1:9999/complexcalcservice"/> 17 </baseAddresses> 18 </host> 19 </service> 20 </services> 21 22 </system.serviceModel>
然后服務端的main方法里啟動服務:
1 namespace Cookiezhi.WcfStudy.Hosting 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 using(ServiceHost host = new ServiceHost(typeof(ComplexCalculateService))) 8 { 9 host.Opened += delegate 10 { 11 Console.WriteLine("Service {0} started", host.Description.Name); 12 }; 13 14 host.Open(); 15 16 Console.ReadKey(); 17 } 18 } 19 } 20 }
OK, 服務端好了,我們啟動一下
再看一下WSDL
OK是好的,這些對於做過WCF相關的朋友們都是輕車熟路了,下面是客戶端,通過配置文件加ChannelFactory方式來創建並調用
App.config
1 <system.serviceModel> 2 <client> 3 <endpoint name="ComplexCalculateService" address="http://127.0.0.1:9999/complexcalcservice" binding="basicHttpBinding" contract="Cookiezhi.WcfStudy.Contracts.ServiceContracts.IComplexCalculate" /> 4 </client> 5 </system.serviceModel>
Main方法
1 namespace Cookiezhi.WcfStudy.Client 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 using(ChannelFactory<IComplexCalculate> factory = new ChannelFactory<IComplexCalculate>("ComplexCalculateService")) 8 { 9 IComplexCalculate proxy = factory.CreateChannel(); 10 11 Complex a = new Complex() { A = 1, B = 2 }; 12 Complex b = new Complex() { A = 2, B = 1 }; 13 Complex result = null; 14 15 result = proxy.Add(a, b); 16 Console.WriteLine("Add result is {0} + {1}i", result.A, result.B); 17 18 Console.ReadKey(); 19 } 20 } 21 } 22 }
調用服務:
前戲做完了,我們開始進入主題:
我們需要攔截消息,並把消息打印出來,那么我們就需要一個攔截器,叫做MessageInspector,WCF為我們提供了兩種攔截器:
客戶端攔截器 IClientMessageInspector
提供兩個接口
BeforeSendRequest:向服務器發送請求前執行
AfterReceiveReply:接收到服務器的回復消息后執行
服務端攔截器 IDispatchMessageInspector
他也提供兩個接口
AfterReceiveRequest:invoke操作之前執行
BeforeSendReply:發送reply給客戶端之前執行
在這里我們在服務端設置個攔截器,然后打印出請求和回復的消息,所以我們使用IDispatchMessageInspector這個接口
實現接口 MessageInspector.cs
1 namespace Cookiezhi.WcfStudy.Hosting.MessageInspect 2 { 3 public class MessageInspector : IDispatchMessageInspector 4 { 5 public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) 6 { 7 Console.WriteLine(request.ToString()); 8 return DateTime.Now; 9 } 10 11 public void BeforeSendReply(ref Message reply, object correlationState) 12 { 13 Console.WriteLine(reply.ToString()); 14 DateTime requestTime = (DateTime)correlationState; 15 16 var duration = DateTime.Now - requestTime; 17 Console.WriteLine(duration); 18 } 19 } 20 }
其中AfterReceiveRequest先執行,然后去執行遠程方法,然后再執行BeforeSendReply,所以在這里加了一個操作計時的功能(可選)。
然后我們要將這個攔截器給寄宿在我們的終結點上,所以需要定義一個終結點行為(EndpointBehavior),並寄宿在服務上。
MessageInspectorBehavior.cs,在ApplyDispatchBehavior方法實現中將我們新建的Inspector實例加到dispatcher的MessageInspectors中
1 namespace Cookiezhi.WcfStudy.Hosting.MessageInspect 2 { 3 public class MessageInspectorBehavior : IEndpointBehavior 4 { 5 public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) 6 { 7 } 8 9 public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) 10 { 11 } 12 13 public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) 14 { 15 endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MessageInspector());16 } 17 18 public void Validate(ServiceEndpoint endpoint) 19 { 20 } 21 } 22 }
最后創建一個配置元素用於在配置文件中給終結點配置這個行為.
1 namespace Cookiezhi.WcfStudy.Hosting.MessageInspect 2 { 3 public class MessageInspectorExtensionElement : BehaviorExtensionElement 4 { 5 public override Type BehaviorType 6 { 7 get { return typeof(MessageInspectorBehavior); } 8 } 9 10 protected override object CreateBehavior() 11 { 12 return new MessageInspectorBehavior(); 13 } 14 } 15 }
下面就是配置這個行為了
App.config
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 4 <system.serviceModel> 5 <extensions> 6 <behaviorExtensions> 7 <add name="messageInspector" type="Cookiezhi.WcfStudy.Hosting.MessageInspect.MessageInspectorExtensionElement, Cookiezhi.WcfStudy.Hosting"/> 8 </behaviorExtensions> 9 </extensions> 10 11 <behaviors> 12 <serviceBehaviors> 13 <behavior name="mexBehavior"> 14 <serviceMetadata httpGetEnabled="true"/> 15 </behavior> 16 </serviceBehaviors> 17 <endpointBehaviors> 18 <behavior name="messageInspector"> 19 <messageInspector /> 20 </behavior> 21 </endpointBehaviors> 22 </behaviors> 23 24 <services> 25 <service name="Cookiezhi.WcfStudy.Services.ComplexCalculateService" behaviorConfiguration="mexBehavior"> 26 <endpoint address="" binding="basicHttpBinding" contract="Cookiezhi.WcfStudy.Contracts.ServiceContracts.IComplexCalculate" behaviorConfiguration="messageInspector" /> 27 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> 28 <host> 29 <baseAddresses> 30 <add baseAddress="http://127.0.0.1:9999/complexcalcservice"/> 31 </baseAddresses> 32 </host> 33 </service> 34 </services> 35 36 </system.serviceModel> 37 38 <startup> 39 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> 40 </startup> 41 </configuration>
客戶端的代碼不要做出任何的改變,
然后我們嘗試一下
Great! 我們成功的攔截了請求,並將請求信息打印了出來。
總結,有了這個攔截器,我們可以做很多的事情,比如修改消息頭和消息體,計算消息的大小(流量統計),統計服務調用的次數和平均時間,客戶端情況,等等。