Creating a Custom Channel


Now that you’ve seen the types used in the channel layer, let’s build our own custom channel. The purpose of this channel is to print text in a console window. In the end, the channel that we build will be very useful in demonstrating the lifetime of a channel, as well as when an application invokes different channel members. Because our custom channel is going to print text to the console, it is necessary that our channel delegate all its method calls to the next channel in the stack. We’ll call this channel the DelegatorChannel. Before we get started, it’s important to note that you won’t see all the code required to get our sample running until partway through Chapter 8, “Bindings.” This is simply a byproduct of the way channels are created at run time.

One of the first considerations when building a custom channel is the shape or shapes the channel will support. The DelegatorChannel must work with all channel shapes (IInputChannel, IOutputChannel, IDuplexChannel, IReplyChannel, IRequestChannel, and all of the sessionful variants). As a result, we will build not one channel but rather several channels, and these channels will have a specific hierarchy.

Creating the Base Type

Because all of our channels will use the channel state machine and require a reference to the next channel in the channel stack, it makes sense to generalize those tasks into a base type. All of the types derived from our base type are different channel shapes, so it makes sense to make our base type generic. Because of these requirements, I call this base type DelegatorChannelBase<TShape>, where TShape must be a reference type and implement IChannel. (Remember that all channel shape interfaces implement IChannel.) DelegatorChannelBase<TShape> subclasses ChannelBase because this provides the common state machine and the means to propagate time-outs from the Binding. The initial definition for the DelegatorChannelBase<TShape> type is shown here:

 internal class DelegatorChannelBase<TShape> : ChannelBase   where TShape : class, IChannel {   // implementation not shown yet }

Adding the Constructor

A DelegatorChannelBase<TShape> object must never be placed at the bottom of a channel stack. In other words, a DelegatorChannelBase<TShape> object must have a reference to the next channel in the channel stack. By convention, we will pass this reference to the constructor. The type of this reference is the shape of the next channel in the channel stack, and because the generic parameter represents the channel shape, we will use the generic parameter as the type of this constructor parameter. The DelegatorChannelBase<TShape> constructor also requires a reference to the factory that creates the channel. As you’ve seen, one reason for this reference is to assist in the propagation of the time-outs from the binding all the way to the channel. Another reason for this reference is so that the channel can notify the factory when it is closed. You’ll learn more about this topic in Chapter 7. The constructor of our base type is shown here:

 internal class DelegatorChannelBase<TShape> : ChannelBase     where TShape : class, IChannel {   private TShape _innerChannel; // reference the next channel in the stack   private String _source; // part of the String to print to the console   protected DelegatorChannelBase(ChannelManagerBase channelManagerBase,                                  TShape innerChannel,                                  String source) : base(channelManagerBase){     if(innerChannel == null) {       throw new ArgumentNullException("DelegatorChannelBase requires a non-null channel.", "innerChannel");     }     // set part of the String to print to console     _source = String.Format("{0} CHANNEL STATE CHANGE: DelegatorChannelBase", source);     // set the reference to the next channel     _innerChannel = innerChannel;   }   // other implementation not shown yet }

Notice the addition of the _innerChannel and _source member variables. As their comments indicate, these member variables are for storing the reference to the next channel in the stack and for holding the part of the String that we are going to print to the console. The first parameter in the constructor is of type ChannelManagerBase. The reference to the ChannelManagerBase is stored by the ChannelBase type through the ChannelBase constructor.

Adding the Channel State Machine

Because the DelegatorChannelBase<TShape> subclasses the ChannelBase abstract type and the ChannelBase abstract type subclasses the CommunicationObject abstract type but does not implement the abstract members defined in the CommunicationObject, the DelegatorChannelBase<TShape> type must implement the abstract members defined in CommunicationObject. Since all of the state transitions in the DelegatorChannelBase<TShape> type must propagate to the other channels in the channel stack, our state transition methods delegate the call to the _innerChannel member variable, as shown here:

 internal class DelegatorChannelBase<TShape> : ChannelBase   where TShape : class, IChannel {   private TShape _innerChannel; // reference to the next channel   private String _source; // part of the String to output   // provide the _innerChannel to derived types   protected TShape InnerChannel {         get { return _innerChannel; }     } protected DelegatorChannelBase(ChannelManagerBase channelManagerBase,                                  TShape innerChannel,                                  String source) : base(channelManagerBase){     if(innerChannel == null) {       throw new ArgumentNullException("DelegatorChannelBase requires a non-null channel.", "innerChannel");     }     // set part of the String to print to console     _source = String.Format("{0} CHANNEL STATE CHANGE: DelegatorChannelBase", source);     // set the reference to the next channel     _innerChannel = innerChannel;   }   // IChannel implementation   public override T GetProperty<T>() {     return this._innerChannel.GetProperty<T>();   }   #region CommunicationObject members   protected override void OnAbort() {     PrintHelper.Print(_source, "OnAbort");     this._innerChannel.Abort();   }   protected override IAsyncResult OnBeginClose(TimeSpan timeout,                                                AsyncCallback callback,                                                Object state) {         // output that the method was called         PrintHelper.Print( _source, "OnBeginClose");         // delegate the call to the next channel         return this._innerChannel.BeginClose(timeout, callback, state);     }   protected override IAsyncResult OnBeginOpen(TimeSpan timeout,                                               AsyncCallback callback,                                               Object state) {         // output that the method was called         PrintHelper.Print(_source, "OnBeginOpen");         // delegate the call to the next channel         return this._innerChannel.BeginOpen(timeout, callback, state);   }   protected override void OnClose(TimeSpan timeout) {     // output that the method was called     PrintHelper.Print(_source, "OnClose");     // delegate the call to the next channel     this._innerChannel.Close(timeout);   }   protected override void OnEndClose(IAsyncResult result) {     // output that the method was called     PrintHelper.Print(_source, "OnEndClose");     // delegate the call to the next channel     this._innerChannel.EndClose(result);   }   protected override void OnEndOpen(IAsyncResult result) {     // output that the method was called     PrintHelper.Print(_source, "OnEndOpen");     // delegate the call to the next channel     this._innerChannel.EndOpen(result);   }   protected override void OnOpen(TimeSpan timeout) {     // output that the method was called     PrintHelper.Print(_source, "OnOpen");     // delegate the call to the next channel     this._innerChannel.Open(timeout);   }   #endregion }

Each of the state transition methods (OnAbort, OnBeginClose, OnBeginOpen, OnClose, OnEndClose, OnEndOpen, and OnOpen) invokes the corresponding public state transition method on the next channel in the channel stack. Each state transition method also calls the static Print method on the PrintHelper type. The PrintHelper type does little more than print the String passed to it to the console.

Creating the Datagram Channels

Now that we have defined the base type for all of our channels, let’s define the channels required for datagram message exchange. Since a datagram sending channel must implement the IInputChannel interface and the receiving channel must implement the IOutputChannel interface, we simply need to derive two types from DelegatorChannelBase<TShape> and implement the interfaces. Because the datagram interfaces are used by the duplex interfaces as well as the datagram and duplex sessionful interfaces, we will make our datagram channels generic.

Note 

We will start with the receiver, and then continue by defining the sender. For brevity, I will not show all of the members required in these derived types but rather show the pattern required in a full implementation.

The Datagram Receiving Channel

The datagram receiving channel subclasses the DelegatorChannelBase<TShape> type and implements the IInputChannel interface. Like DelegatorChannelBase<TShape>, our datagram receiving channel will also be generic, thereby allowing the channel to be reused by a duplex channel, as well as by the datagram and duplex variants. Because of these requirements, the name of our datagram receiving channel is DelegatorInputChannel<TShape>, as shown here:

 internal class DelegatorInputChannel<TShape> :   DelegatorChannelBase<TShape>, IInputChannel   where TShape:class, IInputChannel {   // implementation not shown }

The DelegatorInputChannel<TShape> constructor must call the constructor on its base type, set the value of the output String, and call the PrintHelper.Print method, as shown here:

 internal class DelegatorInputChannel<TShape> :   DelegatorChannelBase<TShape>, IInputChannel   where TShape:class, IInputChannel {      private String _source; // store the String to output   internal DelegatorInputChannel(ChannelManagerBase channelManagerBase,                                  TShape innerChannel,                                  String source) : base(channelManagerBase,                                                        innerChannel,                                                        source) {      // assign the name and generic parameter to the String      _source = String.Format("{0} CHANNEL: DelegatorInputChannel<{1}>",                               source,                               typeof(TShape).Name);      // output that the method was called      PrintHelper.Print(_source, "ctor");     }   // other implementation not shown }

Next we need to implement the IInputChannel interface. For brevity, I will show only three of the members here:

 public IAsyncResult BeginReceive(TimeSpan timeout,                                  AsyncCallback callback,                                 Object state) {   // output that the method was called   PrintHelper.Print(_source, "BeginReceive");   // delegate the call to the next channel   return this.InnerChannel.BeginReceive(timeout, callback, state); } public IAsyncResult BeginReceive(AsyncCallback callback, Object state) {   // output that the method was called   PrintHelper.Print(_source, "BeginReceive");   // delegate the call to the next channel   return this.InnerChannel.BeginReceive(callback, state); } public IAsyncResult BeginTryReceive(TimeSpan timeout,                                     AsyncCallback callback,                                     Object state) {   // output that the method was called   PrintHelper.Print(_source, "BeginTryReceive");   // delegate the call to the next channel   return this.InnerChannel.BeginTryReceive(timeout, callback, state); }

The DelegatorInputChannel<TShape> type definition is complete only after the other members are added (BeginWaitForMessage, EndReceive, EndTryReceive, EndWaitForMessage, LocalAddress, Receive, TryReceive, and WaitForMessage).

The Datagram Sending Channel

The datagram sending channel is very similar to the datagram receiving channel, except that it implements the IOutputChannel interface. To avoid repetition, I will show the type definition here and leave it to the reader to draw the parallels with the DelegatorInputChannel<TShape> type definition:

 internal class DelegatorOutputChannel<TShape> :   DelegatorChannelBase<TShape>, IOutputChannel where   TShape: class, IOutputChannel {   private String _source; // store the String to output   internal DelegatorOutputChannel(ChannelManagerBase channelManagerBase,                                   TShape innerChannel,                                   String source) : base(channelManagerBase,                                                         innerChannel,                                                         source) {     _source = String.Format("{0} CHANNEL: DelegatorOutputChannel<{1}>", source, typeof(TShape).Name);     // output that the method was called     PrintHelper.Print(_source, "ctor");   }   #region IOutputChannel Members   public IAsyncResult BeginSend(Message message,                                 TimeSpan timeout,                                 AsyncCallback callback,                                 Object state) {     // output that the method was called     PrintHelper.Print(_source, "BeginSend");     // delegate the call to the next channel     return this.InnerChannel.BeginSend(message, timeout, callback, state);   }   public IAsyncResult BeginSend(Message message, AsyncCallback callback, object state) {     // output that the method was called     PrintHelper.Print(_source, "BeginSend");     // delegate the call to the next channel     return this.InnerChannel.BeginSend(message, callback, state);   }   public void EndSend(IAsyncResult result) {     // output that the method was called     PrintHelper.Print(_source, "EndSend");     // delegate the call to the next channel     this.InnerChannel.EndSend(result);   }   public EndpointAddress RemoteAddress {     get {       // output that the method was called       PrintHelper.Print(_source, "RemoteAddress");       // delegate the call to the next channel       return this.InnerChannel.RemoteAddress; }     }   public void Send(Message message, TimeSpan timeout) {     // output that the method was called     PrintHelper.Print(_source, "Send");     // delegate the call to the next channel     this.InnerChannel.Send(message, timeout);     }   public void Send(Message message) {     // output that the method was called     PrintHelper.Print(_source, "Send");     // delegate the call to the next channel     this.InnerChannel.Send(message);   }   public Uri Via {     get {       // output that the method was called       PrintHelper.Print(_source, "Via");       // delegate the call to the next channel       return this.InnerChannel.Via;     }   }   #endregion }

The Duplex Channel

Recalling our examination of channel shapes, remember that the IDuplexChannel interface is really the union of the IInputChannel and IOutputChannel interfaces. Because we already have type definitions that implement the IInputChannel and IOutputChannel interfaces, we can reuse one of them as the base type for our duplex channel. The IInputChannel interface has more members than the IOutputChannel, so (for no other reason) the DelegatorInputChannel<TShape> type will serve as the base type for our duplex channel.

Because our duplex channel implements the IDuplexChannel interface, let’s call our duplex channel the DelegatorDuplexChannel and choose IDuplexChannel as the generic parameter in the base type, as shown here:

 internal class DelegatorDuplexChannel :   DelegatorInputChannel<IDuplexChannel>, IDuplexChannel {   // implementation not shown yet }

Because the DelegatorDuplexChannel is very similar to the DelegatorInputChannel<TShape> type definition, I will show only part of the type definition here:

 internal class DelegatorDuplexChannel :   DelegatorInputChannel<IDuplexChannel>, IDuplexChannel {   private String _source; // store the String to output   internal DelegatorDuplexChannel(ChannelManagerBase channelManagerBase,                                   // use IDuplexSession as the 2nd parameter                                   IDuplexChannel innerChannel,                                   String source) : base(channelManagerBase,                                                         innerChannel,                                                         source) {      _source = String.Format("{0} CHANNEL: DelegatorDuplexChannel", source);      PrintHelper.Print(_source, "ctor");     }     #region IOutputChannel Members   public IAsyncResult BeginSend(Message message,                                 TimeSpan timeout,                                 AsyncCallback callback,                                 Object state) {     PrintHelper.Print(_source, "BeginSend");     return this.InnerChannel.BeginSend(message, timeout, callback, state);   }   // other IOutputChannel Members omitted for brevity   #endregion }

The Duplex Session Channel

From an object model perspective, sessionful channel shapes differ only slightly from the nonsessionful ones. For example, the IDuplexSessionChannel is really the union of the IDuplexChannel and the ISessionChannel<IDuplexSession> interfaces. Because we have already defined the DelegatorDuplexChannel type (which implements the IDuplexChannel interface), creating a sessionful variant is simply a matter of subclassing the DelegatorDuplexChannel and implementing the IDuplexSessionChannel interface, as shown here:

 internal sealed class DelegatorDuplexSessionChannel :   DelegatorDuplexChannel, IDuplexSessionChannel {   private IDuplexSessionChannel _innerSessionChannel; // reference the next                                                       // sessionful channel   private String _source;  // store the String to output   internal DelegatorDuplexSessionChannel(ChannelManagerBase     channelManagerBase, IDuplexSessionChannel innerChannel, String source)     : base(channelManagerBase, innerChannel, source) {     _source = String.Format("{0} CHANNEL: DelegatorDuplexSessionChannel",       source);     PrintHelper.Print(_source, "ctor");     // assign the reference to the next sessionful channel     this._innerSessionChannel = innerChannel;   }   // IDuplexSessionChannel member that is not defined in IDuplexChannel   public IDuplexSession Session {     get {       PrintHelper.Print(_source, "Session");       return this._innerSessionChannel.Session; }     } }

Because the DelegatorDuplexChannel has a member variable of type IDuplexChannel, we need to store an additional reference to the same object via a local variable of type IDuplexSessionChannel. Doing so allows us to easily add the Session property to our type definition.

Note 

Given the patterns shown in the DelegatorChannelBase<TShape>, DelegatorInputChannel<TShape>, DelegatorOutputChannel<TShape>, DelegatorDuplexChannel, and DelegatorDuplexSessionChannel, it should be fairly easy for the reader to add channel implementations for IInputSessionChannel, IOutputSessionChannel, IRequestChannel, IReplyChannel, IRequestSessionChannel, and IReplySessionChannel. In the next two chapters, we will build the other types necessary to add these channels to a WCF application.




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