miércoles, 4 de mayo de 2016

¿Cómo utilizar la librería Exchange Web Services?

Este artículo pretende brindar la información necesaria para el aprendizaje básico del uso de la librería EWS que brinda Outlook a través de su suite Office para automatizar tareas relacionadas con los mails.

Para empezar, debemos conocer qué significa EWS: Exchange Web Services. Para ponerlo simple, es el conjunto de funciones que actualmente usamos en Outlook pero disponibles como código fuente.

Por ejemplo, supongamos que abrimos el Outlook, creamos un nuevo e-mail y lo enviamos. En ese caso, identificamos tres acciones: abrir, crear, enviar. Esas acciones, también están disponibles mediante EWS. Podemos llamar a una función que nos permita conectarnos al servidor de Exchange para establecer la comunicación, tal como lo hace Outlook cuando lo abrimos, podemos llamar otra función que cree un objeto de tipo mail y a una última que envíe ese objeto al destinatario que indiquemos.

Esto nos introduce a un nuevo abanico de posibilidades, brindándonos el poder de automatizar tareas que hacemos a diario en nuestro trabajo. De esta forma, podemos incluir estas funciones en programas ya existentes para potenciarlos, e incluso podríamos crear una nueva versión customizada de un cliente de correos tal como lo es el Outlook. 

Ahora bien, una limitación importante es que EWS hace referencia al universo Exchange, o sea a los servidores de correo de Microsoft, por lo cual no podemos usarlo con cualquier cuenta que queramos como Gmail o Yahoo!. Esas cuentas usan otros protocolos de correo, por lo tanto estamos fuertemente relacionados a contar con un servidor Exchange disponible.


EWS vs EWS Api

Cuando hablamos sobre EWS, cabe destacar que estamos abarcando dos protocolos de comunicación bien distintos. EWS en sí, son Web Services que se ejecutan en el servidor de mail Exchange. Rápidamente, podemos decir que un Web Service es un protocolo de comunicación que expone el servidor. Significa que en el código fuente del servidor de mails hay funciones que podemos utilizar. Para hacerlo, debemos respetar un contrato, o más bien un formato para poder hablar con el servidor. El servidor nos expone el contrato para que sepamos cómo hablarle, si armamos un mensaje respetando ese formato y se lo enviamos al servidor, éste va a saber interpretarlo y obrar en consecuencia. Sus respuestas pueden ser insertar datos en una tabla interna, enviar un mail o incluso devolvernos una respuesta a nosotros, por ejemplo, informarnos la cantidad de mails que hay en el inbox.
La manera de armar el mensaje, es utilizando un formato tipo XML, lo cual puede ser engorroso y darnos más de un dolor de cabeza. Por esa razón, Microsoft desarrolló una API conocida formalmente como EWS Managed Api. Una librería que ya contiene el armado de esos contratos y nos permite con sólo hacer un llamado a una función y enviarle parámetros , armar el contrato y enviarlo al servidor. Por este motivo, debemos diferenciar EWS de EWS Api. 
Otra diferencia importante, es que no todos los contratos de EWS están contenidos en la EWS Api, por lo tanto hay mensajes específicos que sólo se pueden armar enviando un contrato XML. Sin embargo, con cada nueva versión de la librería se incluyen más funcionalidades.
En esta serie de artículos,  haremos referencia a EWS Api.

Manos a la obra 

Veamos cómo armar un programa simple. Su función será:


1. Conectarse al servidor.
2. Crear y enviarnos un mail a nosotros mismos.
3. Buscar el mail previamente enviado en el Inbox.
4. Si lo encontró, reenviarlo nuevamente pero con un mensaje agregado. Si no lo encontró, enviar un mail avisando de que no lo encontró.


Requisitos

Para empezar, debemos contar con las herramientas necesarias

  • Visual Studio: cualquier versión medianamente moderna de Visual Studio alcanzará.
  • EWS Managed Api: al momento de escribir este artículo la última versión disponible  es la 2.2. Puede ser descargada desde aquí: http://www.microsoft.com/en-us/download/details.aspx?id=42951 o bien podemos buscar "EWS Managed Api version" en cualquier buscador.

Su instalación es bien sencilla, ya que bajaremos un paquete de instalación que nos guiará a través de todo el proceso.

Creando el proyecto

Ahora sí entonces abriremos Visual Studio y crearemos un nuevo proyecto. Para nuestro
ejemplo, alcanzará con crear un proyecto de tipo consola y en C#, lo que significa que no tendrá formularios y utilizará una interfaz similar al MS-DOS.



Una vez creado el proyecto, debemos mover la DLL de EWS que descargamos y ubicarla dentro de los archivos del proyecto para un mejor ordenamiento: 



