在C#中生成HTML电子邮件正文


114

是否有比使用Stringbuilder进行以下操作更好的方法来生成C#HTML电子邮件(用于通过System.Net.Mail发送):

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

等等等等?


好吧,这确实取决于我所看到的解决方案。我已经完成了所有工作,从获取用户输入并从不同模式自动对其进行格式化。我对html邮件所做的最佳解决方案实际上是xml + xslt格式,因为我们预先知道了邮件的输入。
2009年

这取决于您的要求有多复杂。我曾经有一个在HTML电子邮件中呈现表格的应用程序,而我使用ASP.NET Gridview呈现HTML并置字符串以生成表格将是一团糟。
RichardOD

Answers:


181

您可以使用MailDefinition类

这是您的用法:

MailDefinition md = new MailDefinition();
md.From = "test@domain.com";
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("you@anywhere.com", replacements, body, new System.Web.UI.Control());

另外,我已经写了一篇博客文章,介绍如何使用MailDefinition类使用模板在C#中生成HTML电子邮件正文


11
我什至不知道它的存在,这真是个天才……+1并被接受
罗布

5
+1。尼斯虽然有限,但可能涵盖许多用途。如果您要以编程方式包括HTML的各个部分和/或遍历需要渲染的一组项目,则没有那么有用。
AnthonyWJones

我最近才意识到这一点。很酷。我想它告诉您在编写任何问题的类之前阅读MSDN文档有多么重要。我编写了自己的类,与MailDefinition几乎一样。对我来说太糟糕了。浪费时间。
MartinHN

4
使用MailDefinition类使我感到不舒服,因为用于指定from,to,cc和bcc字段的选项有限。它还依赖于Web UI控件的名称空间-对我而言这没有意义。请参阅下面的答案...
赛斯

+1,因为我不知道该类的存在,尽管底层实现只是在替换列表上迭代并Regex.Replace(body, pattern, replacement, RegexOptions.IgnoreCase);为每个键/值对运行……鉴于这一细节,该类没有提供如上所述的大量值除非使用对的现有引用System.Web.UI.Controls
克里斯·巴克斯特

27

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

对于包含样式属性创建的广泛HTML,HtmlTextWriter可能是最好的方法。但是,使用起来可能有点笨拙,一些开发人员喜欢标记本身很容易阅读,但是HtmlTextWriter在缩进方面的选择有些麻烦。

在此示例中,您还可以非常有效地使用XmlTextWriter:-

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

2
这似乎超过时
丹尼尔

1
这是对我最好的答案。简单明了(尽管很笨拙)。MailDefinition很漂亮,但不是我想要的。
–thehelix

嗨,如何将内联样式添加到h1标签中?
Izzy

电子邮件弹出打开时,身体用的IAM在我的上下文div标签它被渲染为<25>。可以ü请用最后一步的帮助如何做一个适当的渲染
澄清

17

更新的答案

SmtpClient现在,此答案中使用的类的文档显示为'Obsolete(“ SmtpClient及其类型网络设计不良,我们强烈建议您使用https://github.com/jstedfast/MailKithttps:// github .com / jstedfast / MimeKit代替”)”。

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

原始答案

使用MailDefinition类是错误的方法。是的,它很方便,但是它也是原始的,并且依赖于Web UI控件-对于通常是服务器端任务的东西没有意义。

下面介绍的方法基于MSDN文档和Qureshi在CodeProject.com上的帖子

注意:此示例从嵌入式资源中提取HTML文件,图像和附件,但是使用其他替代方法来获取这些元素的流很好,例如,硬编码的字符串,本地文件等。

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

9
请不要发布实质上只是您博客文章链接的答案。如果/当您的博客消失时,您在这里的答案几乎毫无用处。考虑在此处将帖子的“关键”部分纳入您的答案。
罗布

1
已将博客文章内容移到此处。谢谢@Rob。
赛斯

1
FWIW此代码已经过测试,已在生产应用程序中使用。
赛斯

1
这有点令人困惑,因为在其他一些库中,HTML是作为附件发送的。我建议从此示例中删除PDF附件部分,以使其更加清晰。此外,在此示例代码中从未设置msg.Body,我认为应该为其指定body变量?
Gudlaugur Egilsson 2014年

1
缺少一个关键步骤,该步骤设置msg.IsBodyHtml = true。在这里看到这样的回答:stackoverflow.com/questions/7873155/...
Gudlaugur Egilsson

6

我使用dotLiquid来完成此任务。

它使用一个模板,并用匿名对象的内容填充特殊的标识符。

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

它也可以与集合一起使用,请参见一些在线示例


templateSource可以是.html文件吗?还是更好的.cshtml剃须刀文件?
ozzy432836

1
@Ozzy它实际上可以是任何(文本)文件。DotLiquid甚至允许更改模板语法,以防干扰模板文件。
Marcel

5

我建议使用某种模板。有多种方法可以解决此问题,但本质上是将电子邮件模板保存在某个位置(在磁盘上,在数据库中等),然后只需将密钥数据(IE:收件人名称等)插入模板即可。

这要灵活得多,因为它意味着您可以根据需要更改模板而不必更改代码。以我的经验,您可能会收到最终用户对模板进行更改的请求。如果您想全力以赴,则可以包括模板编辑器。


同意,所有答案几乎都使用不同的方法做同样的事情。您只需要String.Format,就可以使用其中的任何一个创建自己的模板。
user1040975

4

作为MailDefinition的替代方法,请查看RazorEngine https://github.com/Antaris/RazorEngine

这看起来是一个更好的解决方案。

归因于...

如何使用电子邮件模板C#发送电子邮件

例如

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

哪个输出...

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

前往这里

亲爱的弗雷德:

电子邮件正文的第一部分在这里


当需要循环和ifs时,这将提供最多的选择
CMS

3

只要标记不是太复杂,发出这样的手工html可能是最好的方法。在大约三个串联之后,stringbuilder才开始在效率方面给您回报,因此对于真正简单的东西,string + string可以。

除此之外,您可以开始使用html控件(System.Web.UI.HtmlControls)并呈现它们,这样,您有时就可以继承它们并为复杂的条件布局创建自己的样式。


1

您可能想看看目前可用的一些模板框架。其中一些是MVC的衍生产品,但这不是必需的。Spark是一个很好的人。


仅供参考-您答案中的网址参考不再相关;导致新闻网站。
Geovani Martinez

1

如果您不想依赖整个.NET Framework,那么还有一个库可以使您的代码看起来像:

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

它是开源的,您可以从http://sourceforge.net/projects/htmlplusplus/下载

免责声明:我是该库的作者,它的编写完全是为了解决同一问题-从应用程序发送HTML电子邮件。


1

我在生产中使用的并且易于维护的商业版本是LimiLabs Template Engine,使用了3年以上,使我可以更改文本模板而无需更新代码(免责声明,链接等)。-可能像

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

像我一样值得一看;在尝试了这里提到的各种答案之后。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.