Building A Custom Message Encoder to Record Throughput, Part 2

Last time we looked at building a custom message encoder that counted the number of bytes that the transport was reading and writing from the message. We're building it inside out so I started off by making the encoder itself. An encoder comes from a message encoder factory, so now we need to put something together that spits out an instance of the encoder. The encoder doesn't need to keep any state about the message, which means that we can make the encoder a singleton of the factory.

 using System.ServiceModel.Channels;

namespace CountingEncoder
{
   class CountingEncoderFactory : MessageEncoderFactory
   {
      readonly CountingEncoder encoder;
      readonly MessageEncoderFactory innerFactory;

      public CountingEncoderFactory(CountingEncoderBindingElement bindingElement, MessageEncoderFactory innerFactory)
      {
         this.innerFactory = innerFactory;
         this.encoder = new CountingEncoder(bindingElement, innerFactory.Encoder);
     }

      public override MessageEncoder Encoder
      {
          get { return this.encoder; }
      }

      public override MessageVersion MessageVersion
      {
         get { return this.innerFactory.MessageVersion; }
      }
   }
}

Now, the factory itself comes from a message encoder binding element. The binding element also is what keeps track of the running counts for data. I put the tracking in the binding element because it's typical to clone the channel stack pieces. By keeping the data in the binding element, you get the correct counts regardless of the instance that was actually used by the channel.

 using System.ServiceModel.Channels;

namespace CountingEncoder
{
   public class CountingEncoderBindingElement : MessageEncodingBindingElement
   {
      readonly CountingEncoderBindingElement baseBindingElement;
      readonly MessageEncodingBindingElement innerBindingElement;
      long readBytes;
      int readCount;
      long writeBytes;
      int writeCount;

      public CountingEncoderBindingElement(MessageEncodingBindingElement innerBindingElement)
         : base()
      {
         this.innerBindingElement = innerBindingElement;
      }

      CountingEncoderBindingElement(CountingEncoderBindingElement originalBindingElement)
         : this(originalBindingElement.innerBindingElement)
      {
         if (originalBindingElement.baseBindingElement == null)
         {
            this.baseBindingElement = originalBindingElement;
         }
         else
         {
            this.baseBindingElement = originalBindingElement.baseBindingElement;
         }
      }

      public override AddressingVersion AddressingVersion
      {
         get { return this.innerBindingElement.AddressingVersion; }
         set { this.innerBindingElement.AddressingVersion = value; }
      }

      public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
      {
         context.UnhandledBindingElements.Add(this);
         return base.BuildChannelFactory<TChannel>(context);
      }

      public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
      {
         context.UnhandledBindingElements.Add(this);
         return base.BuildChannelListener<TChannel>(context);
      }

      public override MessageEncoderFactory CreateMessageEncoderFactory()
      {
         return new CountingEncoderFactory(this, innerBindingElement.CreateMessageEncoderFactory());
      }

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

      public override T GetProperty<T>(BindingContext context)
      {
         return innerBindingElement.GetProperty<T>(context) ?? context.GetInnerProperty<T>();
      }

      public long ReadBytes
      {
         get { return BaseBindingElement.readBytes; }
         internal set { BaseBindingElement.readBytes = value; }
      }

      public int ReadCount
      {
         get { return BaseBindingElement.readCount; }
         internal set { BaseBindingElement.readCount = value; }
      }

      public long WriteBytes
      {
         get { return BaseBindingElement.writeBytes; }
         internal set { BaseBindingElement.writeBytes = value; }
      }

      public int WriteCount
      {
         get { return BaseBindingElement.writeCount; }
         internal set { BaseBindingElement.writeCount = value; }
      }

      CountingEncoderBindingElement BaseBindingElement
      {
         get { return this.baseBindingElement ?? this; }
      }
   }
}

The important piece here is that the binding element put itself in the list of unhandled elements when creating the channel. An unhandled element is one that didn't get turned into a piece of the channel stack immediately during creation. The transport looks at this list of elements when searching for its message encoder. Remember, many transports have a default message encoding that they use if you don't specify one. If your custom message encoder fails to put itself in the list of elements, then it looks like the binding never contained a message encoder. Everything will appear to work because the transport used its default encoder, but the encoding specified in the binding is not getting called.

This is actually all the code you need for a custom message encoder. Later this week I'll put together some test programs using this encoder to demonstrate how it works.

Next time: One Month Until TechEd 2006