One of the out-of-the-box XML encoders for messages in WCF is the MTOM encoder. It provides an efficient way of transmitting large binary data in the body of SOAP envelopes (by *not* applying the base-64 encoding to such data, which increases its size by roughly 33%), while still being interoperable with other platforms (MTOM is a W3C standard). The MTOM encoder in WCF can even read “normal” XML-encoded messages, but it will always write MTOM-encoded messages.
One scenario which appears quite frequently is the need for a “smart” encoder, which will reply with the same encoding as its input – i.e., if a client sent a request using normal XML, the server should reply in normal XML; if the client sent a request using MTOM, the server should also reply using MTOM. It seems like a simple scenario – create a custom encoder which wraps both a Text and a Mtom encoder, use some inspector to correlate the request with the reply indicating via a message property whether request was Text or Mtom, and on output, simply use the same encoder used to decode the input to write out the response.
This, however, doesn’t work. When we look at the output, it actually looks like a valid MTOM/XOP document inside the HTTP body. And in a strict sense it is: the body of the response is indeed a valid XOP document – very similar to the one at http://www.w3.org/TR/2005/REC-xop10-20050125/#example.
However, this is not valid in the context of MTOM used within SOAP/HTTP. As described in the Serialization of a SOAP message section of the MTOM specification, the outer (HTTP) content-type must contain all the content-type of the MIME package. Also, the MIME-Version header must be “promoted” to an outer package header (HTTP) as well., so that the example above should be encoded as follows:
So the MTOM encoder doesn’t write the message in a way that is compatible with the HTTP binding. The out-of-the-box MTOM encoder in WCF works (i.e., it creates the correct body) because the HttpTransport uses an internal method in the MtomEncoder class (which is by itself internal) to write the appropriate body (see the MTOM Encoding section at http://msdn.microsoft.com/en-us/library/ms735115.aspx). If we use a custom encoder, the HTTP transport has no way to know how to call that method, so we get the (incorrect) mapping shown before.
So how can we enable this scenario? The solution needs to be broken down in two parts. First, we need to add the correct headers (MIME-Version and Content-Type) to the HTTP response. This cannot be done at the encoder level, since at that point the headers have already been written to the wire, and the transport only needs the body from the encoder. My sample uses an IDispatchMessageInspector to add the headers to the HTTP level. The second part is to change the way the message is written, both to prevent the MIME header from being output, and to use the same boundary value as the one specified in the Content-Type HTTP header.
The first part is shown below. Notice that it’s passing, in the message properties, all the information that the encoder needs to create the MTOM body.
Next is the encoder part. Here I’m only showing the [Read/Write]Message implementation, as it contains the main change to a “normal” custom wrapping encoder (and also only the buffered version; the streamed version is similar). On ReadMessage, we always use the MTOM encoder to decode the message – since it can read both text and mtom-encoded ones. On ReadMessage we also set the flag which will be picked up by the inspector to identify whether the request is MTOM or not. On WriteMessage, we’re handling the writing of the message ourselves, creating a MTOM writer directly. One of the overloads of the XmlDictionaryWriter.CreateMtomWriter method does exactly what we need – it allows us to pass the start-info, boundary, start-uri parameters, and also a flag indicating whether the MIME headers should be written. With that writer, we simply ask for the message to write itself (Message.WriteMessage) and that’s essentially it (plus some buffer management required by the encoder contract).
This custom encoder should fulfill the requirement of using MTOM in a custom encoder. The full project with the inspector and the encoder can be found here.