Crear un email con imágenes embebidas

Cuando creamos un email desde .NET solemos intentar que el formato del mismo sea bastante aceptable… O deberíamos :P

En ocasiones, es necesario incluir imágenes en el mismo y aquí es donde comienzan los problemas: Si referenciamos una imagen que está en un servidor concreto puede que ese servidor deje de existir, o eliminen la imagen, cambien la ruta, etcétera.

Por ello, creo que lo mejor es embeber estas imágenes dentro del correo y hacer referencia a las mismas. ¡Vamos a verlo!


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mail;
using System.Net;
using System.IO;

namespace EmailAndImages
{
    public class Email
    {
        public static void EnviarMail(MyEmail email)
        {

            MailMessage mailMessage = new MailMessage();

            mailMessage.From = new MailAddress(email.sender);

            foreach (string addressee in email.addressees)
                mailMessage.To.Add(addressee);

            mailMessage.Subject = email.subject;

            AlternateView alternateView = AlternateView.CreateAlternateViewFromString("<img id='header' alt='header' src='cid:idImage'/><p>Hello <strong>world</strong></p>", null, "text/html");

            LinkedResource linkedResource;

            using (WebClient webClient = new WebClient())
            {
                webClient.UseDefaultCredentials = true;
                byte[] buf = webClient.DownloadData(email.imgURI);
                MemoryStream memoryStream = new MemoryStream(buf);
                linkedResource = new LinkedResource(memoryStream);
            }

            linkedResource.ContentId = "idImage";

            alternateView.LinkedResources.Add(linkedResource);
            mailMessage.AlternateViews.Add(alternateView);

            SmtpClient smtpClient = new SmtpClient("10.10.10.50");

            smtpClient.Send(mailMessage);
        }
    }
}

Un dato muy importante a tener en cuenta es que LinkedResource no soporta URIs, por lo que tuve que hacer uso de WebClient para recuperar la imagen en un MemoryStream y posteriormente adjuntarla.

Espero que sea de utilidad.

 ¡Saludos!

ASP.NET Chart Controls y ASP.NET MVC 2

Hace ya más de un año apareció una nueva librería para la creación de gráficos, tanto para Web Forms como Win Forms, con una enorme cantidad de posibilidades. Hoy en día se nos puede plantear la posibilidad de utilizar estos mismos controles para una aplicación ASP.NET MVC y cómo sería la forma más sencilla de adaptarlo.
Si bien he leído y probado varias opciones, voy a escribir sobre la que me ha parecido más sencilla y más práctica.

Instalación de Librerías y componentes

Para poder trabajar con estos controles, es necesario descargar dos ejecutables:

La librería que nos permite la creación de un gráfico se llama System.Web.DataVisualization, la cual está disponible instalando el primero de los paquetes. El segundo de ellos es interesante instalarlo para poder visualizar en el cuadro de herramientas el control de tipo Chart. Existen numerosas combinaciones y propiedades configurables para este tipo de controles, por lo que hay veces que es más práctico crear una aplicación Web Forms de prueba, en modo diseño arrastrar el control y personalizarlo. De esta forma podemos ver el código generado en el aspx y las propiedades utilizadas para ese chart en concreto. Otra de las posibilidades es descargar el proyecto de ejemplos y localizar alguno que ya esté definido y adaptarlo.

CREACIÓN DEL GRÁFICO

Para entender mejor el código, podríamos decir que un chart se compone de 4 partes principalmente: Chart, que es el control principal, Legend, encargado de la leyenda del gráfico, ChartArea, el cual representa el área donde se mostrarán los valores y las Series que son los valores en sí.

El primer paso es agregar una referencia a la librería System.Web.DataVisualization. Una vez añadida, creamos una nueva acción donde se instanciarán los controles necesarios y, finalmente, nos devolverá la imagen generada según las propiedades establecidas.

public ActionResult GetChart()
{
    const string legend = "Legend";
    const string chartArea = "chartArea";

    var chart = ChartFactory.CreateChart("ASP.NET MVC and MSChart by GiS!");

    ChartFactory.CreateSerie(chart, "januarySeries", chartArea,
                            legend, "Emboss", Color.ForestGreen,
                            ChartValueType.DateTime, ChartValueType.Double,
                            _repository.GetMonthData(1));

    ChartFactory.CreateSerie(chart, "februarySeries", chartArea,
                            legend, "Cylinder", Color.DodgerBlue,
                            ChartValueType.DateTime, ChartValueType.Double,
                            _repository.GetMonthData(1));

    ChartFactory.CreateSerie(chart, "marchSeries", chartArea,
                            legend, "Wedge", Color.DarkViolet,
                            ChartValueType.DateTime, ChartValueType.Double,
                            _repository.GetMonthData(3));

    var memoryStream = new MemoryStream();
    chart.SaveImage(memoryStream);

    return File(memoryStream.GetBuffer(), @"image/png");
}

Para intentar refactorizar parte del código necesario para generar el gráfico, he creado una clase llamada ChartFactory en la cual podemos formar tanto el chart, la leyenda, el chartArea y las series que se mostrarán.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Web.UI.DataVisualization.Charting;
using ChartControlASPNETMVC.Models;

namespace ChartControlASPNETMVC
{
    public static class ChartFactory
    {
        public static Chart CreateChart(string chartTitle)
        {
            //Chart
            var chart = new Chart
                            {
                                Height = 296,
                                Width = 412,
                                BorderWidth = 2,
                                ImageType = ChartImageType.Png,
                                BackColor = ColorTranslator.FromHtml("#F3DFC1"),
                                BorderlineDashStyle = ChartDashStyle.Solid,
                                BackGradientStyle = GradientStyle.LeftRight,
                                BorderColor = Color.FromArgb(181, 64, 1),
                                BorderSkin = { SkinStyle = BorderSkinStyle.Sunken },
                            };

            //Title
            var title = chart.Titles.Add("chartTitle");
            title.ShadowColor = Color.FromArgb(32, 0, 0, 0);
            title.ShadowOffset = 3;
            title.ForeColor = Color.FromArgb(26, 59, 105);
            title.Font = new Font("Trebuchet MS", 14, FontStyle.Bold);
            title.Text = chartTitle;
            title.Alignment = ContentAlignment.TopLeft;

            //Legend
            var legend = chart.Legends.Add("chartLegend");
            legend.LegendStyle = LegendStyle.Row;
            legend.Font = new Font("Trebuchet MS", 10, FontStyle.Bold);
            legend.Enabled = true;
            legend.Name = "Legend";
            legend.BackColor = Color.Transparent;
            legend.Position.Y = 95;
            legend.Position.X = 5;
            legend.Position.Height = 20;
            legend.Position.Width = 100;

            //Chart Area
            var chartArea = chart.ChartAreas.Add("chartArea");
            chartArea.BorderColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.BackSecondaryColor = Color.White;
            chartArea.BackColor = Color.OldLace;
            chartArea.ShadowColor = Color.Transparent;

            //Chart Area 3D Style
            chartArea.Area3DStyle.Enable3D = true;
            chartArea.Area3DStyle.Rotation = -20;
            chartArea.Area3DStyle.Perspective = 8;
            chartArea.Area3DStyle.Inclination = 18;
            chartArea.Area3DStyle.IsRightAngleAxes = false;
            chartArea.Area3DStyle.WallWidth = 0;
            chartArea.Area3DStyle.IsClustered = false;
            chartArea.Area3DStyle.PointDepth = 50;
            chartArea.Area3DStyle.PointGapDepth = 200;

            chartArea.AxisY.LineColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.AxisY.IsLabelAutoFit = false;
            chartArea.AxisY.LabelStyle.Font = new Font("Trebuchet MS", 8, FontStyle.Bold);
            chartArea.AxisY.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);

            chartArea.AxisX.LineColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.AxisX.IsLabelAutoFit = false;
            chartArea.AxisX.LabelStyle.Font = new Font("Trebuchet MS", 8, FontStyle.Bold);
            chartArea.AxisX.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);

