65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C#、.NET 2.0 和 Microsoft® SQL Server 2005 Service Broker 实现即用型大规模邮件发送功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (36投票s)

2006 年 9 月 8 日

13分钟阅读

viewsIcon

308239

downloadIcon

4621

本文演示了一个可扩展的大规模邮件发送框架(Smart Mass Email SME)。演示实现使用了当今最前沿的 .NET 技术,例如 C#、.NET 2.0、Microsoft® SQL Server 2005 Service Broker、MS Provider Pattern、Enterprise Library 2006 年 1 月版等。

摘要

本文演示了一个可扩展的大规模邮件发送框架(Smart Mass Email SME)。演示实现使用了当今最前沿的 .NET 技术,例如 C#、.NET 2.0、Microsoft® SQL Server 2005 Service Broker、MS Provider Pattern、Enterprise Library 2006 年 1 月版等。

引言

我想以一种不同的方式来写这篇文章,就像在 MSN Messenger / Windows Live Messenger 中的一次对话会话。

Alex: 你好。我正在写一篇关于 .NET 2.0 中 Smart Mass Emailing (SME) 功能的新文章。

Bob: 听起来很有趣。能多告诉我一些吗?

A: 大规模邮件发送是许多网站或应用程序的常见功能。我为此 SME 功能编写了一个可扩展且可插入的框架。文章附带的演示应用程序提供了将 SME 作为 Windows 服务和网站运行的示例。第二个功能在您想在 Windows 服务不可用的共享 Web 环境中运行 SME 时非常有用。SME 将使用本文中提出的相同技术,“使用 ASP.NET 模拟 Windows 服务来运行计划任务”。

演示应用程序展示了适用于 Microsoft® SQL Server 2000 和 Microsoft® SQL Server 2005 的即用型大规模邮件发送功能。对于 Microsoft® SQL Server 2005,我使用了 Service Broker,对于 Microsoft® SQL Server 2000,我使用了一个普通的数据库表。为了使框架可插入和可扩展,我使用了 Microsoft® Provider Pattern。

SME 中使用的技术

B: 我知道 Microsoft® Provider Design Pattern,但我很少听说 Microsoft® SQL Server Service Broker。请多告诉我一些。

A: 要回顾 Provider Pattern,可以看看我之前的文章 “使用 Provider Pattern 构建灵活且基于插件的 .NET 应用程序”

Microsoft® SQL Server 2005 Service Broker

Service Broker 是 Microsoft® SQL Server 2005 的一项新功能。它提供异步消息支持,并与 SQL Server 数据库引擎紧密集成。Service Broker 提供了一个新的、基于队列的持久消息传递框架,可以在服务终结点之间发送和接收消息,例如服务器实例、数据库和 .NET 2.0 客户端。消息的大小可以高达 2 GB,并且可以使用 varbinaryvarbinary(max) 数据类型。我在 博客上发布了一些 SSB 的链接和资源。查看这些链接将是一个很好的起点。

SME 框架

B: 非常有趣。等不及想了解更多关于 SME 框架的信息了。

A: 我也很兴奋能分享它。请看下面的图表。

:Smart Mass Email 工作流

从图表中,您会注意到使用多个提供程序来完成此任务。SME 在其框架中涉及五个不同的提供程序,我将很快逐一介绍它们。

要理解 SME 框架,我们需要详细了解 EmailMessageTemplate 对象,然后是所有五个提供程序。

SME 框架中的 EmailMessage 对象

EmailMessage 对象只是一个保存电子邮件详细信息的业务对象。我必须创建并使用此对象来实现框架中的序列化功能。

业务对象:EmailMessage

SME 框架中的 Template 对象

Template 对象只是另一个业务对象,它保存电子邮件模板。

业务对象:Template

客户端应用程序只需创建一个 EmailMessage,对其进行模板化,然后将其发送到指定的队列。

SME 中 Provider Pattern 的实现

Smart Mass Email 提供程序

SME 提供程序

EmailQueueProvider:此提供程序提供电子邮件排队功能。

public abstract class EmailQueueProvider : ProviderBase {
  public abstract bool Send(EmailMessage message); 
}

EmailTemplateProvider:此提供程序查找模板并作为模板化的消息提供。

