Channel Interfaces and Base Types


As mentioned at the beginning of this chapter, one of the key facets of learning about the WCF channel infrastructure is unfolding the list of interfaces and types that the WCF type system uses in the channel layer. This section condenses this complex type system into manageable chunks, making it more palatable to the newcomer.

The IChannel Interface

The System.ServiceModel.Channels.IChannel interface is deceptively simple, but its implementation is vital to the channel layer. All channels and channel factories must implement it. To put it another way, a type that derives from CommunicationObject usually also implements the IChannel interface. Before we delve into the purpose of the IChannel interface, let’s examine its structure:

 public interface IChannel : ICommunicationObject {       T GetProperty<T>() where T: class; }

You might ask yourself: “What’s so important about that?” Remember that each CommunicationObject in a CommunicationObject stack supports some capability, and only the topmost CommunicationObject in the CommunicationObject stack is visible to the caller. When implemented properly, the GetProperty<T> method provides the means to query the CommunicationObject stack for certain capabilities. For example, you might want to query a CommunicationObject stack for its support of a particular channel shape, MessageVersion, or even security capabilities. The following code snippet shows how a caller can use the IChannel.GetProperty<T> method:

 // assume channel stack (myChannelStack) created MessageVersion messageVersion =   myChannelStack.GetProperty<MessageVersion>(); if(MessageVersion != null){   // do something } // app continues

