miércoles, 25 de julio de 2012

MailMessage - Envia mail con GMail (1/3)

 

Introducción


Enviar un mail es una tarea bastante recurrente en las características de toda aplicación, pero existen varias formas de configurarla dependiendo de la necesidad

Analizaremos en este artículo como enviar un mail haciendo uso de un servicio de SMTP publico como es GMail.

Para llevar a cabo la tarea se puede configurar directamente las propiedades, pero es aun mejor si se configuran, lo cual permitiría realizar cambios sin tener que recompilar el código.

 

Envió del Mail configurando las propiedades


Enviar un mail definiendo toda la configuración por código no es aconsejable, pero si es útil cuando uno aun esta realizando prueba del funcionamiento del envió del correo para definir la información que después se volcaría al config

 

[TestMethod]
public void SendMailSinConfig()
{

    List<string> destinatarios = new List<string>()
    {
        "xx@gmail.com",
        "xx@hotmail.com"
    };


    //
    // se crea el mensaje
    //
    string body = "";

    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MailSendTest.MailBody.txt"))
    using (StreamReader reader = new StreamReader(stream))
    {
        body = reader.ReadToEnd();
    }

    MailMessage mail = new MailMessage()
    {
        From = new MailAddress("xx@gmail.com"),
        Body = body,
        Subject = "Mail Test",
        IsBodyHtml = false
    };


    //
    // se asignan los destinatarios
    //
    foreach (string item in destinatarios)
    {
        mail.To.Add(new MailAddress(item));
    }


    //
    // se define el smtp
    //
    SmtpClient smtp = new SmtpClient()
    {
        Host = "smtp.gmail.com",
        Port = 587,
        UseDefaultCredentials = false,
        Credentials = new NetworkCredential("xx@gmail.com", "password"),
        EnableSsl = true
    };
    

    smtp.Send(mail);

}

En este ejemplo además se añadió un adicional que ayuda bastante en la forma de declarar el texto del cuerpo del mensaje, consiste básicamente en embeber un archivo en el assembly del proyecto.

imagen1

En el código se toma el assembly que se esta ejecutando y de este el stream basado en el namespace que define la ruta al archivo dentro del proyecto.

 

Envió del mail usando Archivo de Configuración


Si bien el MailMessage no varia en la forma de programarse, si lo hizo notablemente la definición de datos que suelen modificarse con mas frecuencia durante la implementación, lo cual implicaría recompilar el código continuamente.

[TestMethod]
public void SendMailUsandoConfig()
{
    List<string> destinatarios = new List<string>()
    {
        "xx@gmail.com",
        "xx@hotmail.com"
    };


    //
    // se crea el mensaje
    //
    string body = "";
   
    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MailSendTest.MailBody.txt"))
    using (StreamReader reader = new StreamReader(stream))
    {
        body = reader.ReadToEnd();
    }

    MailMessage mail = new MailMessage()
    {
        Body = body,
        Subject = "Mail Test",
        IsBodyHtml = false
    };


    //
    // se asignan los destinatarios
    //
    foreach (string item in destinatarios)
    {
        mail.To.Add(new MailAddress(item));
    }


    //
    // se define el smtp
    //
    SmtpClient smtp = new SmtpClient();
    smtp.Send(mail);

}

El cliente de smtp toma la información del .config

<system.net>
  <mailSettings>
    <smtp from="xx@gmail.com">
      <network host="smtp.gmail.com" port="587" password="password" userName="xx@gmail.com" enableSsl="true" defaultCredentials="false" />
    </smtp>
  </mailSettings>
</system.net>

Pero seguramente habrán notado un problema, la información esta disponible a simple vista, lo cual no es bueno si se define el usuario y password de la cuenta de correo. Es por eso que puede aplicarse encriptación a esta sección del config

 

Envió del mail usando config seguro


Para probarlo es que se creo el segundo proyecto de test, en este se han agregado método para poder proteger la sección del archivo de configuración.

Es necesario mencionar que si bien podría haberse usado de forma directa un sistema de protección como es el RSAProtectedConfigurationProvider, se opta por crear una key propia, esto es debido a que será necesario exportar la clave que se genere para poder llevarla a las PCs (o al servidor) donde resida la aplicación cuando se realice el deploy de la misma.

