Tengo un servicio C# WCF utilizando un punto final webHttpBinding que recibirá y devolverá datos en formato JSON. Los datos a enviar/recibir necesitan usar un tipo polimórfico para que los datos de diferentes tipos puedan intercambiarse en el mismo "paquete de datos". Tengo el siguiente modelo de datos:Conservación de tipos polimórficos en un servicio WCF utilizando 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);
}
}
Mi servicio será enviar/recibir los eventos sub-tipo (IntEvent, BoolEvent etc.) en un solo paquete de datos, como sigue:
[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
}
}
Cuando sin embargo, enviar mi paquete al método SubmitDataEvents
, obtengo los tipos DataEvent
y trato de devolverlos a sus tipos base (solo para fines de prueba) da como resultado un InvalidCastException
. Mi paquete es:
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
}]
}
Disculpas por el largo post, pero ¿hay algo que pueda hacer para preservar los tipos de base de cada objeto? Pensé que agregar la sugerencia de tipo a los atributos JSON y KnownType a DataEvent
me permitiría conservar los tipos, pero parece que no funciona.
Editar: Si envío de la solicitud a SubmitDataEvents
en formato XML (con Content-Type: text/xml
en lugar de text/json
) entonces el List<DataEvent> DataEvents
contiene los subtipos lugar del supertipo. Tan pronto como establezco la solicitud en text/json
y envío el paquete anterior, solo obtengo el super-tipo y no puedo convertirlo al subtipo. Mi solicitud XML cuerpo es:
<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>
Editar 2: descripción del servicio Actualización después de los comentarios de Pavel abajo. Esto aún no funciona al enviar el paquete JSON en Fiddler2. Acabo de obtener un List
que contiene DataEvent
en lugar de IntEvent
y BoolEvent
.
Editar 3: Como sugirió Pavel, aquí está la salida de System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString()
. Me parece bien
<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>
Cuando trazando la deserialización del paquete, consigo los siguientes mensajes en la traza:
<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>
Este mensaje se repite 4 veces (dos veces con __type
como el elemento y dos veces con Value
). Parece que se está ignorando la información de sugerencia tipo, entonces los elementos Value
se ignoran ya que el paquete se deserializa a DataEvent
en lugar de IntEvent
/BoolEvent
.
Wow ... Me alegra saber que su problema ha sido resuelto.Sin embargo, el JSON formateado funcionó bien para mí, siempre copié y pegué al Fiddler y no tuve problemas. Creo que no ha instalado todas las actualizaciones para .NET framework, o que la última versión tiene un extraño error relacionado con el procesamiento de caracteres en espacio en blanco. Estoy completamente avergonzado. –
No se avergüence del todo, nunca lo hubiera encontrado sin su ayuda. La única forma en que me enteré fue mediante la depuración del código fuente de WCF que publicó anteriormente. Noté que el valor de 'offset' era siempre demasiado bajo y me di cuenta de que la inclusión de los caracteres' \ r' y '\ n' en el búfer de mensajes estaba arrojando el cálculo de compensación de alguna manera. Puedo publicar otra pregunta para ver si alguien sabe por qué el espacio en blanco interfiere con el análisis sintáctico, ya que me gustaría tener la flexibilidad de incluirlo o no. –
Oh, eso debería haber sido 'confundido' en lugar de 'avergonzado' :). Por cierto, he encontrado la descripción de un problema similar [en este artículo] (http://blog.js-development.com/2010/05/n-will-break-your-json-jquery-wcf.html) –