ASP.NET 通用网页类库 - 第三部分






4.83/5 (18投票s)
一个能够发送其渲染内容的电子邮件的 ASP.NET 页面类。
目录
引言
这是我为 ASP.NET 应用程序开发的一个类库系列文章的第三篇。它包含了一组通用的、可重用的页面类,这些类可以直接在 Web 应用程序中使用,以提供一致的外观、感觉和功能集。还可以从它们派生新类来扩展它们的功能。这些功能都是模块化的,也可以提取并放入您自己的类中。有关该系列文章的完整列表以及演示应用程序和类的代码,请参阅 第一部分 [^]。
对于演示的电子邮件部分,您需要在 Web 服务器上安装 SMTP 服务或访问单独的 SMTP 服务器。错误页面演示使用存储在 Web.config 文件中的电子邮件地址,该地址目前设置为一个占位符地址。您应该修改 appSettings
部分中 ErrorRptEMail
键指定的地址,使其有效。电子邮件页面类还可以使用可选的配置选项来控制 SMTP 服务器的名称 (EMailPage_SmtpServer
)。
BasePage 类的电子邮件功能
本文档描述了 BasePage
类能够发送其渲染内容的电子邮件的功能。本文档将识别并描述此类中存在的附加功能。该类可以按原样使用,也可以将其功能提取并添加到您自己的页面类中。
两种常用方法
一种常用的方法是使用 WebRequest
和 WebResponse
对象来发送页面的渲染内容,如下所示:
// Create a request for the page that you want
WebRequest wreq = System.Net.HttpWebRequest.Create(
"http://www.mysite.com/mypage.html");
// Get the response
WebResponse wrsp = wreq.GetResponse()
// Get the HTML for the page
StreamReader sr = new StreamReader(wrsp.GetResponseStream())
string strHTML = sr.ReadToEnd()
sr.Close()
// Send it by e-mail
MailMessage msg = new MailMessage();
msg.From = "Somebody@Domain.com"
msg.To = "Anyone@Domain.com"
msg.Subject = "HTML page"
msg.Body = strHTML;
msg.BodyFormat = MailFormat.Html;
SmtpMail.Send(msg);
另一种常用方法是重写页面的 Render
事件并使用类似以下代码:
// Create a string builder to contain the HTML
StringBuilder sb = new StringBuilder();
HtmlTextWriter htw =
new HtmlTextWriter(new StringWriter(sb));
// Render the page to it
base.Render(htw);
string strHTML = sb.ToString();
// Send it by e-mail
MailMessage msg = new MailMessage();
msg.From = "Somebody@Domain.com"
msg.To = "Anyone@Domain.com"
msg.Subject = "HTML page"
msg.Body = strHTML;
msg.BodyFormat = MailFormat.Html;
SmtpMail.Send(msg);
// And finally, write the HTML to original writer
writer.Write(strHTML);
虽然这些方法有效,但它们也有一些局限性:
- 代码必须添加到每个需要它的页面中,并且它们不能轻易地扩展或覆盖电子邮件过程的行为。
- 如果正在发送电子邮件的页面通过查询字符串以外的任何方法(例如,通过页面的
Context
对象)接收参数,则可能会遇到困难。 - 消息正文中的任何相对 URL 都很可能在收件人查看电子邮件时显示为损坏的链接、图像或丢失的样式表,除非您采取措施修复它们。
- 视图状态仍然存在于消息正文中,不必要地增加了其大小。
- 消息正文中的脚本块仍然存在,并且可能导致收件人电子邮件客户端软件发出安全警报,除非您删除它们。
BasePage 类的优势
BasePage
类提供了以下优势:
- 电子邮件功能已内置到页面类中,因此您无需编写所有代码来实现它。
- 可以通过
EMailRenderedPage
属性来开启或关闭电子邮件功能。禁用时(默认),页面会正常渲染到浏览器。启用时,页面会尝试在渲染到浏览器的情况下发送一份副本的电子邮件。 - 页面会引发一个事件,让您可以自定义或完全替换呈现给客户端浏览器的 HTML,以及邮件消息参数和作为邮件消息正文发送的 HTML 内容。这使您可以轻松地为邮件消息渲染内容,然后向浏览器显示完全不同的内容,例如发送消息的确认。
- 如果电子邮件发送失败,页面会引发一个事件,从而允许您调整呈现给浏览器的 HTML 以指示问题或采取替代操作。还内置了支持,在进行电子邮件调整(例如更改使用的 SMTP 服务器)后尝试重试发送。
- 邮件消息正文中所有相对 URL 都被转换为绝对 URL,以防止收件人查看时链接损坏。
- 邮件消息正文中的 HTML 将移除视图状态,以减小其大小。
- 脚本块已从邮件消息正文中移除,以免引起收件人电子邮件客户端软件的不必要警告。
- 通过使用自定义注释标记,您可以让页面从邮件消息正文中移除不需要的部分。这种方法可以在派生类中进行扩展,以进一步修改消息正文和呈现给浏览器的 HTML。
工作原理
该类包含一个名为 EMailRenderedPage
的属性,用于控制页面是否会尝试发送电子邮件;一个 IsRenderingForEMail
属性,用于检查页面当前是否正在准备发送电子邮件;一个 EMailThisPage
事件,允许您自定义电子邮件的各个方面;一个 EMailError
事件,允许您处理发生错误且电子邮件未能发送的情况;一个重写的 Render
方法;以及一个 RenderForEMail
方法,实现所有这些功能。
EMailRenderedPage 属性
此属性默认设置为 false
,以便页面以正常方式渲染到浏览器。您可以在构造函数、Page_Load
事件或派生页面的事件处理程序中将其设置为 true
,以尝试发送渲染内容的电子邮件。
Render 方法
重写的 Render
方法控制电子邮件过程。很可能您永远不会重写此方法。相反,您可以通过添加 EMailThisPage
和 EMailError
事件的处理程序来控制电子邮件详细信息和渲染内容。请注意,BasePage
类版本的此方法还会调用 ConvertValMsgsToLinks
方法,该方法将验证消息转换为链接。这在 第四部分 中有描述。会检查 IsRenderingForEmail
属性以查看我们是否已在为电子邮件进行渲染。如果是,它只是正常渲染页面。如果不是,它会检查 EMailRenderedPage
属性,并在必要时调用 RenderForEMail
方法来处理所有工作。
protected override void Render(HtmlTextWriter writer)
{
// If already rendering for e-mail, just render the page
if(!this.IsRenderingForEMail)
{
this.ConvertValMsgsToLinks();
// Rendering normally or going to e-mail?
if(this.EMailRenderedPage)
{
this.RenderForEMail(writer);
return;
}
}
base.Render(writer);
}
RenderForEMail 方法
此方法负责渲染页面、发送电子邮件和处理可能发生的任何错误的所有详细信息。请注意,下面显示的示例代码来自 .NET 1.1 版本。 .NET 2.0 版本几乎相同,只是在发送电子邮件方面有一些小的改动。在 .NET 2.0 中,它使用了新的 System.Net.Mail
类来发送电子邮件。
protected void RenderForEMail(HtmlTextWriter writer)
{
StringBuilder sb = new StringBuilder();
HtmlTextWriter hw = new HtmlTextWriter(
new StringWriter(sb, CultureInfo.InvariantCulture));
try
{
isRenderingForEMail = true;
this.Render(hw);
}
finally
{
isRenderingForEMail = false;
}
MailMessage msg = new MailMessage();
// The string builder contains all of the HTML
msg.BodyFormat = MailFormat.Html;
msg.Body = sb.ToString();
EMailPageEventArgs args =
new EMailPageEventArgs(msg, this);
// Get the SMTP server from the Web.config file
// if specified
args.SmtpServer = ConfigurationSettings.AppSettings[
"EMailPage_SmtpServer"];
// Give the derived page a chance to modify or cancel
// the e-mail.
this.OnEMailThisPage(args);
首先,页面被渲染到一个 StringBuilder
。这是通过将 IsRenderingForEMail
属性设置为 true
并再次调用 Render
方法,使用我们本地创建的 HTML 文本编写器来实现的。接下来,创建一个新的 MailMessage
对象,将格式设置为 HTML,并将渲染的内容设置为消息正文。然后创建一个 EMailPageEventArgs
对象,并将其作为 EmailThisPage
事件的参数传递。稍后将对此进行介绍。
if(args.Cancel == false)
{
// Try sending until it succeeds or told to stop
do
{
try
{
// Turn off retry so we don't get stuck here.
// The error event handler must turn it on if
// a retry is wanted.
args.RetryOnFailure = false;
// It has to come from somebody
if(msg.From == null || msg.From.Length == 0)
throw new ArgumentNullException("From",
"A sender must be specified for " +
"the e-mail message");
// It has to go to somebody
if(msg.To == null || msg.To.Length == 0)
throw new ArgumentNullException("To",
"A recipient must be specified for " +
"the e-mail message");
// Set the server?
if(args.SmtpServer != null &&
args.SmtpServer.Length != 0)
SmtpMail.SmtpServer = args.SmtpServer;
SmtpMail.Send(msg);
}
catch(Exception excp)
{
// Raise an EMailError event if it fails so that
// the derived page can take any action it wants
// including a retry.
EMailErrorEventArgs err = new EMailErrorEventArgs(
args, excp);
this.OnEMailError(err);
}
} while(args.RetryOnFailure == true);
}
// And finally, write the HTML to the original writer.
// It's rendered from the event args in case the handler
// modified it.
writer.Write(args.RenderedContent);
EMailThisPage
事件可以被取消。无论事件是否被取消,它都会将内容渲染到浏览器,但它会从事件参数类的 RenderedContent
属性而不是 StringBuilder
进行渲染。这允许派生类中的事件处理程序修改发送到浏览器的 HTML。例如,您可能希望在页面内容上方插入一条消息,说明电子邮件已发送或未发送的原因。
如果发送电子邮件,该方法会检查事件参数中是否由事件处理程序指定了 SMTP 服务器。如果指定了,它将在发送电子邮件时使用此服务器名称。这在您的 IIS 服务器未设置为 SMTP 邮件服务器的情况下很有用。它还会检查是否指定了发件人和收件人的电子邮件地址。如果没有,电子邮件将无法发送,并会抛出一个说明该事实的异常。
如果消息成功发送,它会将内容渲染到浏览器并如上所述退出。如果发送电子邮件时发生错误,该方法会引发 EMailError
事件,让派生页面有机会处理错误并采取替代操作。稍后将对此进行介绍。错误事件处理程序还允许您修改渲染的内容以添加有关错误原因的额外信息,或进行更改并尝试重新发送消息。
电子邮件事件处理程序
EMailThisPage 事件
每当页面需要发送其内容的电子邮件时,都会引发此事件。您应该始终为该事件添加一个处理程序,以便您的页面至少可以指定电子邮件的发件人和收件人。没有它们,电子邮件将无法发送。该事件传递一个自定义事件参数类,称为 EMailPageEventArgs
,它允许处理程序修改电子邮件内容、渲染内容、设置 SMTP 服务器或完全取消事件。最简单的形式,处理程序将如下所示。演示中包含一些更复杂的示例。
// This event fires when the page is ready to be e-mailed
void Page_EMailThisPage(Object sender,
EWSoftware.Web.EMailPageEventArgs args)
{
// Set the SMTP server if running locally for testing
if(Page.Request.ServerVariables["HTTP_HOST"] == "localhost")
args.SmtpServer = "REALSMTPSERVER"
// Set sender, recipient, and subject
args.EMail.From = "Somebody@Domain.com"
args.EMail.To = "Anyone@Domain.com"
args.EMail.Subject = this.PageTitle
}
下面将描述事件参数类的属性和方法。
public bool Cancel
此布尔属性可以设置为
true
以取消事件并阻止发送内容。如果取消,RenderedContent
属性中的 HTML 仍然会发送到客户端浏览器。在取消电子邮件时,您可以根据需要用备用响应替换它(例如,重定向用户到另一个位置等)。public string SmtpServer
此属性允许您设置发送电子邮件时应使用的 SMTP 服务器。通常只有在您在 **localhost** 上运行应用程序或您的 IIS 服务器未配置 SMTP 服务时才需要设置此属性。
public MailMessage EMail
此属性为您提供了对电子邮件消息对象的访问权限。使用它来设置发件人、收件人和主题,以及修改消息正文。返回的
MailMessage
的Body
属性将包含将要发送到电子邮件中的 HTML。您可以根据需要进行修改。public string RenderedContent
此属性允许您修改电子邮件发送后将呈现给客户端的页面内容。使用它来更改呈现给客户端浏览器的内容。例如,您可能希望删除与显示内容无关的部分,插入一条告诉用户消息已发送以及发送给谁的消息,或者用全新的内容替换它。
public bool RetryOnFailure
此属性允许您指定页面在失败时是否应重试发送电子邮件。默认情况下设置为
false
。在每次发送尝试之前,它也被设置为false
,以防止无限循环,以防您忘记将其关闭。在错误事件处理程序中将其设置为true
,以便页面在您更改消息属性或 SMTP 服务器属性后重试发送电子邮件。public int RetryCount
此属性可用于跟踪尝试发送电子邮件失败的次数。您可以在错误事件处理程序中递增它,并使用其值来确定何时停止尝试。
构造函数
您不会自己创建此类实例。相反,页面的 RenderForEMail
方法会为您完成此操作,并填充必要的参数。当由页面创建时,实例将执行以下步骤来修改电子邮件消息正文和渲染的内容:
- 电子邮件正文经过处理,以移除
<!-- NOEMAIL -->
注释标记之间的任何 HTML。这可以用于自动标记不应包含在电子邮件中但应呈现给浏览器的页面部分。演示中包含此示例。 - 由于它没有用途,因此从电子邮件正文中移除了视图状态标记,以减小其大小。
- 可以安全地假设电子邮件中的脚本是不受欢迎的,并且大多数(如果不是全部)电子邮件客户端都会阻止脚本运行以防止病毒。因此,将所有脚本标记块从电子邮件正文中移除。
- 尝试将相对 URL 转换为绝对 URL,应用于所有出现的
src
和href
属性,以便页面中的链接、图像和样式表在收件人在电子邮件中查看时能正常工作。我不保证 100% 的覆盖率,但它应该能捕获并转换绝大多数。
EMailError 事件
处理此事件是可选的。如果您想修改渲染的页面,在电子邮件发送失败时采取替代措施,或者进行更改并尝试重试发送电子邮件,可以为此事件添加一个处理程序。该事件传递一个自定义事件参数类,称为 EMailErrorEventArgs
,它允许处理程序检查异常的原因。它继承自 EventArgs
并包含两个属性。
第一个是 EMailEventArguments
,它是传递给 EMailThisPage
事件的事件参数对象的副本。可以检查或修改上述任何属性。要重试发送电子邮件,请将 RetryOnFailure
属性设置为 true
。您还可以递增 RetryCount
属性以跟踪已尝试重发消息的次数。
此类中的第二个属性是 EMailException
,它返回一个 Exception
对象,其中包含有关遇到的问题的详细信息。演示中包含了一些使用示例。
连接事件处理程序
.NET 1.1 版本包含两个事件的委托。 .NET 2.0 版本的类使用了通用的事件处理程序类型。因此,在 .NET 2.0 版本中,事件的挂接语法略有不同。
// .NET 1.1
this.EMailThisPage += new EMailThisPageEventHandler(
this.Page_EMailThisPage);
this.EMailError += new EMailErrorEventHandler(
this.Page_EMailError);
// .NET 2.0
this.EMailThisPage += new EventHandler<EMailPageEventArgs>(
this.Page_EMailThisPage);
this.EMailError += new EventHandler<EMailErrorEventArgs>(
this.Page_EMailError);
结论
将渲染页面的内容发送电子邮件在许多情况下都很有用。演示应用程序包含一些常见的用法示例,例如自定义错误页面、用户反馈、生成并发送报告的电子邮件等。通过使用此类,您可以轻松地将此功能添加到应用程序中的任何 Web Form。希望您会发现此类以及库中的其他类(或其部分)像我一样有用。
修订历史
- 04/02/2006
破坏性更改
EMailPage
类已被移除。电子邮件功能已合并到BasePage
类中。这是为了将渲染代码移动到其自己的派生类(请参阅本系列的第一部分)。- 属性和方法名称已修改,以符合 .NET 在大小写方面的命名约定(
EMailPageEventArgs.SmtpServer
)。 - 在 .NET 2.0 版本中,已移除
EMailThisPageEventHandler
和EMailErrorEventHandler
委托。要为此两个事件添加事件处理程序,请改用新的 .NET 2.0 EventHandler<> 通用类型。
- 11/26/2004
此版本中的更改
- 根据 shtwang 的建议,我在
EMailPage
类中添加了代码,以从 Web.config 中的应用程序键EMailPage_SmtpServer
中检索 SMTP 服务器名称,这样您就不必在事件处理程序中手动设置它,除非您想覆盖它。如果未定义,它将保持设置为 null,除非像以前那样在事件处理程序中更改。 - 根据 Lynn Evans 的建议,我重新设计了
EMailPage
类,以在初始发送失败时支持重试操作。这可以通过EMailPageEventArgs
类上的新RetryOnFailure
和RetryCount
属性来控制。注意:这会引入对
EMailErrorEventArgs
类的重大更改。它现在继承自EventArgs
,并包含一个EMailEventArguments
属性,允许您访问和修改电子邮件事件参数(例如,递增重试计数并指定在返回时重试等)。
- 根据 shtwang 的建议,我在
- 12/01/2003
- 初始发布。