public abstract class EmailTemplateProvider : ProviderBase { 
  public abstract Template GetTemplate(string templateName);
  public abstract EmailMessage GetTemplatedMessage(string templateName, 
         EmailMessage message, StringDictionary namevalue);
}

EmailDequeueProvider:此提供程序提供电子邮件出队功能。

public abstract class EmailDeQueueProvider : ProviderBase {
  public abstract TList<EmailMessage> Recieve();
  public abstract bool Delete(EmailMessage message);
}

EmailDispatchProvider:通过 SMTPClientSMTPMail 提供电子邮件调度功能。

public abstract class EmailDispatchProvider : ProviderBase { 
  public abstract bool Dispatch(TList<EmailMessage> list);
}

ProcessFailureProvider:处理电子邮件调度过程中失败的任何消息。例如,比较消息的最大重试次数和重试次数,并决定是再次排队消息还是将其永久删除。

public abstract class ProcessFailureProvider : ProviderBase {
  public abstract void Process(TList<EmailMessage> list); 
}

配置信息:如 Microsoft® Provider 规范所述,在具体提供程序实现后,必须在配置文件部分对其进行描述。Provider Pattern 的优点在于,可以从配置文件中的信息在运行时实例化适当的提供程序,并且可以定义无限数量的提供程序。

<smartMassEmail.providers>
  <emailQueue defaultProvider="Sql2005EmailQueueProvider">
    <providers>
      <add name="Sql2000EmailQueueProvider" 
           type="SmartMassEmail.ImplementedProviders.Sql2000EmailQueueProvider, 
                 SmartMassEmail.ImplementedProviders" />
      <add name="Sql2005EmailQueueProvider" 
           type="SmartMassEmail.ImplementedProviders.Sql2005EmailQueueProvider, 
                 SmartMassEmail.ImplementedProviders" />
    </providers>
  </emailQueue>
</ smartMassEmail.providers>

在上面的部分中,我们将 Sql2000EmailQueueProviderSql2000EmailQueueProvider 添加到了我们的提供程序列表中。

SME 框架中使用 Enterprise Library:SME 使用 Enterprise Library 2006 年 1 月版进行数据访问、缓存、日志记录和异常处理。

电子邮件队列和模板

B: 所以,如果我没记错的话,过程是从排队 EmailMessage 开始的,对吗?

A: 是的。客户端应用程序需要创建一个 EmailMessage 对象,然后可以使用 EmailTemplateProvider 获取一个模板化的消息,然后再进行排队。

//Creating a EmailMessage Object
EmailMessage message = new EmailMessage();
message.ID = Guid.NewGuid();
message.EmailSubject = "Subject";
message.EmailTo = "skhan@veraida.com";
message.EmailFrom = "psteele@veraida.com";
…..
message.EmailBody = "Test email";
…..
message.MaximumRetry = 3;
message.NumberOfRetry = 0;
…..
//Getting the NameValue pair to pass in the TemplateProvider
StringDictionary namevalue = GetPreparedNameValue(message); 
//Get Templated EmailMessage Object
EmailMessage templatedmessage = 
   EmailTemplate.GetTemplatedMessage("GenericEmailTemplate", 
                                      message, namevalue);
//Finally Queue the Email
EmailQueue.Send(templatedmessage);

这里,您会注意到我只是创建了一个 EmailMessage 对象,然后这段代码返回一个将用于模板的键值对。

StringDictionary namevalue = GetPreparedNameValue(message);

private StringDictionary GetPreparedNameValue( EmailMessage message ) {
    //prepare namevalue for the template.
    StringDictionary dict = new StringDictionary();
    dict.Add("[recievername]", "Shahed");
    dict.Add("[sendername]", "Khan");
    dict.Add("[body]", message.EmailBody);
    return dict;
}

此演示中使用的模板看起来与下面的代码类似,其中 [recievername]、[body] 和 [sendername] 将被匹配名称并替换为值的键值对替换。

<html>
<body>
Hi <b>[recievername]</b>
This is a Generic Mail.
[body]
Regards,
[sendername]
</body>
</html>

下面这段代码会获取模板化的消息。

EmailMessage templatedmessage = 
     EmailTemplate.GetTemplatedMessage("GenericEmailTemplate", 
                                        message, namevalue);

“幕后”所做的是,它使用演示应用程序中可用的 FileSystemEmailTemplateProvider,通过名称从预定义路径获取模板。然后读取文件并将其转换为 SME Template 对象。然后它用提供的键值对替换 TemplateBody,并返回模板化的 EmailMessage 对象。

