Virtually the only hard rule applied to a Binding is that one of the BindingElement objects returned from CreateBindingElements must have the capability to create a transport channel factory or transport channel listener. From a theoretical perspective, this seems completely reasonable, since a messaging endpoint is of little value unless it is going to use some form of transport. Out of the necessity of this requirement, the WCF type system defines an abstract type named System.ServiceModel.Channels.TransportBindingElement. The TransportBindingElement type defines several members needed by transport channel factories and listeners only, but it derives from BindingElement.
Because the BindingElement collection is a blueprint for the channel listener and channel factory stacks, a TransportBindingElement must appear at the end of the collection returned from the CreateBindingElements method on the Binding type. Both the Binding type and the BindingContext type enforce this rule.
The TransportBindingElement is shown here:
public abstract class TransportBindingElement : BindingElement { protected TransportBindingElement(); protected TransportBindingElement(TransportBindingElement elementToBeCloned); public override T GetProperty<T>(BindingContext context) where T: class; // does the channel add WS-Addressing info to messages public bool ManualAddressing { get; set; } // the size of the buffer pool public virtual long MaxBufferPoolSize { get; set; } // the maximum received message size public virtual long MaxReceivedMessageSize { get; set; } // the URI scheme public abstract string Scheme { get; } }
The names of the MaxBufferPoolSize and MaxReceivedMessageSize properties describe their purpose. The MaxBufferPoolSize sets the maximum size of the entire buffer pool in bytes, which can consist of zero or more buffers, while the MaxReceivedMessageSize property sets the maximum size of a received message in bytes. The ManualAddress property, however, requires some explanation. By default, this property has a value of false. When this property is set to false, the channel stack can add addresses to a message before it is sent. The format of the address depends on the binding used. More specifically, it depends on the MessageVersion of the Message objects that the channel stack uses. When this property is set to true, the channel stack does not add any addresses, but instead assumes that the caller has placed the appropriate addresses in outgoing messages. This capability is quite useful in more advanced addressing scenarios intrinsic to applications that serve as a router or an intermediary between other messaging participants.
The Binding and BindingElement objects delegate most of the work of building a channel factory stack and channel listener stack to the System.ServiceModel.Channels.BindingContext type. As mentioned earlier in this chapter, the BindingContext type provides contextual information to the BindingElement collection during the creation, testing, or querying of the channel factory stack or channel listener stack. Each BindingElement must know the next BindingElement in the collection so that a channel factory or channel listener can reference the next channel factory or channel listener in the stack. Furthermore, each BindingElement must have access to any additional information (security options, transactional options, and so on) required to build each channel factory or channel listener. To this end, the BindingContext type stores a collection of BindingElement objects, exposes methods that build the channel factory or channel listener stack in an orderly manner, and maintains a collection of additional information that a channel factory or channel listener can use during its instantiation. The BindingContext type is shown here:
public class BindingContext { // calls the other ctor, passing null for addresses public BindingContext(CustomBinding binding, BindingParameterCollection parameters); public BindingContext(CustomBinding binding, BindingParameterCollection parameters, Uri listenUriBaseAddress, String listenUriRelativeAddress, ListenUriMode listenUriMode); // factory methods for building channel factory / listener stacks public IChannelFactory<TChannel> BuildInnerChannelFactory<TChannel>(); public IChannelListener<TChannel> BuildInnerChannelListener<TChannel>() where TChannel: class, IChannel; // test methods public bool CanBuildInnerChannelFactory<TChannel>(); public bool CanBuildInnerChannelListener<TChannel>() where TChannel: class, IChannel; // shallow copy of the BindingContext public BindingContext Clone(); // Query mechanism public T GetInnerProperty<T>() where T: class; // removes the next BindingElement in the collection // (private method shown intentionally) private BindingElement RemoveNextElement(); // the Binding public CustomBinding Binding { get; } // extra information used in factory / listener creation public BindingParameterCollection BindingParameters { get; } // listening base address (channel listener only) public Uri ListenUriBaseAddress { get; set; } // listening mode (channel listener only) public ListenUriMode ListenUriMode { get; set; } // relative address (channel listener only) public string ListenUriRelativeAddress { get; set; } // the remaining binding elements public BindingElementCollection RemainingBindingElements { get; } }
Notice that the constructor has arguments of type CustomBinding and BindingParameterCollection, as well as the listening arguments required to build a channel listener. The CustomBinding argument is a general way to reference a Binding, and the constructor uses this Binding to create a private collection of BindingElement objects. The BindingElement object collection is available via the RemainingBindingElements property. In essence, a CustomBinding object can take the shape of any other Binding derived type.
The size of the collection returned from this method monotonically decreases as the BuildInnerChannelFactory<TChannel> or BuildInnerChannelListener<TChannel> is invoked. The general implementation of the BuildInnerChannelFactory<TChannel> and BuildInnerChannelListener<TChannel> methods is shown here:
public IChannelFactory<TChannel> BuildInnerChannelFactory<TChannel>() { // removes the next BindingElement from the private list, // then calls BuildChannelFactory on the removed BindingElement // the "this" argument contains the new list of BindingElements return this.RemoveNextElement().BuildChannelFactory<TChannel>(this); } public IChannelListener<TChannel> BuildInnerChannelListener<TChannel>() where TChannel: class, IChannel { // removes the next BindingElement from the private list, // then calls BuildChannelListener on the removed BindingElement // the "this" argument contains the new list of BindingElements return this.RemoveNextElement().BuildChannelListener<TChannel>(this); }
The RemoveNextElement private method removes and then returns the next BindingElement from the internal list of BindingElement objects. When RemoveNextElement returns, the BuildChannelListener<TChannel> or BuildChannelFactory<TChannel> method executes on the newly removed BindingElement. Notice that this is passed to the BuildChannelListener <TChannel> and BuildChannelFactory<TChannel> methods, and this contains the shorter list of BindingElement objects.
Note | The test methods on the BindingContext type operate much the same way-that is, they use an internal collection of BindingElement objects and consume nodes in that collection until there are no more to consume. |