Search code examples
xmlwcfrestpostdatacontract

Unable to deserialize contract using REST WebInvoke with custom gzip Encoder


Given a REST service POST call:

 [WebInvoke] // POST
 string PostItem(PostItemContract contract);

That uses a custom gzip encoder within a custom binding (web.config):

  <customBinding>
    <binding name="customHttpGzipBindingConfig">          
      <!-- Normally would use binaryMessageEncoding, textMessageEncoding or webMessageEncoding here  -->          
      <gzipMessageEncoding innerMessageEncoding="webMessageEncoding"/>          
      <httpTransport ...>        
    </binding>        
  </customBinding>     

I get an error:

InvalidOperationException: The incoming message has an unexpected message format 'Raw'. The expected message formats for the operation are 'Xml', 'Json'. This can be because a WebContentTypeMapper has not been configured on the binding. See the documentation of WebContentTypeMapper for more details.

Modifying the web config to use webMessageEncoding instead of gzipMessageEncoding clears the error.

So I am thinking I would like to make the custom gzipMessageEncoding behave as if it is a webMessageEncoding:

I first created class GzipWebContentTypeMapper : WebContentTypeMapper where GetMessageFormatForContentType() just returns WebContentFormat.Xml;

Then some mods were made to the encoder which was based on the MSDN sample:

Custom Message Encoder: Compression Encoder

class GZipMessageEncoder : MessageEncoder
{   
   public override bool IsContentTypeSupported(string contentType)
   {
       // A BP here says it is returning true, this is where the
       // custom GetMessageFormatForContentType() is getting called.

       return innerEncoder.IsContentTypeSupported(contentType);
   }     
   // ...
}

public class GZipMessageEncodingElement : BindingElementExtensionElement
{        
    public override Type BindingElementType
    {
       get { return typeof(WebMessageEncodingBindingElement); } // just to see
    }
    public override void ApplyConfiguration(BindingElement bindingElement)
    {                        
        GZipMessageEncodingBindingElement binding = (GZipMessageEncodingBindingElement)bindingElement;

        WebMessageEncodingBindingElement element = new WebMessageEncodingBindingElement();
        // responsible for mapping from Content-Type to WCF xml/json/raw/default formats.
        element.ContentTypeMapper = new GzipWebContentTypeMapper();
        binding.InnerMessageEncodingBindingElement = element;                              
    }
    // ...
}

The debugger shows that:

GzipWebContentTypeMapper is indeed getting called and returning WebContentFormat.Xml;

GZipMessageEncoder.IsContentTypeSupported is returning true.

Explicitly setting the format does not work either (?):

[WebInvoke(RequestFormat=WebMessageFormat.Xml)] 
string PostItem(PostItemContract contract);

Within the web.config, I tried adding a webMessageEncoding before/after the custom gzip encoder but it did not help.

Any ideas what happens after the GZipMessageEncoder.IsContentTypeSupported returns true?

Where would the result of the GzipWebContentTypeMapper (which is successfully returning WebContentFormat.Xml) be stored and how could it be getting disregarded?


Solution

  • The problem was that the ContentType was not being passed to the innerEncoder within the encoder.

    That is where a message.Property[] is added to the message with key

    WebBodyFormatMessageProperty.Name 
    

    and value

    new WebBodyFormatMessageProperty(WebContentFormat).  
    

    It was adding "Raw" because it did not know what content type it was reading.

    Added contentType argument here:

    class GZipMessageEncoder : MessageEncoder
    {
       // ...
       ReadMessage(ArraySegment<byte> buffer, ...)  
       { 
          //...
          _innerEncoder.ReadMessage(decompressedBuffer, bufferManager, **contentType**);
       }
    
       ReadMessage(Stream stream, ...)
       { 
          //...
          _innerEncoder.ReadMessage(stream, maxSizeOfHeaders, **contentType**);
       }
    }