[WCF]设置拦截器捕捉到request和reply消息
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! 我们成功的拦截了请求,并将请求信息打印了出来。
总结,有了这个拦截器,我们可以做很多的事情,比如修改消息头和消息体,计算消息的大小(流量统计),统计服务调用的次数和平均时间,客户端情况,等等。