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 Filtering、Operation Selection、Must Understand Validation等等。
一、创建一个简单的WCF应用
由于我们将要创建的message interceptor需要应用到具体的WCF应用中进行工作和检验,我们需要首先创建一个简单的WCF应用。我们创建一个简单的Calculation的例子。这个solution采用我们熟悉的四层结构(Interceptor用于host我们的message intercept service):
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进行消息的拦截、处理、转发的功能(如下图所示)。
一般地我们有两种不同的方案来来实现我们的功能:
我们先采用第一种实现方案。
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具有如下两个特点:
我们先来讲将第一个特征,之所以我们要使用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的定义,有下面几点需要说明:
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真正的地址。基本的原理如下图所示:
我们现在只需要改变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:
作者:Artech 出处:http://artech.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。