Gerando corpo de e-mail HTML em C #

113

Existe uma maneira melhor de gerar e-mail HTML em C # (para enviar via System.Net.Mail) do que usar um Stringbuilder para fazer o seguinte:

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>");

e assim por diante?

Roubar
fonte
Bem, realmente depende da solução a meu ver. Eu fiz de tudo, desde pegar a entrada do usuário e formatá-la automaticamente a partir de diferentes padrões. A melhor solução que fiz com emails em html foi, na verdade, a formatação xml + xslt, já que sabíamos a entrada do email com antecedência.
cibernético
Isso depende da complexidade dos seus requisitos. Certa vez, tive um aplicativo que renderizou uma tabela em um e-mail em HTML e usei um ASP.NET Gridview para renderizar as strings de concatenação em HTML para gerar uma tabela. Seria complicado.
RichardOD

Respostas:

180

Você pode usar a classe MailDefinition .

É assim que você o 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());

Além disso, escrevi uma postagem no blog sobre como gerar o corpo do e-mail HTML em C # usando modelos usando a classe MailDefinition .

MartinHN
fonte
11
Eu nem sabia que isso existia, pura genialidade ... +1 e Aceito
Rob
5
+1. Bom, embora limitado, provavelmente cobre muitos usos. Não é tão útil se você deseja incluir programaticamente seções de HTML e / ou percorrer um conjunto de itens que precisam ser renderizados.
AnthonyWJones
Tomei conhecimento disso recentemente. É legal. Acho que isso mostra como é importante consultar a documentação do MSDN antes de escrever uma aula para qualquer problema. Eu escrevi minha própria aula, que fez quase o mesmo que MailDefinition. Muito ruim para mim. Perda de tempo.
MartinHN de
4
Não me senti confortável ao usar a classe MailDefinition por causa das opções limitadas para especificar os campos from, to, cc e bcc. Ele também depende de um namespace para controles de IU da Web - isso não faz sentido para mim. Veja minha resposta abaixo ...
Seth
+1 como eu não sabia que esta classe existia, embora a implementação subjacente seja simplesmente iterar sobre a lista de substituições e executar Regex.Replace(body, pattern, replacement, RegexOptions.IgnoreCase);para cada par chave / valor ... à luz desse detalhe, esta classe não fornece muito valor como usado acima a menos que esteja trabalhando com uma referência existente para System.Web.UI.Controls.
Chris Baxter
27

Use a classe 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 extensivo que inclui a criação de atributos de estilo, HtmlTextWriter é provavelmente o melhor caminho a seguir. No entanto, pode ser um pouco complicado de usar e alguns desenvolvedores gostam que a própria marcação seja facilmente lida, mas as escolhas perversas do HtmlTextWriter em relação ao recuo são um pouco estranhas.

Neste exemplo, você também pode usar XmlTextWriter de forma bastante eficaz: -

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
fonte
2
Isso parece super antiquado
Daniel
1
Essa foi a melhor resposta para mim. Simples e direto ao ponto (embora reconhecidamente muito desajeitado). MailDefinition era bacana, mas não era o que eu procurava.
thehelix
Olá, como alguém adicionaria um estilo embutido, por exemplo, à h1tag?
Izzy,
quando o pop-up do e-mail foi aberto, o corpo, iam usando a tag div no meu contexto, ele estava renderizando como <25>. Você pode ajudar na última etapa como fazer uma renderização adequada
esclarecedor
16

Resposta atualizada :

A documentação para SmtpClienta classe usada nesta resposta agora diz 'Obsoleto ("SmtpClient e sua rede de tipos são mal projetados, recomendamos fortemente que você use https://github.com/jstedfast/MailKit e https: // github .com / jstedfast / MimeKit em vez ") '.

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

Resposta Original :

Usar a classe MailDefinition é a abordagem errada. Sim, é prático, mas também é primitivo e depende dos controles da IU da web - isso não faz sentido para algo que normalmente é uma tarefa do lado do servidor.

A abordagem apresentada a seguir é baseada na documentação do MSDN e na postagem de Qureshi em CodeProject.com .