//Get template from filesystem and return a Template Object
public override SmartMassEmail.Entities.Template 
                GetTemplate(string templateName) {
  string path = 
    ConfigurationManager.AppSettings.Get("TemplateFolderPath").ToString();
  string fileContents;

  using (System.IO.StreamReader sr = new System.IO.StreamReader(
                  string.Format(@"{0}{1}.txt",path,templateName)))
  {
    fileContents = sr.ReadToEnd();
  }
  SmartMassEmail.Entities.Template template = 
                 new SmartMassEmail.Entities.Template();
  template.TemplateName = templateName;
  template.TemplateBody = fileContents;
  return template;
}

//Gets Templated EmailMessage.
public override SmartMassEmail.Entities.EmailMessage 
       GetTemplatedMessage(string templateName, 
       SmartMassEmail.Entities.EmailMessage message, 
       StringDictionary namevalue){
  //Getting the predefined template.
  Template template = GetTemplate(templateName);
  if (message != null)
    ….
  foreach (DictionaryEntry de in namevalue)
  {
    template.TemplateBody = 
             template.TemplateBody.Replace(de.Key.ToString(), 
                                           de.Value.ToString());
  }
  message.EmailBody = template.TemplateBody;
  …..
  return message;
}

然后使用以下代码将返回的 EmailMessage 排队以进行调度

EmailQueue.Send(templatedmessage);

B: 好的,我明白了。在这里,您演示了如何使用 EmailTemplateProviderEmailMessage 转换为模板化的电子邮件消息,然后您展示了如何对电子邮件进行排队。

在这种情况下,您使用了 FileSystemEmailTemplateProvider 来模板化电子邮件消息,但任何人也可以通过继承 EmailTemplateProvider 来编写自己的提供程序。

A: 是的,您说得对。SME 框架提供了使用模板化消息的功能,并且演示应用程序实现了 FileSystemEmailTemplateProvider,该提供程序从文件系统中读取模板文件。也可以编写不同的提供程序,它们可以从数据库或类似来源读取模板。

电子邮件出队、调度和处理失败

B: 好的,我明白了。我们稍作回顾。您之前提到演示应用程序为 Microsoft® SQL Server 2000 和 Microsoft® SQL Server 2005 提供了即用型大规模邮件发送功能,您能进一步说明一下吗?

A: 当然。我的意思是,我为 SME 编写了几个提供程序。我为 Microsoft SQL Server 2005 编写了提供程序,使用了 Service Broker 技术。因此,电子邮件将异步地排队到 Service Broker Queues,然后电子邮件调度框架将从队列读取并发送电子邮件。另一方面,我为没有 Microsoft SQL Server 2005 访问权限的用户提供了一个解决方案。他们可以使用 Microsoft SQL Server 2000,其中消息作为一行添加到数据库表中,然后从中进行调度。我将一一讨论这两种实现。

Microsoft® SQL Server 2005 的 SME 提供程序

为了使 MessageType、Queue 和 Services 组件准备就绪,我不得不在数据库上运行以下 T-SQL 脚本。

/****** Sql script ******/ 
USE [SmartMassEmailDB2005]
GO
/****** Object: MessageType [SMEMessageType] ******/
CREATE MESSAGE TYPE [SMEMessageType] AUTHORIZATION 
       [dbo] VALIDATION = WELL_FORMED_XML

/****** Object: ServiceContract [SMEContract] ******/
CREATE CONTRACT [SMEContract] AUTHORIZATION [dbo] 
     ([SMEMessageType] SENT BY INITIATOR)

/****** Object: ServiceQueue [dbo].[SMEPostQueue] ******/
CREATE QUEUE [dbo].[SMEPostQueue] WITH STATUS = ON , 
       RETENTION = OFF ON [PRIMARY] 

/****** Object: ServiceQueue [dbo].[SMEResponseQueue] ******/
CREATE QUEUE [dbo].[SMEResponseQueue] WITH STATUS = ON , 
       RETENTION = OFF ON [PRIMARY] 

/****** Object: BrokerService [SMEPostingService] ******/
CREATE SERVICE [SMEPostingService] AUTHORIZATION [dbo] 
       ON QUEUE [dbo].[SMEPostQueue] 

