Creating Custom Bindings


Now that you’ve seen the different types that are important in the Binding object model and learned how to use them to send and receive messages, let’s build our own binding. To continue the arc of the previous two chapters, our custom Binding will create channel factory and channel listener stacks with DelegatorChannelFactory and DelegatorChannelListener objects at the top of their respective stacks. Remembering that a Binding is really composed of a collection of BindingElement objects, let’s begin by creating the BindingElement that interacts directly with the DelegatorChannelFactory and DelegatorChannelListener types. The DelegatorBindingElement is shown here:

 using System; using System.Collections.Generic; using System.Text; using System.ServiceModel.Channels; // since the DelegatorBindingElement is part of // the developer-facing API, make this class public public sealed class DelegatorBindingElement : BindingElement {   public override bool CanBuildChannelFactory<TShape>(       BindingContext context) {     if(context == null) {       throw new ArgumentNullException("context");     }     // this BindingElement can wrap any shape of channel,     // so defer to the context     return context.CanBuildInnerChannelFactory<TShape>();   }   public override bool CanBuildChannelListener<TShape>(       BindingContext context) {     if(context == null) {       throw new ArgumentNullException("context");     }     // this BindingElement can wrap any shape of channel,     // so defer to the context     return context.CanBuildInnerChannelListener<TShape>();   }   public override IChannelFactory<TShape> BuildChannelFactory<TShape>(       BindingContext context) {     if (context == null) {       throw new ArgumentNullException("context");     }     // ensure that TShape is compatible     if(!this.CanBuildChannelFactory<TShape>(context)) {       throw new InvalidOperationException("Unsupported channel type");     }     // create a new DelegatorChannelFactory, passing context as argument     // a channel factory stack is actually returned     DelegatorChannelFactory<TShape> factory =       new DelegatorChannelFactory<TShape>(context);     // cast to IChannelFactory<TShape> and return     return (IChannelFactory<TShape>) factory;   }   public override IChannelListener<TShape> BuildChannelListener<TShape>(       BindingContext context) {     if (context == null) {       throw new ArgumentNullException("context");     }     // ensure that TShape is compatible     if(!this.CanBuildChannelListener<TShape>(context)) {       throw new InvalidOperationException("Unsupported channel type");     }     // create a new DelegatorChannelListener, passing context as argument     // a channel listener stack is actually returned     DelegatorChannelListener<TShape> listener =       new DelegatorChannelListener<TShape>(context);     return (IChannelListener<TShape>) listener;   }   public override BindingElement Clone() {     // since there are no fields, use the default ctor     return new DelegatorBindingElement();   }   public override T GetProperty<T>(BindingContext context) {     // delegate the call to the context arg     return context.GetInnerProperty<T>();   } }

Notice that the test methods and the query mechanism delegate to the BindingContext. Notice also that the factory methods instantiate either a DelegatorChannelFactory<TShape> or a DelegatorChannelListener<TShape> and pass the BindingContext as an argument to the constructor. It is important to pass the BindingContext to the constructor so that the channel factory or the channel listener can access the Binding property of the BindingContext, because this is the only way that the channel factory and channel listener can set the default time-outs that can be set in the Binding.

Now that the DelegatorBindingElement is in place, let’s turn our attention to the Binding that will add a DelegatorBindingElement to a BindingElement collection. Certainly this is possible without creating a Binding-derived type. All we would have to do is instantiate a CustomBinding object and pass a collection of BindingElement objects to the constructor. However, this does not provide an easy-to-use and reusable type. To best provide reusable code, let’s define a Binding that will create a collection of BindingElement objects that contains a DelegatorBindingElement at the head of the collection.

