BizTalk, MTOM y transferencia de archivos binarios

Creado el día 27/01/2010 23:23 por zorry

En el proyecto en que estoy trabajando actualmente, ha surgido la necesidad de integrarnos con un servicio externo, el cual nos realizará envíos de archivos binarios. En las especificaciones del mismo nos comentan que el envío ha de ser mediante el protocolo Mtom, así que como no había tenido la oportunidad de trabajar previamente con éste, me he decidido a hacer una prueba de concepto y un artículo ya de paso. :-)

Primero un poco de introducción: El estándar Mtom (Message Transmission Optimization Mechanism) es una recomendación W3C (ver http://www.w3.org/TR/2005/REC-soap12-mtom-20050125/) mediante la cual se optimiza la transferencia de archivos binarios de gran tamaño. Emplea mensajes Mime-multipart para realizar el envío de los archivos sin necesidad de codificar como base64, de manera que, para archivos grandes, se ahorra aproximadamente un 30% de ancho de banda en comparación con la comunicación SOAP tradicional.

Bien, volviendo a nuestro caso, desarrollaremos un servicio que emplee este protocolo, al cual llamará la entidad externa para enviar un archivo para que luego nosotros lo almacenemos en algún lugar. Para implementarlo, tengo la idea de crear una orquestación que se encargue de almacenarlo en una ruta en disco. Esta orquestación será expuesta como servicio WCF.

El primer paso es generar un esquema que permita recibir un archivo binario. Para ello, el campo donde recibimos el contenido del archivo ha de ser del tipo xs:base64Binary, el cual definimos dentro del mensaje SendAttachmentRequest. Así, creamos dos parámetros, FileName donde nos especificarán el nombre del archivo, y FileContents, donde nos enviarán su contenido. De paso, en el esquema, también definimos el mensaje de respuesta SendAttachmentResponse de nuestro servicio:

clip_image002

A continuación, definiremos una orquestación que implemente la recepción y el envío de la respuesta empleando nuestro esquema, En esta, definiremos un puerto ReceiveAttachmentPort a través del cual recibimos el archivo binario en el mensaje MsgRequest, y enviamos de vuelta el Ack de respuesta en el mensaje MsgResponse. Este Ack lo generaremos asignando un valor true al valor Ack del mensaje de respuesta mediante un mapa:

clip_image002[7]

Hay que recordar que el puerto ReceiveAttachmentPort debe ser público, puesto que queremos ofrecerlo como servicio WCF. Compilaremos y desplegaremos la solución a BizTalk.

Una vez compilado el proyecto, vamos a exponer la orquestación como servicio WCF con el asistente que nos ofrece BizTalk 2006 R2. En el primer paso, especificamos que queremos emplear el adaptador WCF-WSHttp para poder soportar Mtom. Especificamos al asistente que nos cree las Receive Locations en la aplicación correspondiente, y en el paso posterior, que queremos publicar la orquestación como servicio WCF.

clip_image002[9]clip_image002[11]

Especificamos el assembly donde hemos compilado la orquestación y el esquema y a continuación, el puerto que queremos publicar.

clip_image002[13]clip_image002[15]

Especificamos el Target namespace del servicio y finalmente, la Url donde se publicará el servicio WCF.

clip_image002[17]clip_image002[19]

Una vez finalizado el asistente, nos habrá generado una aplicación en IIS y un Receive Location en BizTalk que enlazará nuestro servicio WCF con nuestra aplicación BizTalk. Recordemos cambiar el pool por defecto de la aplicación IIS a uno que corra con una identidad con permisos para escribir en la base de datos de MessageBox de BizTalk. Arrancamos la aplicación BizTalk, y comprobamos que el servicio funciona abriendolo en un Internet Explorer. Si al abrir en IE el servicio vemos que hay algún error, habrá que repasar la configuración: comprobar si el pool está bien configurado y su identidad pertenece a los grupos IIS_WPG y BizTalk Isolated Users, y si la aplicación BizTalk está levantada.

clip_image002[23]

El siguiente paso es especificar a la Receive Location que vamos a emplear Mtom, en las propiedades del adaptador WCF-WSHttp. De paso, especificamos el tamaño máximo que admitirá el servicio (para este ejemplo, 1 Mb):

clip_image002[25]

Tras todos estos pasos, ya tendríamos nuestro servicio listo para recibir binarios. Creamos una aplicación de ejemplo que consuma este servicio WCF para realizar una prueba, e invoque el servicio mediante este código:

  1. PoC_MTOM_ReceiveAttachment_ReceiveAttachmentPortClient client = new
  2.    PoC_MTOM_ReceiveAttachment_ReceiveAttachmentPortClient();
  3. SendAttachmentRequest req = new SendAttachmentRequest()
  4. req.FileName = new SendAttachmentRequestFileName();
  5. req.FileName.Value = fileName;
  6. req.FileContents = new SendAttachmentRequestFileContents()
  7. req.FileContents.Value = File.ReadAllBytes(fileNamePath);
  8. SendAttachmentResponse resp = client.SendAttachment(req);

Para poder observar que realmente esta siendo llamado el servicio mediante Mtom, empleo Fiddler para inspeccionar el tráfico Http entre mi cliente de prueba y el servicio. Este sería el tráfico Http que podremos observar con esta herramienta, al realizar una llamada de prueba con un archivo cualquiera:

clip_image002[27]

Podemos observar como el POST se realiza mediante Mtom, ya que se envía una petición Mime Multipart y su tipo Mime es “application/xop+xml”, y vemos como el adjunto se envía en binario con el tipo Mime "application/octet-stream". En caso de que la petición no se realizara mediante el protocolo Mtom, sino mediante SOAP estándar, podríamos observar que el POST se realiza con el tipo mime "application/soap+xml", y que todo el adjunto iría codificado en base64 dentro de la petición SOAP.

Con esto ya tenemos hecho la recepción y la respuesta, ahora nos queda hacer el almacenamiento del archivo adjunto. Para ello modificamos la orquestación, añadiendo las siguientes acciones al final de la misma, para realizar el almacenamiento del archivo en disco:

clip_image002[29]

En el Expression shape llamado Read file contents, obtendremos mediante la consulta XPath el nombre y el contenido del archivo adjunto en el mensaje entrante:

  1. //Read Base64 with xpath
  2. auxBase64 = xpath(MsgReceive, "string(/*
  3.    local-name()='SendAttachmentRequest']/*[local-name()='FileContents'
  4.   and namespace-uri() = ''])")
  5.  
  6. auxFileName = xpath(MsgReceive, "string(/*
  7.   local-name()='SendAttachmentRequest']/*[local-name()='FileName' and
  8.   namespace-uri() = ''])");

Posteriormente, almacenaremos el contenido del archivo en un XmlDocument. Este objeto nos permitirá contener lo que necesitemos aunque no sea un Xml, siempre que el objeto que carguemos dentro de él sea un MemoryStream o bien herede de IStreamFactory y se realice la asignación dentro de un scope atómico, y claro está, que no intentemos leer su contenido o bien consultarlo mediante XPath. Asignamos el contenido del archivo al mensaje MsgBinary mediante las siguientes instrucciones dentro del Assignment shape:

  1. //Message Initialization
  2. MsgBinary = new System.Xml.XmlDocument();
  3.  
  4. //Store binary into message
  5. PoC.MTOM.Helpers.MessageCreator.CreateBinaryMessageFromBase64(
  6.    MsgBinary, auxBase64);
  7.  
  8. //Set output file name
  9. MsgBinary(FILE.ReceivedFileName) = auxFileName;

Para que funcione la asignación será necesario crear un proyecto de clase nuevo al que referenciará  el proyecto de Biztalk. Dentro de este proyecto crearemos dos clases. La primera será una clase que herede de IStreamFactory:

  1. [Serializable]
  2. public class MemoryStreamFactory: IStreamFactory
  3. {
  4.     private byte[] _byteContents;
  5.     public MemoryStreamFactory(string base64Contents)
  6.     {
  7.          _byteContents = Convert.FromBase64String(base64Contents);
  8.     }
  9.  
  10.     #region IStreamFactory Members
  11.     public System.IO.Stream CreateStream()
  12.     {
  13.         return new MemoryStream(_byteContents);
  14.     }
  15. }

La segunda clase será  el MessageCreator que será una clase auxiliar que se llamará desde el Assignment shape de la orquestación:

  1. public class MessageCreator
  2. {
  3.     public static void CreateBinaryMessageFromBase64(XLANGMessage
  4.       outMessage, string base64)
  5.     {
  6.         outMessage[0].LoadFrom(new MemoryStreamFactory(base64));
  7.     }
  8. }

Finalmente, compilaremos este proyecto de clase, lo subiremos a la GAC y redesplegaremos la orquestación. Habrá que crear un puerto One way de tipo FILE para escribir el archivo en disco, con la siguiente configuración (%SourceFileName% tomará el nombre de archivo que nos llega en MsgReceive para almacenarlo físicamente).

De nuevo probaremos el envío al servicio WCF con nuestro ejecutable de pruebas, y veremos como el archivo se almacenará en la ruta que hayamos elegido en el puerto de salida.

Sólo me queda dejaros el ejemplo que he creado para la descarga. Espero que os sea de provecho.


No se aceptan más comentarios