/****** Object: BrokerService [SMEService] ******/
CREATE SERVICE [SMEService] AUTHORIZATION [dbo] ON 
       QUEUE [dbo].[SMEPostQueue] ([SMEContract])
Sql2005EmailQueueProvider

此提供程序将电子邮件消息排队到 Service Broker。

public override bool Send(EmailMessage message)
{
  if (message != null)
  {
    string xml = "";
    using (MemoryStream stream = new MemoryStream())
    {
      System.Xml.Serialization.XmlSerializer x = new 
        System.Xml.Serialization.XmlSerializer(message.GetType());
      x.Serialize(stream, message);
      xml = ConvertByteArrayToString(stream.ToArray());
    }
    string sql = 
           string.Format (@"DECLARE @dialog_handle UNIQUEIDENTIFIER; 
                            BEGIN DIALOG CONVERSATION @dialog_handle
                            FROM SERVICE [SMEPostingService]
                            TO SERVICE 'SMEService'
                            ON CONTRACT [SMEContract] ;

                            -- Send message on dialog conversation
                            SEND ON CONVERSATION @dialog_handle
                            MESSAGE TYPE [SMEMessageType]
                            ('{0}') ;
                            End Conversation @dialog_handle
                            With cleanup", xml);

    string connectionString = 
      ConfigurationManager.ConnectionStrings[
     "SmartMassEmailConnectionString2005"].ConnectionString;
    SqlConnection conn = new SqlConnection(connectionString);
    SqlCommand cmd = null;
    try
    {
      conn.Open();
      cmd = conn.CreateCommand();
      cmd.CommandText = sql;
      cmd.Transaction = conn.BeginTransaction();
      cmd.ExecuteNonQuery();
      cmd.Transaction.Commit();
      conn.Close();
      return true;
    }
    …..
  }

正如您所注意到的,首先使用 XmlSerializer 序列化 EmailMessage 对象,然后使用 T-SQL SEND 命令将其排队到数据库。

Sql2005EmailDequeueProvider

此提供程序从 Service Broker 中出队电子邮件消息。

m_queueSchema = "dbo";
m_queueName = "SMEPostQueue";
private SqlCommand CreateReceiveCommand()
{
    SqlCommand cmd = m_connection.CreateCommand();
    cmd.CommandText = @"WAITFOR (RECEIVE TOP (10) *, " + 
                      @"CONVERT( NVARCHAR(max), " + 
                      @"message_body ) as mgb FROM [" +
                      m_queueSchema.Replace("]", "]]") + "].[" +
                      m_queueName.Replace("]", "]]") + "]), 
                      TIMEOUT @timeout";
    cmd.Parameters.Add("@timeout", System.Data.SqlDbType.Int);
    cmd.CommandTimeout = 0;
    return cmd;
}

执行上述命令以从队列接收消息。接收消息后,将其转换回 EmailMessage 对象,然后传递给 EmailDispatchProvider

string xml = reader["mgb"].ToString();
if (xml != null)
{
    byte[] bytes = Encoding.Unicode.GetBytes(xml);
    EmailMessage message = new EmailMessage();
    message = (EmailMessage)LoadFromXml(message, bytes); 
}
Dotnet2EmailDispatchProvider

此提供程序使用 SmtpClient 将电子邮件发送到客户端。它遍历 EmailMessage 列表并发送每封电子邮件。错误处理程序会捕获异常,使应用程序继续运行并发送其他电子邮件,但会将失败的电子邮件添加到失败列表中,然后将其传递给 ProcessFailureProvider 进行进一步处理。

public override bool Dispatch(TList<EmailMessage> list)
{
    TList<EmailMessage> failurelist = new TList<EmailMessage>();
    //Gets SmtpSettins from the config
    SmtpSetting site = GetSmtpSetting();
    …..
    SmtpClient client = new SmtpClient();
    …..
    foreach (EmailMessage em in list)
    {
        try
        {
            MailMessage message = new MailMessage();
            message.From = new MailAddress(em.EmailFrom);
            message.To.Add(new MailAddress(em.EmailTo));
            message.Subject = em.EmailSubject;
            message.Body = em.EmailBody;
            …..
            message.IsBodyHtml = em.IsHtml;
            …..
            client.Send(message);
            if (connectionLimit != -1 && ++sentCount >= connectionLimit)
            {
                Thread.Sleep(new TimeSpan(0, 0, 0, 
                       site.WaitSecondsWhenConnectionLimitExceeds, 0));
                sentCount = 0;
            }
            // on error, loop so to continue sending other email.
        }
        catch (Exception e)
        {
            …..
            // Add it to the failure list
            failurelist.Add(em);
        }
        ++totalSent;
    }
    if (failurelist.Count > 0)
    {
        //Process failures by passing the failed 
        //messages to the ProcessFailure provider
        ProcessFailedMessages(failurelist);
        return false;
    }
    return true;
}
Sql2005ProcessFailureProvider

此提供程序遍历每个失败的消息,并比较 NumberOfRetry < MaximumRetry(由消息定义),然后会再次排队消息,或完全丢弃它。

public override void Process(TList list)
{
    …..
    foreach (EmailMessage message in list)
    {
        message.NumberOfRetry = message.NumberOfRetry + 1;
        // put the message in queue again if maximum retry not exceeded
        if (message.NumberOfRetry < message.MaximumRetry)
        {
            string xml = "";
            using (MemoryStream stream = new MemoryStream())
            {
                ……
                xml = ConvertByteArrayToString(stream.ToArray());
            }
            string sql =
                string.Format(@"DECLARE @dialog_handle UNIQUEIDENTIFIER; 
                                BEGIN DIALOG CONVERSATION @dialog_handle
                                FROM SERVICE [SMEPostingService]
                                TO SERVICE 'SMEService'
                                ON CONTRACT [SMEContract] ;
                
                                -- Send message on dialog conversation
                                SEND ON CONVERSATION @dialog_handle
                                MESSAGE TYPE [SMEMessageType]
                                ('{0}') ;

                                End Conversation @dialog_handle
                                With cleanup", xml); ………
            try
            {
                ………
                cmd.Transaction = conn.BeginTransaction();
                cmd.ExecuteNonQuery();
                cmd.Transaction.Commit();
                conn.Close();
            }
            catch (Exception x)
            {
            …..

B: 现在我清楚您是如何使用 Microsoft® SQL Server 2005 Service Broker Messaging Framework 来创建可靠的大规模邮件发送功能了,但您是如何在 Microsoft® SQL Server 2000 中实现的,因为它缺乏消息引擎功能?

A: 是的,您说得对。SQL Server 2000 没有消息引擎,我不得不使用表和存储过程来模拟 SQL Server 2005 中使用的消息功能。

MSSQL 2000 的 SME 提供程序

Sql2000EmailQueueProvider

此提供程序只需向表 EmailMessage 添加一行,状态为 0(待处理),供 EmailDeQueueProvider 读取。

SQL2000EmailDeQueueProvider

此提供程序使用以下存储过程来接收待处理的 EmailMessage

SELECT TOP 5 [ID], [ChangeStamp], [Priority], [Status], 
             [NumberOfRetry], [RetryTime], [MaximumRetry], 
             [ExpiryDatetime], [ArrivedDateTime], [SenderInfo], 
             [EmailTo], [EmailFrom], [EmailSubject], 
             [EmailBody], [EmailCC], [EmailBCC], [IsHtml]
FROM dbo.[EmailMessage]
WHERE NumberOfRetry < MaximumRetry and 
      RetryTime < getdate() and Status = 0 
ORDER BY Priority,RetryTime

B: 我注意到这里包含了优先级,这在您的 Microsoft® SQL Server 2005 实现中是没有的。

A: 是的,我正要说这一点;为了保持 Microsoft® SQL Server 2005 实现的简单性,我忽略了消息优先级选项。但这篇博客 Message Priority 将指明正确的方向,并且对现有提供程序进行一些修改就能解决问题。

此演示的 Microsoft SQL Server 2000 提供程序与消息优先级兼容,正如您在存储过程中注意到的那样,消息是按照 PriorityRetryTime 排序接收的。

B: 是的,而且我注意到您的 Microsoft® SQL Server 2000 实现中还有另一个属性 RetryTime。您能详细解释一下吗?

A: 当然。在此实现中,当您排队消息时,可以定义何时调度消息。另外,在消息传递失败时,您可以为消息定义一个新的 RetryTime 以再次传递。例如,如果您想在 2 天后再次调度电子邮件,只需将 RetryTime 设置为如此。EmailDeQueueProvider 仅在 RetryTime < getdate() 时才拾取它。

SMTPDotnet2EmailDispatchProvider

我在这里使用了与 Microsoft® SQL Server 2005 实现相同的调度提供程序来调度消息。

SMTPDotnet1EmailDispatchProvider

在演示应用程序中,您还将找到此调度提供程序,它使用 SmtpMail,这是发送邮件的旧方法。然而,.NET Framework 2.0 建议使用 SmtpClient 而不是已标记为过时的 SmtpMail。我编写此提供程序时使用了 SmtpMail,因为最初我曾计划也为 .NET 1.1 编写 SME,但这并未实现。

SQL2000ProcessFailureProvider

此提供程序遍历失败的消息,并比较 NumberOfRetry 是否小于 MaximumRetry。如果此条件为真,则将 10 分钟添加到 RetryTime,并将状态改回 Pending。否则,将删除该消息。

public override void Process(TList<EmailMessage> list )
{ 
    foreach (EmailMessage em in list)
    {
        //Check NumberOfRetry< MaximumRetry
        if (em.NumberOfRetry < em.MaximumRetry)
        {
            //Change the status back to pending
            em.Status = (int)EmailMessage.EmailMessageStatus.Pending;
            em.NumberOfRetry = em.NumberOfRetry + 1;
            //ReTry again after 10 min
            em.RetryTime = em.RetryTime.AddMinutes(10);
            DataRepository.EmailMessageProvider.Update(em);
        }
        else 
        {
            //Delete the Message
            DataRepository.EmailMessageProvider.Delete(em);
        }
    }
}

B: 那么,到目前为止我所理解的是,SME 是一个可扩展的大规模邮件发送框架,您已经为 Microsoft® SQL Server 2005 和 Microsoft® SQL Server 2000 创建了提供程序。然而,最终用户可以修改现有提供程序,或者如果他们愿意,可以实现自己的提供程序。例如,如果有人想在 Microsoft® Message Queuing (MSMQ) 中排队电子邮件消息,他们只需编写一个提供程序,继承 SME 框架的 EmailQueueProvider。要实现 DeQueueProcessFailures 方法,还需要以同样的方式编写另外两个提供程序。

A: 您说得对。使用 Microsoft® Provider Pattern 的全部目的是实现这种灵活性。现在,我将讨论一下将 SME 作为 ASP.NET 应用程序和 Windows 服务运行。

将 EmailDispatcher 作为 ASP.NET 应用程序或 Windows 服务运行

将 SME 作为 ASP.NET 应用程序运行

我将不详细解释这是如何工作的,因为我的朋友、同事和导师 Omar Al Zabir 在他的文章 “使用 ASP.NET 模拟 Windows 服务来运行计划任务” 中对此进行了详尽的描述。为了实现这一点,我们将一个虚拟页面添加到 ASP.NET 缓存中,并设置一个缓存过期时间。当缓存过期时,ASP.NET 会调用 CacheItemRemovedCallback 函数。在该函数中,我们还调用 DoWork() 函数,该函数会出队电子邮件消息。还会调用 HitPage() 函数,该函数会访问一个虚拟页面并创建 ASP.NET 注册缓存项所需的 HttpContext。这将在定义的缓存过期时间后再次过期,并且在缓存过期时,ASP.NET 会调用 CacheItemRemovedCallback 函数……依此类推。因此,我们创建了一个恒定的循环并使任务持续运行。

private void RegisterCacheEntry( )
{
  // Prevent duplicate key addition
  if (null != HttpContext.Current.Cache[DummyCacheItemKey])
      return;

  HttpContext.Current.Cache.Add(DummyCacheItemKey, 
              "Test", null, DateTime.MaxValue,
              TimeSpan.FromMinutes(1), 
              CacheItemPriority.NotRemovable,
              new CacheItemRemovedCallback(CacheItemRemovedCallback));
}

public void CacheItemRemovedCallback( string key, object value,
                                      CacheItemRemovedReason reason)
{
  System.Diagnostics.Debug.WriteLine("Cache item callback: " + 
                                     DateTime.Now.ToString());
  // Do the service works
  DoWork();
  // We need to register another cache
  // item which will expire again in one
  // minute. However, as this callback
  // occurs without any HttpContext, we do not
  // have access to HttpContext and thus
  // cannot access the Cache object. The
  // only way we can access HttpContext is
  // when a request is being processed which
  // means a webpage is hit. So, we need
  // to simulate a web page hit and then 
  // add the cache item.
  HitPage();
}

private void HitPage( )
{
  string siteprefix = ConfigurationManager.AppSettings.Get("SitePrefix");
  System.Net.WebClient client = new System.Net.WebClient();
  client.DownloadData(siteprefix + DummyPageUrl);
}

/// <summary>
/// Asynchronously do the 'service' works
/// </summary>
private void DoWork( )
{
  System.Diagnostics.Debug.WriteLine("Begin DoWork...");
  System.Diagnostics.Debug.WriteLine("Running as: " + 
         System.Security.Principal.WindowsIdentity.GetCurrent().Name);
  DequeueEmail();
  System.Diagnostics.Debug.WriteLine("End DoWork...");
}

private void DequeueEmail()
{
  //Calling the SME EmailDeQueue.Recieve 
  //to receive list the pending emails
  TList<EmailMessage> list = EmailDeQueue.Recieve(); 
  //Calling the SME EmailDispatch.Dispatch to finally deliver the emails
  EmailDispatch.Dispatch(list); 
}
将 SME 作为 Windows 服务运行

我还提供了将 SME 作为 Windows 服务运行的示例。我使用了一个定时器组件,以便在一定间隔后出队邮件。下面这段代码可以实现。

JobRunning jRun = JobRunning.Instance();
private void timer_Elapsed(object source,System.Timers.ElapsedEventArgs e)
{
    if (!jRun.IsRunning)
    {
        try
        {
            jRun.IsRunning = true;
            DequeueEmail();
        }
        finally
        {
            jRun.IsRunning = false;
        }
    }
}

JobRunning 类实现了 Singleton 设计模式,并检查前一个作业是否仍在运行。这可以防止同时启动多个 DeQueueEmail 操作。

如何在自己的应用程序中使用 Smart Mass Email

客户端应用程序步骤

客户端需要创建一个 EmailMessage 对象,然后可以使用 TemplateEmailProvider 对消息进行模板化,最后使用 EmailQueue.Send(templatedmessage) 排队消息。

客户端应用程序需要添加 SmartMassEmail.Entities 和提供程序引用。另外,在您的解决方案中添加 EmailQueueProviderTemplateEmailProvider 的已实现提供程序。检查演示应用程序的 app.config / web.config 文件以了解如何配置提供程序。

服务器应用程序(EmailDeque 和 Dispatching)步骤

EmailDequeueDispatch 可以作为 Windows 服务或 ASP.NET 应用程序运行,并将异步发送电子邮件。

该服务需要对 SmartMassEmail.Entities 提供程序以及 EmailDeQueueProviderEmailDispatchProviderProcessFailureProvider 的已实现提供程序的引用。另外,检查演示应用程序的 app.config/web.config 文件以了解如何配置提供程序。

B: 所以您的意思是,任何人都可以将所需的 DLL 添加到他们的解决方案中,并调整 app.config/web.config,然后大规模邮件发送功能就准备好了。他们还可以根据自己的访问权限选择 Microsoft® SQL Server 2000 或 Microsoft® SQL Server 2005。此外,您还提供了 FileSystemEmailTemplateProvider 以便轻松地对消息进行模板化。

A: 对。将大规模邮件发送功能添加到您现有的 .NET 应用程序就这么简单。

结论

SME 是一个即用型大规模邮件发送功能,可以添加到您现有的 .NET 应用程序中。它的架构设计充分考虑了灵活性和可扩展性。SME 附带了一些有用的提供程序,但您也可以编写自己的提供程序。Microsoft® SQL Server 2005 Service Broker 是一个强大的异步处理技术,SME 充分利用了这一点。SME 也可以与现有的 SQL Server 2000 平台一起使用,保持与许多已实现的 .NET 2.0 应用程序的兼容性。

它还可以用作一个参考应用程序,说明如何通过与 Service Broker 进行通信来从 .NET 应用程序发送和接收消息。SME 使用当今最前沿的 .NET 技术,例如:Enterprise Library、Microsoft® Provider Pattern 和 Microsoft® SQL Server 2005 Service Broker。

特别感谢 Christopher Heale 审校本文。

© . All rights reserved.