Generando cuerpo de correo electrónico HTML en C #

114

¿Existe una mejor manera de generar correo electrónico HTML en C # (para enviar a través de System.Net.Mail), que usar un Stringbuilder para hacer lo siguiente:

string userName = "John Doe";
StringBuilder mailBody = new StringBuilder();
mailBody.AppendFormat("<h1>Heading Here</h1>");
mailBody.AppendFormat("Dear {0}," userName);
mailBody.AppendFormat("<br />");
mailBody.AppendFormat("<p>First part of the email body goes here</p>");

¿y así sucesivamente y así sucesivamente?

Robar
fuente
Bueno, realmente depende de la solución a mi modo de ver. He hecho de todo, desde capturar la entrada del usuario y formatearla automáticamente desde diferentes patrones. La mejor solución que he hecho con los correos html fue en realidad el formato xml + xslt, ya que conocíamos la entrada del correo por adelantado.
Cyberzed
Eso depende de la complejidad de sus requisitos. Una vez tuve una aplicación que mostraba una tabla en un correo electrónico HTML y utilicé ASP.NET Gridview para representar las cadenas de HTML; la concatenación de cadenas para generar una tabla habría sido desordenada.
RichardOD

Respuestas:

181

Puede utilizar la clase MailDefinition .

Así es como se usa:

MailDefinition md = new MailDefinition();
md.From = "[email protected]";
md.IsBodyHtml = true;
md.Subject = "Test of MailDefinition";

ListDictionary replacements = new ListDictionary();
replacements.Add("{name}", "Martin");
replacements.Add("{country}", "Denmark");

string body = "<div>Hello {name} You're from {country}.</div>";

MailMessage msg = md.CreateMailMessage("[email protected]", replacements, body, new System.Web.UI.Control());

Además, escribí una publicación de blog sobre cómo generar el cuerpo de un correo electrónico HTML en C # usando plantillas usando la clase MailDefinition .

MartinHN
fuente
11
Ni siquiera sabía que esto existía, es pura genialidad ... +1 y aceptado
Rob
5
+1. Agradable, aunque limitado, probablemente cubra muchos usos. No es tan útil si desea incluir secciones de HTML mediante programación y / o recorrer un conjunto de elementos que necesitan renderizarse.
AnthonyWJones
Me di cuenta de ello recientemente. Es genial. Supongo que le dice lo importante que es mirar la documentación de MSDN antes de escribir una clase para cualquier problema. Había escrito mi propia clase, que hacía casi lo mismo que MailDefinition. Demasiado malo para mí. Pérdida de tiempo.
MartinHN
4
No me sentía cómodo usando la clase MailDefinition debido a las opciones limitadas para especificar los campos desde, hasta, cc y bcc. También se basa en un espacio de nombres para los controles de la interfaz de usuario web, eso no tiene sentido para mí. Vea mi respuesta a continuación ...
Seth
+1 ya que no sabía que existía esta clase, aunque la implementación subyacente simplemente está iterando sobre la lista de reemplazos y ejecutándose Regex.Replace(body, pattern, replacement, RegexOptions.IgnoreCase);para cada par clave / valor ... a la luz de ese detalle, esta clase no proporciona mucho valor como se usó anteriormente a menos que trabaje con una referencia existente a System.Web.UI.Controls.
Chris Baxter
27

Utilice la clase System.Web.UI.HtmlTextWriter.

StringWriter writer = new StringWriter();
HtmlTextWriter html = new HtmlTextWriter(writer);

html.RenderBeginTag(HtmlTextWriterTag.H1);
html.WriteEncodedText("Heading Here");
html.RenderEndTag();
html.WriteEncodedText(String.Format("Dear {0}", userName));
html.WriteBreak();
html.RenderBeginTag(HtmlTextWriterTag.P);
html.WriteEncodedText("First part of the email body goes here");
html.RenderEndTag();
html.Flush();

string htmlString = writer.ToString();

Para HTML extenso que incluye la creación de atributos de estilo, HtmlTextWriter es probablemente la mejor manera de hacerlo. Sin embargo, puede ser un poco complicado de usar y a algunos desarrolladores les gusta que el marcado en sí sea fácil de leer, pero las opciones perversamente de HtmlTextWriter con respecto a la sangría son un poco extrañas.

En este ejemplo, también puede usar XmlTextWriter con bastante eficacia: -

writer = new StringWriter();
XmlTextWriter xml = new XmlTextWriter(writer);
xml.Formatting = Formatting.Indented;
xml.WriteElementString("h1", "Heading Here");
xml.WriteString(String.Format("Dear {0}", userName));
xml.WriteStartElement("br");
xml.WriteEndElement();
xml.WriteElementString("p", "First part of the email body goes here");
xml.Flush();
AnthonyWJones
fuente
2
Esto parece muy anticuado
Daniel
1
Esta fue la mejor respuesta para mí. Simple y al grano (aunque es cierto que bastante torpe). MailDefinition era ingenioso, pero no era lo que estaba buscando.
thehelix
Hola, ¿cómo se agregaría un estilo en línea a, por ejemplo, la h1etiqueta?
Izzy
cuando el correo electrónico POP UP fue abierto, el cuerpo, iam usando la etiqueta div en mi contexto, se rindiendo como <25> .can u por favor ayuda con último paso cómo hacer una correcta prestación
clarificador
17

