我可以用 ASP.NET 设置 HTML/Email 模板吗?

我在一个网站上工作,将发出大量的电子邮件。我想设置页眉和页脚文本,或者甚至模板,让用户可以轻松地编辑这些电子邮件,如果他们需要的话。

如果我将 HTML 嵌入到 C # 字符串文字中,它会很丑陋,而且他们还要担心转义。包含页眉和页脚的平面文件可能有用,但是有些地方感觉不太对。

什么将是理想的什么是使用一个 .ASPX页面作为模板,然后只是告诉我的代码服务该页面,并使用 HTML 返回的电子邮件。

有没有简单易行的方法?有没有更好的方法来解决这个问题?

更新:
我添加了一个答案,使您能够使用一个标准。Aspx 页面作为电子邮件模板。像平常一样替换所有的变量,使用数据绑定等等。然后捕获页面的输出,瞧!你有你的 HTML 电子邮件!

更新警告:
我曾经在一些 aspx 页面上使用 MailDefinition 类,但是当在运行的服务器进程中尝试使用这个类时,它失败了。我相信是因为邮件定义。CreateMailMessage ()方法需要引用一个有效的控件,即使它并不总是执行某些操作。因此,我推荐使用 aspx 页面的方法,或者使用似乎更好一点的 ascx 页面的 Mun’s 方法。

144528 次浏览

Sure you can create an html template and I would recommend also a text template. In the template you can just put [BODY] in the place where the body would be placed and then you can just read in the template and replace the body with the new content. You can send the email using .Nets Mail Class. You just have to loop through the sending of the email to all recipients after you create the email initially. Worked like a charm for me.

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

If you are able to allow the ASPNET and associated users permission to read & write a file, you can easily use an HTML file with standard String.Format() placeholders ({0}, {1:C}, etc.) to accomplish this.

Merely read in the file, as a string, using classes from the System.IO namespace. Once you have that string, pass it as the first argument to String.Format(), and provide the parameters.

Keep that string around, and use it as the body of the e-mail, and you're essentially done. We do this on dozens of (admittedly small) sites today, and have had no issues.

I should note that this works best if (a) you're not sending zillions of e-mails at a time, (b) you're not personalizing each e-mail (otherwise you eat up a ton of strings) and (c) the HTML file itself is relatively small.

You could try the MailDefinition class

If you want to pass parameters like user names, product names, ... etc. you can use open source template engine NVelocity to produce your final email / HTML's.

