Encoding Messages

I've briefly covered protocol channels and transport channels in the past, which transform and transmit messages respectively.  However, the subject of today's post is how a transport channel converts the message, which is an object, to bytes that can be sent across a network.  The message encoder is the part of the channel stack that is responsible for doing this conversion.  There can only be one message encoder in the channel stack and there always has to be one as well.  If you don't specify a message encoder, then you'll either get whatever default message encoder the transport has or an error depending on the implementation of the transport channel.  Transport implementations are nice enough to give you a default message encoder most of the time although you may not want to rely on this behavior.

Creating a message encoder is a three (or more) step process.  Let's start by assuming that we can just new up a binding element instance for the message encoder.  There are ways of specifying the channel stack that abstract away creating binding elements in code, but that's how we get to the "or more" steps version.  From the binding element, we can get the factory for the message encoder that manages encoder instances.

 public abstract class MessageEncoderFactory
{
   protected MessageEncoderFactory();

   public abstract MessageEncoder Encoder { get; }
   public abstract MessageVersion MessageVersion { get; }

   public virtual MessageEncoder CreateSessionEncoder();
}

The only interesting things about a message encoder factory are that it specifies the supported SOAP message version and there's the idea that an encoder can either track a conversation (sessionful) or not care about individual threads of exchange (sessionless, usually not given any special name).  Either way, we can then generate the actual message encoder from the factory.

 public abstract class MessageEncoder
{
   protected MessageEncoder();

   public abstract string ContentType { get; }
   public abstract string MediaType { get; }
   public abstract MessageVersion MessageVersion { get; }

   public virtual bool IsContentTypeSupported(string contentType);
   public abstract Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager);
   public abstract Message ReadMessage(Stream stream, int maxSizeOfHeaders);
   public override string ToString();
   public abstract void WriteMessage(Message message, Stream stream);
   public ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager);
   public abstract ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset);
}

Now we have a few more interesting members including two that distinguish between different types and formats of messages.  What we need at this particular moment though is just the ability to read and write messages.  There are separate ways of doing this depending on whether message transmissions need to be buffered or streamed.

I'll wrap up this mini-introduction to bindings with the piece that actually holds everything together. 

Next time: The Ties that Bind Us, Part 2: Binding