            return chart;
        }
        public static void CreateSerie(Chart chart, string serieName, string chartAreaName, string legendName, string         drawingStyle, Color color, ChartValueType xValue, ChartValueType yValue, List<TableSample> monthData)
        {
            var newSerie = chart.Series.Add(serieName);
            newSerie.ChartArea = chartAreaName;
            newSerie.XValueType = xValue;
            newSerie.YValueType = yValue;
            newSerie.Name = serieName;
            newSerie.ShadowColor = Color.Transparent;
            newSerie.BorderColor = color;
            newSerie.Color = color;
            newSerie.Legend = legendName;

            /**** Drawing Style ****/
            /*Cylinder - Data points are drawn as cylinders.
            Emboss - Data points are drawn with an embossed effect.
            LightToDark - Data points are drawn with a light-to-dark effect.
            Wedge - Data points are drawn with a wedge effect.
            Default  - Data points are drawn as cubes.*/

            newSerie["DrawingStyle"] = drawingStyle;
            newSerie["PointWidth"] = "0.8";

            WriteSeries(newSerie, monthData);
        }

        private static void WriteSeries(Series serie, IEnumerable<TableSample> list)
        {
            foreach (TableSample t in list)
            {
                serie.Points.Add(new DataPoint
                                     {
                                         YValues = new Double[] { t.Amount },
                                         LegendText = t.Date.Day.ToString(),
                                         AxisLabel = t.Date.Day.ToString()
                                     });
            }
        }
    }
}

Este código es más que candidato a ser refactorizado y hacer una librería que nos permita mayor flexibilidad, pero el objetivo de este post es mostrar que existe la posibilidad de utilizar este tipo de controles.

La función de este control, a fin de cuentas, es generar una imagen con las propiedades y los datos obtenidos. Para llamar a la acción GetChart desde nuestra vista únicamente debemos asignar la misma al source de una imagen.


<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
        <%= Html.Encode(ViewData["Message"]) %></h2>
    <div id="container">
        <img src="<%=Url.Action("GetChart") %>" alt="Chart Sample" />
    </div>
</asp:Content>

 Si arrancamos la aplicación, el resultado sería el siguiente:

Y, lo mejor de todo, el renderizado de la página:


!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd</a>">
html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
head><title>

   Home Page

</title><link href="Content/Site.css" rel="stylesheet" type="text/css" />

</head>
<body>
   <div>
       <div id="header">
           <div id="title">
               <h1>
                   My MVC Application</h1>
           </div>
           <div id="logindisplay">

       [ <a href="/Account/LogOn">Log On</a> ]

           </div>
           <div id="menucontainer">
               <ul id="menu">
                   <li>
                       <a href="/">Home</a></li>
                   <li>
                       <a href="/Home/About">About</a></li>
               </ul>
           </div>
       </div>
       <div id="main">

   <h2>
       Chart Control Sample!</h2>
   <div id="container">
       <img src="/Home/GetChart" alt="Chart Sample" />
   </div>

           <div id="footer">
           </div>
       </div>
   </div>
</body>
</html>

Por último, debido a que esta librería es adicional al framework, sería recomendable modificar las propiedades de la misma para tener una copia en el paquete final.

Adjunto el proyecto por si fuera de utilidad.

¡Saludos!

Subir una aplicación ASP.NET MVC a IIS 5.1

Si todavía tenemos que trabajar con Windows XP y estamos utilizando proyectos en ASP.NET MVC es posible que queramos publicar los mismos en el IIS local para realizar las pruebas necesarias. En este caso, vamos a aprovecharnos de un bug de la versión 5.1 de Internet Information Server para hacer funcionar nuestro proyecto:

  1. Accedemos a las propiedades del directorio virtual donde tengamos alojada la aplicación.
  2. Nos aseguramos de que tenemos permisos para ejecutar sólo secuencias de comandos.

  3. Pulsamos sobre el botón Configuración… y, en la pestaña Asignaciones, pulsamos en Agregar.

  4. Rellenamos la ventana con los siguientes datos:
    • En el campo Ejecutable debemos seleccionar la DLL aspnet_isapi.dll, ubicada en la siguiente ruta: C:WINDOWSMicrosoft.NETFrameworkv2.0.50727aspnet_isapi.dll.
    • En el campo Extensión escribimos .*
    • Por último, eliminamos el check en Comprobar si el archivo existe.
  5. Al hacer estos pasos nos daremos cuenta de que el botón de Aceptar aparece deshabilitado debido a que la extensión proporcionada no es válida.
  6. Para poder aceptar los cambios, hacemos click sobre el campo Ejecutable y … ¡magia! :P

Espero que sea de utilidad.

Fuente

¡Saludos!