我正在开发一个会发送大量电子邮件的网站。我想设置页眉和页脚文本,甚至是模板,以便用户在需要时轻松编辑这些电子邮件。
如果我将HTML嵌入到C#字符串文字中,那就太丑了,他们不得不担心转义。包括页眉和页脚的平面文件可能会有效,但有些事情感觉不对。
以某种方式将.ASPX
页面用作模板是什么是理想的,然后告诉我的代码提供该页面,并使用为电子邮件返回的HTML。
有一个很好的方法吗?有没有更好的方法来解决这个问题?
更新
我添加了一个答案,使您可以使用标准的.aspx页面作为电子邮件模板。只需像通常那样替换所有变量,使用数据绑定等。然后只需捕获页面的输出,瞧!你有HTML电子邮件!
更新了CAVEAT !!!:
我在一些aspx页面上使用MailDefinition类就好了,但是当在运行的服务器进程中尝试使用这个类时,它失败了。我相信这是因为MailDefinition.CreateMailMessage()方法需要一个有效的控件来引用,即使它并不总是做某事。因此,我推荐使用aspx页面的方法,或使用ascx页面的Mun方法,这看起来好一点。
答案 0 :(得分:71)
这里已经有很多答案,但我偶然发现了一篇关于如何将Razor与电子邮件模板结合使用的精彩文章。使用ASP.NET MVC 3推动了Razor,但MVC不需要使用Razor。这是处理电子邮件模板的非常流畅的处理
正如文章所指出的那样,“Razor的最佳之处在于它与其前身(webforms)不同,它与网络环境无关,我们可以轻松地将其托管在网络之外,并将其用作各种用途的模板引擎。”
Generating HTML emails with RazorEngine - Part 01 - Introduction
Leveraging Razor Templates Outside of ASP.NET: They’re Not Just for HTML Anymore!
Smarter email templates in ASP.NET with RazorEngine
类似Stackoverflow QA
答案 1 :(得分:55)
您可能还想尝试加载控件,然后将其呈现为字符串并将其设置为HTML正文:
// Declare stringbuilder to render control to
StringBuilder sb = new StringBuilder();
// Load the control
UserControl ctrl = (UserControl) LoadControl("~/Controls/UserControl.ascx");
// Do stuff with ctrl here
// Render the control into the stringbuilder
StringWriter sw = new StringWriter(sb);
Html32TextWriter htw = new Html32TextWriter(sw);
ctrl.RenderControl(htw);
// Get full body text
string body = sb.ToString();
然后您可以照常构建您的电子邮件:
MailMessage message = new MailMessage();
message.From = new MailAddress("from@email.com", "from name");
message.Subject = "Email Subject";
message.Body = body;
message.BodyEncoding = Encoding.ASCII;
message.IsBodyHtml = true;
SmtpClient smtp = new SmtpClient("server");
smtp.Send(message);
用户控件可以包含其他控件,例如页眉和页脚,还可以利用数据绑定等功能。
答案 2 :(得分:35)
您可以尝试MailDefinition class
答案 3 :(得分:18)
如果您想传递用户名,产品名称等信息,您可以使用开源模板引擎NVelocity来制作最终的电子邮件/ HTML。
NVelocity模板示例( MailTemplate.vm ):
A sample email template by <b>$name</b>.
<br />
Foreach example :
<br />
#foreach ($item in $itemList)
[Date: $item.Date] Name: $item.Name, Value: $itemValue.Value
<br /><br />
#end
在应用程序中通过MailTemplate.vm生成邮件正文:
VelocityContext context = new VelocityContext();
context.Put("name", "ScarletGarden");
context.Put("itemList", itemList);
StringWriter writer = new StringWriter();
Velocity.MergeTemplate("MailTemplate.vm", context, writer);
string mailBody = writer.GetStringBuilder().ToString();
结果邮件正文是:
示例电子邮件模板 的 ScarletGarden 强>
Foreach示例:
[日期:2009年12月12日]姓名:第1项, 价值:09
[日期:2009年2月21日]姓名:第4项, 价值:52
[日期:01.03.2009]姓名:第2项, 价值:21
[日期:2009年3月23日]姓名:第6项, 价值:24
要编辑模板,您可以使用FCKEditor并将模板保存到文件中。
答案 4 :(得分:7)
Mail.dll email component包含电子邮件模板引擎:
以下是语法概述:
<html>
<body>
Hi {FirstName} {LastName},
Here are your orders:
{foreach Orders}
Order '{Name}' sent to <strong>{Street}</strong>.
{end}
</body>
</html>
加载模板的代码填充来自c#对象的数据并发送电子邮件:
Mail.Html(Template
.FromFile("template.txt")
.DataFrom(_contact)
.Render())
.Text("This is text version of the message.")
.From(new MailBox("alice@mail.com", "Alice"))
.To(new MailBox("bob@mail.com", "Bob"))
.Subject("Your order")
.UsingNewSmtp()
.WithCredentials("alice@mail.com", "password")
.Server("mail.com")
.WithSSL()
.Send();
您可以在email template engine博文上获得更多信息。
或者只需下载Mail.dll email component并尝试一下。
请注意,这是我创建的商业产品。
答案 5 :(得分:6)
如果灵活性是您的先决条件之一,那么XSLT可能是一个不错的选择,.NET框架完全支持它,您甚至可以让用户编辑这些文件。这篇文章(http://www.aspfree.com/c/a/XML/XSL-Transformations-using-ASP-NET/)可能对一开始很有用(msdn有更多关于它的信息)。 正如ScarletGarden所说,NVelocity是另一个不错的选择,但我更喜欢XSLT的“内置”.NET框架支持和平台无关。
答案 6 :(得分:4)
我认为你也可以这样做:
创建和.aspx页面,并将其放在OnLoad方法的末尾,或手动调用。
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
HtmlTextWriter htmlTW = new HtmlTextWriter(sw);
this.Render(htmlTW);
我不确定这是否存在任何潜在问题,但看起来它会起作用。这样,您可以使用功能齐全的.aspx页面,而不是仅支持文本替换的MailDefinition类。
答案 7 :(得分:4)
以下是另一种将XSL转换用于更复杂的电子邮件模板的替代方案:Sending HTML-based email from .NET applications。
答案 8 :(得分:4)
当然你可以创建一个html模板,我也会推荐一个文本模板。在模板中,您可以将[BODY]放在放置正文的位置,然后您只需读取模板并用新内容替换正文。你可以发送 使用.Nets邮件类的电子邮件。在最初创建电子邮件后,您只需循环向所有收件人发送电子邮件。对我来说就像一个魅力。
using System.Net.Mail;
// Email content
string HTMLTemplatePath = @"path";
string TextTemplatePath = @"path";
string HTMLBody = "";
string TextBody = "";
HTMLBody = File.ReadAllText(HTMLTemplatePath);
TextBody = File.ReadAllText(TextTemplatePath);
HTMLBody = HTMLBody.Replace(["[BODY]", content);
TextBody = HTMLBody.Replace(["[BODY]", content);
// Create email code
MailMessage m = new MailMessage();
m.From = new MailAddress("address@gmail.com", "display name");
m.To.Add("address@gmail.com");
m.Subject = "subject";
AlternateView plain = AlternateView.CreateAlternateViewFromString(_EmailBody + text, new System.Net.Mime.ContentType("text/plain"));
AlternateView html = AlternateView.CreateAlternateViewFromString(_EmailBody + body, new System.Net.Mime.ContentType("text/html"));
mail.AlternateViews.Add(plain);
mail.AlternateViews.Add(html);
SmtpClient smtp = new SmtpClient("server");
smtp.Send(m);
答案 9 :(得分:2)
执行此操作时要小心,SPAM过滤器似乎会阻止ASP.net生成的html,显然是因为ViewState,所以如果你打算这样做,请确保生成的Html是干净的。
我个人会考虑使用Asp.net MVC来实现您想要的结果。或者NVelocity非常擅长这个
答案 10 :(得分:1)
我对其中每天必须发送大量电子邮件的项目有类似要求,客户希望完全控制不同类型电子邮件的html模板。
由于要发送大量电子邮件,性能是主要问题。我们想出的是sql server中的静态内容,你可以保存整个html模板标记(以及占位符,如[UserFirstName],[UserLastName],它们在运行时被实际数据替换)用于不同类型的电子邮件
然后我们将这些数据加载到asp.net缓存中 - 所以我们不要一遍又一遍地读取html模板 - 但只有当它们实际被更改时
我们给客户端一个WYSIWYG编辑器,通过管理员Web表单修改这些模板。无论何时进行更新,我们都会重置asp.net缓存。
然后我们有一个单独的电子邮件日志表 - 其中记录了要发送的每封电子邮件。此表包含名为emailType,emailSent和numberOfTries的字段。
我们只是每隔5分钟为一个重要的电子邮件类型(例如新会员注册,忘记密码)运行一个工作,需要尽快发送
我们每15分钟为不太重要的电子邮件类型(如促销电子邮件,新闻电子邮件等)运行另一份工作
通过这种方式,您不会阻止您的服务器发送不间断的电子邮件,而是批量处理邮件。发送电子邮件后,您将emailSent字段设置为1。答案 11 :(得分:1)
我认为简单的答案是MvcMailer。它是NuGet包,可让您使用自己喜欢的视图引擎生成电子邮件。请参阅NuGet包here和project documentation
希望它有所帮助!
答案 12 :(得分:1)
请注意,aspx和ascx解决方案需要当前的HttpContext,因此不能在没有大量工作的情况下异步使用(例如在线程中)。
答案 13 :(得分:1)
DotLiquid是另一种选择。您可以将类模型中的值指定为{{ user.name }}
,然后在运行时提供该类中的数据,以及带有标记的模板,它将为您合并值。它类似于在许多方面使用Razor模板引擎。它支持更复杂的事情,如循环和各种功能,如ToUpper。好处是这些是“安全的”,因此创建模板的用户不会像剃刀一样崩溃您的系统或编写不安全的代码:http://dotliquidmarkup.org/try-online
答案 14 :(得分:1)
以某种方式将.ASPX页面用作模板是什么是理想的,然后告诉我的代码提供该页面,并使用为电子邮件返回的HTML。
您可以轻松地构建WebRequest以访问ASPX页面并获取结果HTML。有了更多的工作,你可以在没有WebRequest的情况下完成它。 PageParser和Response.Filter将允许您运行页面并捕获输出...虽然可能有一些更优雅的方式。
答案 15 :(得分:0)
以下是使用 WebClient 类的简单方法:
public static string GetHTMLBody(string url)
{
string htmlBody;
using (WebClient client = new WebClient ())
{
htmlBody = client.DownloadString(url);
}
return htmlBody;
}
然后就这样称呼它:
string url = "http://www.yourwebsite.com";
message.Body = GetHTMLBody(url);
当然,您的CSS需要内嵌才能在大多数电子邮件客户端(例如Outlook)中显示网页的样式。如果您的电子邮件显示动态内容(例如客户名称),那么我建议您在网站上使用QueryStrings来填充数据。 (例如http://www.yourwebsite.com?CustomerName=Bob)
答案 16 :(得分:0)
我会使用像TemplateMachine这样的模板库。这允许您主要将您的电子邮件模板与普通文本放在一起,然后根据需要使用规则注入/替换值。与Ruby中的ERB非常相似。这使您可以分离邮件内容的生成,而不会过多地与ASPX等相关联。然后,一旦使用此内容生成内容,您就可以发送电子邮件。
答案 17 :(得分:0)
@bardev提供了一个很好的解决方案,但遗憾的是它在所有情况下并不理想。我是其中之一。
我在网站上使用WebForms(我发誓我再也不会在VS 2013中使用网站了 - 这是一个PITA)。
我尝试了Razor的建议,但我的网站却没有得到IDE在MVC项目中提供的最重要的智能感知。我也喜欢将设计师用于我的模板 - 这是UserControl的理想选择。
Nix再次使用Razor。
所以我提出了这个小框架(为UserControl提供了@mun的提示,为强打字提供了@imatoria的提示)。我能看到的唯一潜在的麻烦点是你必须小心保持.ASCX文件名与其类名同步。如果您迷路了,您将收到运行时错误。
FWIW:在我的测试中,至少RenderControl()调用不喜欢Page控件,所以我选择了UserControl。
我很确定我已将所有内容都包括在内;如果我遗漏了一些东西,请告诉我。
HTH
用法:
Partial Class Purchase
Inherits UserControl
Private Sub SendReceipt()
Dim oTemplate As MailTemplates.PurchaseReceipt
oTemplate = MailTemplates.Templates.PurchaseReceipt(Me)
oTemplate.Name = "James Bond"
oTemplate.OrderTotal = 3500000
oTemplate.OrderDescription = "Q-Stuff"
oTemplate.InjectCss("PurchaseReceipt")
Utils.SendMail("{0} <james.bond@mi6.co.uk>".ToFormat(oTemplate.Name), "Purchase Receipt", oTemplate.ToHtml)
End Sub
End Class
基类:
Namespace MailTemplates
Public MustInherit Class BaseTemplate
Inherits UserControl
Public Shared Function GetTemplate(Caller As TemplateControl, Template As Type) As BaseTemplate
Return Caller.LoadControl("~/MailTemplates/{0}.ascx".ToFormat(Template.Name))
End Function
Public Sub InjectCss(FileName As String)
If Me.Styler IsNot Nothing Then
Me.Styler.Controls.Add(New Controls.Styler(FileName))
End If
End Sub
Private ReadOnly Property Styler As PlaceHolder
Get
If _Styler Is Nothing Then
_Styler = Me.FindNestedControl(GetType(PlaceHolder))
End If
Return _Styler
End Get
End Property
Private _Styler As PlaceHolder
End Class
End Namespace
“工厂”类:
Namespace MailTemplates
Public Class Templates
Public Shared ReadOnly Property PurchaseReceipt(Caller As TemplateControl) As PurchaseReceipt
Get
Return BaseTemplate.GetTemplate(Caller, GetType(PurchaseReceipt))
End Get
End Property
End Class
End Namespace
模板类:
Namespace MailTemplates
Public MustInherit Class PurchaseReceipt
Inherits BaseTemplate
Public MustOverride WriteOnly Property Name As String
Public MustOverride WriteOnly Property OrderTotal As Decimal
Public MustOverride WriteOnly Property OrderDescription As String
End Class
End Namespace
ASCX标题:
<%@ Control Language="VB" ClassName="_Header" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--
See https://www.campaignmonitor.com/blog/post/3317/ for discussion of DocType in HTML Email
-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<asp:PlaceHolder ID="plcStyler" runat="server"></asp:PlaceHolder>
</head>
<body>
ASCX页脚:
<%@ Control Language="VB" ClassName="_Footer" %>
</body>
</html>
ASCX模板:
<%@ Control Language="VB" AutoEventWireup="false" CodeFile="PurchaseReceipt.ascx.vb" Inherits="PurchaseReceipt" %>
<%@ Register Src="_Header.ascx" TagName="Header" TagPrefix="uc" %>
<%@ Register Src="_Footer.ascx" TagName="Footer" TagPrefix="uc" %>
<uc:Header ID="ctlHeader" runat="server" />
<p>Name: <asp:Label ID="lblName" runat="server"></asp:Label></p>
<p>Order Total: <asp:Label ID="lblOrderTotal" runat="server"></asp:Label></p>
<p>Order Description: <asp:Label ID="lblOrderDescription" runat="server"></asp:Label></p>
<uc:Footer ID="ctlFooter" runat="server" />
ASCX模板CodeFile:
Partial Class PurchaseReceipt
Inherits MailTemplates.PurchaseReceipt
Public Overrides WriteOnly Property Name As String
Set(Value As String)
lblName.Text = Value
End Set
End Property
Public Overrides WriteOnly Property OrderTotal As Decimal
Set(Value As Boolean)
lblOrderTotal.Text = Value
End Set
End Property
Public Overrides WriteOnly Property OrderDescription As Decimal
Set(Value As Boolean)
lblOrderDescription.Text = Value
End Set
End Property
End Class
助手:
'
' FindNestedControl helpers based on tip by @andleer
' at http://stackoverflow.com/questions/619449/
'
Public Module Helpers
<Extension>
Public Function AllControls(Control As Control) As List(Of Control)
Return Control.Controls.Flatten
End Function
<Extension>
Public Function FindNestedControl(Control As Control, Id As String) As Control
Return Control.Controls.Flatten(Function(C) C.ID = Id).SingleOrDefault
End Function
<Extension>
Public Function FindNestedControl(Control As Control, Type As Type) As Control
Return Control.Controls.Flatten(Function(C) C.GetType = Type).SingleOrDefault
End Function
<Extension>
Public Function Flatten(Controls As ControlCollection) As List(Of Control)
Flatten = New List(Of Control)
Controls.Traverse(Sub(Control) Flatten.Add(Control))
End Function
<Extension>
Public Function Flatten(Controls As ControlCollection, Predicate As Func(Of Control, Boolean)) As List(Of Control)
Flatten = New List(Of Control)
Controls.Traverse(Sub(Control)
If Predicate(Control) Then
Flatten.Add(Control)
End If
End Sub)
End Function
<Extension>
Public Sub Traverse(Controls As ControlCollection, Action As Action(Of Control))
Controls.Cast(Of Control).ToList.ForEach(Sub(Control As Control)
Action(Control)
If Control.HasControls Then
Control.Controls.Traverse(Action)
End If
End Sub)
End Sub
<Extension()>
Public Function ToFormat(Template As String, ParamArray Values As Object()) As String
Return String.Format(Template, Values)
End Function
<Extension()>
Public Function ToHtml(Control As Control) As String
Dim oSb As StringBuilder
oSb = New StringBuilder
Using oSw As New StringWriter(oSb)
Using oTw As New HtmlTextWriter(oSw)
Control.RenderControl(oTw)
Return oSb.ToString
End Using
End Using
End Function
End Module
Namespace Controls
Public Class Styler
Inherits LiteralControl
Public Sub New(FileName As String)
Dim _
sFileName,
sFilePath As String
sFileName = Path.GetFileNameWithoutExtension(FileName)
sFilePath = HttpContext.Current.Server.MapPath("~/Styles/{0}.css".ToFormat(sFileName))
If File.Exists(sFilePath) Then
Me.Text = "{0}<style type=""text/css"">{0}{1}</style>{0}".ToFormat(vbCrLf, File.ReadAllText(sFilePath))
Else
Me.Text = String.Empty
End If
End Sub
End Class
End Namespace
Public Class Utils
Public Shared Sub SendMail(Recipient As MailAddress, Subject As String, HtmlBody As String)
Using oMessage As New MailMessage
oMessage.To.Add(Recipient)
oMessage.IsBodyHtml = True
oMessage.Subject = Subject.Trim
oMessage.Body = HtmlBody.Trim
Using oClient As New SmtpClient
oClient.Send(oMessage)
End Using
End Using
End Sub
End Class
答案 18 :(得分:0)
只需将我正在使用的库扔进去:https://github.com/lukencode/FluentEmail
它使用RazorLight呈现电子邮件,使用流利的样式来构建电子邮件,并支持开箱即用的多个发件人。它也带有ASP.NET DI的扩展方法。使用简单,设置简单,具有纯文本和HTML支持。
答案 19 :(得分:0)
与Canavar的答案类似,但我总是使用“StringTemplate而不是NVelocity” 我从配置文件加载模板,或使用File.ReadAllText()加载外部文件并设置值。
这是一个Java项目,但C#端口是可靠的,我在几个项目中使用它(只是用它来在外部文件中使用模板进行电子邮件模板化)。
替代方案总是好的。
答案 20 :(得分:0)
我喜欢Raj的回答。像ListManager和&amp;像DNN这样的框架做类似的事情,如果需要非技术用户轻松编辑,WYSIWYG编辑器修改存储在SQL中的HTML是一种非常简单,直接的方式,可以轻松地容纳编辑标题,独立于页脚等。使用标记动态插入值。
如果使用上述方法(或任何,真的),请记住一件事是严格和谨慎地考虑允许编辑者插入哪种类型的样式和标签。如果您认为浏览器很挑剔,请等到您看到电子邮件客户端呈现相同内容的方式有多么不同......
答案 21 :(得分:0)
请看SubSonic(www.subsonicproject.com)。他们这样做是为了生成代码 - 模板是标准ASPX,它输出c#。相同的方法可以重复使用。
答案 22 :(得分:0)
设置电子邮件消息IsBodyHtml = true
获取包含电子邮件内容的对象序列化对象 并使用xml / xslt生成html内容。
如果你想做AlternateViews做同样的事情,jmein只使用不同的xslt模板来创建纯文本内容。
这方面的一个主要优点是,如果您想要更改布局,只需要更新xslt模板。
答案 23 :(得分:0)
如果您能够允许ASPNET和相关用户阅读&amp;写一个文件,您可以轻松地使用带有标准String.Format()
占位符({0}
,{1:C}
等)的HTML文件来完成此任务。
仅使用System.IO
命名空间中的类,以字符串形式读取文件。获得该字符串后,将其作为第一个参数传递给String.Format()
,并提供参数。
保留该字符串,并将其用作电子邮件的正文,您基本上已完成。我们今天在数十个(当然是小型)网站上这样做,并且没有任何问题。
我应该注意,如果(a)你一次不发送数以万计的电子邮件,这是最好的,(b)你没有个性化每封电子邮件(否则你吃掉了大量的字符串) (c)HTML文件本身相对较小。