Remembering that a Binding-derived type must implement a CreateBindingElements method that returns a collection of BindingElement objects, it is important to consider how our Binding will create the collection of BindingElement objects. Because there are several bindings included in WCF, we can call the CreateBindingElements method on one of these existing bindings and insert our DelegatorBindingElement at the head of the collection. This approach ensures that the BindingElement objects in the collection are compatible with each other. With this in mind, which default Binding should we choose? My guess is as good as yours, and it might not be the same choice another person might make. Let’s attempt to please everyone by allowing the caller to choose one among several of the default WCF bindings. To do this, we will need an enumerated type that represents the WCF bindings we will mimic:

 public enum BindingMode {     Tcp,          // NetTcpBinding     TcpRM,        // NetTcpBinding w/WS-ReliableMessaging     WSHttp,       // WsHttpBinding     WSHttpRM,     // WsHttpBinding w/WS-ReliableMessaging     BasicHttp,    // BasicHttpBinding     PeerChannel,  // NetPeerTcpBinding     MSMQ,         // NetMsmqBinding     MSMQSession   // NetMsmqBinding w/ExactlyOnce = true }

The constructor of our Binding will include a parameter of type BindingMode. Furthermore, callers might want to insert the DelegatorBindingElement in a place other than the head of the list. This can be helpful in cases where WS-ReliableMessaging is used. Placing the DelegatorBindingElement between the TransportBindingElement and the ReliableSessionBindingElement will show the messages generated by the WS-ReliableMessaging channels, and placing it after the ReliableSessionBindingElement will not show as many messages. For this, we will need an Int32 parameter that represents the place in the BindingElement collection where we want to put the DelegatorBindingElement. With this in mind, our DelegatorBinding looks like the following:

 using System; using System.ServiceModel.Channels; using System.ServiceModel; using System.Text; // since this is part of the developer-facing API, // make it public public sealed class DelegatorBinding : Binding {   String _scheme; // the scheme of the Binding   BindingElementCollection _elements; // the BindingElement collection   // this ctor delegates to the other ctor   public DelegatorBinding(BindingMode mode) : this(mode, 0) {   }   public DelegatorBinding(BindingMode bindingMode, Int32 elementPosition) {     // check the BindingMode arg and create     // a BindingElement collection from it     switch (bindingMode) {       case (BindingMode.BasicHttp):         BasicHttpBinding httpBinding =           new BasicHttpBinding(BasicHttpSecurityMode.None);         _elements = httpBinding.CreateBindingElements();         _scheme = "http";         break;       case (BindingMode.Tcp):         _elements = new NetTcpBinding(SecurityMode.None,                                       false).CreateBindingElements();         _scheme = "net.tcp";         // set manual addressing (optional)         TransportBindingElement transport =           _elements.Find<TransportBindingElement>();         transport.ManualAddressing = false;         break;       case (BindingMode.TcpRM):         _elements = new NetTcpBinding(SecurityMode.None,                                       true).CreateBindingElements();         _scheme = "net.tcp";         break;       case (BindingMode.WSHttp):         _elements = new WSHttpBinding(SecurityMode.None,                                       false).CreateBindingElements();         _scheme = "http";         break;       case (BindingMode.WSHttpRM):         _elements = new WSHttpBinding(SecurityMode.None,                                       true).CreateBindingElements();         _scheme = "http";         break;       case (BindingMode.MSMQ):         NetMsmqBinding msmqBinding =           new NetMsmqBinding(NetMsmqSecurityMode.None);         msmqBinding.ExactlyOnce = false;         _elements = msmqBinding.CreateBindingElements();         _scheme = "net.msmq";         break;       case (BindingMode.MSMQSession):         NetMsmqBinding msmqTransactionalBinding =           new NetMsmqBinding(NetMsmqSecurityMode.None);         msmqTransactionalBinding.ExactlyOnce = true;         _elements = msmqTransactionalBinding.CreateBindingElements();         _scheme = "net.msmq";         break;       default:         throw new ArgumentOutOfRangeException("bindingMode");     }     // add the DelegatorBindingElement in the specified position     _elements.Insert(elementPosition, new DelegatorBindingElement());   }   // returns the BindingElement collection built in ctor   public override BindingElementCollection CreateBindingElements() {     return _elements;   }   public override String Scheme {     get {       return _scheme;     }   } }

In this example, the constructor builds the BindingElement collection. Other bindings defer the creation of the BindingElement collection until the CreateBindingElements method. Because the DelegatorBinding does not expose any settable properties or contain any other relevant state, I opted to build the BindingElement collection in the constructor.

With the DelegatorBinding in place, we can now write an application that uses it. Let’s borrow from the preceding section, where we used the BasicHttpBinding to send and receive a Message. For this example, all we need to do is replace the binding instantiation as follows:

 BasicHttpBinding binding = new BasicHttpBinding();

Change to the following:

 DelegatorBinding binding = new DelegatorBinding(BindingMode.BasicHttp);

If we run that application, we get the following output:

 1.  BUILDING THE RECEIVER, Thread:1 2.  LISTENER: DelegatorChannelListener.GetProperty<       System.ServiceModel.Channels.ISecurityCapabilities>, Thread:1 3.  LISTENER: DelegatorChannelListener.OnOpen, Thread:1 4.  LISTENER: DelegatorChannelListener.OnAcceptChannel, Thread:1 5.  RECEIVE CHANNEL: DelegatorReplyChannel.ctor, Thread:1 6.  RECEIVE CHANNEL STATE CHANGE: DelegatorChannelBase.OnOpen, Thread:1 7.  TRYING TO RECEIVE A MESSAGE, Thread:1 8.  RECEIVE CHANNEL: DelegatorReplyChannel.BeginReceiveRequest, Thread:1 9.  BUILDING THE SENDER, Thread:1 10. FACTORY: DelegatorChannelFactory.ctor, Thread:1 11. FACTORY: DelegatorChannelFactory.GetProperty<ISecurityCapabilities>,       Thread:1 12. FACTORY: DelegatorChannelFactory.OnOpen, Thread:1 13. FACTORY: DelegatorChannelFactory.OnCreateChannel, Thread:1 14. SEND CHANNEL: DelegatorRequestChannel.ctor, Thread:1 15. SEND CHANNEL STATE CHANGE: DelegatorChannelBase.OnOpen, Thread:1 16. SEND CHANNEL: DelegatorRequestChannel.Request (BLOCKING), Thread:1 17. RECEIVE CHANNEL: DelegatorReplyChannel.EndReceiveRequest, Thread:4 18. Message received:     <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">       <s:Header>         <To s:mustUnderstand="1"xmlns=           "http://schemas.microsoft.com/ws/2005/05/addressing/none">           http://localhost:4000/MyListener         </To>         <Action s:mustUnderstand="1" xmlns=           "http://schemas.microsoft.com/ws/2005/05/addressing/none">           urn:SomeAction         </Action>       </s:Header>       <s:Body>         <string xmlns=           "http://schemas.microsoft.com/2003/10/Serialization/">           Hi there         </string>       </s:Body>     </s:Envelope>, Thread:4 19. Reply received:     <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">       <s:Header />       <s:Body>         <string xmlns=           "http://schemas.microsoft.com/2003/10/Serialization/">           Hi there back         </string>       </s:Body>     </s:Envelope>, Thread:1 20. RECEIVE CHANNEL STATE CHANGE: DelegatorChannelBase.OnClose, Thread:4 21. SEND CHANNEL STATE CHANGE: DelegatorChannelBase.OnClose, Thread:1 22. FACTORY: DelegatorChannelFactory.OnClose, Thread:1 23. LISTENER: DelegatorChannelListener.OnClose, Thread:1

As shown here, the DelegatorBinding allows us to see when an application creates a channel, all of the methods called on a channel, and the state changes of the channel and channel factory or channel listener stack.

Note 

I have found the DelegatorBinding to be very helpful in seeing how changes that I make in my application impact the channel layer. I encourage the reader to experiment with the DelegatorBinding to see how the different bindings impact channel shape, as well as which channel methods are called.




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