2010-02-19 24 views
14

Soy un poco nuevo en WCF e intentaré describir claramente lo que intento hacer.Deserialización WCF JSON genérica

Tengo un servicio web WCF que utiliza solicitudes JSON. Me está yendo bien enviando/recibiendo JSON en su mayor parte. Por ejemplo, el siguiente código funciona bien y como se esperaba.

JSON envió:

{ "guy": {"FirstName":"Dave"} } 

WCF:

[DataContract] 
    public class SomeGuy 
    { 
     [DataMember] 
     public string FirstName { get; set; } 
    } 

    [OperationContract] 
    [WebInvoke(Method = "POST", 
       BodyStyle = WebMessageBodyStyle.WrappedRequest, 
       RequestFormat = WebMessageFormat.Json, 
       ResponseFormat = WebMessageFormat.Json)] 
    public string Register(SomeGuy guy) 
    { 
     return guy.FirstName; 
    } 

Esto devuelve un objeto JSON con "Dave" como se esperaba. El problema es que no siempre puedo garantizar que el JSON que recibo coincidirá exactamente con los miembros de mi DataContract. Por ejemplo, el JSON:

{ "guy": {"firstname":"Dave"} } 

no se serializarán correctamente porque la carcasa no coincide. guy.FirstName será nulo. Este comportamiento tiene sentido, pero realmente no sé cómo evitarlo. ¿Tengo que forzar los nombres de los campos en el cliente o hay alguna manera de reconciliarme en el lado del servidor?

Una pregunta posiblemente relacionada: ¿puedo aceptar y serializar un objeto JSON genérico en un StringDictionary o algún tipo de estructura de valor de clave simple? Entonces, no importa qué nombres de campo se envíen en el JSON, ¿puedo acceder a los nombres y valores que me han enviado? En este momento, la única forma en que puedo leer los datos que recibo es si coincide exactamente con un DataContract predefinido.

+1

* * Por supuesto que se puede garantizar que el JSON es conforme al contrato.Solo si no tiene el control del cliente, no podría controlarlo. Si no tiene el control del cliente, probablemente esté fuera del dominio de su servicio y el punto es discutible. No veo el problema. Todas las soluciones que veo son inútiles y solo agregarán dolor. Lea las especificaciones en DataContract/DataMember y sígalas. 2 pesos –

+1

Tienes razón. Técnicamente, podría escribirlo en el JS antes de que se envíe la información. Y, insinué en un comentario diferente que esto es probablemente lo que haré al final. Sin embargo, me gustaría que un servicio pueda manejar un número arbitrario de campos que no son necesariamente conocidos de antemano. ¿Tiene sentido? Solo quiero un servicio que pueda aceptar una lista completamente arbitraria de pares de "clave": "valor" y luego decidir qué hacer con ellos sin tener que conocer los nombres de "clave" de antemano. – davidmdem

Respuesta

11

Aquí es una forma alternativa de leer JSON en el diccionario:

[DataContract] 
public class Contract 
    { 
    [DataMember] 
    public JsonDictionary Registration { get; set; } 
    } 

[Serializable] 
public class JsonDictionary : ISerializable 
    { 
    private Dictionary<string, object> m_entries; 

    public JsonDictionary() 
     { 
     m_entries = new Dictionary<string, object>(); 
     } 

    public IEnumerable<KeyValuePair<string, object>> Entries 
     { 
     get { return m_entries; } 
     } 

    protected JsonDictionary(SerializationInfo info, StreamingContext context) 
     { 
     m_entries = new Dictionary<string, object>(); 
     foreach (var entry in info) 
      { 
      m_entries.Add(entry.Name, entry.Value); 
      } 
     } 

    public void GetObjectData(SerializationInfo info, StreamingContext context) 
     { 
     foreach (var entry in m_entries) 
      { 
      info.AddValue(entry.Key, entry.Value); 
      } 
     } 
    } 
+0

+1. También se encuentra una solución similar aquí: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/071f73bb-e141-4a68-ae61-05635382934f –

0

Realmente depende de para qué uses los datos. En teoría, puede mantener este genérico teniendo un tipo de retorno de cadena. Sin embargo, los datos representarían un objeto y el control del lado del cliente debería saber cómo enumerar el objeto JSON devuelto. Puede devolver el tipo que también puede ayudar.

Luego, en su código del lado del cliente puede tener una función que sepa cómo determinar y leer diferentes tipos.

3

Puede intentar y agregar otro DataMember attribute con el nombre en minúscula, pero supongo que quiere un enfoque insensible a mayúsculas y minúsculas para reconciliar los nombres de los miembros, en cuyo caso, el uso de atributos DataMember adicionales no es razonable.

Usted podría proporcionar una implementación IDataContractSurrogate, pero eso va a implicar una gran cantidad de trabajo adicional de su parte, así como una gran cantidad de reflexión (no hay manera de hacer esto de una manera estática que puede verificarse en tiempo de compilación).

Personalmente, he dejado de usar el DataContractSerializer class cuando se trata de JSON. Por el contrario, utilizo Json.NET para todas mis necesidades JSON. Puedo ver dónde puede enchufar Json.NET en el DataContractSerializer a través de un IDataContractSurrogate, pero aún así va a ser un poco difícil.

Json.NET fue una elección fácil dada la dificultad de usar el DataContractSerializer. Además, agregue al hecho de que el DataContractSerializer para JSON no maneja los valores DateTimeOffset correctamente, y fue una obviedad para mí, especialmente porque estaba trabajando en un entorno ASP.NET MVC (lo que me permite dar forma al resultado camino que quiero).

