Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
domingo, 30 de noviembre de 2008
Es habitual que las aplicaciones que desarrollamos necesiten enviar emails: alertas, notificaciones automáticas, formularios de contacto, o envíos masivos de información, entre otros, son ejemplos de utilización muy habituales.

Y en estos casos la inclusión de imágenes incrustadas suele ser un requisito fundamental cuando se trata de enviar contenidos con formato HTML, de forma que, aunque normalmente se incrementa de forma notable el tamaño del paquete a enviar, se evita que los clientes tengan que descargar estos recursos adicionales desde sus equipos, cosa que además suele estar bloqueada por defecto.

.NET framework nos ofrece varias vías para hacerlo usando las clases provistas en System.Net.Mail, pero vamos a utilizar una que nos ofrece dos ventajas importantes. La primera es que las imágenes enviadas no se muestran como adjuntos (evitando, por ejemplo, el curioso efecto presente en Outlook Express, que repite las imágenes a continuación del texto del mensaje) y la segunda es que permite especificar distintas vistas dentro desde el mismo mensaje, para que el cliente de correo utilice la que sea más apropiada.

El siguiente código muestra una forma de montar un mensaje con dos vistas: la primera será utilizada por aquellos agentes de usuario (clientes de correo) que únicamente pueden mostrar texto plano, mientras que en la segunda utilizará HTML para maquetar el contenido, incluyendo una imagen que aparecerá totalmente integrada en el cuerpo del mensaje, sin mostrarse como elemento adjunto del mismo.

Podréis observar que aunque en el ejemplo muestro un código muy rígido, es fácilmente generalizable para poder utilizarlo en cualquier escenario. Como en otras ocasiones, está en C#, mi lenguaje favorito, pero sería fácilmente portable a VB.NET, por ejemplo.

// Necesitaremos estos namespaces...
using System.Net.Mail;
using System.Net.Mime;
...

// Montamos la estructura básica del mensaje...
MailMessage mail = new MailMessage();
mail.From = new MailAddress("origen@miservidor.com");
mail.To.Add("destinatario@miservidor.com");
mail.Subject = "Mensaje con imagen";

// Creamos la vista para clientes que
// sólo pueden acceder a texto plano...


string text = "Hola, ayer estuve disfrutando de "+
"un paisaje estupendo.";

AlternateView plainView =
AlternateView.CreateAlternateViewFromString(text,
Encoding.UTF8,
MediaTypeNames.Text.Plain);


// Ahora creamos la vista para clientes que
// pueden mostrar contenido HTML...


string html = "<h2>Hola, mira dónde estuve ayer:</h2>" +
"<img src='cid:imagen' />";

AlternateView htmlView =
AlternateView.CreateAlternateViewFromString(html,
Encoding.UTF8,
MediaTypeNames.Text.Html);

// Creamos el recurso a incrustar. Observad
// que el ID que le asignamos (arbitrario) está
// referenciado desde el código HTML como origen
// de la imagen (resaltado en amarillo)...


LinkedResource img =
new LinkedResource(@"C:\paisaje.jpg",
MediaTypeNames.Image.Jpeg);
img.ContentId = "imagen";

// Lo incrustamos en la vista HTML...

htmlView.LinkedResources.Add(img);

// Por último, vinculamos ambas vistas al mensaje...

mail.AlternateViews.Add(plainView);
mail.AlternateViews.Add(htmlView);

// Y lo enviamos a través del servidor SMTP...

SmtpClient smtp = new SmtpClient("smtp.miservidor.com");
smtp.Send(mail);
 

La siguiente imagen muestra una captura de pantalla del mismo mensaje leído desde un cliente con capacidad HTML como Outlook Express y uno que no la tiene, en este caso basado en web:

El mismo mensaje visto desde dos agentes de usuario distintos. Pulsa para ampliar.


Publicado en: www.variablenotfound.com.

22 Comentarios:

Anónimo dijo...

Estupenmdo y utilisimo post, mi enhorabuena

josé M. Aguilar dijo...

Me alegro de que pueda serte útil, Julio!

Ah, y gracias por comentar. :-)

Ricardo Velásquez dijo...

Excelente código, justo lo que andaba buscado :) Gracias men...!!!

Rodrigo dijo...

Excelente, ahora tengo un problema, he subido mi imagen en mi ftp y no se que poner entre los parentesis del linkedresource.

josé M. Aguilar dijo...

@RodrigoCba, LinkedResource necesita tener acceso al archivo que deseas adjuntar al correo electrónico.

La forma más sencilla sería tener la ruta física al archivo, tal y como se describe en el ejemplo. Si el recurso está en un servidor diferente, como en el caso que comentas, deberás traértelo a local para realizar esta tarea. Puedes hacerlo, por ejemplo, descargándolo con un HttpWebRequest o de la forma que veas más sencilla.

Saludos y gracias por comentar.

Unknown dijo...

Hola tengo un programita para enviar correos con mvc 3 el problema es que no puedo enviar adjuntos por que la propiedad fichero.SaveAs(Path.Combine(@"C:\Temp", Path.GetFileName(fichero.FileName)));
no guarda una copia del fichero a enviar donde la deberia y me marca System.NullReferenceException: Referencia a objeto no establecida como instancia de un objeto.
no se como resolverlo y me urge, ojala y me puedas ayudar, Saludos

josé M. Aguilar dijo...

Hola, @Zahid

Si te aparece un NullReferenceException es porque en esa expresión tienes un valor nulo ;-)

El candidato ideal para contener ese nulo es la variable "fichero", que probablemente estés intentando recoger desde MVC, pero estarás recibiéndola vacía.

Esto puede deberse, básicamente, a:

- el "name" del "input type=file" que tienes en la vista no es "fichero", y debería serlo para que puedas recibir su valor desde la acción MVC.
- el formulario no tiene el enctype establecido a "multipart/form-data". Esto es necesario para que el archivo se suba al servidor.

Saludos.

Unknown dijo...

Hola Jose:

Ya reolvi ese error, efectivamente no estaba pasando ningun parametro, reestructure mi codigo y volvi a editar la funcionlidad del ActionResult, el problema ahora es que el programa ni siquiera accede a la instruccion, coloque un brakpoint donde esta la linea de acceso al servidor y nada, no entra el bp, solo envia el correo sin el adjunto.
¿Que puedo hacer?

Unknown dijo...

Excelente post,oye no sabes de alguna forma para que el mensaje no se vaya directamente como SPAM? eso me sería de gran ayuda saludos

josé M. Aguilar dijo...

Hola, José!

Desde el código me temo que poco podemos hacer para que no nos "etiqueten" como spam. Si fuera posible todo el mundo lo haría y entonces los detectores de spam no servirían para nada ;-)

Un saludo!

biko dijo...

Hola, Esta muy bien el post. Pero ahora nos enfrentamos a nuevos retos. Necesitamos traernos la imagen de una pagina web... solo que marca un error de: No se admiten los formatos de URI.

En la parte que marca es esta:

LinkedResource logo = new LinkedResource(("http://fsc-test.ax-ap.com/erpweb" + @logoImg.Replace("~", string.Empty)));
---------
@logoImg - esta parte bien de la base de datos

Saludos y espero puedas ayudarnos

ppkapo dijo...

hola, alguna idea como conectar con usando ssl?

josé M. Aguilar dijo...

Hola!

Bueno, la clase SmtpClient permite indicar que se debe conectar usando SSL:

http://msdn.microsoft.com/es-es/library/system.net.mail.smtpclient.enablessl.aspx

Saludos.

ppkapo dijo...

Hola, gracias por responder, si, lo he visto a eso pero por algun motivo cuando hace el smtp.send(mail) devuelve un error que dice "comando no reconocido" o algo así (supongo que la autenticación en este paso ya se ha realizado exitosamente). Alguna idea que puede ser?. Te ha pasado alguna vez?. Yo quiero utilizarlo para mandar mails con una cuenta de gmail.

josé M. Aguilar dijo...

Hola!

Pues la verdad es que no... de todas formas, en una búsqueda rápida por google he visto que da algunos problemas, y algunos desarrolladores que dicen haberlos solucionado.

Lee este hilo, por ejemplo, a ver si tienes suerte: http://stackoverflow.com/questions/9801224/smtpclient-with-gmail

Saludos

Anónimo dijo...

Mi estimado este aporte ha sido super bueno..

y me ayudo bastante.. :)
sigue adelante amigo :) yo te apoyo :)

por otro lado..
sabes como enviar el correo por RTF?

mi correo jdak67@hotmail.com

PauRpo dijo...

Mil gracias por este post! estuve buscando solucion a mi problema mucho tiempo y esto me funciono de maravillas.

Como aportacion, en caso de que no tengan el url de la imagen, sino que la tengan en byte[] pueden usar esto:

Stream s = new MemoryStream(bytearray);

Luis dijo...

man quiero hacer esto porque tengo problemas para enviar mi reporte rdl de reporting services como suscripción programada de entre por correo Outlook ya que si tiene mucho cuerpo mime no envía el correo archivo rdl mas de 658 kb, puedes ayudarme si el código es el mismo

Unknown dijo...

Hola buen día, como hacerle para que la imagen sea cargada de manera dinamica, es decir que las imagenes sean por ejemplo las que se seleccionen de un input file desde asp c# .net

José María Aguilar dijo...

Hola!

Es igual, la única diferencia es que debes subir la imagen al servidor antes de incrustarla y o bien la almacenas en disco y la incrustas como se muestra en el post, o bien usas directamente el Stream para hacerlo.

Saludos!

Unknown dijo...

Gracias, disculpa mi falta de conocimiento, podrias enviarme un ejemplo, es que no necesito subir nada al servidor seria como dises guardarlo en memoria, pero no tengo claro la idea

Anónimo dijo...

Amigo te agradezco, en todo lo que investigué, ningún método me funcionó como lo necesitaba así como este. literalmente me sirvió al cambiar los datos y algunas cosas del smtp para que no arrojara error en las credenciales. Si tal vez alguien necesita la info, lo que hice fue pasar el código que está en c# a Visual Basic en esta página https://www.elguille.info/net/webservices/decsavb_web.aspx , (copiar el código sin los comentarios), y configurar el smtp. Muchas gracias.