Volviendo a Visual Studio, nos encontramos con una clase llamada Program.cs:



Importando la DLL de EWS

El próximo paso es referenciar la DLL de EWS dentro del proyecto: 



Luego, hacemos clic en el botón “Browse”: 



Buscamos la ubicación dentro del proyecto en la cual dejamos las DDL de EWS Managed Api que descargamos: 





 Verificamos que las referencias hayan sido agregadas: 



Usando EWS

Habiendo hecho esto, volvemos a Program.cs para empezar a usarlas.Para ello las importamos escribiendo:

using Microsoft.Exchange.WebServices.Data;
using Microsoft.Exchange.WebServices.Auth;
using Microsoft.Exchange.WebServices;



Configurando la conexión


El próximo paso consiste en autentificarnos con el servidor de mails. Para ellos crearemos una nueva clase “DatosConexion” que contendrá la configuración. A fines educativos, lo haremos dentro del mismo Program.cs. 
Para una mejor organización, dichos valores deberían ir en un archivo de configuración app.config y el código que lee esa configuración y establece la conexión debería ir en un proyecto aparte.

class DatosConexion
    {
        public ExchangeVersion Version;
        public Uri AutodiscoverUrl;
        public string EmailAddress;
        public string Password;       
    }



Luego instanciaremos dicha clase y setearemos los valores:


