One of the many cool things about WCF is that it abstracts network communication into the lower layers of the framework and leaves us developers to deal with the actual data for our applications instead of the nitty gritty of connection handling and data casting.
Operation Contracts can define parameter types explicitly and that works as expected. But what’s super neat is that a service method can define a parameter of a generic type (i.e. object) and the service can still auto-magically figure out the underlying type and cast the object appropriately as long as the ServiveKnownType attribute of the service contract is properly specified…
Recently, I was working with an Android client that interfaced with a WCF service. I used JSON to encode and transfer that data. Everything was great, until I called one of my service methods that had a generic parameter:
[OperationContract] string DoSomeStuff(int id, object inputObject);
I should mention that I already had a .Net client (that DID NOT use JSON) for this service and everything worked great. However, calling this method from Java resulted in the following exception:
The server encountered an error processing the request. The exception message is ‘The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:inputObject. The InnerException message was ‘Expecting state ‘Element’.. Encountered ‘Text’ with name ”, namespace ”. ‘. Please see InnerException for more details.’. See server logs for more details. The exception stack trace is:
at System.ServiceModel.Dispatcher.DataContractJsonSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part)
at System.ServiceModel.Dispatcher.DataContractJsonSerializerOperationFormatter.DeserializeParameters(XmlDictionaryReader reader, PartInfo parts, Object parameters, PartInfo returnInfo, Object& returnValue)
at System.ServiceModel.Dispatcher.DataContractJsonSerializerOperationFormatter.DeserializeBodyCore(XmlDictionaryReader reader, Object parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.DataContractJsonSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object parameters)
at System.ServiceModel.Dispatcher.DemultiplexingDispatchMessageFormatter.DeserializeRequest(Message message, Object parameters)
at System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter.DeserializeRequest(Message message, Object parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
Yikes! Well, clearly it was having trouble deserializing the inputObject parameter.
In short, this is a classic case of expecting a little too much magic from WCF, which after all is not really a magician at all.
The issue here is that my .Net client used SOAP via the ‘traditional’ wsHttpBinding configuration. For the Java client, I had webHttpBinding setup for JSON. If you look at the underlying message, SOAP transfers a lot of metadata with it, including the actual type of inputObject. Thus, as long as this “actual type” is listed in the ServiceKnownType attributes, everything is dandy. JSON message, on the other hand, is quite a bit more bare and does not contain the necessary metadata for WCF to know how to cast the incoming byte stream.
Sadly, there is no silver bullet here. However, the solution/workaround is quite easy, albeit annoying – specify the inputObject type explicitly, which for me required defining several new methods (since I expected several possible object types) and call those instead. So I ended up with:
[OperationContract] string DoSomeStuff(int id, object inputObject); [OperationContract] string DoSomeStuffMyObject(int id, MyObject inputObject); [OperationContract] string DoSomeStuffMyOtherObject(int id, MyOtherObject inputObject);
The .Net client could still call the more generic method, but the Java client had to chose the specific one.
Hope this helps,
Please let me know if you have a better way,