在上面的内容中,先后介绍了信道、信道管理器、信道监听器和信道工厂。从对象的创建来讲,信道管理器是信道的创建者。说的再具体点,客户端的信道通过信道工厂创建,服务端的信道通过信道监听器创建。但是信道工厂和信道监听器又是如果被创建出来的呢?
我们在一开始就已经说过,作为终结点三要素的绑定对象实现了所有的通信细节,并且通过创建信道栈实现了消息的传递。从这一点来说,绑定对象无疑是信道层所有通信对象的最终缔造者,所以信道工厂和信道监听器最终的创建都是靠绑定对象实现的。关于这个创建过程又和另一个重要的对象密切相关,那就是绑定元素。
绑定元素,顾名思义就是构成一个绑定对象的元素。绑定对象最根本的目的就是创建信道栈,借此实现对消息的传输、编码和基于消息交换的其他功能,比如安全、可靠传输、事务流转等等。组成信道栈的单个信道对象基于对某项单一功能的实现,在不同环境中,我们需要根据具体的需要创建相应的信道,并根据一定的顺序把这些信道组成一个完整的信道栈。对于绑定对象来说,如何实现这种灵活、自由的信道常创建方式,这得益于基于绑定元素的设计模式。
一、 关于绑定元素
从结构的角度讲,一个绑定对象有一系列绑定元素组成,每个绑定元素负责创建相应的信道。所以绑定元素几何的构成以及它们之间的先后顺序,决定了最终生成的信道栈中的信道组成已经它们位于栈中的先后顺序。WCF之所以在设计的时候将绑定和绑定元素分离开发,是基于灵活性、可扩展性考虑的。
在介绍信道和信道栈的时候我们说过,我们不可能、也不应该创建一个万能的信道能够提供消息交换中的所有的功能,所以我们让一个信道只承载某个单一的功能,比如传输信道专注于网络传输,消息编码信到专注于消息的编码,WCF还定义了一一系列的信道,他们分别关注与安全、可靠传输和事务流转等等。这种信道组合的设计方式使得我们可以根据具体的需求来定制我们将要创建的信道栈,让它只具有我们必须的功能,而去除不必要的功能。
同理,我们可以根据具体实际需求,将必要的绑定元素进行有序的组合,从而创建最能适合具体场景的绑定对象。由于信道可以分为必须的传输信道、消息编码信道和可选的基于某种WS-*协议实现的协议信道,与之相对地,我们的绑定元素可以分为传输绑定元素、消息编码绑定元素和协议绑定元素。
由于信道的实际创建者是信道管理器(信道工厂和信道监听器),所以绑定元素只需要实现对信道管理器的创建,而最终实现对具体信道的创建。所以绑定元素的最根本的功能就是实现对信道监听器和信道工厂的创建。这可以从所有绑定元素的基类, System.ServiceModel.Channels.BindingElement的定义上看出来:
1: public abstract class BindingElement<!--CRLF-->
2: {
<!--CRLF-->
3: // Methods<!--CRLF-->
4: protected BindingElement();<!--CRLF-->
5: protected BindingElement(BindingElement elementToBeCloned);<!--CRLF-->
6:
<!--CRLF-->
7: public virtual IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context);<!--CRLF-->
8: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context) where TChannel : class, IChannel;<!--CRLF-->
9: public virtual bool CanBuildChannelFactory<TChannel>(BindingContext context);<!--CRLF-->
10: public virtual bool CanBuildChannelListener<TChannel>(BindingContext context) where TChannel : class, IChannel;<!--CRLF-->
11: public abstract BindingElement Clone();<!--CRLF-->
12: public abstract T GetProperty<T>(BindingContext context) where T : class;<!--CRLF-->
13: }
<!--CRLF-->
BindingElement的核心方法成员有两个:BuildChannelListener<TChannel>和BuildChannelFactory<TChannel〉,用于创建相应的信道监听器和信道工厂。两个Build方法的参数都是BindingContext,关于BindingContext,我将在本章后续的部分中介绍。而CanBuildChannelFactory<TChannel>和CanBuildChannelListener<TChannel〉 则属性两个测试性质的方法,用于检验相应的相应的信道监听器和信道功能是否可以被创建。
二、 案例演示:如何自定义绑定元素
接下来,我们通过一个案例来演示如果自定义一个绑定元素。通过该绑定元素来创建我们在上面一个案例中创建的两个自定义信道管理器:SimpleChannelFactory和SimpleChannelListener。按照上面的命名方式,我们把这个自定义绑定元素命名为:SimpleBindingElement,下面是整个SimpleBindingElement的定义:
1: public class SimpleBindingElement : BindingElement<!--CRLF-->
2: {
<!--CRLF-->
3: public SimpleBindingElement()<!--CRLF-->
4: {
<!--CRLF-->
5: PrintHelper.Print(this, "SimpleBindingElement");<!--CRLF-->
6: }
<!--CRLF-->
7:
<!--CRLF-->
8: public override BindingElement Clone()<!--CRLF-->
9: {
<!--CRLF-->
10: PrintHelper.Print(this, "Clone");<!--CRLF-->
11: return new SimpleBindingElement();<!--CRLF-->
12: }
<!--CRLF-->
13:
<!--CRLF-->
14: public override T GetProperty<T>(BindingContext context)<!--CRLF-->
15: {
<!--CRLF-->
16: PrintHelper.Print(this, string.Format("GetProperty<{0}>", typeof(T).Name));<!--CRLF-->
17: return context.GetInnerProperty<T>();<!--CRLF-->
18: }
<!--CRLF-->
19:
<!--CRLF-->
20: public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)<!--CRLF-->
21: {
<!--CRLF-->
22: PrintHelper.Print(this, "BuildChannelFactory<TChannel>");<!--CRLF-->
23: return new SimpleChannelFactory<TChannel>(context) as IChannelFactory<TChannel>;<!--CRLF-->
24: }
<!--CRLF-->
25:
<!--CRLF-->
26: public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)<!--CRLF-->
27: {
<!--CRLF-->
28: PrintHelper.Print(this, "BuildChannelListener<TChannel>");<!--CRLF-->
29: return new SimpleChannelListener<TChannel>(context) as IChannelListener<TChannel>;<!--CRLF-->
30: }
<!--CRLF-->
31: }
<!--CRLF-->
SimpleBindingElement直接继承自抽象的基类BindingElement,对SimpleChannelFactory和SimpleChannelListener的创建分别实现在两个被重写的方法中:BuildChannelFactory<TChannel>和BuildChannelListener<TChannel>中。此外还重写了两个额外的方法:Clone和GetProperty<T>,前者用于克隆一个新的绑定元素,后一个和定义在信道、信道管理器的同名方法一样,用于获取基于某种类型的属性。
三、 绑定揭秘
前面我们一直在谈论信道、信道管理器、信道监听器、信道工厂和绑定元素,现在我们才进体本章的主题。不过由于前面的铺垫已经很足了,绑定本身反而没有太多可大书特书了。如果从结构上给绑定下个定义,那么我的定义很简单:“绑定是绑定元素的有序集合”。
由于绑定的终极目标是实现对信道栈的创建,而对于一个信道栈来说,信道的构成和次序决定着该信道栈在最终消息通信中的特性与能力,而绑定元素有决定着信道的创建,所以绑定对象本身的特性与能力由构成它的所有绑定元素以及这些绑定元素之间的先后次序决定。
正因为如此,当我们需要判断某一个绑定类型是否支持某种特性的时候,可以通过查看该绑定是否具有与此特性相关的绑定元素。比如,我们要判断某种绑定是否支持某种类型的传输协议,只需要看看构成该绑定的传输绑定元素就可以了,WsHttpBinding的传输绑定元素是HttpTransportBindingElement,所以 只能支持基于HTTP或者HTTPS的传输协议;如果需要确定某种类型的绑定是否支持事务的流转,只需要查看该绑定的绑定元素集合中是否包含TransactionFlowBindingElement就可以了。
在WCF中,所有的绑定都直接或者间接继承自抽象基类:System.ServiceModel.Channels.Binding,我们现在来简单地分析一下这个基类。Binding实现了接口IDefaultCommunicationTimeouts。
1: public abstract class Binding : IDefaultCommunicationTimeouts<!--CRLF-->
2: {
<!--CRLF-->
3: ... ...
<!--CRLF-->
4:
<!--CRLF-->
5: public TimeSpan OpenTimeout { get; set; }<!--CRLF-->
6: public TimeSpan CloseTimeout { get; set; }<!--CRLF-->
7: public TimeSpan SendTimeout { get; set; }<!--CRLF-->
8: public TimeSpan ReceiveTimeout { get; set; }<!--CRLF-->
9: }
<!--CRLF-->
四个默认的超时时限可以通过编程的方式显式指定,也可以通过配置的方式进行设置。他们的默认值分别为:OpenTimeout和CloseTimeout为1分钟,而SendTimeout和ReceiveTimeout为10分钟。
对于Binding,最为重要的就是如果构建组成该绑定对象的所有绑定元素集合。基于绑定元素的创建,通过抽象方法CreateBindingElements实现,所有具体的绑定类型均需要实现该方法。
1: public abstract class Binding : IDefaultCommunicationTimeouts<!--CRLF-->
2: {
<!--CRLF-->
3: ... ...
<!--CRLF-->
4: public abstract BindingElementCollection CreateBindingElements();<!--CRLF-->
5: }
<!--CRLF-->
由于信道管理器栈创建相应的信道栈,而绑定创建信道管理器栈,因此在Binding中定义了一系列BuildChannelFactory<TChannel>和BuildChannelListener<TChannel>方法重载,用于创建信道工厂栈和信道监听器栈。此外,和BindingElement一样,CanBuildChannelFactory<TChannel>和CanBuildChannelListener<TChannel>用于检测绑定对象创建信道工厂和信道监听器的能力:
1: public abstract class Binding : IDefaultCommunicationTimeouts<!--CRLF-->
2: {
<!--CRLF-->
3: ...
<!--CRLF-->
4: public IChannelFactory<TChannel> BuildChannelFactory<TChannel>(params object[] parameters);<!--CRLF-->
5: public virtual IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingParameterCollection parameters);<!--CRLF-->
6:
<!--CRLF-->
7: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(params object[] parameters) where TChannel : class, IChannel;<!--CRLF-->
8: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingParameterCollection parameters) where TChannel : class, IChannel;<!--CRLF-->
9: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, params object[] parameters) where TChannel : class, IChannel;<!--CRLF-->
10: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, BindingParameterCollection parameters) where TChannel : class, IChannel;<!--CRLF-->
11: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, params object[] parameters) where TChannel : class, IChannel;<!--CRLF-->
12: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, BindingParameterCollection parameters) where TChannel : class, IChannel;<!--CRLF-->
13: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, ListenUriMode listenUriMode, params object[] parameters) where TChannel : class, IChannel;<!--CRLF-->
14: public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, ListenUriMode listenUriMode, BindingParameterCollection parameters) where TChannel : class, IChannel;<!--CRLF-->
15:
<!--CRLF-->
16: public virtual bool CanBuildChannelFactory<TChannel>(BindingParameterCollection parameters);<!--CRLF-->
17: public bool CanBuildChannelFactory<TChannel>(params object[] parameters);<!--CRLF-->
18:
<!--CRLF-->
19: public bool CanBuildChannelListener<TChannel>(params object[] parameters) where TChannel : class, IChannel;<!--CRLF-->
20: public virtual bool CanBuildChannelListener<TChannel>(BindingParameterCollection parameters) where TChannel : class, IChannel;<!--CRLF-->
21: }
<!--CRLF-->
四、案例演示:如何创建自定义绑定
在上一个案例演示中,我们创建了自定义的绑定元素:SimpleBindingElement,在案例中我们来真正使用绑定元素,为此我们创建一个直接继承自Binding的自定义绑定。为了简单起见,对于我们自定义的绑定,他仅仅包含三个必须的绑定元素:传输绑定元素和消息编码绑定元素,外加我们自定义的绑定元素。对于传输,我们采用基于HTTP协议的HttpTransportBindingElement,而对应消息编码,则采用基于文本编码方式的TextMessageEncodingBindingElement。我们索性将我们自定义的绑定命名为SimpleBinding,下面是SimpleBinding的定义:
1: namespace Artech.CustomChannels<!--CRLF-->
2: {
<!--CRLF-->
3: public class SimpleBinding : Binding<!--CRLF-->
4: {
<!--CRLF-->
5: private string _scheme;<!--CRLF-->
6: private TransportBindingElement _transportBindingElement = new HttpTransportBindingElement();<!--CRLF-->
7: private MessageEncodingBindingElement _messageEncodingBindingElement = new TextMessageEncodingBindingElement();<!--CRLF-->
8: private SimpleBindingElement _SimpleBindingElement = new SimpleBindingElement();<!--CRLF-->