DatosConexion conectar = new DatosConexion();
        conectar.Version = ExchangeVersion.Exchange2013;
        conectar.AutodiscoverUrl = new Uri("https://outlook.office365.com/EWS/
Exchange.asmx");
        conectar.EmailAddress = "tuMail@baufest.com";
        conectar.Password = "tuPassword";

Deberemos modificar los siguientes valores con los datos de nuestra dirección de mail.

conectar.EmailAddress = "tuMail@baufest.com";
conectar.Password = "tuPassword";

 



Conectándonos


Para conectarnos debemos utilizar un 'Listener', por ello debemos crear una clase Listener que utilice la interface iListener:

class TraceListener : ITraceListener
    {
        /*public void Trace(string traceType, string traceMessage)
        {
            //CreateXMLTextFile(traceType, traceMessage);
        }*/
        private void CreateXMLTextFile(string fileName, string traceContent)
        {
            try
            {
                if (!Directory.Exists(@"..\\TraceOutput"))
                    Directory.CreateDirectory(@"..\\TraceOutput");
                System.IO.File.WriteAllText(@"..\\TraceOutput\\" + fileName + DateTime.Now.Ticks + ".txt", traceContent);
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }


Para utilizar las funciones de escritura en disco que utiliza esta clase, debemos importar la librería System.IO:

using System.IO;


  
El próximo paso consiste en consumir el TraceListener y crear una nueva NetworkCredential:

ExchangeService servicio = new ExchangeService(conectar.Version);
servicio.TraceListener = new TraceListener();
servicio.TraceFlags = TraceFlags.All;
servicio.TraceEnabled = true;
servicio.Credentials = new NetworkCredential(conectar.EmailAddress, conectar.Password);
servicio.Url = conectar.AutodiscoverUrl;



Deberemos importar una nueva librería para utilizar el método NetworkCredential:

using System.Net;

 

Enviando un mail


El siguiente paso será enviar un mail. Para ello, crearemos un nuevo método. Nuevamente, para fines prácticos, lo crearemos dentro de la clase Main:
public static void EnviarMail(ExchangeService unServicio, string unMensaje)
        {
            EmailMessage email = new EmailMessage(unServicio);
            email.Subject = "Soy un asunto";
            email.Body = @"<html><head></head><body><p>" + unMensaje + "<br></p></body>
</html>";
            email.Body.BodyType = BodyType.HTML;
            email.ToRecipients.Add("tuMail@baufest.com");
            email.SendAndSaveCopy();
        }

Deberemos cambiar la línea a continuación con nuestra dirección de mail:

email.ToRecipients.Add("tuMail@baufest.com");



Consumiremos dicho método de la siguiente manera:

EnviarMail(servicio, "Soy un e-mail.");


 Buscando el mail que enviamos previamente
A continuación, buscaremos en el inbox el mail que acabamos de enviar. Esto puede llegar a fallar (lo trataremos más adelante) debido a la demora que se presenta entre que enviamos el mail y que lo recibimos.
Primero prepararemos una vista, que es el conjunto de items que devolverá la búsqueda.Por default lo limitamos a 100 items:

ItemView vista = new ItemView(100);
            vista.PropertySet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject
ItemSchema.ItemClass, EmailMessageSchema.Sender);


Luego crearemos los filtros de búsqueda:
List<SearchFilter> searchFilterSubjectCollection = new List<SearchFilter>();
            searchFilterSubjectCollection.Add(new SearchFilter.ContainsSubstring
(EmailMessageSchema.Subject, "Soy un asunto"));
            SearchFilter filtroDeBusqueda = new SearchFilter.SearchFilterCollection
(LogicalOperator.Or, searchFilterSubjectCollection.ToArray());



Finalmente, ejecutamos nuestra búsqueda y el resultado queda guardado en una colección:
FindItemsResults<Item> resultadoDeBusqueda = servicio.FindItems(WellKnownFolderName.Inbox,
 filtroDeBusqueda, vista);



Ahora verificamos el resultado. Si lo encontró, entonces nos reenviamos el mail y agregamos el texto "encontré el mail.". En caso contrario, se envía un mail avisando que no pudo encontrar el mail (esto puede ser porque aún no ha llegado el mail que enviamos al principio). En ese caso, esperaremos unos minutos y volveremos a ejecutar el programa.

            if (resultadoDeBusqueda.TotalCount > 0) {
                EmailMessage emailEncontrado = (EmailMessage) resultadoDeBusqueda.First();
                emailEncontrado.Reply("Encontré el mail", true);
            }
            else
            {
                EnviarMail(servicio, "No encontré el mail.");
            }



Código fuente final


Este es el resultado final de todo lo mencionado:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Exchange.WebServices.Data;
using Microsoft.Exchange.WebServices.Auth;
using Microsoft.Exchange.WebServices;
using System.IO;
using System.Net;
namespace EjemploEWS
{
    class Program
    {
        static void Main(string[] args)
        {
            DatosConexion conectar = new DatosConexion();
            conectar.Version = ExchangeVersion.Exchange2013;
            conectar.AutodiscoverUrl = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
            conectar.EmailAddress = "tuMail@baufest.com";
            conectar.Password = "tuPassword";
            ExchangeService servicio = new ExchangeService(conectar.Version);
            servicio.TraceListener = new TraceListener();
            servicio.TraceFlags = TraceFlags.All;
            servicio.TraceEnabled = true;
            servicio.Credentials = new NetworkCredential(conectar.EmailAddress, conectar.Password);
            servicio.Url = conectar.AutodiscoverUrl;
 
            EnviarMail(servicio, "Soy un e-mail.");
            ItemView vista = new ItemView(100);
            vista.PropertySet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject,
ItemSchema.ItemClass, EmailMessageSchema.Sender);
            List<SearchFilter> searchFilterSubjectCollection = new List<SearchFilter>();
            searchFilterSubjectCollection.Add(new SearchFilter.ContainsSubstring
(EmailMessageSchema.Subject, "Soy un asunto"));
            SearchFilter filtroDeBusqueda = new SearchFilter.SearchFilterCollection
(LogicalOperator.Or, searchFilterSubjectCollection.ToArray());
             FindItemsResults<Item> resultadoDeBusqueda = servicio.FindItems
(WellKnownFolderName.Inbox, filtroDeBusqueda, vista);
            if (resultadoDeBusqueda.TotalCount > 0) {
                EmailMessage emailEncontrado = (EmailMessage) resultadoDeBusqueda.First();
                emailEncontrado.Reply("Encontré el mail", true);
            }
            else
            {
                EnviarMail(servicio, "No encontré el mail.");
            }
         }
        public static void EnviarMail(ExchangeService unServicio, string unMensaje)
        {
            EmailMessage email = new EmailMessage(unServicio);
email.Subject = "Soy un asunto";
            email.Body = @"<html><head></head><body><p>" + unMensaje + "<br></p></body>
</html>";
            email.Body.BodyType = BodyType.HTML;
            email.ToRecipients.Add("tuMail@baufest.com");
            email.SendAndSaveCopy();
        }
    }
    class DatosConexion
    {
        public ExchangeVersion Version;
        public Uri AutodiscoverUrl;
        public string EmailAddress;
        public string Password;       
    }
class TraceListener : ITraceListener
    {
        public void Trace(string traceType, string traceMessage)
        {
            //CreateXMLTextFile(traceType, traceMessage);
        }
  private void CreateXMLTextFile(string fileName, string traceContent)
        {
            try
            {
                if (!Directory.Exists(@"..\\TraceOutput"))
                    Directory.CreateDirectory(@"..\\TraceOutput");
                System.IO.File.WriteAllText(@"..\\TraceOutput\\" + fileName + DateTime.Now.Ticks +
".txt", traceContent);
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Links:
Más información sobre las diferencias entre EWS y EWS Managed Api: https://msdn.microsoft.com/en-us/library/office/dd633678(v=exchg.80).aspx


Autor:


 







Julián Haeberli
Baufest Technical Leader