Building a Custom File Transport, Part 4: Binding and Binding Element

Creating a binding and binding element for your transport is entirely optional if you're just using the channel model. It is possible to do everything you need through the channel factory and listener as long as you make those public. This stops being true at higher levels of abstraction in WCF. If you want to use the service model, then having at least a binding element is required. Including a binding that shows off your binding element is polite because it removes the need for users to create their own custom binding for your transport. Due to this, it's customary to just always create both a binding and binding element, and hide the channel factory and listener inside the assembly.

The binding for this example is extremely simple. We just have the transport element and a message encoder element. Remember, this transport does not have a default encoder so supplying a message encoder is mandatory. I've used the text message encoder so that you can look inside the last message sent if something goes wrong.

 using System.ServiceModel.Channels;

namespace FileTransport
{
   public class FileTransportBinding : Binding
   {
      readonly MessageEncodingBindingElement messageElement;
      readonly FileTransportBindingElement transportElement;

      public FileTransportBinding()
      {
         this.messageElement = new TextMessageEncodingBindingElement();
         this.transportElement = new FileTransportBindingElement();
      }

      public override BindingElementCollection CreateBindingElements()
      {
         BindingElementCollection elements = new BindingElementCollection();
         elements.Add(this.messageElement);
         elements.Add(this.transportElement);
         return elements.Clone();
      }

      public override string Scheme
      {
         get { return this.transportElement.Scheme; }
      }
   }
}

The transport binding element provides three things in this example. The first is that it provides the scheme that this transport will use. I've picked the "my.file" scheme in this case. The second is that it provides the constraints on creating channel factories and listeners. This example only supports the request-reply message exchange pattern, so the binding element checks that you have the correct channel shape. The third is that it provides configurable settings for the transport. I've included a setting to control whether the transport uses streamed transfers. Additionally, you inherit the quota settings for the maximum message size and maximum buffer pool size from the abstract base class. We'll make sure to implement those quotas when we get there.

 using System;
using System.ServiceModel.Channels;

namespace FileTransport
{
   public class FileTransportBindingElement : TransportBindingElement
   {
      public bool Streamed;

      public FileTransportBindingElement()
      {
         Streamed = false;
      }

      FileTransportBindingElement(FileTransportBindingElement other)
         : base(other)
      {
         Streamed = other.Streamed;
      }

      public override string Scheme
      {
         get { return "my.file"; }
      }

      public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
      {
         return typeof(TChannel) == typeof(IRequestChannel);
      }

      public override bool CanBuildChannelListener<TChannel>(BindingContext context)
      {
         return typeof(TChannel) == typeof(IReplyChannel);
      }

      public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
      {
         if (context == null)
         {
            throw new ArgumentNullException("context");
         }
         if (!CanBuildChannelFactory<TChannel>(context))
         {
            throw new ArgumentException(String.Format("Unsupported channel type: {0}.", typeof(TChannel).Name));
         }
         return (IChannelFactory<TChannel>)(object)new FileRequestChannelFactory(this, context);
      }

      public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
      {
         if (context == null)
         {
            throw new ArgumentNullException("context");
         }
         if (!CanBuildChannelListener<TChannel>(context))
         {
            throw new ArgumentException(String.Format("Unsupported channel type: {0}.", typeof(TChannel).Name));
         }
         return (IChannelListener<TChannel>)(object)new FileReplyChannelListener(this, context);
      }

      public override BindingElement Clone()
      {
         return new FileTransportBindingElement(this);
      }
   }
}

Next time: Building a Custom File Transport, Part 5: Channel Basics