Note that there are some explanatory texts on larger screens.

plurals
  1. POPreserving Polymorphic Types in a WCF Service using JSON
    primarykey
    data
    text
    <p>I have a C# WCF service using a webHttpBinding endpoint that will receive and return data in JSON format. The data to send/receive needs to use a polymorphic type so that data of different types can be exchanged in the same "data packet". I have the following data model:</p> <pre><code>[DataContract] public class DataPacket { [DataMember] public List&lt;DataEvent&gt; DataEvents { get; set; } } [DataContract] [KnownType(typeof(IntEvent))] [KnownType(typeof(BoolEvent))] public class DataEvent { [DataMember] public ulong Id { get; set; } [DataMember] public DateTime Timestamp { get; set; } public override string ToString() { return string.Format("DataEvent: {0}, {1}", Id, Timestamp); } } [DataContract] public class IntEvent : DataEvent { [DataMember] public int Value { get; set; } public override string ToString() { return string.Format("IntEvent: {0}, {1}, {2}", Id, Timestamp, Value); } } [DataContract] public class BoolEvent : DataEvent { [DataMember] public bool Value { get; set; } public override string ToString() { return string.Format("BoolEvent: {0}, {1}, {2}", Id, Timestamp, Value); } } </code></pre> <p>My service will send/receive the sub-type events (IntEvent, BoolEvent etc.) in a single data packet, as follows:</p> <pre><code>[ServiceContract] public interface IDataService { [OperationContract] [WebGet(UriTemplate = "GetExampleDataEvents")] DataPacket GetExampleDataEvents(); [OperationContract] [WebInvoke(UriTemplate = "SubmitDataEvents", RequestFormat = WebMessageFormat.Json)] void SubmitDataEvents(DataPacket dataPacket); } public class DataService : IDataService { public DataPacket GetExampleDataEvents() { return new DataPacket { DataEvents = new List&lt;DataEvent&gt; { new IntEvent { Id = 12345, Timestamp = DateTime.Now, Value = 5 }, new BoolEvent { Id = 45678, Timestamp = DateTime.Now, Value = true } } }; } public void SubmitDataEvents(DataPacket dataPacket) { int i = dataPacket.DataEvents.Count; //dataPacket contains 2 events, but both are type DataEvent instead of IntEvent and BoolEvent IntEvent intEvent = dataPacket.DataEvents[0] as IntEvent; Console.WriteLine(intEvent.Value); //null pointer as intEvent is null since the cast failed } } </code></pre> <p>When I submit my packet to the <code>SubmitDataEvents</code> method though, I get <code>DataEvent</code> types and trying to cast them back to their base types (just for testing purposes) results in an <code>InvalidCastException</code>. My packet is:</p> <pre><code>POST http://localhost:4965/DataService.svc/SubmitDataEvents HTTP/1.1 User-Agent: Fiddler Host: localhost:4965 Content-Type: text/json Content-Length: 340 { "DataEvents": [{ "__type": "IntEvent:#WcfTest.Data", "Id": 12345, "Timestamp": "\/Date(1324905383689+0000)\/", "Value": 5 }, { "__type": "BoolEvent:#WcfTest.Data", "Id": 45678, "Timestamp": "\/Date(1324905383689+0000)\/", "Value": true }] } </code></pre> <p>Apologies for the long post, but is there anything I can do to preserve the base types of each object? I thought adding the type hint to the JSON and the KnownType attributes to <code>DataEvent</code> would allow me to preserve the types - but it doesn't seem to work.</p> <p><strong>Edit</strong>: If I send the request to <code>SubmitDataEvents</code> in XML format (with <code>Content-Type: text/xml</code> instead of <code>text/json</code>) then the <code>List&lt;DataEvent&gt; DataEvents</code> does contain the sub-types instead of the super-type. As soon as I set the request to <code>text/json</code> and send the above packet then I only get the super-type and I can't cast them to the sub-type. My XML request body is:</p> <pre><code>&lt;ArrayOfDataEvent xmlns="http://schemas.datacontract.org/2004/07/WcfTest.Data"&gt; &lt;DataEvent i:type="IntEvent" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"&gt; &lt;Id&gt;12345&lt;/Id&gt; &lt;Timestamp&gt;1999-05-31T11:20:00&lt;/Timestamp&gt; &lt;Value&gt;5&lt;/Value&gt; &lt;/DataEvent&gt; &lt;DataEvent i:type="BoolEvent" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"&gt; &lt;Id&gt;56789&lt;/Id&gt; &lt;Timestamp&gt;1999-05-31T11:20:00&lt;/Timestamp&gt; &lt;Value&gt;true&lt;/Value&gt; &lt;/DataEvent&gt; &lt;/ArrayOfDataEvent&gt; </code></pre> <p><strong>Edit 2</strong>: Updated service description after Pavel's comments below. This still doesn't work when sending the JSON packet in Fiddler2. I just get a <code>List</code> containing <code>DataEvent</code> instead of <code>IntEvent</code> and <code>BoolEvent</code>.</p> <p><strong>Edit 3</strong>: As Pavel suggested, here is the output from <code>System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString()</code>. Looks OK to me.</p> <pre><code>&lt;root type="object"&gt; &lt;DataEvents type="array"&gt; &lt;item type="object"&gt; &lt;__type type="string"&gt;IntEvent:#WcfTest.Data&lt;/__type&gt; &lt;Id type="number"&gt;12345&lt;/Id&gt; &lt;Timestamp type="string"&gt;/Date(1324905383689+0000)/&lt;/Timestamp&gt; &lt;Value type="number"&gt;5&lt;/Value&gt; &lt;/item&gt; &lt;item type="object"&gt; &lt;__type type="string"&gt;BoolEvent:#WcfTest.Data&lt;/__type&gt; &lt;Id type="number"&gt;45678&lt;/Id&gt; &lt;Timestamp type="string"&gt;/Date(1324905383689+0000)/&lt;/Timestamp&gt; &lt;Value type="boolean"&gt;true&lt;/Value&gt; &lt;/item&gt; &lt;/DataEvents&gt; &lt;/root&gt; </code></pre> <p>When tracing the deserialization of the packet, I get the following messages in the trace:</p> <pre><code>&lt;TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Verbose"&gt; &lt;TraceIdentifier&gt;http://msdn.microsoft.com/en-GB/library/System.Runtime.Serialization.ElementIgnored.aspx&lt;/TraceIdentifier&gt; &lt;Description&gt;An unrecognized element was encountered in the XML during deserialization which was ignored.&lt;/Description&gt; &lt;AppDomain&gt;1c7ccc3b-4-129695001952729398&lt;/AppDomain&gt; &lt;ExtendedData xmlns="http://schemas.microsoft.com/2006/08/ServiceModel/StringTraceRecord"&gt; &lt;Element&gt;:__type&lt;/Element&gt; &lt;/ExtendedData&gt; &lt;/TraceRecord&gt; </code></pre> <p>This message is repeated 4 times (twice with <code>__type</code> as the element and twice with <code>Value</code>). Looks like the type hinting information is being ignored then the <code>Value</code> elements are ignored as the packet is deserialized to <code>DataEvent</code> instead of <code>IntEvent</code>/<code>BoolEvent</code>.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload