All bindings derive from the System.ServiceModel.Channels.Binding abstract type, and as a result, all bindings share common characteristics. Unlike channel factories, channel listeners, and channels, the Binding type does not have a very complex type hierarchy. In fact, the Binding type derives directly from Object and implements only the IDefaultCommunicationTimeouts interface. As you saw in Chapter 7, channel factories and channel listeners use this interface for time-outs and they pass these time-outs to the channels they create. The origin of this handoff of time-out values starts with the Binding type. In addition to the members defined in the IDefaultCommunicationTimeouts interface, the Binding type also defines several factory methods and properties that relate to creating channel factories and channel listeners. The Binding type is shown here:
public abstract class Binding : IDefaultCommunicationTimeouts { // constructors protected Binding(); protected Binding(String name, String ns); // test Methods for Channel Factories public virtual Boolean CanBuildChannelFactory<TChannel>( BindingParameterCollection parameters); public Boolean CanBuildChannelFactory<TChannel>( params Object[] parameters); // test Methods for Channel Listeners public virtual Boolean CanBuildChannelListener<TChannel>( BindingParameterCollection parameters) where TChannel: class, IChannel; public Boolean CanBuildChannelListener<TChannel>( params Object[] parameters) where TChannel: class, IChannel; // channel Factory Factory Methods public IChannelFactory<TChannel> BuildChannelFactory<TChannel>( params Object[] parameters); public virtual IChannelFactory<TChannel> BuildChannelFactory<TChannel>( BindingParameterCollection parameters); // channel Listener Factory Methods public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( BindingParameterCollection parameters) where TChannel: class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( params Object[] parameters) where TChannel: class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( Uri listenUriBaseAddress, params Object[] parameters) where TChannel: class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( Uri listenUriBaseAddress, BindingParameterCollection parameters) where TChannel: class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( Uri listenUriBaseAddress, String listenUriRelativeAddress, BindingParameterCollection parameters) where TChannel: class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( Uri listenUriBaseAddress, String listenUriRelativeAddress, params Object[] parameters) where TChannel: class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( Uri listenUriBaseAddress, String listenUriRelativeAddress, ListenUriMode listenUriMode, BindingParameterCollection parameters) where TChannel: class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel>( Uri listenUriBaseAddress, String listenUriRelativeAddress, ListenUriMode listenUriMode, params Object[] parameters) where TChannel: class, IChannel; // timeouts public TimeSpan CloseTimeout { get; set; } public TimeSpan OpenTimeout { get; set; } public TimeSpan ReceiveTimeout { get; set; } public TimeSpan SendTimeout { get; set; } // factory Method for BindingElementCollection public abstract BindingElementCollection CreateBindingElements(); // query mechanism public T GetProperty<T>(BindingParameterCollection parameters) where T: class; // the MessageVersion supported public MessageVersion MessageVersion { get; } // the Name and Namespace of the Binding public String Name { get; set; } public String Namespace { get; set; } // the URI Scheme public abstract String Scheme { get; } }
The constructors provided by the Binding type are fairly straightforward, but the constructor that accepts two String parameters requires some explanation. These two parameters (name and ns) represent the XML name and namespace of the Binding. These values are distinctly different from the name of the Binding. They are important when an application needs to represent the capabilities of a Binding in an XML-based metadata format such as Web Services Description Language (WSDL). Because applications frequently need endpoint-specific information in WSDL and bindings are a key ingredient in the construction of an endpoint, this is a handy feature to have. Remember that the Binding type is abstract, so types derived from it can also define their own constructors with different parameters. Indeed, all of the default WCF bindings define at least one constructor that is not defined in the Binding type.
The Binding type also defines several methods that test whether the Binding can create a channel factory stack or channel listener stack associated with a particular channel shape. These methods are named CanBuildChannelFactory<TChannel> and CanBuildChannelListener<TChannel>, and they return a Boolean. The TChannel generic parameter can be any valid channel shape, and these methods will return true if the binding can create a channel factory stack or channel listener stack associated with that channel shape.
Note | The test methods in the Binding type interact with the BindingContext type and the BindingElement type. We will revisit how these test methods work in sections “The BindingElement Type” and “The BindingContext Type” later in this chapter. |
As mentioned at the beginning of this chapter, the primary purpose of a Binding is to create channel factories and channel listeners. Bindings do this via the BuildChannelListener and BuildChannelFactory methods. One of the BuildChannelFactory methods accepts zero or more objects, and the other accepts a parameter of type BindingParameterCollection. Because a BindingParameterCollection is simply a generic collection of objects keyed by type, the former calls the latter. A BindingParameterCollection is simply a way to store information required to build channel factories and channel listeners. We will revisit the BindingParameterCollection type in the section “The BindingElement Type” later in this chapter.
The Binding type defines eight BuildChannelListener methods. The BuildChannelListener methods need more overloads because listening for a message is inherently more complex than sending one. The important arguments in the BuildChannelListener overloads are the BindingParameterCollection, listenUriBaseAddress, listenUriRelativeAddress, and listenUriMode. The BindingParameterCollection argument serves the same relative purpose that it does in the BuildChannelFactory methods-that is, it stores information that might be required during the creation of a channel listener stack.
The listening arguments listed earlier provide flexibility in how the channel listener listens for incoming connections. The listenUriBaseAddress is of type Uri, and the listenUriRelativeAddress is a String. Together, they are combined to form the Uri to listen on. For example, if the listenUriBaseAddress is net.tcp://localhost:4000 and the listenUriRelativeAddress is ISomeContract, the Uri the channel listener uses is net.tcp://localhost:4000/ISomeContract. At first glance, this capability might seem like it is of little value. In practice, however, it is very useful in scenarios where several channel listener stacks use the same base address. For example, a set of order processing services can use the same base address, and each channel listener stack can append its own String to the base address to create its own Uri. If the base address changes, changing the base address will automatically update all of the channel listener stacks the next time the listeners are built.
The listenUriMode argument is of type ListenUriMode. ListenUriMode is an enumerated type that defines two values: ListenUriMode.Explicit and ListenUriMode.Unique. When the listenUriMode argument is ListenUriMode.Explicit, the channel listener stack will listen on the Uri specified by the listenUriBaseAddress and listenUriRelativeAddress. When the listenUriMode argument is ListenUriMode.Unique, however, the channel listener stack will listen on a unique address. The unique address chosen by the transport channel listener can ignore some of the values of the listenUriBaseAddress and listenUriRelativeAddress. The exact form that the Uri takes in this case depends on the transport used by the transport channel listener. When listening on a TCP address, the channel listeners use a free port. When listening on an HTTP or a named pipe address, however, the channel listeners append a globally unique identifier (GUID) to the end of the Uri. In effect, when this argument is ListenUriMode.Unique, the values of the listenUriBaseAddress and listenUriRelativeAddress might be only part of the actual Uri that the channel listener stack listens on. For the next example, assume that the channel listener stack uses TCP.
Given the property values and parameters shown in Table 8-1, the address that the channel listener stack actually listens on would be something like this:
net.tcp://localhost:56446/ISomeContract
Argument | Value |
---|---|
listenUriMode | ListenUriMode.Unique |
listenUriBaseAddress | net.tcp://localhost:4000 |
listenUriRelativeAddress | ISomeContract |
Even though the listenUriBaseAddress value uses port 4000, the channel listener chose port 56446. In essence, part of the listenUriBaseAddress is ignored. For the next example, assume that the channel listener stack uses HTTP.
Given the property values and parameters shown in Table 8-2, the address that the channel listener stack actually listens on would be something like this:
http://localhost:4000/ISomeContract/
Argument | Value |
---|---|
listenUriMode | ListenUriMode.Unique |
listenUriBaseAddress | http://localhost:4000 |
listenUriRelativeAddress | ISomeContract |
In this case, the transport channel listener kept port 4000 and the listenUriRelativeAddress but appended a GUID to the end of the listenUriRelativeAddress.
Note | At first glance, this might look like a great capability for scenarios where you want the application to use a port or an address that is not already in use. In some cases (like in duplex communication on a sender), this capability is indeed useful. For many messaging scenarios, however, this form of unique addressing has a drawback. Because the address is not known until run time, there must be some out-of-band mechanism for informing sending applications of the address of the receiving application. The actual address that the receiving application uses is not published in metadata, so dynamic metadata discovery is not possible by default. As a result of this usability hurdle, I do not recommend using ListenUriMode.Unique for anything other than callbacks in duplex communication. |
Like channels, channel factories, and channel listeners, the Binding type has a query mechanism that follows the GetProperty<T> paradigm. And as in channel factories and channel listeners, this query mechanism is not part of the IChannel interface, but it is very similar in its purpose. It simply allows the caller to interrogate the Binding for capabilities. For example, if you are building a custom hosting infrastructure, you might not know all of the bindings that other developers will use in your hosting infrastructure. If, in this case, your company has a corporate policy regarding security, you can interrogate the bindings used for a specific security capability before building any messaging infrastructure. Like channel factories, channel listeners, and channels, GetProperty<T> returns null if the capability is not found.
As you saw in Chapter 5, “Messages,” a Message must have a MessageVersion associated with it. A MessageVersion is often associated with a particular set of messaging capabilities. For example, a Message associated with MessageVersion.None cannot participate in a WS-ReliableMessage (WS-RM) exchange, because by definition, there are no WS-Addressing headers to support such an exchange. Because a Binding is the primary means by which developers can express their intent for the messaging capabilities of an application and the MessageVersion is closely tied to those messaging capabilities, the Binding type exposes a MessageVersion property. The value returned by this property represents the MessageVersion used by the channel factories and channel listeners (and the channels) that the Binding creates.
All bindings use a transport, and that transport must have a Uniform Resource Identifier (URI) scheme associated with it. As you saw in Chapter 2, “Service Orientation,” a URI scheme is nothing more than a string that identifies the transport. Some schemes, like HTTP, are well known. Others, like net.tcp and net.msmq, are arbitrary-that is to say, they are not known outside the world of WCF. In fact, if you were to develop your own transport and build the WCF infrastructure to use that transport, you would have to decide on the scheme for your transport (think carrier pigeons or baby strollers).
This aptly named method returns a collection of BindingElement objects. Conceptually, bindings share the stack archetype that we see in channels, channel listeners, and channel factories. This archetype splits the total messaging functionality of an application into discrete entities and arranges those entities in an ordered stack. The collection returned by the CreateBindingElements method is a blueprint for creating channel factory and channel listener stacks. As such, each node in this collection represents some part of the total messaging functionality of an endpoint.
Although bindings do conceptually adhere to the stack archetype, they do not arrange discrete messaging capabilities into a stack, but rather into a collection. The difference between the two is subtle, but important nonetheless. With channel stacks, channel factory stacks, and channel listener stacks, only the topmost node in the stack is visible. Any code interacting with the stack does not know how many nodes are in the stack and cannot interact directly with nodes below the top node. By returning a collection of nodes, the Binding type allows calling code to see and interact with any node in the stack. For most developers, this is a much more familiar model than an opaque stack, and this makes it a much more suitable model for such an essential part of the developer-facing API.
All types in the collection returned from the CreateBindingElements method are derived from the BindingElement type, and the ways that this collection is used are closely related to the behavior of the BindingElement type. Because the topic of the next section of this chapter is the BindingElement type, the full purpose of the collection is described in that section. By examining the contents of the collection returned from the CreateBindingElements method of a Binding, we should be able to glean some of the messaging functionality that Binding represents. Consider the following code sample:
using System; using System.ServiceModel; using System.Reflection; using System.Collections.Generic; internal sealed class ShowBindingElements { static void Main() { // Create a list of some Bindings List<Binding> bindings = new List<Binding>(); bindings.Add(new BasicHttpBinding()); bindings.Add(new NetTcpBinding()); // change the security arg for NetTcpBinding bindings.Add(new NetTcpBinding(SecurityMode.Message, true)); bindings.Add(new WSHttpBinding()); bindings.Add(new NetMsmqBinding()); // change the security arg for NetMsmqBinding bindings.Add(new NetMsmqBinding(NetMsmqSecurityMode.Message)); OutputBindingElements(bindings); } private static void OutputBindingElements(List<Binding> bindings){ // iterate through all the Bindings foreach (Binding binding in bindings) { // show the Binding name Console.WriteLine("Showing Binding Elements for {0}", binding.GetType().Name); // iterate through all the BindingElements in the collection foreach (BindingElement element in binding.CreateBindingElements()) { // show the name of the BindingElement Console.WriteLine("\t{0}", element.GetType().Name); } } } }
The preceding application simply creates a list of Binding objects and then iterates through that list, calls CreateBindingElements on each Binding, iterates through the collection returned from the CreateBindingElements method, and outputs the name of each BindingElement to the console. The output of this program is the shown here:
Showing Binding Elements for BasicHttpBinding TextMessageEncodingBindingElement HttpTransportBindingElement Showing Binding Elements for NetTcpBinding TransactionFlowBindingElement BinaryMessageEncodingBindingElement WindowsStreamSecurityBindingElement TcpTransportBindingElement Showing Binding Elements for NetTcpBinding TransactionFlowBindingElement ReliableSessionBindingElement SymmetricSecurityBindingElement BinaryMessageEncodingBindingElement TcpTransportBindingElement Showing Binding Elements for WSHttpBinding TransactionFlowBindingElement SymmetricSecurityBindingElement TextMessageEncodingBindingElement HttpTransportBindingElement Showing Binding Elements for NetMsmqBinding BinaryMessageEncodingBindingElement MsmqTransportBindingElement Showing Binding Elements for NetMsmqBinding SymmetricSecurityBindingElement BinaryMessageEncodingBindingElement MsmqTransportBindingElement
Notice that one Binding type can create different BindingElement collections. In the preceding example, two NetTcpBinding objects are in the bindings list in Main, and they output different BindingElement collections. The contributing factor is the constructor. The default constructor was called one time, and the constructor that accepts some security options and a Boolean was called the other time. In the default constructor case, the BindingElement collection contains four BindingElement objects. The other case yields a BindingElement collection that contains five BindingElement objects. The same principle applies to the NetMsmqBinding. The point here is that the nodes in the collection returned from the CreateBindingElements method are determined at run time, and the state of the Binding object contributes to which nodes are included in the collection.
There is one more item worth noting about the preceding example. The names of the BindingElement objects in the collection reveal their purpose, and as a result, we can use the contents of the collection to get a general idea of the messaging functionality that a Binding encompasses. Notice that the BasicHttpBinding object creates a BindingElement collection containing BindingElement objects: TextMessageEncodingBindingElement and HttpTransportBindingElement. The BasicHttpBinding object creates a messaging infrastructure for sending and receiving text-encoded messages over the HTTP transport. As another example, notice that the second NetTcpBinding object creates a BindingElement collection containing five BindingElement objects: TransactionFlowBindingElement, ReliableSessionBindingElement, SymmetricSecurityBindingElement, BinaryMessageEncodingBindingElement, and TcpTransportBindingElement. In this case, the state of each BindingElement is important. In general, however, we can see that this Binding creates messaging infrastructure that has some transactional capability, some WS-ReliableMessaging capability (ReliableSession is the term that WCF uses for WS-RM), and some security capability. Furthermore, we see that the messaging infrastructure uses the TCP transport and that all messages are binary encoded.