NOTA: Este exemplo extrai o arquivo HTML, imagens e anexos de recursos incorporados, mas usar outras alternativas para obter fluxos para esses elementos é bom, por exemplo, strings codificadas, arquivos locais e assim por diante.

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
fonte
9
Não poste uma resposta que seja essencialmente apenas um link para a postagem do seu blog. Se / quando seu blog desaparecer, sua resposta aqui se tornará praticamente inútil. Considere incorporar as partes "principais" da postagem em sua resposta aqui.
Rob
1
Conteúdo do artigo do blog movido aqui. Obrigado, @Rob.
Seth
1
FWIW, este código foi testado e está sendo usado em um aplicativo de produção.
Seth
1
Isso é um pouco confuso, pois em algumas outras bibliotecas, o HTML é enviado como um anexo. Sugiro remover a parte do anexo em PDF desta amostra para torná-la mais clara. Além disso, o msg.Body nunca é definido neste código de exemplo, suponho que deva ser atribuído a variável body?
Gudlaugur Egilsson
1
Está faltando uma etapa importante, que define msg.IsBodyHtml = true. Veja esta resposta aqui: stackoverflow.com/questions/7873155/…
Gudlaugur Egilsson
6

Eu uso o dotLiquid exatamente para esta tarefa.

Ele pega um modelo e preenche identificadores especiais com o conteúdo de um 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));

Também funciona com coleções, veja alguns exemplos online .

Marcel
fonte
pode templateSource ser um arquivo .html? ou melhor ainda, um arquivo razor .cshtml?
ozzy432836
1
@Ozzy Na verdade, pode ser qualquer arquivo (texto). DotLiquid permite até mesmo mudar a sintaxe do template caso interfira com o seu arquivo de template.
Marcel
5

Eu recomendaria usar modelos de algum tipo. Existem várias maneiras diferentes de abordar isso, mas essencialmente mantenha um modelo de e-mail em algum lugar (no disco, em um banco de dados, etc.) e simplesmente insira os dados principais (IE: nome do destinatário, etc.) no modelo.

Isso é muito mais flexível porque significa que você pode alterar o modelo conforme necessário, sem ter que alterar seu código. Na minha experiência, é provável que você receba solicitações de mudanças nos modelos dos usuários finais. Se você quiser ir até o fim, pode incluir um editor de modelos.

Couro
fonte
Concordo, todas as respostas fazem praticamente a mesma coisa usando métodos diferentes. String.Format é tudo que você precisa e você pode usar qualquer um deles para criar seu próprio modelo.
user1040975
4

Como uma alternativa para MailDefinition, dê uma olhada em RazorEngine https://github.com/Antaris/RazorEngine .

Esta parece ser a melhor solução.

Atribuído a ...

como enviar e-mail com o modelo de e-mail c #

Por exemplo

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);
        }
    }
}

Quais saídas ...

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

Indo aqui

Caro Fred,

A primeira parte do corpo do e-mail vai aqui

Mick
fonte
isto dará mais opções quando houver necessidade de loops e ifs
CMS
3

Emitir html feito à mão como este é provavelmente a melhor maneira, desde que a marcação não seja muito complicada. O construtor de strings só começa a pagar em termos de eficiência depois de cerca de três concatenações, então, para coisas realmente simples, string + string servem.

Além disso, você pode começar a usar os controles html (System.Web.UI.HtmlControls) e renderizá-los, dessa forma, às vezes você pode herdá-los e fazer sua própria classe para um layout condicional complexo.

Mark Dickinson
fonte
1

Você pode querer dar uma olhada em algumas das estruturas de modelo que estão disponíveis no momento. Alguns deles são derivados do MVC, mas isso não é obrigatório. Spark é um bom exemplo.

Simon Farrow
fonte
Apenas uma referência FYI - Url em sua resposta não é mais relevante; leva a um site de notícias.
Geovani Martinez
1

Se você não quiser uma dependência do .NET Framework completo, há também uma biblioteca que faz seu código se parecer com:

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();

É código aberto, você pode baixá-lo em http://sourceforge.net/projects/htmlplusplus/

Isenção de responsabilidade: sou o autor desta biblioteca, ela foi escrita para resolver exatamente o mesmo problema - envie um e-mail em HTML de um aplicativo.

Vladislav Zorov
fonte
1

Uma versão comercial que uso em produção e permite fácil manutenção é o LimiLabs Template Engine , que o utilizo há mais de 3 anos e me permite fazer alterações no modelo de texto sem ter que atualizar o código (isenções de responsabilidade, links etc.) - ele poderia ser tão simples quanto

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

Vale a pena dar uma olhada, como eu; depois de tentar várias respostas mencionadas aqui.

Geovani Martinez
fonte