Sera necesario ejecutar algunos comandos, es por eso que se accede a la consola que proporciona el VS, ya que se hará uso del aspnet_regiis, se hace uso de la misma técnica que se aplica en un entorno web para proteger el config.

imagen2

Los pasos para poder realizar esto serian:

1 - Se crea la key

aspnet_regiis -pc "MailSendKeys" –exp

Es muy importante el –exp ya que este permitirá la exportación de las keys a un archivo

2 - Se exporta la key

aspnet_regiis -px "MailSendKeys" "C:\MailSendKeysFile.xml" -pri

El parámetro –pri exporta la ket publica y privada necesarias para poder trabajar en la encriptación

 

3- Se importa en las PCs donde se hará uso la aplicación

aspnet_regiis -pi "MailSendKeys" "C:\MailSendKeysFile.xml"

Sin esta key en la pc donde estará la aplicación no se podrá leer la sección del config

 

Nota: Las key importadas podrán encontrar en la carpeta:

C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

 

4 – Encriptar la sección del .config

Para ello se definieron dos métodos que solo serán usados una única vez cuando se defina la información que debe configurarse

private void ProtegerMailSettings()
{

    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    SmtpSection configSection = config.GetSection("system.net/mailSettings/smtp") as SmtpSection;

    if (!configSection.SectionInformation.IsProtected)
    {
        //configSection.SectionInformation.ProtectSection("RSAProtectedConfigurationProvider");
        configSection.SectionInformation.ProtectSection("MailProtectedProvider");
        
        config.Save();
    }

}

private void DesprotegerMailSettings()
{

    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    SmtpSection configSection = config.GetSection("system.net/mailSettings/smtp") as SmtpSection;

    if (configSection.SectionInformation.IsProtected)
    {
        configSection.SectionInformation.UnprotectSection();
        config.Save();
    }

}

El test de encriptación solo se invoca en una única oportunidad, para que algo como esto:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <configProtectedData>
    <providers>
      <add name="MailProtectedProvider"
           type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0,
                 Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
           keyContainerName="MailSendKeys"
           useMachineContainer="true" />
    </providers>
  </configProtectedData>
  
  <system.net>
    <mailSettings>
      <smtp from="xx@gmail.com">
        <network host="smtp.gmail.com" port="587" password="password" userName="xx@gmail.com" enableSsl="true" defaultCredentials="false" />
      </smtp>
    </mailSettings>
  </system.net>

</configuration>

 

Pase a ser algo como ser:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <configProtectedData>
    <providers>
      <add name="MailProtectedProvider"
           type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0,&#xD;&#xA;                 Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

           keyContainerName="MailSendKeys"
           useMachineContainer="true" />
    </providers>
  </configProtectedData>
  
  <system.net>
    <mailSettings>
      <smtp configProtectionProvider="MailProtectedProvider">
        <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
          xmlns="http://www.w3.org/2001/04/xmlenc#">
          <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
              <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
              <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <KeyName>Rsa Key</KeyName>
              </KeyInfo>
              <CipherData>
                <CipherValue>OBiEstszUoonJNlFT9FwMePjtX+AdCufd2I8OAzHDYoi7A6GTTuaSqvM6KR9ANLC8fPmcRt/8PMoOK9nnVkRMlc4haKjoL+V1CmEBaqYOD23lBA5bGGIYdtDCZH9N597juNXkx8ISiESNPi9phO999T7cKPbAQ1rkQXmOGOQ9KU=</CipherValue>
              </CipherData>
            </EncryptedKey>
          </KeyInfo>
          <CipherData>
            <CipherValue>zEAsDA84EFb7ta4I0yzOS200HWB9JfPPtioQjn/GEZSGY3rqvmOwKhYGErxIR0E2k+ewPNJKypedeMyB3lAebrHs8kvwKY6R3glrDuvkcJlfN1Ihl3CcFjeBwE5Rkqmx4Jso5d6d8t4P5+GwqxStjJn/evfXBINv3EawkwATc11ch/aSKM1OUMXSz2XUnfAV7mwFehCk+V09YCXL+KjlgqAm3FwSAHCxCV0g1lLnebf7T1d2fELIjw==</CipherValue>
          </CipherData>
        </EncryptedData>
      </smtp>
    </mailSettings>
  </system.net>

