• WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇]


    WCF是.NET平台下实现SOA的一种手段,SOA的一个重要的特征就基于Message的通信方式。从Messaging的角度讲,WCF可以看成是对Message进行发送、传递、接收、基础的工具。对于一个消息交换的过程,很多人只会关注message的最初的发送端和最终的接收端。实际上在很多情况下,在两者之间还存在很多的中间结点(Intermediary),这些中间结点在可能在实际的应用中发挥中重要的作用。比如,我们可以创建路由器(Router)进行消息的转发,甚至是Load Balance;可以创建一个消息拦截器(Interceptor)获取request或者response message,并进行Audit、Logging和Instrumentation。今天我们就我们的目光转向这些充当着中间人角色的Intermediary上面来。

    在本篇文章中,我们将会创建一个message的拦截和转发工具(message interceptor)。它将被置于WCF调用的client和service之间,拦截并转发从client到service的request message,以及service到client的response message,并将request message和response message显示到一个可视化的界面上。我们将讨论这个message interceptor若干种不同的实现方式。

    有一点需要明确说明的是,这个工具的创建并非我写作这篇文章的目的,我的目的是通过一个具体的例子让大家以一种直观方式对WCF的Addressing机制有一个深刻的认识。在介绍message interceptor的创建过程中,我会穿插介绍一个WCF的其它相关知识,比如Message FilteringOperation Selection、Must Understand Validation等等。

    一、创建一个简单的WCF应用

    由于我们将要创建的message interceptor需要应用到具体的WCF应用中进行工作和检验,我们需要首先创建一个简单的WCF应用。我们创建一个简单的Calculation的例子。这个solution采用我们熟悉的四层结构(Interceptor用于host我们的message intercept service):

    image

    1、Contract:Artech.MessageInterceptor.Contracts.ICalculate

       1: using System.ServiceModel;
       2: namespace Artech.MessageInterceptor.Contracts
       3: {
       4:     [ServiceContract]
       5:     public interface ICalculate
       6:     {
       7:         [OperationContract]
       8:         double Add(double x, double y);
       9:     }
      10: } 

    2、Service:Artech.MessageInterceptor.Services.CalculateService

       1: using Artech.MessageInterceptor.Contracts;
       2: namespace Artech.MessageInterceptor.Services
       3: {
       4:     public class CalculateService : ICalculate
       5:     {
       6:         #region ICalculate Members 
       7:  
       8:         public double Add(double x, double y)
       9:         {
      10:             return x + y;
      11:         } 
      12:  
      13:         #endregion
      14:     }
      15: } 
      16:  

    3、Hosting:Artech.MessageInterceptor.Hosting.Program

       1: using System;
       2: using System.ServiceModel;
       3: using Artech.MessageInterceptor.Services;
       4: namespace Artech.MessageInterceptor.Hosting
       5: {
       6:     class Program
       7:     {
       8:         static void Main(string[] args)
       9:         {
      10:             using (ServiceHost host = new ServiceHost(typeof(CalculateService)))
      11:             {
      12:                 host.Opened += delegate
      13:                 {
      14:                     Console.WriteLine("The calculate service has been started up!");
      15:                 };
      16:                 host.Open();
      17:                 Console.Read();
      18:             }
      19:         }
      20:     }
      21: } 
      22:  

    Configuration

       1: <?xml version="1.0" encoding="utf-8" ?>
       2: <configuration>
       3:     <system.serviceModel>
       4:         <bindings>
       5:             <customBinding>
       6:                 <binding name="MyCustomeBinding">
       7:                     <textMessageEncoding />
       8:                     <httpTransport />
       9:                 </binding>
      10:             </customBinding>
      11:         </bindings>
      12:         <services>
      13:             <service name="Artech.MessageInterceptor.Services.CalculateService">
      14:                 <endpoint binding="customBinding" bindingConfiguration="MyCustomeBinding"
      15:                     contract="Artech.MessageInterceptor.Contracts.ICalculate" 
      16:                     address="http://127.0.0.1:9999/calculateservice"/>              
      17:             </service>
      18:         </services>
      19:     </system.serviceModel>
      20: </configuration> 

    在host我们的calculateservice的时候,我们使用了抛弃了系统定义的binding,而采用一个custom binding。是因为custom binding基于更好的可扩展能力,以利于我们后续的介绍。为了简单起见,我们仅仅需要bing为了提供最基本的功能:传输与编码,为此我仅仅添加了两个binding element:textMessageEncoding 和httpTransport。我们将在后面部分应用其他的功能,比如WS-Security.

    4、Client:Artech.MessageInterceptor.Clients.Program

       1: using System;
       2: using System.ServiceModel;
       3: using Artech.MessageInterceptor.Contracts;
       4: namespace Artech.MessageInterceptor.Clients
       5: {
       6:     class Program
       7:     {
       8:         static void Main(string[] args)
       9:         {
      10:             using (ChannelFactory<ICalculate> channelFactory = new ChannelFactory<ICalculate>("calculateservice"))
      11:             {
      12:                 ICalculate calculator = channelFactory.CreateChannel();
      13:                 using (calculator as IDisposable)
      14:                 {
      15:                     Console.WriteLine("x + y = {2} where x = {0}  ans y = {1}", 1, 2, calculator.Add(1, 2));
      16:                 }
      17:             } 
      18:  
      19:             Console.Read();
      20:         }
      21:     }
      22: } 
      23:  

    Configuration:

       1: <?xml version="1.0" encoding="utf-8" ?>
       2: <configuration>
       3:     <system.serviceModel>
       4:          <bindings>
       5:             <customBinding>
       6:                 <binding name="MyCustomBinding">
       7:                     <textMessageEncoding />
       8:                     <httpTransport />
       9:                 </binding>
      10:             </customBinding>
      11:         </bindings>
      12:         <client>
      13:             <endpoint name="calculateservice" address="http://127.0.0.1:9999/calculateservice" binding="customBinding" bindingConfiguration="MyCustomBinding"
      14:                 contract="Artech.MessageInterceptor.Contracts.ICalculate" />
      15:         </client>
      16:     </system.serviceModel>
      17: </configuration> 
      18:  

    二、创建Message Interceptor

    现在我们正式开始进行我们的消息拦截与转发工具的创建。这个工具本质是一个WCF service(我们姑且称它为Intercept service),在该service中定义一个operation进行消息的拦截、处理、转发的功能(如下图所示)。

    wcf_13_01

    一般地我们有两种不同的方案来来实现我们的功能:

    • Client调用service的时候,主动将message发送到Intercept service;Intercept service获取request并对其进行相应处理后,将message原封不动地转发到真正的service,并接受response message。对response message进行相应处理后,将其返回给client。
    • Client照常访问service,但是将Intercept service监听地址设置为service的地址(并对service的监听地址也作相应的修改),那么 client对service访问过程中发送的message将会被Intercept service截获,Intercept service向上面一样进行message处理和转发。

    我们先采用第一种实现方案。

    1、Contract定义:Artech.MessageInterceptor.Contracts.IIntercept

    我们来介绍Intercept service的定义,先来看看Contract的定义(Intercept service的contract和ICalculate定义在同一个project中):

       1: using System.ServiceModel.Channels;
       2: using System.ServiceModel;
       3: namespace Artech.MessageInterceptor.Contracts
       4: {
       5:    [ServiceContract]
       6:    public interface IIntercept
       7:     {
       8:         [OperationContract(Action ="*", ReplyAction="*")]
       9:        Message Intercept(Message request);
      10:     }
      11: }
      12:  

    Intercept service的contract具有如下两个特点:

    • Intercept的参数和返回值都是Message对象。
    • Operation的Action和ReplyAction为*。

    我们先来讲将第一个特征,之所以我们要使用untyped message作为参数和返回值,是因为我们要将Intercept打造成一个“万能”的操作:能够处理任何请求和返回。我们知道,虽然我们在进行WCF service调用的时候,我们的参数列表,无论是个数、数据类型和次序,都千差万别,我们的返回值类型也各有不同,但是WCF service的调用最终是基于Message的,所以我们的参数或者返回值最终都将转变成message对象(input参数:request message;ref/out 参数和返回值:response message),我们我们的Intercept将是一个“万能”的operation。

    至于第二个问题,我们就需要了解WCF的一个重要的机制了:Operation Selection。WCF的Channel Listener监听并接收request message后,Channel Dispatcher通过Contract Message Filter和Address Message Filter选择对应的Endpoint Dispatcher;Endpoint Dispatcher通过InstanceContext/InstanceProvider获得或者创建service intance,并通过reflection调用对应Operation。但是Operation是如何选择的呢?默认的情况下是根据Message的Action Header进行选择的,一般地将会按照这样的匹配规则进行:Contract Namespace(default:http://tempuri.org)/Contract Name(default:Interface name)/Action(default:method name)= action in SOAP header。如果将Action设为“*”将意味着:对intercept service的调用,无路SOAP Header中action是什么,都将交付Intercept来处理。

    2、Service的定义:Artech.MessageInterceptor.Services.InterceptService

    Intercept service将会完整这样的功能:拦截request message并将其显示到一个Windows form的TextBox中;将message原封不动地向service转发;向处理request message一样拦截并显示response message。

       1: using System;
       2: using Artech.MessageInterceptor.Contracts;
       3: using System.ServiceModel.Channels;
       4: using System.Threading;
       5: using System.ServiceModel;
       6: using System.ServiceModel.Description;
       7: namespace Artech.MessageInterceptor.Services
       8: {
       9:     [ServiceBehavior(UseSynchronizationContext = false, AddressFilterMode = AddressFilterMode.Any)]
      10:     public class InterceptService : IIntercept
      11:     {
      12:         private const string CalculateServiceEndpoint = "calculateService";
      13:         public static SynchronizationContext SynchronizationContext
      14:         { get; set; }
      15:         public static System.Windows.Forms.TextBox MessageDisplayPanel
      16:         { get; set; } 
      17:  
      18:         #region IIntercept Members 
      19:  
      20:         public Message Intercept(Message request)
      21:         {
      22:             using (ChannelFactory<IIntercept> channelFactory = new ChannelFactory<IIntercept>(CalculateServiceEndpoint))
      23:             {
      24:                  IIntercept interceptor = channelFactory.CreateChannel();
      25:                 using (interceptor as IDisposable)
      26:                 {
      27:                     MessageBuffer requstBuffer = request.CreateBufferedCopy(int.MaxValue);
      28:                     Message response = interceptor.Intercept(requstBuffer.CreateMessage());
      29:                     MessageBuffer responseBuffer = response.CreateBufferedCopy(int.MaxValue);
      30:                     SynchronizationContext.Post(delegate
      31:                     {
      32:                         MessageDisplayPanel.Text += string.Format("Request:{0}{1}{0}", Environment.NewLine, request);
      33:                         MessageDisplayPanel.Text += string.Format("Response:{0}{1}{0}", Environment.NewLine, response);
      34:                     }, null);
      35:                     return responseBuffer.CreateMessage();
      36:                 }
      37:             }
      38:         } 
      39:  
      40:         #endregion
      41:     }
      42: } 
      43:  

    对于InterceptService的定义,有下面几点需要说明:

    • UseSynchronizationContext 和SynchronizationContext:这是关于Windows Form 线程关联性的相关设置与应用,在我的前两篇已有详细的介绍,不清楚的可以参阅这篇文章(WCF下的线程关联性
    • AddressFilterMode = AddressFilterMode.Any:在上面我们提到过,ChannelDispatcher在选择EndpointDispacher的时候是基于两个Message Filter:Address Filter和Contract Filter。也就是说,ChannelDispatcher通过这两个Filter选择合适Endpoint。在默认的情况下,Address Filter是根据SOAP的To Message Header的URI来进行栓选的,所以需要Endpoint的Address和To Header中的Addres完全匹配。但是在我们CalculateService的例子中,由于Client最终是访问的时CalculateService,所以生成的SOAP的To Headler的地址是CalculateService的地址:http://127.0.0.1:9999/calculateservice,而我们需要是用InterceptService 来处理该请求,Address Filtering肯定是不能通过的。好在我们可以在ServiceBehavior设置AddressFilterMode 来改变Address Filtering的方式。AddressFilterMode = AddressFilterMode.Any意味着,Address Filtering会被忽略。
    • Message的转发,直接通过CalculateService的endpoint name创建的Proxy对象的service调用完成。
    • CreateBufferedCopy:可能有人会奇怪,为什么不对request message和response message进行直接操作(将他们显示在TextBox上)?这是应为Message在WCF有一个特殊的处理机制:只有Message的State为Created的时候,才能获取MessageBody的内容,否则会抛出异常。而我们在对Message进行相应操作的时候,会改变Message 的State(Read,Written,Copied,Closed)。所以对response message来讲,对message的显示实际上将Sate改为Read,如何将response message直接返回到client,对该message的读取操作将是不允许的,所以先调用CreateBufferedCopy创建该message的一个memory buffer,最有返回的时通过该buffer重新创建的Message。

    3、Service的Hosting:

    我们创建了一个Windows Form Application来host InterceptService,并在一个Form的Load事件中完成host。

       1: using System;
       2: using System.Windows.Forms;
       3: using System.ServiceModel;
       4: using Artech.MessageInterceptor.Services;
       5: using System.Threading;
       6: namespace Artech.MessageInterceptor.Interceptor
       7: {
       8:     public partial class MessageInterceptor : Form
       9:     {
      10:         private ServiceHost _serviceHost;
      11:         public MessageInterceptor()
      12:         {
      13:             InitializeComponent();
      14:         } 
      15:  
      16:         private void MessageInterceptor_Load(object sender, EventArgs e)
      17:         {
      18:             this._serviceHost = new ServiceHost(typeof(InterceptService));
      19:             this._serviceHost.Opened += delegate
      20:             {
      21:                 this.Text += ":Started";
      22:             }; 
      23:  
      24:             InterceptService.SynchronizationContext = SynchronizationContext.Current;
      25:             InterceptService.MessageDisplayPanel = this.textBoxMessage;
      26:             this._serviceHost.Open();
      27:         }
      28:     }
      29: } 
      30:  

    下面是configuration:

       1: <?xml version="1.0" encoding="utf-8" ?>
       2: <configuration>
       3:     <system.serviceModel>
       4:         <bindings>
       5:             <customBinding>
       6:                 <binding name="MyCustomBinding">
       7:                     <textMessageEncoding />
       8:                     <httpTransport manualAddressing="true" />
       9:                 </binding>
      10:             </customBinding>
      11:         </bindings>
      12:         <client>
      13:             <endpoint address="http://127.0.0.1:9999/calculateservice" binding="customBinding"
      14:                 bindingConfiguration="MyCustomBinding" contract="Artech.MessageInterceptor.Contracts.IIntercept"
      15:                 name="calculateService" />
      16:         </client>
      17:         <services>
      18:             <service name="Artech.MessageInterceptor.Services.InterceptService">
      19:                 <endpoint binding="customBinding" bindingConfiguration="MyCustomBinding"
      20:                     contract="Artech.MessageInterceptor.Contracts.IIntercept" 
      21:                           address="http://127.0.0.1:8888/Interceptservice"/>
      22:             </service>
      23:         </services>
      24:     </system.serviceModel>
      25: </configuration> 
      26:  

    这里需要注意的client的配置,可能有人会有这样的疑惑:Address是CalculateService的地址,但是Contract确是InterceptService的Contract,这不是不匹配吗?实际上由于IIntercept中Intercept方式的参数和返回值都是Message,所以他们代表一切操作。

    三、应用InteceptService

    现在我们将我们创建InteceptService应用到我们CalculateService中。我们在上面已经提到过,我们现在是方案时要client自动将message发送到InteceptService。在WCF中有一个特殊的EndpointBehavior。(System.ServiceModel.Description.ClientViaBehavior),来实现这样的功能:Message真正发送的地址不同是service真正的地址。基本的原理如下图所示:

    image

    我们现在只需要改变client端的配置即可:

       1: <?xml version="1.0" encoding="utf-8" ?>
       2: <configuration>
       3:     <system.serviceModel>
       4:         <behaviors>
       5:             <endpointBehaviors>
       6:                 <behavior name="ClientViaBehavior">
       7:                     <clientVia viaUri="http://127.0.0.1:8888/Interceptservice" />
       8:                 </behavior>
       9:             </endpointBehaviors>
      10:         </behaviors>
      11:         <bindings>
      12:             <customBinding>
      13:                 <binding name="MyCustomBinding">
      14:                     <textMessageEncoding />
      15:                     <httpTransport />
      16:                 </binding>
      17:             </customBinding>
      18:         </bindings>
      19:         <client>
      20:             <endpoint name="calculateservice" address="http://127.0.0.1:9999/calculateservice" behaviorConfiguration="ClientViaBehavior"
      21:                 binding="customBinding" bindingConfiguration="MyCustomBinding"
      22:                 contract="Artech.MessageInterceptor.Contracts.ICalculate" />
      23:         </client>
      24:     </system.serviceModel>
      25: </configuration> 
      26:  

    当我们运行我们的程序(先启动两个host程序,然后是client),Interceptor Windows Forms Appliction的窗体上将会看到被拦截的request message和response message:

    image

    WCF后续之旅: 
    WCF后续之旅(1): WCF是如何通过Binding进行通信的 
    WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel 
    WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher 
    WCF后续之旅(4):WCF Extension Point 概览 
    WCF后续之旅(5): 通过WCF Extension实现Localization 
    WCF后续之旅(6): 通过WCF Extension实现Context信息的传递 
    WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成 
    WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成 
    WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I] 
    WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II] 
    WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance 
    WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity) 
    WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响 
    WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇] 
    WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇] 
    WCF后续之旅(14):TCP端口共享 
    WCF后续之旅(15): 逻辑地址和物理地址 
    WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter) 
    WCF后续之旅(17):通过tcpTracer进行消息的路由


    作者:Artech
    出处:http://artech.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    感谢梦想
    感谢迷茫
    CSS动画
    ES6笔记③
    Object的增。删。查。改。遍历
    js创建对象的方式 三种
    js 控制台的错误提示
    ES6笔记② 箭头函数
    ES6笔记① var 和 let的区别
    js冒泡排序
  • 原文地址:https://www.cnblogs.com/artech/p/1280939.html
Copyright © 2020-2023  润新知