使用JSON在WCF服务中保留多态类型

Ada*_*ger 11 c# polymorphism wcf json

我有一个使用webHttpBinding端点的C#WCF服务,该端点将以JSON格式接收和返回数据.要发送/接收的数据需要使用多态类型,以便可以在同一"数据包"中交换不同类型的数据.我有以下数据模型:

[DataContract]
public class DataPacket
{
    [DataMember]
    public List<DataEvent> 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我的服务将在单个数据包中发送/接收子类型事件(IntEvent,BoolEvent等),如下所示:

[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<DataEvent>
            {
                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
    }
}
Run Code Online (Sandbox Code Playgroud)

当我将数据包提交给SubmitDataEvents方法时,我得到DataEvent类型并尝试将它们转换回它们的基类型(仅用于测试目的)结果InvalidCastException.我的包是:

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
    }]
}
Run Code Online (Sandbox Code Playgroud)

为长篇文章道歉,但我能做些什么来保留每个对象的基本类型?我想将类型提示添加到JSON和KnownType属性以DataEvent允许我保留类型 - 但它似乎不起作用.

编辑:如果我以SubmitDataEventsXML格式发送请求(Content-Type: text/xml而不是text/json),那么List<DataEvent> DataEvents确实包含子类型而不是超类型.一旦我将请求设置为text/json并发送上述数据包,那么我只获得超类型,我无法将它们转换为子类型.我的XML请求体是:

<ArrayOfDataEvent xmlns="http://schemas.datacontract.org/2004/07/WcfTest.Data">
  <DataEvent i:type="IntEvent" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Id>12345</Id>
    <Timestamp>1999-05-31T11:20:00</Timestamp>
    <Value>5</Value>
  </DataEvent>
  <DataEvent i:type="BoolEvent" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Id>56789</Id>
    <Timestamp>1999-05-31T11:20:00</Timestamp>
    <Value>true</Value>
  </DataEvent>
</ArrayOfDataEvent>
Run Code Online (Sandbox Code Playgroud)

编辑2:在Pavel的评论之后更新了服务描述.在Fiddler2中发送JSON数据包时,这仍然不起作用.我只是得到一个List包含DataEvent而不是IntEventBoolEvent.

编辑3:正如Pavel建议的那样,这里是输出System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString().看起来对我好.

<root type="object">
    <DataEvents type="array">
        <item type="object">
            <__type type="string">IntEvent:#WcfTest.Data</__type> 
            <Id type="number">12345</Id> 
            <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp> 
            <Value type="number">5</Value> 
        </item>
        <item type="object">
            <__type type="string">BoolEvent:#WcfTest.Data</__type> 
            <Id type="number">45678</Id> 
            <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp> 
            <Value type="boolean">true</Value> 
        </item>
    </DataEvents>
</root>
Run Code Online (Sandbox Code Playgroud)

跟踪数据包的反序列化时,我在跟踪中收到以下消息:

<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Verbose">
    <TraceIdentifier>http://msdn.microsoft.com/en-GB/library/System.Runtime.Serialization.ElementIgnored.aspx</TraceIdentifier>
    <Description>An unrecognized element was encountered in the XML during deserialization which was ignored.</Description>
    <AppDomain>1c7ccc3b-4-129695001952729398</AppDomain>
    <ExtendedData xmlns="http://schemas.microsoft.com/2006/08/ServiceModel/StringTraceRecord">
        <Element>:__type</Element>
    </ExtendedData>
</TraceRecord>
Run Code Online (Sandbox Code Playgroud)

此消息重复4次(两次__type作为元素,两次作为元素Value).看起来类型提示信息被忽略,然后Value元素被忽略,因为数据包被反序列化DataEvent而不是IntEvent/ BoolEvent.

Ada*_*ger 3

感谢 Pavel Gatilov,我现在找到了这个问题的解决方案。对于将来可能会遇到此问题的任何人,我会将其添加为单独的答案。

问题是 JSON 反序列化器似乎不太接受空格。我发送的数据包中的数据“打印得很漂亮”,带有换行符和空格,以使其更具可读性。但是,当反序列化此数据包时,这意味着在查找提示时"__type",JSON 反序列化器正在查看数据包的错误部分。这意味着错过了类型提示,并且数据包被反序列化为错误的类型。

以下数据包工作正常:

POST http://localhost:6463/DataService.svc/SubmitDataEvents HTTP/1.1
User-Agent: Fiddler
Content-Type: text/json
Host: localhost:6463
Content-Length: 233

{"DataEvents":[{"__type":"IntEvent:#WebApplication1","Id":12345,"Timestamp":"\/Date(1324905383689+0000)\/","IntValue":5},{"__type":"BoolEvent:#WebApplication1","Id":45678,"Timestamp":"\/Date(1324905383689+0000)\/","BoolValue":true}]}
Run Code Online (Sandbox Code Playgroud)

但是,这个包不起作用:

POST http://localhost:6463/DataService.svc/SubmitDataEvents HTTP/1.1
User-Agent: Fiddler
Content-Type: text/json
Host: localhost:6463
Content-Length: 343

{
    "DataEvents": [{
        "__type": "IntEvent:#WebApplication1",
        "Id": 12345,
        "Timestamp": "\/Date(1324905383689+0000)\/",
        "IntValue": 5
    }, {
        "__type": "BoolEvent:#WebApplication1",
        "Id": 45678,
        "Timestamp": "\/Date(1324905383689+0000)\/",
        "BoolValue": true
    }]
}
Run Code Online (Sandbox Code Playgroud)

除了换行符和空格之外,这些数据包完全相同。