Friday, September 28, 2012

WCF handles streamed content type badly

While trying to help a colleague with a perplexing deserialization problem with derived types, which the KnownTypes attribute wouldn't fix.

Take One: we tried to pass JSON data into our REST service through a Stream parameter. We were thinking that we could simply read the raw object data and perform our own deserialization. But, no, we began to get a "500 Internal Server Error" with the exception:

Incoming message for operation 'test' (contract 'RuleService' with namespace '') contains an unrecognized http body format value 'Json'. The expected body format value is 'Raw'. This can be because a WebContentTypeMapper has not been configured on the binding. See the documentation of WebContentTypeMapper for more details.

With .NET, it seems nothing is ever as easy as advertised.

The data was getting to our service, but it was throwing an exception before dispatching the request to our service method, so the Visual Studio debugger was useless to run down the problem.

Take Two: We created a test method to accept a Stream and return it as a string. This failed, too. WCF, WTF!

Turns out WCF is really that stupid. A Stream is supposed to accept any data regardless of format. It doesn't. WCF still tries to interpret the content according to the Content-Type header, instead of ignoring it as it should. The client must tell the service the data is unformatted before it will work without throwing an exception. So a client that isn't completely well-behaved and strictly compliant with the web service interface will blow up your program. I guess it's a security feature.

Here's the raw deal on raw data: if your method accepts a Stream, the client must set the request's Content-Type header to application/octet-stream or text/plain or some other type that indicates unformatted content. If you only switch your input parameter from a class to a Stream, a message that could have worked before will immediately fail. Using WebContentTypeMapper requires substituting your own mapper for the entire service, not just for a single operation. Apparently there is no simple workaround.

Take Three: luckily for us, the client is our own program. We can make it compliant. If it were a third-party application, we would be SOL.

The End: After we changed the client to set the message header to Content-Type: application/octet-stream, the service starting receiving data again and our program went on happily deserializing the object that WCF's stock deserializer wouldn't.