Respuesta actualizada :

La documentación de SmtpClientla clase utilizada en esta respuesta ahora dice: 'Obsolete ("SmtpClient y su red de tipos están mal diseñados, le recomendamos encarecidamente que use https://github.com/jstedfast/MailKit y https: // github .com / jstedfast / MimeKit en lugar de ") '.

Fuente: https://www.infoq.com/news/2017/04/MailKit-MimeKit-Official

Respuesta original :

Usar la clase MailDefinition es el enfoque incorrecto. Sí, es útil, pero también es primitivo y depende de los controles de la interfaz de usuario web; eso no tiene sentido para algo que suele ser una tarea del lado del servidor.

El enfoque que se presenta a continuación se basa en la documentación de MSDN y la publicación de Qureshi en CodeProject.com .

NOTA: Este ejemplo extrae el archivo HTML, las imágenes y los archivos adjuntos de los recursos incrustados, pero está bien usar otras alternativas para obtener flujos de estos elementos, por ejemplo, cadenas codificadas, archivos locales, etc.

Stream htmlStream = null;
Stream imageStream = null;
Stream fileStream = null;
try
{
    // Create the message.
    var from = new MailAddress(FROM_EMAIL, FROM_NAME);
    var to = new MailAddress(TO_EMAIL, TO_NAME);
    var msg = new MailMessage(from, to);
    msg.Subject = SUBJECT;
    msg.SubjectEncoding = Encoding.UTF8;
 
    // Get the HTML from an embedded resource.
    var assembly = Assembly.GetExecutingAssembly();
    htmlStream = assembly.GetManifestResourceStream(HTML_RESOURCE_PATH);
 
    // Perform replacements on the HTML file (if you're using it as a template).
    var reader = new StreamReader(htmlStream);
    var body = reader
        .ReadToEnd()
        .Replace("%TEMPLATE_TOKEN1%", TOKEN1_VALUE)
        .Replace("%TEMPLATE_TOKEN2%", TOKEN2_VALUE); // and so on...
 
    // Create an alternate view and add it to the email.
    var altView = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
    msg.AlternateViews.Add(altView);
 
    // Get the image from an embedded resource. The <img> tag in the HTML is:
    //     <img src="pid:IMAGE.PNG">
    imageStream = assembly.GetManifestResourceStream(IMAGE_RESOURCE_PATH);
    var linkedImage = new LinkedResource(imageStream, "image/png");
    linkedImage.ContentId = "IMAGE.PNG";
    altView.LinkedResources.Add(linkedImage);
 
    // Get the attachment from an embedded resource.
    fileStream = assembly.GetManifestResourceStream(FILE_RESOURCE_PATH);
    var file = new Attachment(fileStream, MediaTypeNames.Application.Pdf);
    file.Name = "FILE.PDF";
    msg.Attachments.Add(file);
 
    // Send the email
    var client = new SmtpClient(...);
    client.Credentials = new NetworkCredential(...);
    client.Send(msg);
}
finally
{
    if (fileStream != null) fileStream.Dispose();
    if (imageStream != null) imageStream.Dispose();
    if (htmlStream != null) htmlStream.Dispose();
}
Seth
fuente
9
No publique una respuesta que sea esencialmente un enlace a la publicación de su blog. Si / cuando su blog desaparece, su respuesta aquí se vuelve prácticamente inútil. Considere incorporar las partes "clave" de la publicación en su respuesta aquí.
Rob
1
Se movió el contenido del artículo del blog aquí. Gracias, @Rob.
Seth
1
FWIW este código ha sido probado y se está utilizando en una aplicación de producción.
Seth
1
Esto es un poco confuso, ya que en algunas otras bibliotecas, HTML se envía como un archivo adjunto. Sugiero eliminar la parte del archivo adjunto PDF de esta muestra para que quede más claro. Además, el mensaje msg.Body nunca se establece en este código de muestra, supongo que debería asignarse la variable body
Gudlaugur Egilsson
1
Falta un paso clave, que configura msg.IsBodyHtml = true. Vea esta respuesta aquí: stackoverflow.com/questions/7873155/…
Gudlaugur Egilsson
6

Utilizo dotLiquid exactamente para esta tarea.

Toma una plantilla y llena identificadores especiales con el contenido de un objeto anónimo.

//define template
String templateSource = "<h1>{{Heading}}</h1>Dear {{UserName}},<br/><p>First part of the email body goes here");
Template bodyTemplate = Template.Parse(templateSource); // Parses and compiles the template source

