WCF Modificar la respuesta SOAP antes de la Deserialización

  
Hola, después de mucho tiempo sin escribir debido a vacaciones, trabajo agobiante, prepararme para mi otra afición (correr)…, me he propuesto volver a escribir en el blog, quizás no con tanta periodicidad como antes pero no olvidarme de el.

Para este primer articulo después del kitkat, voy a hablar de un problema que he tenido recientemente.  La aplicación que estamos desarrollando es muy consumidora de servicios web de Java servidos por WebLogic 8. Recientemente la parte de WebLogic ha hecho una migración de la versión 8 a la 11 y han activado la multireferencia para los servicios Web (Consultar http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383520), esto modificaba el mensaje SOAP devuelto por WebLogic (no puedo poner ninguno de ejemplo por confidencialidad) la teoría dice que no debía de afectar a los clientes porque era estándar.
Como siempre, yo me rio de los estándares que implementan las diferentes empresas, por supuesto los clientes .Net eran incapaces de deserializarlos ya que no entendían el mensaje SOAP, estos mensajes tenían en los campos que aplicaba la multireferencia el indicativo href on el valor ID_XXX y al final del mensaje venia el valor de esos campos

<xsd:int id="ID_7" xsi:type="xsd:int">62</xsd:int>

<xsd:int id="ID_73" xsi:type="xsd:int">104</xsd:int>

A la hora de deserializar WCF no devolvía ningún error sino que el valor de esos campos eran siempre 0.

WCF también implementa multireferencia y es capaz de deserializarla pero los mensajes SOAP que se generan son diferentes a los que generaba WebLogic 11.

La solución que encontré fue crear un behavior de WCF para el cliente, para crear un behavior os recomiendo que leáis esta entrada de Juanlu http://geeks.ms/blogs/jlguerrero/archive/2008/12/07/wcf-un-behavior-para-personalizar-cabeceras-http.aspx que indica como crear un behavior de WCF que personaliza las cabeceras SOAP para todos los mensajes enviados por el cliente.

Si os fijáis en el código del behavior el codifica el método BeforeSendRequest en el que puedes modificar el mensaje que envía el cliente al servidor, pero en este caso queríamos modificar lo que recibíamos del servidor para crear un mensaje SOAP que .NET podía deserializar, para ello debemos de codificar el método AfterReceiveReply , este método tiene un parámetro (reply) que es el mensaje enviado por el servidor, pero para tratarlo debemos de realizar unos cuantos pasos, primero debemos de saber que este mensaje puede ser leído solo una vez, si intentas leerlo una segunda vez te da una bonita excepción.

El primer paso es convertir este mensaje a un XmlDocument para poder tratarlo, el código seria el siguiente  

1 Message newMessage = null; 2 MessageBuffer msgbuf = reply.CreateBufferedCopy(int.MaxValue); 3 4 var copy = msgbuf.CreateMessage(); 5 6 7 XmlDocument doc = new XmlDocument(); 8 9 MemoryStream ms = new MemoryStream(); 10 11 XmlWriter writer = XmlWriter.Create(ms); 12 13 copy.WriteMessage(writer); 14 15 writer.Flush(); 16 17 ms.Position = 0; 18 19 doc.Load(ms);

Una vez que lo tenemos en formato XmlDocument, lo podemos tratar para cambiar las referencias de href por los valores correspondientes de manera que el servicio pudiera deserializarlos

1 /// <summary> 2 /// Se cambia el mensaje Soap, cambiando los atributos href por los valores correspondiente 3 /// </summary> 4 /// <param name="xmldoc"></param> 5 private void ChangeMessage(ref XmlDocument xmldoc) 6 { 7 //Se transforma a Xdocument para realizar LinQ 8 XDocument xdoc = ToXDocument(xmldoc); 9 10 11 List<string> href = new List<string>(); 12 Dictionary<string, string> id = new Dictionary<string, string>(); 13 Dictionary<string, XAttribute> type = new Dictionary<string, XAttribute>(); 14 15 //Se buscan los elementpos con atributo id 16 17 IEnumerable<XElement> elList = 18 from el in xdoc.Descendants() 19 where el.Attribute("id") != null 20 select el; 21 foreach (XElement el in elList) 22 { 23 if (el.Attribute("id").Value.StartsWith("ID_")) 24 { 25 id.Add(el.Attribute("id").Value.ToString(), el.Value); 26 type.Add(el.Attribute("id").Value.ToString(), el.Attribute("{http://www.w3.org/2001/XMLSchema-instance}type")); 27 } 28 } 29 30 //Se buscan los elementos con atributo href y se sustituye por el valor que se 31 IEnumerable<XElement> elListHref = 32 from el in xdoc.Descendants() 33 where el.Attribute("href") != null 34 select el; 35 foreach (XElement el in elListHref) 36 { 37 string value = id[el.Attribute("href").Value.ToString().Replace("#", "")]; 38 el.Add(new XAttribute(type[el.Attribute("href").Value.ToString().Replace("#", "")].Name, type[el.Attribute("href").Value.ToString().Replace("#", "")].Value)); 39 el.Attribute("href").Remove(); 40 el.Value = value; 41 } 42 xmldoc = ToXmlDocument(xdoc); 43 }

Una vez que tenemos el XMLDocument cambiado, la manera de asignarlo a la variable reply es a través de XMLReader,para ello transformamos el XMLDocument a XMLReader

1 ms.SetLength(0); 2 3 writer = XmlWriter.Create(ms); 4 5 doc.WriteTo(writer); 6 7 writer.Flush(); 8 9 ms.Position = 0; 10 11 XmlReader reader = XmlReader.Create(ms); 12 13 reply = Message.CreateMessage(reader, int.MaxValue, reply.Version);

De esta manera hemos cambiado el mensaje SOAP adaptándolo a nuestras necesidades

Libros recomendados para este tema

 
Professional WCF 4: Windows Communication Foundation with .NET 4 (Wrox Programmer to Programmer)
Pro WCF 4: Practical Microsoft SOA Implementation
Expert WCF 4: Maximizing Windows Communication Foundation
     

   

Un comentario sobre “WCF Modificar la respuesta SOAP antes de la Deserialización”

  1. Resources like the one you mentioned here will be very useful to me! I will post a link to this page on my blog. I am sure my visitors will find that very useful

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *