Blog Thinking Together

Microsoft. Biztalk Server 2006, .NET, WCF, WWF, WPF, Linq,...
Welcome to Blog Thinking Together Sign in | Join | Help
in Search

Blog

Blog donde escribré sobre Biztalk Server 2006, WWF, WCF, Arquitectura, ...

Como crear un adaptador con WCF LOB Adapter SDK

He encontrado muy poca información en español sobre el desarrollo de adaptadores con WCF LOB Adapter SDK. Con este post intento poner mi granito de arena para intentar arrojar algo de luz sobre este tema.

¿Porqué la necesidad de WCF LOB Adapter SDK?

Los adaptadores desarrollados hasta ahora eran dependientes tanto del consumidor como de la aplicación destinataria, es decir un adaptador desarrollado para comunicar Microsoft Biztalk Server con SAP no nos vale para comunicar Sharepoint con SAP.

WCF LOB Adapter SDK soluciona esta limitación, permitiendo que el adaptador que desarrollemos para obtener información de una aplicación de línea de negocio pueda ser consumido desde Biztalk, desde SQL Server, desde Microsoft Office Sharepoint Server o por ejemplo desde una aplicación .NET personalizada.

¿Qué es WCF LOB Adapter SDK?

Es un conjunto de herramientas y componentes que nos van a facilitar el desarrollo de adaptadores para aplicaciones de línea de negocio.

Las principales características de WCF LOB Adapter SDK son las siguientes:

  1. Proporciona un Wizard que facilita la creación de nuevos adaptadores guiando al desarrollador paso a paso.
  2. Expone el adaptador como si fuera un binding de WCF.
  3. Proporciona un Add-in que permite añadir referencias a adaptadores desde aplicaciones .net desarrolladas con Visual Studio 2005.
  4. Crea un contrato dinámicamente en tiempo de diseño.

WCF LOB Adapter SDK está basado en el modelo de Canal de WCF.

 

El modelo de canal de WCF consiste en una pila con uno o varios canales que se encargan de procesar los mensajes que fluyen entre ellos. Es muy parecida a la pila de TCP.

WCF LOB Adapter SDK utiliza el modelo de extensibilidad de canales exponiendo los adaptadores mediante binding personalizados.

Pasos para la creación de un adaptador

En la siguiente url podemos descargarnos el SDK con el Service Pack 1.

A continuación vamos a empezar a desarrollar un adaptador de que nos permitirá obtener correos mediante el protocolo POP3. Este adaptador lo podremos usar independientemente en aplicación .net o por ejemplo en Microsoft Biztalk sin modificar una sola línea de código.

Asistente de creación del adaptador

Lo primero que debemos hacer es crear un nuevo proyecto en con Visual Studio 2005 y seleccionar la plantilla WCF LOB Adapter.

image

Una vez creado el proyecto se lanzará un asistente que nos irá guiando paso paso en la creación del adaptador.

  1. En el primer paso debemos definir los siguientes campos:
    • Scheme: esquema usado para el protocolo de transporte del binding del adaptador. Por ejemplo el binding WSHttpBinding usa HTTP como esquema. Nosotros deberemos rellenar esta caja de texto con el valor mail.

    • Project namespace: todas las clases creadas por el asistente pertenecerá al namespace que pongamos aquí. Nosotros usaremos el namespace ejemplo.adaptadorwcf.

    • Service namespace: La concatenación del esquema más el namespace del proyecto dará lugar al namespace del WSDL generado por el adaptador. En nuestro caso el namespace del servicio será mail://ejemplo.adaptadorwcf. Si quisiéremos modificar el namespace propuesto por el wizard solamente tenemos que marcar la opción Overwrite default service namespace y modificar el valor por el que deseemos.

image

   2.  En el siguiente paso debemos definir la dirección del flujo de datos soportado por el adaptador:

    • Synchronous outbound: el adaptador soporta el envío de mensajes al sistema destino.
    • Asynchronous outbound: el adaptador soporta el envío de mensajes de forma asíncrona al sistema destino.
    • Synchronous inbound: el cliente recibe información del sistema destino vía adaptador.
    • Asynchronous inbound: el adaptador soporta la recepción de mensajes del sistema destino de forma asíncrona.

         Además debemos definir las opciones de metadatos soportada por el adatador:

    • Retrieval: Genera la definición de las operaciones y tipos complejos del sistema destino. Esta información será utilizada por el adaptador para la generación del contrato del servicio.
    • Browse: Permite que el usuario navegue de forma jerárquica por los metadatos del sistema destino para seleccionar las operaciones que desea invocar.
    • Search:Permite que el usuario realice una búsqueda en los metadatos del sistema destino para seleccionar las operaciones que desea invocar.

image

    3.  En el siguiente paso debemos definir las propiedades del adaptador:

  En nuestro caso no definiremos nada aquí.

    3.  En el siguiente paso debemos definir las propiedades de conexión con el sistema destino:

image

Las propiedades necesarias para la conexión son el servidor, el usuario y el password.

La gestión de credenciales utilizada en este ejemplo no es ni mucho menos la ideal, ya que se muestra el usuario y el password en texto plano en la URI. Os invito a que modifiquéis el adaptador para dar una solución mas eficiente y segura a la gestión de credenciales.

Finalmente se mostrará un resumen de la configuración del adaptador que hemos ido estableciendo en los pasos precedentes. Si estamos conformes pulsaremos en el botón Finish para que el Wizard cree las clases necesarias para implementar el adaptador.

Gestión de la conexión con el sistema destino

El adaptador crea por nosotros el esqueleto de las clases necesarias para poder gestionar la conexión con el sistema destino.

La clase Pop3AdapterConnectionUri.cs es la encargada de parsear y construir la cadena de conexión de la aplicación de línea de negocio destino.

El desarrollador del adaptador usará esta clase para parsear la URI de conexión proporcionada por el cliente y extraer de ella todos los parámetros de conexión necesarios.

public override Uri Uri
{
   get
   {
      	//Validación de las propiedades de conexión
      	if (Servidor == string.Empty)
      	{
           throw new InvalidUriException("Parámetro Servidor no válido");
      	}
      	if (Usuario == string.Empty)
      	{
           throw new InvalidUriException("Parámetro Usuario no válido");
	}
       if (Password== string.Empty)
       {
       	    throw new InvalidUriException("Parámetro Password no válido");
       }

       return new Uri(Pop3Adapter.SCHEME + "://" +
                      Servidor + "/" + Usuario + "/" + Password);
                                        
   }
   set
   {
      string[] datosUri = value.AbsoluteUri.Split('/');
                Servidor = datosUri[2];
                Usuario = datosUri[3];
                Password = datosUri[4];
               
   }
}

La clase Pop3AdapterConnectionFactory.cs es la responsable de definir y crear la conexión con el sistema destino.

Para ello debemos modificar el método CreateConnection para que devuelva un objeto de conexión con el servidor de correo.

public IConnection CreateConnection()
{
  return new Pop3AdapterConnection(conexionUri.Usuario,conexionUri.Password,
					conexionUri.Servidor, this);
}

El constructor de la clase Pop3AdapterConnectionFactory pasa en uno de los parámetros un objeto de tipo ConnectionUri con la información de conexión con el sistema destino. El problema es que el wizard no asigna ese objeto a ninguna propiedad de la clase con lo cual se pierde la información. Para solucionar este problema debemos declarar una variable privada llamada conexionUri y modificar el constructor de la clase para asignar a esta propiedad la información de conexión.

public Pop3AdapterConnectionFactory(ConnectionUri connectionUri
            , ClientCredentials clientCredentials
            , Pop3Adapter adapter)
{
    this.clientCredentials = clientCredentials;
    this.adapter = adapter;
    this.conexionUri = (Pop3AdapterConnectionUri)connectionUri;
}

Por último la clase Pop3AdapterConnection.cs es la encargada de realizar las llamadas a bajo nivel para gestionar la conexión con el sistema destino.

Debemos modificar el constructor de la clase para inicializar el objeto de conexión con el servidor de correo. La clase Pop3 es una clase gratuita obtenida de la web Codeproject en la url http://www.codeproject.com/KB/IP/despop3client.aspx que emplearemos para obtener los mail del servidor de correo.

public Pop3AdapterConnection(string Usuario,string Password, string Servidor, 
				Pop3AdapterConnectionFactory connectionFactory)
{
    this.connectionFactory = connectionFactory;
    this.connectionId = Guid.NewGuid().ToString();
    objConexionPop3 = new Pop3.Pop3Client(Usuario,Password,Servidor);
}

Debemos modificar los métodos que se encargan de abrir y cerrar la conexión:

public void Close(TimeSpan timeout)
{
    objConexionPop3.CloseConnection();
}

 public bool IsValid(TimeSpan timeout)
{
    return true;
}
 public void Open(TimeSpan timeout)
{
    objConexionPop3.OpenInbox();
}
Gestión de metadatos

La clase Pop3AdapterMetadataBrowseHandler.cs se encargará de mostrar de forma jerárquica las categorías y operaciones de la aplicación de línea de negocio al consumidor para que este seleccione las operaciones que desea invocar.

Debemos implementar el método Browse de esta clase para mostrar la operación de Monitorización de correo al consumidor.

Como podemos ver en el siguiente código, este método devuelve cada una de las operaciones y categorías del sistema destino como una lista de objetos de tipo MetadataRetrievalNode.

public MetadataRetrievalNode[] Browse(string nodeId
            , int childStartIndex
            , int maxChildNodes, TimeSpan timeout)
{
	List<MetadataRetrievalNode> lista = new List<MetadataRetrievalNode>();
       //Nodo raiz
       if (MetadataRetrievalNode.Root.NodeId.Equals(nodeId))
       {

          MetadataRetrievalNode nodo = new MetadataRetrievalNode("Operaciones");
          nodo.Direction = MetadataRetrievalNodeDirections.Inbound;
          nodo.DisplayName = "Operaciones";
          nodo.IsOperation = false;
          return new MetadataRetrievalNode[] { nodo };
       }
       else
       {
          MetadataRetrievalNode nodoInbound = new MetadataRetrievalNode("MonitorizacionCorreo");
          nodoInbound.DisplayName = "MonitorizacionCorreo";
          nodoInbound.IsOperation = true;
          nodoInbound.Direction = MetadataRetrievalNodeDirections.Inbound;
          lista.Add(nodoInbound);

          return lista.ToArray();
       }
}

La clase Pop3AdapterMetadataSearchHandler.cs nos permite buscar operaciones en la aplicación de línea de negocio para su posterior invocación.

El usuario introducirá un criterio de búsqueda en el asistente del adaptador y el método Search de la clase Pop3AdapterMetadataSearchHandler.cs se encargará de realizar la búsqueda de metadatos en el sistema destino utilizando dicho criterio.

La implementación del método Search es muy similar al del método Browse. El método debe devolver como resultado una lista de objetos de tipo MetadataRetrievalNode que el adaptador usará para mostrar las operaciones encontradas al usuario para su posterior consumo.

En este caso concreto comparamos el criterio de búsqueda introducido por el usuario con la palabra "monitorizacioncorreo" devolviendo, si hay coincidencia, una lista con un nodo que contendrá la operación MonitorizacionCorreo.

Este es el caso mas simple que nos podemos encontrar, lo ideal es permitir al usuario que introduzca caracteres comodín para que la búsqueda sea mas sencilla para el.

public MetadataRetrievalNode[] Search(string nodeId
		, string searchCriteria
            	, int maxChildNodes, TimeSpan timeout)
{
	List<MetadataRetrievalNode> lista = new List<MetadataRetrievalNode>();
       
       if (searchCriteria.ToLower() == "monitorizacioncorreo")
       {
          MetadataRetrievalNode nodoInbound = new MetadataRetrievalNode("MonitorizacionCorreo");
          nodoInbound.DisplayName = "MonitorizacionCorreo";
          nodoInbound.IsOperation = true;
          nodoInbound.Direction = MetadataRetrievalNodeDirections.Inbound;
          lista.Add(nodoInbound);
       }
       return lista.ToArray();
}

La clase Pop3AdapterMetadataResolverHandler.cs es la encargada de obtener la definición de las operaciones y tipos complejos de estas operaciones, para que el adaptador pueda generar en base a esta información el contrato del servicio.

Esta clase tiene dos métodos que debemos desarrollar:

    • ResolveOperationMetadata: este método es el responsable de obtener los metadatos que representan las operaciones ofrecidas por el sistema destino.
    • ResolveTypeMetadata: este método es el responsable de obtener los metadatos de los tipos complejos usados por el sistema destino.

En el método ResolveOperationMetadata vamos a crear un objeto de tipo ParameterizedOperationMetadata que va a contener la definición de la operación.

Una vez creado el objeto que contiene la operación, vamos a ir añadiendo a la colección Parameters de este cada uno de los parámetros del la operación.

public OperationMetadata ResolveOperationMetadata(string operationId, TimeSpan timeout, 
					out TypeMetadataCollection extraTypeMetadataResolved)
{
  extraTypeMetadataResolved = null;

  string nombreOperacion = "MonitorizacionCorreo";
  ParameterizedOperationMetadata operationMetadata = 
		new ParameterizedOperationMetadata(operationId, nombreOperacion);
  operationMetadata.DisplayName = "RecibirNuevoCorreo";
  operationMetadata.OriginalName = "Evento_RecibirNuevoCorreo";

  OperationParameter asuntoCorreo = new OperationParameter("asuntoCorreo", 
			OperationParameterDirection.In, 
			QualifiedType.StringType, false);
  asuntoCorreo.Description = "Asunto del correo";
  operationMetadata.Parameters.Add(asuntoCorreo);

  OperationParameter cuerpoCorreo = new OperationParameter("cuerpoCorreo", 
					OperationParameterDirection.In, 
					QualifiedType.StringType, false);
  asuntoCorreo.Description = "Contenido del correo";
  operationMetadata.Parameters.Add(cuerpoCorreo);

  OperationParameter remitente = new OperationParameter("remitente", 
					OperationParameterDirection.In, 
					QualifiedType.StringType, false);
  asuntoCorreo.Description = "Remitente del correo";
  operationMetadata.Parameters.Add(remitente);

  OperationParameter destinatario = new OperationParameter("destinatario", 
					 OperationParameterDirection.In, 
					 QualifiedType.StringType, false);
  asuntoCorreo.Description = "Destinatario del correo";
  operationMetadata.Parameters.Add(destinatario);


  operationMetadata.OperationResult = null;
  return operationMetadata;
}

En nuestro caso no tenemos ningún tipo complejo por lo tanto no implementaremos el método ResolveTypeMetadata.

Ejecución de las operaciones

Ya por último solo nos queda añadir la lógica que monitorizará el correo y enviará los mensajes obtenidos al consumidor del adaptador.

Para ello vamos a implementar los siguientes métodos:

  • StartListener: lógica de escucha de mensajes asociados a una operación en concreto (action).
  • StopListener: fin de la escucha de mensajes.
  • TryReceive: intenta recibir un mensaje de entrada de la aplicación de negocio destino. Este método devuelve un valor booleano indicando si se ha podido obtener el mensaje del sistema destino o no.
  • WaitForMessage: espera un mensaje de entrada de la aplicación de negocio destino.

En el método StartListener crearemos una cola de mensajes que contendrán todos los mails del servidor de correo pendientes de notificar al consumidor.

Para monitorizar el servidor de correo vamos a crear un timer que cada 10 segundos compruebe los mails que hay.

public void StartListener(string[] actions, TimeSpan timeout)
{
       colaMensajesEntrada = new Queue<Message>();
       foreach (string action in actions)
       {
          if ("MonitorizacionCorreo".Equals(action))
          {
             delegado = new TimerCallback(Monitoriza);
             objThread = new Timer(delegado, null, 1000, 10000);
          }
       }
}

En el método StopListener pararemos la monitorización del servidor de correo.

public void StopListener(TimeSpan timeout)
{
	objThread.Dispose();
}

En el método TryReceive comprobaremos si la cola de mensajes tiene algún correo pendiente de notificar al consumidor del adaptador. La notificación la realizaremos mediante el parámetro de salida message que tiene el propio método. Además debemos de devolver como resultado de la función true en el caso que estemos devolviendo un mensaje o false si no tenemos nada pendiente que notificar del sistema destino.

public bool TryReceive(TimeSpan timeout, out System.ServiceModel.Channels.Message message, 
			out IInboundReply reply)
{
       reply = new Pop3AdapterInboundReply();
       message = null;

       if (colaMensajesEntrada.Count != 0)
       {
          message = colaMensajesEntrada.Dequeue();
          if (message != null)
          {
              return true;
          }
        }
        return false;
}

El método encargado de monitorizar la entrada de nuevos correos obtendrá la conexión del servidor de correo de las propiedades del adaptador e irá encolando cada uno de los mensajes que reciba.

private void Monitoriza(Object stateInfo)
{
  int contadorMail=0;
  Pop3Client email = this.Connection.Conexion;
  while (email.NextEmail(contadorMail))
  {
     string asuntoCorreo = email.Subject;
     string cuerpoCorreo = email.Body;
     string remitente = email.From;
     string destinatario = email.To;

     Message msg = CrearMensajeRecepcionPedido(asuntoCorreo,cuerpoCorreo,remitente,destinatario);
     colaMensajesEntrada.Enqueue(msg);
     contadorMail++;
  }
            
}

El mensaje que debemos enviar al consumidor del adaptador para notificarle de la llegada de nuevos correos debe cumplir un formato concreto.

En el siguiente método podemos ver como transformar la información obtenida del servidor de correo en un formato que podamos enviar al consumidor a través del adaptador.

private Message CrearMensajeRecepcionPedido(string asuntoCorreo, string cuerpoCorreo, 
					string remitente, string destinatario)
{
  Pop3Adapter adapter = this.Connection.ConnectionFactory.Adapter;

  String xmlData = String.Format(@"<RecibirNuevoCorreo xmlns=""{0}""><asuntoCorreo>" + asuntoCorreo 
  + "</asuntoCorreo><cuerpoCorreo>" + cuerpoCorreo + "</cuerpoCorreo><remitente>" + remitente 
  + "</remitente><destinatario>" + destinatario 
  + "</destinatario></RecibirNuevoCorreo>", adapter.ServiceNamespace);
  XmlReader reader = XmlReader.Create(new StringReader(xmlData));

  Message msgResultado = Message.CreateMessage(MessageVersion.Default, 
                          "MonitorizacionCorreo", reader);
  return msgResultado;
}
Despliegue del adaptador

Una vez desarrollado el adaptador, debemos firmarlo con un strong name y subirlo al Global Assembly Cache. Debemos copiar la clave pública del assembly una vez que lo hayamos subido al GAC ya que lo necesitaremos luego para registrar el adaptador en el fichero de configuración.

El adaptador lo podremos registrar tanto en el machine.config en el caso que queramos que el adaptador esté disponible para todos los proyectos de la máquina, o lo podemos registrar en el fichero de configuración del proyecto donde vayamos a consumir el adaptador.

Nosotros en este ejemplo vamos a registrarlo en el machine.config para que esté accesible desde todos los proyectos.

  • Vamos a registrar el adaptador como un elemento de binding. Para ello en la sección   <system.serviceModel><extensions><bindingElementExtensions> añadiremos el

siguiente xml:

<add name="AdapterPOP3" type="ejemplo.adaptadorwcf.Pop3AdapterBindingElementExtensionElement,
Pop3Adapter, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=447f918ac099d73e" />

 

  • También podemos registrar el adaptador como un binding. Para ello añadiremos el siguiente XML en la sección  <system.serviceModel><extensions><bindingExtensions>.:
<add name="AdapterPOP3Binding" type="ejemplo.adaptadorwcf.Pop3AdapterBindingCollectionElement,
Pop3Adapter, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=447f918ac099d73e" />

Una vez registrado el adaptador ya podemos utilizarlo en un endpoint de cliente. Para ello en la sección <system.serviceModel><client> vamos a añadir el siguiente XML:

<endpoint binding="AdapterPOP3Binding" contract="IMetadataExchange"
        name="AdaptadorPOP3" />

Debemos asegurarnos que el nombre del binding que pongamos en el endpoint sea el mismo que hayamos utilizado al registrar el binding.

En los próximos días publicaré un post indicando como consumir este adaptador desde una aplicación .NET.

Adjunto un archivo con el código fuente para que os quede un poco mas claro la implementación del adaptador. Solamente decir que este código ha sido implementado para servir de apoyo al post y en ningún momento está optimizado ni ha sido pensado para poder utilizarse en un entorno real.

Pop3Adapter.rar

Published Monday, June 16, 2008 12:10 AM by Roberto
Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

No Comments

Leave a Comment

(required) 
(optional)
(required) 
Submit
Powered by Community Server (Personal Edition), by Telligent Systems