An example of NVelocity template (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

Generating mail body by MailTemplate.vm in your application :

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

The result mail body is :

A sample email template by ScarletGarden.

Foreach example :

[Date: 12.02.2009] Name: Item 1, Value: 09

[Date: 21.02.2009] Name: Item 4, Value: 52

[Date: 01.03.2009] Name: Item 2, Value: 21

[Date: 23.03.2009] Name: Item 6, Value: 24

For editing the templates, maybe you can use FCKEditor and save your templates to files.

What would be ideal what be to use a .ASPX page as a template somehow, then just tell my code to serve that page, and use the HTML returned for the email.

You could easily just construct a WebRequest to hit an ASPX page and get the resultant HTML. With a little more work, you can probably get it done without the WebRequest. A PageParser and a Response.Filter would allow you to run the page and capture the output...though there may be some more elegant ways.

You might also want to try loading a control, and then rendering it to a string and setting that as the HTML Body:

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

You could then construct your email as usual:

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

You user control could contain other controls, such as a header and footer, and also take advantage of functionality such as data binding.

Set the set the Email Message IsBodyHtml = true

Take your object that contains your email contents Serialize the object and use xml/xslt to generate the html content.

If you want to do AlternateViews do the same thing that jmein only use a different xslt template to create the plain text content.

one of the major advantages to this is if you want to change your layout all you have to do update the xslt template.

I think you could also do something like this:

Create and .aspx page, and put this at the end of the OnLoad method, or call it manually.

    StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
HtmlTextWriter htmlTW = new HtmlTextWriter(sw);
this.Render(htmlTW);

I'm not sure if there are any potential issues with this, but it looks like it would work. This way, you could use a full featured .aspx page, instead of the MailDefinition class which only supports Text replacements.

If flexibility is one of your prerequisites, XSLT might be a good choice, which is completely supported by .NET framework and you would be able to even let the user edit those files. This article (http://www.aspfree.com/c/a/XML/XSL-Transformations-using-ASP-NET/) might be useful for a start (msdn has more info about it). As said by ScarletGarden NVelocity is another good choice but I do prefer XSLT for its " built-in" .NET framework support and platform agnostic.

i had a similar requirement on 1 of the projects where you had to send huge number of emails each day, and the client wanted complete control over html templates for different types of emails.

due to the large number of emails to be sent, performance was a primary concern.

what we came up with was static content in sql server where you save entire html template mark up (along with place holders, like [UserFirstName], [UserLastName] which are replaced with real data at run time) for different types of emails

then we loaded this data in asp.net cache - so we dont read the html templates over and over again - but only when they are actually changed

we gave the client a WYSIWYG editor to modify these templates via a admin web form. whenever updates were made, we reset asp.net cache.

and then we had a seperate table for email logs - where every email to be sent was logged. this table had fields called emailType, emailSent and numberOfTries.

we simply ran a job every 5 minutes for important email types (like new member sign up, forgot password) which need to be sent asap

we ran another job every 15 minutes for less important email types (like promotion email, news email, etc)

this way you dont block your server sending non stop emails and you process mails in batch. once an email is sent you set the emailSent field to 1.

Be careful when doing this, SPAM filters seem to block ASP.net generated html, apparently because of ViewState, so if you are going to do this make sure the Html produced is clean.

I personally would look into using Asp.net MVC to achieve your desired results. or NVelocity is quite good at this

Look at SubSonic (www.subsonicproject.com). They're doing exactly this to generate code - the template is standard ASPX, and it outputs c#. The same method would be reusable for your scenario.

I'd use a templating library like TemplateMachine. this allows you mostly put your email template together with normal text and then use rules to inject/replace values as necessary. Very similar to ERB in Ruby. This allows you to separate the generation of the mail content without tying you too heavily to something like ASPX etc. then once the content is generated with this, you can email away.

I like Raj's answer. Programs like ListManager & frameworks like DNN do similar things, and if easy editing by non-technical users is required, WYSIWYG editors to modify HTML stored in SQL is a mostly easy, straightforward way to go and can easily accommodate editing headers independently from footers, etc, as well as using tokens to dynamically insert values.

One thing to keep in mind if using the above method (or any, really) is to be strict and careful about which types of styling and tags you allow the editors to insert. If you think browsers are finicky, just wait until you see how differently email clients render the same thing...

Here is one more alternative that uses XSL transformations for more complex email templates: Sending HTML-based email from .NET applications.

Note that the aspx and ascx solutions require a current HttpContext, so cannot be used asynchronously (eg in threads) without a lot of work.

Similar to Canavar's answer, but instead of NVelocity, I always use "StringTemplate" which I load the template from a configuration file, or load an external file using File.ReadAllText() and set the values.

It's a Java project but the C# port is solid and I've used it in several projects (just used it for email templating using the template in an external file).

Alternatives are always good.

Mail.dll email component includes email template engine:

Here's the syntax overview:

<html>
<body>
Hi {FirstName} {LastName},


Here are your orders:
{foreach Orders}
Order '{Name}' sent to <strong>{Street}</strong>.
{end}


</body>
</html>

And the code that loads the template, fills data from c# object and sends an email:

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

You can get more info on email template engine blog post.

Or just download Mail.dll email component and give it a try.

Please note that this is a commercial product I've created.

There's a ton of answers already here, but I stumbled upon a great article about how to use Razor with email templating. Razor was pushed with ASP.NET MVC 3, but MVC is not required to use Razor. This is pretty slick processing of doing email templates

As the article identifies, "The best thing of Razor is that unlike its predecessor(webforms) it is not tied with the web environment, we can easily host it outside the web and use it as template engine for various purpose. "

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

Similar Stackoverflow QA

Templating using new RazorEngine API

Using Razor without MVC

Is it possible to use Razor View Engine outside asp.net

I think the easy answer is MvcMailer. It s NuGet package that lets you use your favorite view engine to generate emails. See the NuGet package here and the project documentation

Hope it helps!

Here is a simple way using the WebClient class:

public static string GetHTMLBody(string url)
{
string htmlBody;


using (WebClient client = new WebClient ())
{
htmlBody = client.DownloadString(url);
}


return htmlBody;
}

Then just call it like this:

string url = "http://www.yourwebsite.com";
message.Body = GetHTMLBody(url);

Of course, your CSS will need to be in-lined in order to show the styles of the webpage in the most email clients (such as Outlook). If your e-mail displays dynamic content (ex. Customer Name), then I would recommend using QueryStrings on your website to populate the data. (ex. http://www.yourwebsite.com?CustomerName=Bob)

DotLiquid is another option. You specify values from a class model as \{\{ user.name }} and then at runtime you provide the data in that class, and the template with the markup, and it will merge the values in for you. It is similar to using the Razor templating engine in many ways. It supports more complex things like loops and various function like ToUpper. The nice thing is these are "safe" so that user's who create the templates can't crash your system or write unsafe code like you would in razor: http://dotliquidmarkup.org/try-online

@bardev provides a good solution, but unfortunately it's not ideal in all cases. Mine was one of them.

I'm using WebForms in a Website (I swear I'll never use a Website again--what a PITA) in VS 2013.

I tried the Razor suggestion, but mine being a Website I didn't get the all-important IntelliSense that the IDE delivers in an MVC project. I also like to use the designer for my templates--a perfect spot for a UserControl.

Nix on Razor again.

So I came up with this little framework instead (hat tips to @mun for UserControl and @imatoria for Strong Typing). Just about the only potential trouble spot I can see is that you have to be careful to keep your .ASCX filename in sync with its class name. If you stray, you'll get a runtime error.

FWIW: In my testing at least the RenderControl() call doesn't like a Page control, so I went with UserControl.

I'm pretty sure I've included everything here; let me know if I left something out.

HTH

Usage:

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

Base 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

"Factory" Class:

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

Template Class:

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 Header:

<%@ 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 Footer:

<%@ Control Language="VB" ClassName="_Footer" %>


</body>
</html>

ASCX Template:

<%@ 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 Template 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

Helpers:

'
' 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

Just throwing the library I'm using into the mix: https://github.com/lukencode/FluentEmail

It renders emails using RazorLight, uses the fluent style to build emails, and supports multiple senders out of the box. It comes with extension methods for ASP.NET DI too. Simple to use, little setup, with plain text and HTML support.