Esta es realmente la mejor opción si expone servicios RESTful utilizando JSON como codificación, puede mezclar y combinar eso con WCF para exponer todos los puntos finales sobre todos los protocolos de transporte y mensajes que necesita.

+0

Esto es interesante, ¿de alguna manera has combinado Json.NET con WCF o estás usando un controlador HTTP personalizado? – Aaronaught

+0

Me gustaría utilizar una de las bibliotecas para deserializar manualmente, pero no estoy seguro de cómo anular la deserialización predeterminada que ocurre cuando el servicio recibe la llamada. Esto probablemente encaja con mi comentario/pregunta anterior sobre cómo acceder al JSON sin formato. – davidmdem

+0

@Aaronaught: puedes hacerlo de ambas formas. DataContractJsonSerializer se deriva de XmlObjectSerializer, y usted puede hacer lo mismo, simplemente inyectando Json.NET para su lógica (nota, esto no es trivial). Si está utilizando ASP.NET MVC para una interfaz RESTful, lo único que debe hacer es crear un ActionResult personalizado que tome un objeto y, en la anulación de ExecuteResult, use Json.NET para volver a escribir JSON en la secuencia de salida. – casperOne

4

Como su nombre lo indica, un contrato de datos es un conjunto de reglas. Si desea asignar mensajes de manera confiable a las operaciones, entonces esas reglas deben seguirse.

Por qué no puede usted garantizar que la carcasa sea correcta? Si lo que desea es utilizar identificadores minúsculas desde JavaScript en su lugar, puede utilizar el atributo MessageParameter para eso - pero todavía tiene que elegir un nombre específico.

En teoría, podría aceptar JSON sin procesar y deserializarlo manualmente (solo tome un parámetro de cadena y use cualquier biblioteca JSON para la deserialización), pero eso realmente no está en el espíritu de WCF.

creo que lo que realmente necesita para arreglar no es el hecho de que el contrato de datos entre mayúsculas y minúsculas, pero el hecho de que el JSON no está siendo montado correctamente en el lado del cliente.


Si desea aceptar la cadena JSON crudo en su operación, a continuación, cambiar el BodyStyle a WebMessageBodyStyle.Bare, y cambiar su método de firma para aceptar un único parámetro de cadena, que se rellena con cualquier cadena JSON fue enviado por el cliente.

Tenga en cuenta que todo lo que obtiene es una sola cadena de esto, usted tiene que hacer todo el análisis y el mapeo de la propiedad y la validación y el manejo de errores usted mismo. No es la ruta que elegiría tomar, pero si está dispuesto a asumir el riesgo y el riesgo involucrados, es una posible opción.

+0

"lo que realmente necesita corregir es [..] el hecho de que el JSON no se está reuniendo correctamente en el lado del cliente". Estoy completamente de acuerdo y, al final, esto puede ser lo que termine haciendo. El problema es que tengo que aceptar llamadas de muchos formularios preexistentes que no usan una estructura de 'incluir' o plantilla, por lo que sería mucho retroceder y cambiar los nombres de campo (y algunos JS correspondientes) manualmente. ¿Cómo puedo acceder al JSON sin formato? Lo he estado intentando pero no quería complicar la pregunta. – davidmdem

+0

@ d12: ver edición. Probablemente no haga esta elección, pero si lo hace, la respuesta de capserOne podría ayudar con algunos de los detalles más finos una vez que intente averiguar qué hacer con JSON sin formato. – Aaronaught

3

Para lograr mi objetivo de tener un servicio que pueda aceptar una lista completamente arbitraria de pares "clave": "valor" como una cadena JSON sin procesar y luego decidir qué hacer con ellos sin tener que conocer la "clave" "Nombres de antemano, combiné el consejo de Casper y Aaron.

primero, para acceder a la cadena JSON prima, this MSDN blog was very helpful.

he podido simplemente cambiar el parámetro de un método único de cuerda y la BodyStyle a WebMessageBodyStyle.Bare sin problemas. Al establecer bodystyle a Bare, asegúrese de que el punto final se establece en behaviorConfiguration<webHttp/> y no <enableWebScript/>.

La segunda nota es que, como casperOne mentioned, el método sólo puede tener 1 parámetro. Este parámetro debe ser Stream para acceder al texto sin procesar (consulte el blog de MSDN más arriba).

vez que tenga la cadena JSON en bruto, es sólo una cuestión de deserializar en un StringDictionary. Elegí JSON.Net para esto y funciona maravillosamente. Aquí hay un ejemplo básico para ilustrar.

[OperationContract] 
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare, 
    ResponseFormat = WebMessageFormat.Json)] 
public string Register(Stream rawJSON) 
{ 
    // Convert our raw JSON string into a key, value 
    StreamReader sr = new StreamReader(rawJSON); 
    Dictionary<string, string> registration =  
     JsonConvert.DeserializeObject<Dictionary<string, string>>(
      sr.ReadToEnd()); 

    // Loop through the fields we've been sent. 
    foreach (KeyValuePair<string, string> pair in registration) 
    { 
     switch (pair.Key.ToLower()) 
     { 
      case "firstname": 
       return pair.Value; 
       break; 
     } 

    } 
} 

Esto me permite aceptar una lista arbitraria de campos a través de JSON y de los nombres de los campos que ser sensible a mayúsculas. Sé que no es el enfoque más estricto para la integridad de los datos o cómo los servicios WCF idealmente deberían estructurarse, pero, hasta donde puedo decir, es la forma más sencilla de llegar a donde quiero ir.

Cuestiones relacionadas