//Create DTO for the renderer
var bodyDto = new {
    Heading = "Heading Here",
    UserName = userName
};
String bodyText = bodyTemplate.Render(Hash.FromAnonymousObject(bodyDto));

También funciona con colecciones, vea algunos ejemplos en línea .

Marcel
fuente
¿Puede templateSource ser un archivo .html? o mejor aún, un archivo .cshtml razor?
ozzy432836
1
@Ozzy En realidad, puede ser cualquier archivo (de texto). DotLiquid incluso permite cambiar la sintaxis de la plantilla en caso de que interfiera con su archivo de plantilla.
Marcel
5

Recomendaría usar plantillas de algún tipo. Hay varias formas diferentes de abordar esto, pero esencialmente mantenga una plantilla del correo electrónico en algún lugar (en el disco, en una base de datos, etc.) y simplemente inserte los datos clave (IE: nombre del destinatario, etc.) en la plantilla.

Esto es mucho más flexible porque significa que puede modificar la plantilla según sea necesario sin tener que modificar su código. En mi experiencia, es probable que reciba solicitudes de cambios en las plantillas de los usuarios finales. Si quieres hacer todo lo posible, puedes incluir un editor de plantillas.

Cuero
fuente
De acuerdo, todas las respuestas hacen prácticamente lo mismo utilizando diferentes métodos. String.Format es todo lo que necesita y puede usar cualquiera de estos para crear su propia plantilla.
user1040975
4

Como alternativa a MailDefinition, eche un vistazo a RazorEngine https://github.com/Antaris/RazorEngine .

Esta parece una mejor solución.

Atribuido a ...

cómo enviar correo electrónico con la plantilla de correo electrónico c #

P.ej

using RazorEngine;
using RazorEngine.Templating;
using System;

namespace RazorEngineTest
{
    class Program
    {
        static void Main(string[] args)
        {
    string template =
    @"<h1>Heading Here</h1>
Dear @Model.UserName,
<br />
<p>First part of the email body goes here</p>";

    const string templateKey = "tpl";

    // Better to compile once
    Engine.Razor.AddTemplate(templateKey, template);
    Engine.Razor.Compile(templateKey);

    // Run is quicker than compile and run
    string output = Engine.Razor.Run(
        templateKey, 
        model: new
        {
            UserName = "Fred"
        });

    Console.WriteLine(output);
        }
    }
}

Qué salidas ...

<h1>Heading Here</h1>
Dear Fred,
<br />
<p>First part of the email body goes here</p>

Rumbo aquí

Querido Fred,

La primera parte del cuerpo del correo electrónico va aquí.

Mick
fuente
esto dará la mayor opción cuando se necesiten bucles y ifs
CMS
3

Emitir html hecho a mano como este es probablemente la mejor manera, siempre que el marcado no sea demasiado complicado. El constructor de cadenas solo comienza a pagarle en términos de eficiencia después de aproximadamente tres concatenaciones, por lo que para cosas realmente simples, cadena + cadena funcionará.

Aparte de eso, puede comenzar a usar los controles html (System.Web.UI.HtmlControls) y renderizarlos, de esa manera a veces puede heredarlos y crear sus propias clases para un diseño condicional complejo.

Mark Dickinson
fuente
1

Es posible que desee echar un vistazo a algunos de los marcos de plantilla que están disponibles en este momento. Algunos de ellos son derivados como resultado de MVC, pero eso no es obligatorio. Spark es uno bueno.

Simon Farrow
fuente
Para su información, solo una referencia de URL en su respuesta ya no es relevante; conduce a un sitio web de noticias.
Geovani Martinez
1

Si no desea una dependencia en .NET Framework completo, también hay una biblioteca que hace que su código se vea así:

string userName = "John Doe";

var mailBody = new HTML {
    new H(1) {
        "Heading Here"
    },
    new P {
        string.Format("Dear {0},", userName),
        new Br()
    },
    new P {
        "First part of the email body goes here"
    }
};

string htmlString = mailBody.Render();

Es de código abierto, puede descargarlo desde http://sourceforge.net/projects/htmlplusplus/

Descargo de responsabilidad: soy el autor de esta biblioteca, fue escrito para resolver exactamente el mismo problema: envíe un correo electrónico HTML desde una aplicación.

Vladislav Zorov
fuente
1

Una versión comercial que utilizo en producción y que permite un fácil mantenimiento es LimiLabs Template Engine , lo he estado usando durante más de 3 años y me permite realizar cambios en la plantilla de texto sin tener que actualizar el código (descargos de responsabilidad, enlaces, etc.). podría ser tan simple como

Contact templateData = ...; 
string html = Template
     .FromFile("template.txt")
     .DataFrom(templateData )
     .Render();

Vale la pena echarle un vistazo, como hice yo; después de intentar varias respuestas mencionadas aquí.

Geovani Martínez
fuente