</configuration>

 

La sección <configProtectedData> permite definir el proveedor custom que uno creara para hacer uso de la key registrada desde la linea de comando.

imagen4

El keyContainerName corresponde al nombre de la key creada por línea de comando. Mientras que el name del proveedor se asigna a la sección que se esta encriptado.

También es muy importante validar que este definido

imagen3

 

Link de referencia


How To: Encrypt Configuration Sections in ASP.NET 2.0 Using RSA

Importing and Exporting Protected Configuration RSA Key Containers

 

Código


[C#]
 

sábado, 21 de julio de 2012

[Linq] Distinct y GroupBy usando IEquatable<>

 

Introducción


Al hace uso de linq con objetos se presenta un problema ya que no se resuelve de forma correcta la comparaciones entre entidades, esto se debe al hacer uso de un objeto el mismo se compara por medio de la referencia (o sea del puntero en memoria de la instancia de la entidad) y no su contenido

Si se usara tipos simples no se tendría este inconveniente ya que las comparaciones se harían por valor, pero al ser objetos se usa la referencia, es por eso que se será necesario indicar mediante alguna técnica como comparar las entidades.

Para el ejemplo se trabajara con una lista de artículos, declarando una clase del estilo

public class ArticuloBase
{
    public int Id { get; set; }
    public string Nombre { get; set; }

    public int Stock { get; set; }
    public decimal Precio { get; set; }

}

el contenido de las listas tendrá ítems duplicados, aplicando las diferentes técnicas se podría usar linq directamente con los objetos de la lista.

Distinct


Si usamos distinct de linq sobre una lista para evitar duplicados no obtendremos efecto alguno

imagen1

ya que la lista resultante sigue teniendo 4 ítems, cuando debería haber detectado duplicados, para solucionar el problema será necesario definir como es que la entidad debe ser comparada

La primer opción será por medio de un comparador, se define una clase que implemente IEqualityComparer<>

public class ArticuloComparer : IEqualityComparer<ArticuloBase>
{
    public bool Equals(ArticuloBase x, ArticuloBase y)
    {
        return x.Id == y.Id;
    }

    public int GetHashCode(ArticuloBase obj)
    {
        return obj.Id.GetHashCode();
    }
}

esto hace que al usarse como parte del distinct permita conocer que entidades deben ser iguales

imagen2

ahora si se detecto el ítem duplicado y solo se obtiene como resultado 3, es necesario remarcar que en la clase se definio que compara por id, por lo que un diferente stock será descartado.

Existe otra opción valida cuando se usan entidades la cual consiste en implementar la interfaz IEquatable<>, en este caso se definió una clase heredada para separa la implementación y poder testear, pero se podría haber unido todo en una única clase

public class Articulo : IEquatable<Articulo>
{
    public int Id { get; set; }
    public string Nombre { get; set; }

    public int Stock { get; set; }
    public decimal Precio { get; set; }


    #region IEquatable<Articulo> Members

    public bool Equals(Articulo other)
    {
        if (Object.ReferenceEquals(other, null)) return false;

        if (Object.ReferenceEquals(this, other)) return true;

        return this.Id.Equals(other.Id);
    }


    public override int GetHashCode()
    {
        int hashDescription = this.Id == null ? 0 : this.Id.GetHashCode();

        return hashDescription;
    }

    #endregion

}

con esta otra forma se obtiene el mismo resultado

imagen3

 

Group by


Al usar el group by de linq se presentan los mismo problemas que el distinct, la entidad no puede resolver como debe agrupar cada entidad

imagen4

Pero si lo indicamos por medio del IEquatable<>, en ese caso podra reconocer cual, o cuales, propiedades una entidad es igual a otra

imagen5

El articulo 2 ahora si se agrupa y suma su stock

Código


[C#]