WCF4.0进阶系列—第十一章 编写代码控制配置和通信 (上)
前言
到目前为止,你已经很好地理解如何创建WCF客户端程序和服务,以及如何配置它们使两者之间可以相互通信。WCF一个非常引人注目的特点是可以通过配置文件配置服务和客户端。在配置文件的背后,WCF运行时使用配置信息创建发送和接收消息的架构,该架构使用配置文件中指定的协议,这些协议以某种方式编码并传送消息至服务操作对应的方法中。在实际工作中,你不可避免地会遇到通过代码来完成配置任务,这可能是由于程序或者服务需要自适应其运行的环境并且不需要管理员的介入。或者,由于安全原因,你可能不希望任何人修改程序的配置文件。比如,你可能不希望管理员激活或者关闭服务对外公布的元数据。此外,观察到WCF运行时运行客户端或服务时所执行的各种任务也是非常有好处的。在本小节中,你会进一步了解到WCF服务模型的工作流程,绑定与通道的相关知识,最后在ShoppingCartService服务中使用代码创建和使用自定义绑定。
WCF服务模型
WCF提供了完整基础结构,通过创建许多管理和控制通信的对象以发送和接受消息。 这个基础结构是可以扩展的,如果你需要自定义基础架构的工作方式,你可以使用自己的函数声明这个基础结构。比如,在第十章"实现可靠的会话"中,你已经掌握了如何为自定义绑定组合WCF提供的通道。如果你有非常特别的需求、或者需要使用在.NET
Framework中不存在对应通道的通信协议传输消息,你可以开发自己的通道(或者从第三方购买)然后集成该通道至你的配置文件,而不需要修改服务或者客户端程序的代码。你还可以自定义WCF基础结构的其他部分,比如WCF映射入栈消息至服务操作。在第十四章"发现服务和路由消息"中你将看到一些例子。
首先我们再来看一下下图描述的WCF基础架构
为对于客户端来说,当应用程序调用一个方法,ClientOperation类转化方法调用(包括方法的参数)为一条请求消息,然后传递请求消息至ClientRuntime,ClientRuntime负责创建和管理所需的通道,并处理一些其他的消息处理过程(比如修改消息header),当然还负责处理服务的响应的消息。最后消息经过Encoder层序列化为流,然后通过传输层传输至服务侦听的地址。
对于服务端来说,在端点指定的地址上启动ChannelListener对象,等待客户端发送消息,然后交给Encoder层将流反序列化为消息,然后逐步通过上层的协议(绑定)达到通道Dispatcher;然后通道Dispatcher分发消息至EndpointDispatcher;最后EndpointDispatcher将消息转化成对服务实例方法的调用。然后将方法的返回值同样转化成响应消息,通过Encoder层序列化之后回传给客户端
服务和通道
你可以将绑定认为是一个通道堆栈中所有通道的描述。当一个宿主程序启动服务运行,WCF运行时使用为每个端点定义的绑定实例化一个ChannelListener对象和一个
通道堆栈(SeriveHostBase类的InitializeRuntime方法通过条用DispatcherBuilder方法的InitializeServiceHost方法创建ChannelListener对象)。一个ChannelListener对象连接一个端点和该通道堆栈的传输通道。WCF运行时为每个URI创建一个ChannelListener对象,通过ChannelListener对象服务可以接收消息。当一条请求消息到达一个URI,对应的ChannelListener对象接受该消息并将该消息传递至对应通道堆栈底部的传输通道。对于传输通道来说,一条消息无外乎就是一些字节流;传输通道不会试图去了解这条消息的内容。传输通道传送这条消息至通道堆栈中的下一个通道,一般来说该通道就是编码通道。
编码通道的目的是解析入栈消息并转化成通道堆栈中上层通道可以识别的格式——通常格式为SOAP。当发送出栈响应消息时,编码通道接收来自通道堆栈中上层通道的SOAP消息,并将SOAP消息转化成传输通道可以传输的一种特定格式。.NET Framework 4.0类库提供了编码通道可以在SOAP消息和文本,二进制数据,或者MTOM(消息传输优化机制)之间相互转化。你将在第十三章"实现高性能的WCF服务"中了解到MTOM更多信息。在传输通道上面,你可以添加通道处理可靠性、安全性、事务性和其他SOAP消息传送之外的特性。
编码通道的目的是解析入栈消息并转化成通道堆栈中上层通道可以识别的格式——通常格式为SOAP。当发送出栈响应消息时,编码通道接收来自通道堆栈中上层通道的SOAP消息,并将SOAP消息转化成传输通道可以传输的一种特定格式。.NET Framework 4.0类库提供了编码通道可以在SOAP消息和文本,二进制数据,或者MTOM(消息传输优化机制)之间相互转化。你将在第十三章"实现高性能的WCF服务"中了解到MTOM更多信息。在传输通道上面,你可以添加通道处理可靠性、安全性、事务性和其他SOAP消息传送之外的特性。
注意:二进制编码通道为WCF实现了特定的编码。你不能使用二进制编码通道中与其他非WCF客户端程序和服务通信,因此你只有在不存在互操作问题时才可以使用二进制编码通道。如果你需要以互操作方式传输二进制格式的数据,那么请使用MTOM,因为MTOM遵守OASIS认可的规范
此外,每个传输通道将加载一个默认的编码通道除非你在通道堆栈中指定了一个具体的编码通道。HTTP和HTTPS传输通道加载文本编码通道,TCP通道默认使用二进制编码通道。
当入栈消息达到通道堆栈的顶部,一个Channel Dispatcher对象带走消息,检查消息,并把消息中的数据项作为的参数传递至方法。当你使用ServiceHost对象运行服务时,WCF自动地创建ChannelDispatcher和EndpointDispatcher对象
注意:相同的URI可以关联多个端点。当一个WCF服务接收到一条消息,ChannelDispatcher对象查询每一个端点的EndpointDispatcher对象依次确定哪一个端点可以处理该消息。你将在第十四章了解到更多详细内容。
实现服务操作的方法结束后,返回数据经过通道堆栈回传至传输通道,传输通道将返回数据回传至客户端程序。客户端的WCF运行时创建于服务类型的结构,但是该结构相对比较简单,因为客户端不需要想服务那样侦听请求或者管理程序多个实例。客户端的WCF运行时创建一个ChanelFactory对象并使用该对象根据绑定定义创建一个通道。客户端程序的代理对象负责将方法调用转化成请求消息,并将请求消息传递至传输通道用,传输通道接受到消息后向服务发送请求消息。传输通道接受到服务的响应消息后,传递响应消息代理对象转换方法调用为请求消息的通道堆栈,该通道堆栈将消息转化为客户端代码可以识别的格式,然后将结果作为方法调用的返回值。
行为
通过使用行为你可以自定义WCF基础结构中的组件运行的方式。在配置文件中,一个行为包括一个或多个元素。比如,.NET Framework 4.0提供了许多内建的端点行为元素,你可以使用这些元素修改端点序列化数据的方式,在一个事务中如何将操作聚齐在一起,发送和接受消息时使用的特定用户凭证,等等。
行为具有一定的范围;你可以应用行为至整个服务,一个特性的操作,一个合约,或者一个端点。应用行为元素于整个服务的例子是serviceDebug。在一个配置文件中,添加serviceDebug行为元素用于指定当错误发生时,服务传输的完整错误信息至客户端程序。
请记住,一个配置文件中,行为可以是命名的或者匿名的。匿名行为自动应用于一个服务或者服务端点,而命名行为除非显示地制定了应用于服务或者端点才对服务或端点其作用。
你也可以自己定义行为,并且允许管理员在配置文件中引用自定义行为扩展类。你将在本章后面的内容中看到自定义服务行为和自定义的服务行为元素扩展的例子。
注意:在WCF中行为和行为元素是有区别的。行为是一个类,它实现一个行为接口(IServcieBahavior,IOperationBehavior,IContractBehaviro,或IEndpointBehavior;它们将在本章后面的内容中介绍)。为了使管理员可以在配置文件中引用这些行为类,你可以创建一个自定义的行为扩展元素类。
在配置文件中,"behavior"意味着一个或者多个行为类引用的集合,该集合通过行为元素扩展类配置。比如,serviceDebug仅仅是ServiceDebugElement行为元素扩展类的一个缩写名,它用于配置ServiceDebugBehavior行为类。你将在本章后面内容中看到如何创建一个行为元素扩展类,并为该自定义行为扩展元素类提供一个缩写名。
你可以在服务、端点、操作、和合约上通过特性指定行为;或者可以功过代码实例化这些项目并设置它们的属性。
并不是所有的行为都是可配置的。特别地,WCF服务的配置文件没有提供一个简单的方式供管理员指定合约和操作的行为。一般地,关于行为的是创建服务的程序员而不是配置服务的管理员;并且通用的准则是:当且仅当该行为对服务的运行有重要影响时,你可以通过使用特性或者编写代码去设置改行为。另以方面,服务和端点行为通常是一组管理政策,而不是实现策略,因此WCF提供一种方式,你可以使用这种方式通过配置文件去应用和配置这些行为。
组合通道
通道堆栈的通道实现服务要求的各种协议和规范。客户端程序采用的绑定必须对应于服务实现的与客户端通信的绑定。如果一个通道被遗漏、或替换成另外一个不同的通道,或者在通道堆栈中处于不同的位置,很可能服务或者客户端程序将不能正确的翻译消息,或通讯失败。
在第二章"寄宿WCF服务"中,为你介绍了在.NET
Framework 4.0中预先定义的绑定,比如basicHttpBingding, ws2007HttpBinding,和netTcpBinding。这些绑定在配置中组合通道,可以满足许多常见的场景。.NET
Framework 4.0在System.ServiceModel命名空间下包括这些绑定对应的类。你可以使用这些类的公有属性配置这些绑定使用的通道。你还可以创建自定义绑定:通过组合绑定元素并设置这些绑定元素的属性以确定WCF运行时将使用哪一个通道。
添加绑定元素到一个CustomBinding对象,你可以创建一个自定义绑定。预先定义的绑定限制一个绑定中的所有通道为各种有意义的组合。但是,当你创建自定义绑定时,你必须确保你使用一种有意义的方式组合绑定元素。从一定程序上讲,WCF运行时为你提供保护机制:比如,如果你试图添加两个编码绑定元素到一个绑定中,那么WCF运行时将将抛出异常。然后,WCF并不能对你创建的自定义绑定执行完全明智的检查。如果你在自定义绑定中饭了错误,那么服务和客户端程序可能彼此都不能识别对方的消息,其结果将带来错误、超时或者异常。
自定义中绑定元素的顺序非常重要。在之前已经提到传输通道必须位于通道堆栈的底部,然后上面是编码通道。微软推荐你根据通道的功能放置通道。下表列出了层以及该层适合的通道。
层
|
功能
|
通道绑定元素类
|
1(顶部)
|
事务流
|
TransactionFlowBindingElement
|
2
|
可靠的会话
|
ReliableSessionBindingElement
|
3
|
安全
|
AsymmetricSecurityBindingElement, SymmetricSecruityBindingElement, TransportSecurityBindingElement,
其他使用SecurityBindingElement类中的工厂方法创建的元素
|
4
|
流升级
|
SslStreamSecruityBindingElement, WindowsStreamSecruityBindingElement
|
5
|
编码
|
BinaryMessageEncodingBindingElement, MtomMessageEncodingBindingElement, TextMessageEncodingBindingElement,
WebMessageEncodingBindingElement
|
6(底部)
|
传输
|
HttpTransportBindingElement, HttpsTransportBindingElement, PeerTransportBindingElement,
TcpTransportBindingElement, NamedPipeTransportBindingElement, MsmqTransportBindingElement,
MsqmIntegrationBindingElement
|
上表列出了常用的绑定元素。这里还有其他的,你将在后面的内容中看到。这些绑定元素大多数都是自我解释的,但是有一些需要进一步的介绍。
- SecurityBindingElement是安全绑定元素的工厂类,它对外暴露创建安全通道的方法。
- AsymmetricSecurityBindingElement和SymmetricSecurityBindingElement类实现消息安全通道。TransportSecurityBindingElement类实现传输安全通道。但是,你很可能针对特定的场景使用特别的通道,比如CreateAnonymousForCertificateBindingElement,该类可以创建一个对称绑定元素以支持匿名客户端验证和基于证书的服务端验证。你可以使用SecurityBindingElement类的工厂方法创建这些通道。
- 在流升级中比如SslStreamSecruityBindingElement和WindowsStreamSecurityBindingElement类实际上并不代表任何通道,但是它们是可以修改数据通过网络传输方式的对象。你可以把它们和支持面向流的传输协议(比如TCP和命名管道)结合起来使用。流升级操作数据流而非单个WCF消息。比如,使用WindowsStreamSecurityBindingElement类,你可以指定在传送前加密并且或者签名数据。另外一个例子就是使用流升级通道在传输前压缩数据。
当你在配置文件中定义绑定,你不必关心你将使用哪一个绑定元素。比如,当你指定一个安全元素,该安全元素的"mode"特性确定了WCF运行时要么使用消息安全绑定元素要么使用传输安全绑定元素。设置安全元素的其他特性可以使WCF确定其将使用那些绑定元素以组成安全通道。当你通过程序创建一个绑定时,你需要显示地制定。
到目前为止,你了解到的只不过是理论。下面的练习将为你展示如果把本章你学到的理论知识转化为行动。你将在ShoppingCartService服务中使用代码实现一个自定义的绑定。
在ShoppingCartService服务中使用代码创建和使用自定义绑定
1. 使用Visual Studio, 打开*\ WCF\Step.by.Step\Solutions\Chapter11\CustomBinding文件夹下的ShoppingCart.sln
2. 在解决方案浏览器窗口中,打开ShoppingCartHost项目下的Program.cs文件。添加下面的using语句
using System.ServiceModel.Channels;
WCF在System.ServiceModel.Channels命名空间下提供了各种通道和绑定。
3. 在Programm类的main方法中,添加下面的语句
CustomBinding customBinding = new CustomBinding();
该语句创建一个空的自定义绑定对象,你将在后续步骤中为该绑定对象添加绑定元素。
4. ShoppingCartService服务实现了事务和可靠会话。 因此我们需要实例化绑定元素至实现事务的通道和实现可靠会话的协议,并设置它们的属性,然后添加至第三步创建的自定义绑定对象。
TransactionFlowBindingElement txFlowBindElement = new TransactionFlowBindingElement(); txFlowBindElement.TransactionProtocol = TransactionProtocol.OleTransactions; customBinding.Elements.Add(txFlowBindElement); ReliableSessionBindingElement rsBindElement = new ReliableSessionBindingElement(); rsBindElement.FlowControlEnabled = true; rsBindElement.Ordered = true; customBinding.Elements.Add(rsBindElement);
事务流绑定元素配置为OLE事务;另外你还可以选择WSAtomicTransactionOctober2004,它实现了从2004年10月份开始的WA-AtomicTransactions规范;你还可以选择WSAtomicTransaction11,它实现了更新一些的规范。更多详细信息,请参考第九章"事务支持"
像第十章"实现可靠的会话"一样,可靠会话绑定元素激活了流控制,并确保消息在服务端保存的顺序。
5. ShoppingCartService服务还需要实现安全对话和识别重放。因此,你需要按照下面的方式使用SecurityBindElement类创建SecureConversationBindingElement
SecurityBindingElement secBindElement = SecurityBindingElement.CreateSecureConversationBindingElement(SecurityBindingElement.CreateSspiNegotiationBindingElement()); secBindElement.LocalServiceSettings.DetectReplays = true; customBinding.Elements.Add(secBindElement);
安全对话协议在客户端程序和服务之间建立握手机制以建立一个安全的令牌,对话的双方都可以使用该令牌来验证彼此的消息。握手也需要被保护,安全绑定元素传递一个参数到CreateSecureConversationBindingElement方法以指定如何保护握手消息。本例中的代码使用SOAP SSPI协定在执行握手时验证消息。
6. 添加实现文本编码通道的绑定元素和TCP传输通道
TextMessageEncodingBindingElement tmEncodingBingElement = new TextMessageEncodingBindingElement(); customBinding.Elements.Add(tmEncodingBingElement); TcpTransportBindingElement tcpTransportBindingElement = new TcpTransportBindingElement(); tcpTransportBindingElement.TransferMode = TransferMode.Buffered; customBinding.Elements.Add(tcpTransportBindingElement);
可靠的会话通道要求传输缓存消息。传输通道必须为自定义绑定的最后一个元素。
7. 在创建和配置自定义绑定的语句之后,添加下面的语句实例化ServiceHost对象
ServiceHost host = new ServiceHost(typeof(ShoppingCartService.ShoppingCartService));
你应该已经很熟悉该语句,但现在你已经懂得了ServiceHost对象具体做哪些任务:它创建一个通道堆栈,它管理各个服务实例的生命周期,它还确保客户端的请求分配到对应服务实例。ServiceHost对象结合后续步骤中将要创建的ChannelListener对象,
ChannelDispatcher对象和EndpointDispatcher对象一起协作执行这些任务。
8. 在之前我们学习的练习中,你在配置文件中指定ShoppingCartService的端点定义,然后在ServiceHost构造方法中使用该信息创建一个端点和ChannelListener。现在,我们将使用代买来添加端点
host.AddServiceEndpoint(typeof(ShoppingCartService.IShoppingCartService), customBinding, "net.tcp://localhost:8090/ShoppingCartService");
AddServiceEndpoint方法的参数是服务合约,绑定和用于侦听的URI
9. 现在,你可以运行服务。添加下面的语句到Main方法中
host.Open(); Console.WriteLine("Service running"); Console.WriteLine("Press ENTER to stop the service"); Console.ReadLine(); host.Close();
你在之前已经看过这段代码,但是你现在应该理解Open方法启动一个ChannelListener对象侦听客户端请求。当消息到达时,ChannelListener对象传递该请求至通道。ChannelDispatcher对象从该通道的顶部获取一条消息,然后通过EndpointDispatcher对象传递到ShoppingCartService服务的一个实例。
10. 在非调适模式下运行项目。在客户端控制台窗口中将显示"Press ENTER when the service has started",然后按ENTER键。客户端程序像之前一样地运行:
11. 在客户端控制台窗口中按ENTER键关闭客户端程序;在宿主程序控制台窗口中,按ENTER键停止服务。
参考
- Aedrés: Understanding WCF Extensibility http://www.universalthread.com/ViewPageArticle.aspx?ID=191
- idior: Inside WCF Runtime http://www.cnblogs.com/idior/articles/971252.html
- MSDN: Service: Channel Listeners and Channels http://msdn.microsoft.com/en-us/library/ms789027.aspx
- MSDN: Extending Dispatchers http://msdn.microsoft.com/en-us/library/ms734665.aspx
- MSDN: Channel Model OVerview http://msdn.microsoft.com/en-us/library/ms729840.aspx