Like many other members in a CommunicationObject stack, it is important that a CommunicationObject delegate the call to the next channel in the stack when a channel does not know the answer to the query. A simple implementation of the GetProperty<T> method is shown here:

 public override T GetProperty<T>() where T: class {       if (typeof(T) == typeof(MessageVersion)) {             // this type knows only how to return MessageVersion             return (T) this.MessageVersion;       }       // no other capabalities are known here, so       // delegate the query to the next node       return this.inner.GetProperty<T>(); }

As this example shows, this implementation of GetProperty<T> is able to return only the MessageVersion, and it delegates all other queries to the next node in the stack. If a capability is not known to any node in the stack, GetProperty<T> returns null instead of throwing an exception. As a result of this delegation paradigm, only the bottom node in the stack ever explicitly returns null.

Datagram Channels: IInputChannel and IOutputChannel

As mentioned in Chapter 3, the Datagram MEP is extremely powerful and scalable. In a Datagram MEP, the sender sends one message to the receiver, and the sender expects no message in response. More simply, a sender outputs (sends) a message, and the receiver receives the message as input. As a result, the interface that the WCF infrastructure defines for the sender in a Datagram MEP is named System.ServiceModel.Channels.IOutputChannel, and the interface for the receiver is named the System.ServiceModel.IInputChannel.

Sending: IOutputChannel

Like its role in the Datagram MEP, the IOutputChannel interface is simple, as shown here:

 public interface IOutputChannel : IChannel, ICommunicationObject {       IAsyncResult BeginSend(Message message, AsyncCallback callback,                              Object state);       IAsyncResult BeginSend(Message message, TimeSpan timeout,                              AsyncCallback callback, Object state);       void EndSend(IAsyncResult result);       void Send(Message message);       void Send(Message message, TimeSpan timeout);       EndpointAddress RemoteAddress { get; }       Uri Via { get; } }

First, notice that the IOutputChannel interface implements the IChannel and ICommunicationObject interfaces. Any type that implements the IOutputChannel interface must also define members for the common channel state machine and the GetProperty<T> query method. Also notice that the interface defines both synchronous and asynchronous Send methods in a manner consistent with the APM.

The RemoteAddress property is a way to express the target of the message. It is important to note, however, that the target of the message does not have to be where the message is actually sent. Recalling the postal service example from Chapter 2, “Service Orientation,” it is often useful to address the message to one recipient but deliver it via another address. The Via property on the IOutputChannel represents the other address and is intended to be used as the physical target address of the message.

Receiving: IInputChannel

Channels that receive datagram messages implement the IInputChannel interface. In keeping with the role of a receiver in datagram message exchanges, the IInputChannel interface defines members for receiving messages and does not define members for sending messages. The members in the IInputChannel interface are shown here:

 public interface IInputChannel : IChannel, ICommunicationObject {   EndpointAddress LocalAddress { get; }   // Receive Methods   IAsyncResult BeginReceive(AsyncCallback callback, Object state);   IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback,                             Object state);   Message EndReceive(IAsyncResult result);   Message Receive();   Message Receive(TimeSpan timeout);   // TryReceive Methods   IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback,                                Object state);   bool EndTryReceive(IAsyncResult result, out Message message);   bool TryReceive(TimeSpan timeout, out Message message);   // Waiting Methods   IAsyncResult BeginWaitForMessage(TimeSpan timeout,                                    AsyncCallback callback,                                    Object state);   bool EndWaitForMessage(IAsyncResult result);   bool WaitForMessage(TimeSpan timeout); }

In general, receiving applications passively wait for incoming messages. To this end, the IInputChannel interface defines three sets of methods that provide different ways to wait for an incoming message. There are no universal names for these different sets of methods, but for simplicity, let’s classify them as the Receive, TryReceive, and WaitForMessage method groups. All of these method groups have synchronous and asynchronous variants.

The Receive methods wait for a period of time, and if a Message arrives within that period of time, the Message is returned from the method. If the period of time elapses without the arrival of a Message, these methods throw a TimeoutException. The TryReceive methods wait for a period of time and then return a Message as an out parameter. These methods return a Boolean that represents whether a Message arrived within the allotted period of time. The major difference between the Receive and the TryReceive methods is the way they indicate an expired time-out.

The WaitForMessage methods, in contrast to the Receive and TryReceive methods, do not return a Message as a return value or an out parameter. Instead, the WaitForMessage methods return a Boolean that indicates whether a Message has arrived. This is similar to the Peek functionality available in the other I/O infrastructures. Combining the use of a WaitForMessage method with a Receive or TryReceive method provides the capability to wait for a Message and then receive it.

The WaitForMessage methods can be useful when the arrival of a Message corresponds with some other activity that requires nontrivial overhead. As an example, consider the case when the arrival of a Message must correspond with the creation of a transaction. In this case, the call to Receive or TryReceive must be wrapped in a transaction. If a Message does not arrive, the caller must abort the transaction. If, however, the caller uses the WaitForMessage method, the call does not have to occur within the scope of a transaction. If WaitForMessage returns false, the caller can simply call WaitForMessage again. Once a Message does arrive, the caller can start a transaction and then call Receive or TryReceive and perform the necessary work on the Message.

Request/Reply Channels: IRequestChannel and IReplyChannel

In the Request/Reply MEP, both the messaging participants send and receive messages. The sender sends a message to the receiver and then awaits a reply, while the receiver receives incoming messages and sends a reply message after receipt of a message. As for channel shapes, the IRequestChannel and IReplyChannel interfaces reflect this highly structured form of message exchange for the sender and receiver, respectively.

Sending: IRequestChannel

The IRequestChannel interface defines several members related to sending a request to a receiving application and receiving a Message as a response to the request. As with many other members related to sending and receiving messages in the channel layer, there are both synchronous and asynchronous variants of these members, as shown here:

 public interface IRequestChannel : IChannel, ICommunicationObject {   // Request Methods   IAsyncResult BeginRequest(Message message, AsyncCallback callback,                             Object state);   IAsyncResult BeginRequest(Message message, TimeSpan timeout,                             AsyncCallback callback, Object state);   Message EndRequest(IAsyncResult result);   Message Request(Message message);   Message Request(Message message, TimeSpan timeout);   EndpointAddress RemoteAddress { get; }   Uri Via { get; } } 

As the preceding code snippet shows, the Request methods accept a Message as a parameter and return a Message. The signature of these members ensures compliance with the Request/ Reply MEP.

Receiving: IReplyChannel

Receiving applications that want to use the Request/Reply MEP implement the IReplyChannel interface as follows:

 public interface IReplyChannel : IChannel, ICommunicationObject {   RequestContext ReceiveRequest();   RequestContext ReceiveRequest(TimeSpan timeout);   IAsyncResult BeginReceiveRequest(AsyncCallback callback, Object state);   IAsyncResult BeginReceiveRequest(TimeSpan timeout,                                    AsyncCallback callback, Object state);   RequestContext EndReceiveRequest(IAsyncResult result);   Boolean TryReceiveRequest(TimeSpan timeout, out RequestContext context);   IAsyncResult BeginTryReceiveRequest(TimeSpan timeout,                                       AsyncCallback callback,                                       Object state);   Boolean EndTryReceiveRequest(IAsyncResult result,                                out RequestContext context);   Boolean WaitForRequest(TimeSpan timeout);   IAsyncResult BeginWaitForRequest(TimeSpan timeout,                                    AsyncCallback callback,                                    Object state);   bool EndWaitForRequest(IAsyncResult result);   EndpointAddress LocalAddress { get; } }

No member on the IReplyChannel, however, directly returns a Message. Instead, the IReplyChannel interface members provide access to the received Message via the RequestContext type. The next section discusses the RequestContext type in more detail. For now, it is enough to know that the received Message is visible via the RequestContext type, and it is through the RequestContext type that the IReplyChannel provides access to the received Message.

Like the IInputChannel interface, the IReplyChannel interface defines several categories of methods that provide different ways to receive a Message. The ReceiveRequest methods return a RequestContext object and will throw an exception if a time-out is exceeded. The TryReceiveRequest methods return a Boolean that indicates whether a Message was received in the allotted time. The WaitForRequest methods, like the WaitForMessage methods on the IInputChannel interface, return upon receipt of a request Message or the expiration of the time-out.

Request/Reply Correlation: The RequestContext Type

In the Request/Reply MEP, a request is tightly coupled to a reply. From the sender’s perspective, a request always returns a Message. From the receiver’s perspective, a received Message must always generate a reply Message. As shown in the preceding section, the IReplyChannel uses the RequestContext type as the return type from the ReceiveRequest methods. This type is the primary means by which a receiving channel that uses the Request/Reply MEP correlates a request with a reply.

At a high level, the RequestContext type wraps the request Message and provides the means to send a reply Message back to the sender. The request Message is visible in the RequestContext via the RequestMessage property. Likewise, the Reply methods on the RequestContext type provide the means to send a reply Message back to the sender. Like other methods in the channel type system, the reply methods are available in both synchronous and asynchronous variants. The following code snippet shows the RequestContext types members:

 public abstract class RequestContext : IDisposable {   protected RequestContext();   public abstract void Abort();   public abstract void Reply(Message message);   public abstract void Reply(Message message, TimeSpan timeout);   public abstract IAsyncResult BeginReply(Message message,                                           AsyncCallback callback,                                           Object state);   public abstract IAsyncResult BeginReply(Message message,                                           TimeSpan timeout,                                           AsyncCallback callback,                                           Object state);   public abstract void EndReply(IAsyncResult result);   public abstract void Close();   public abstract void Close(TimeSpan timeout);   protected virtual void Dispose(Boolean disposing);   void IDisposable.Dispose();   public abstract Message RequestMessage { get; } }

As this code snippet shows, the RequestContext type implements the IDisposable interface. Because many other members in the channel layer do not implement IDisposable, it might not be obvious why the RequestContext type does. The RequestContext type implements IDisposable because the RequestContext type contains a Message. As discussed in Chapter 4, “WCF 101,” the Message type might contain a Stream and therefore implements IDisposable. As a result of this association, the Dispose method on the RequestContext type calls the Dispose method on its Message, which in turn disposes the Stream owned by the Message. Keeping in mind that the RequestContext type is an abstract class, classes derived from RequestContext are free to add to this implementation as needed.

Note 

Like the Message type, the RequestContext type explicitly implements IDisposable.

Duplex Channels: IDuplexChannel

Duplex channels enable the Duplex MEP. Unlike the rigid structure of the Datagram and Request/Reply MEPs, the Duplex MEP allows the sender and receiver to freely send and receive messages with one another. As we saw in Chapter 3, the Duplex MEP closely resembles the communication exchange commonplace in telephone conversations. The sender and receiver must establish a communication context before open communication can begin. In the Duplex MEP, the sending and receiving channel shapes are the same, and as a result, the sender and receiver implement the same interface (assuming that both participants are WCF applications). Given the liberal nature of the Duplex MEP and the common interface for the sender and receiver, the only way to truly differentiate the sender from the receiver is to identify the messaging participant that initiated the communication (much the same way the person dialing the phone initiates a phone conversation).

Sending and Receiving: IDuplexChannel

The IDuplexChannel is actually the combination of the IInputChannel and IOutputChannel interfaces. As shown earlier, the IInputChannel interface is for implementing a datagram receiver, and the IOutputChannel interface is for implementing a datagram sender. Because a channel that implements the Duplex MEP must be able to send and receive messages, the logical choice for IDuplexChannel members is the combination of the interfaces used in the Datagram MEP. The definition of IDuplexChannel is shown here:

 public interface IDuplexChannel : IInputChannel, IOutputChannel, IChannel,   ICommunicationObject { }

The IDefaultCommunicationTimeouts Interface

Because channels are frequently hidden from the view of most application developers, there must be a way for layers above the channel layer to dictate the time-outs for a particular set of operations at the channel layer. When considering time-outs for a channel, there are four relevant time-sensitive operations: opening a channel, sending a message, receiving a message, and closing a channel. Like most functionality in the channel layer, the WCF type system contains an interface that describes these time-outs. The System.ServiceModel. IDefaultCommunicationTimeouts interface has the following members:

 public interface IDefaultCommunicationTimeouts {       TimeSpan CloseTimeout { get; }       TimeSpan OpenTimeout { get; }       TimeSpan ReceiveTimeout { get; }       TimeSpan SendTimeout { get; } }

The purpose of each member in the IDefaultCommunicationTimeouts interface is easily derived from the name of that member. Bindings, channel factories, and channels all implement this interface. Since a Binding, channel factory, and channel implement the same interface, these types can pass time-outs down the construction chain. For example, a user can specify a send time-out in a Binding. (The Binding also defines a setter property.) If the Binding is part of a message sender, the Binding passes the send time-out to the channel factory via the channel factory constructor. Similarly, the channel factory passes the send time-out to the channel via the channel constructor. In effect, this series of handoffs provides the user the ability to specify time-outs in a type that is part of the normal developer-facing API, and the impact trickles down to the channel layer.

The ChannelBase Type

All custom channels must implement the common state machine, expose the GetProperty<T> query mechanism, implement one or more channel shapes, and accept time-outs from a channel factory. The System.ServiceModel.Channels.ChannelBase abstract type serves as a single base type for channels and helps ensure that each channel defines the members compatible with the rest of the channel layer. The following code shows the ChannelBase type definition:

 public abstract class ChannelBase : CommunicationObject,                                     IChannel,                                     ICommunicationObject,                                     IDefaultCommunicationTimeouts {   // Constructor with channel factory parameter   protected ChannelBase(ChannelManagerBase channelManager);   // IChannel implementation   public virtual T GetProperty<T>() where T: class;   // CommunicationObject members   protected override TimeSpan DefaultCloseTimeout { get; }   protected override TimeSpan DefaultOpenTimeout { get; }   protected override void OnClosed();   protected TimeSpan DefaultReceiveTimeout { get; }   protected TimeSpan DefaultSendTimeout { get; }   // IDefaultCommunicationTimeouts implementation   TimeSpan IDefaultCommunicationTimeouts.CloseTimeout { get; }   TimeSpan IDefaultCommunicationTimeouts.OpenTimeout { get; }   TimeSpan IDefaultCommunicationTimeouts.ReceiveTimeout { get; }   TimeSpan IDefaultCommunicationTimeouts.SendTimeout { get; }   // reference to channel factory   protected ChannelManagerBase Manager { get; }   private ChannelManagerBase channelManager; }

The members that are of type ChannelManagerBase represent ways to reference the factory that created the channel. The topic of channel factories is covered in greater detail in Chapter 7, “Channel Managers.” For now, assume that the ChannelManagerBase references are ways to retrieve the time-outs from a channel factory. Notice the two sets of TimeSpan members in the ChannelBase type. The property names that start with the word Default retrieve the time-outs from the channel factory, and the explicitly implemented IDefaultCommunicationTimeouts members delegate to the Default members. The following code snippet illustrates:

 protected override TimeSpan DefaultOpenTimeout {   get {    return ((IDefaultCommunicationTimeouts)this.channelManager).OpenTimeout;   } } // delegate to DefaultOpenTimeout property TimeSpan IDefaultCommunicationTimeouts.OpenTimeout {   get {     return this.DefaultOpenTimeout;   } }

The preceding code snippet describes only how the open time-out propagates through a channel. The close, send, and receive time-outs work in a similar manner.




Inside Windows Communication Foundation
Inside Windows Communication Foundation (Pro Developer)
ISBN: 0735623066
EAN: 2147483647
Year: 2007
Pages: 106
Authors: Justin Smith

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net