|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
AbstractThis paper demonstrates an extensible mass emailing framework (Smart Mass Email SME). The demo implementation uses cutting edge .NET technologies available today, such as C#, .NET 2.0, Microsoft® SQL Server 2005 Service Broker, MS Provider Pattern, Enterprise Library January 2006 etc. IntroductionI thought of writing this article in a different way, just like a conversation session in MSN Messenger / Windows Live Messenger. Alex: Hi. I am writing a new article on Smart Mass Emailing (SME) feature in .NET 2.0. Bob: Sounds interesting. Tell me a little bit more about it. A: Mass Emailing is a very common feature for lots of websites or applications. I have written an extensible and pluggable framework for this SME feature. The demo application that comes with the article provides examples of running SME as a Windows Service and also as a website. The second feature is very useful when you want to run SME in a Shared Web Environment where Windows Services is not available. SME will use the same technique presented in this article, “Simulate a Windows Service using ASP.NET to run scheduled jobs”. The demo application demonstrates ready to use mass emailing functionality for both Microsoft® SQL Server 2000 and Microsoft® SQL Server 2005. For Microsoft® SQL Server 2005, I have used Service Broker, and for Microsoft® SQL Server 2000, I have used a normal database table. And to make the framework pluggable and extensible, I have used the Microsoft® Provider Pattern. Technologies Used in SMEB: I am aware of the Microsoft® Provider Design Pattern, but I have heard very little about Microsoft® SQL Server Service Broker. Please, tell me a bit more about it. A: For a refresher on the Provider Pattern, have a look at my previous article “Flexible and Plug-in-based .NET Applications Using the Provider Pattern”. Microsoft® SQL Server 2005 Service BrokerAnd Service Broker is a new feature of Microsoft® SQL Server 2005. It offers asynchronous messaging support, and is tightly integrated with the SQL Server database engine. Service Broker provides a new, queue-based durable messaging framework which can send and receive messages between service endpoints, such as server instances, databases, and .NET 2.0 clients. Messages can be up to 2 GB in size, and can use the SME FrameworkB: Very interesting. Can’t wait to learn more about the SME Framework. A: I am very excited to share it as well. Have a look at the following diagrams.
Fig: Smart Mass Email Workflow From the diagram, you will notice that multiple providers are used to accomplish this task. SME involves five different providers in its framework, and I am going to describe them one by one soon. To understand the SME Framework, we need to understand the EmailMessage Object in the SME FrameworkThe
Fig Business Object: Template Object in the SME FrameworkThe
Fig Business Object: A client application simply needs to create an An implementation of Provider Pattern in SME
Fig Smart Mass Email Providers SME Providers
public abstract class EmailQueueProvider : ProviderBase {
public abstract bool Send(EmailMessage message);
}
public abstract class EmailTemplateProvider : ProviderBase {
public abstract Template GetTemplate(string templateName);
public abstract EmailMessage GetTemplatedMessage(string templateName,
EmailMessage message, StringDictionary namevalue);
}
public abstract class EmailDeQueueProvider : ProviderBase {
public abstract TList<EmailMessage> Recieve();
public abstract bool Delete(EmailMessage message);
}
public abstract class EmailDispatchProvider : ProviderBase {
public abstract bool Dispatch(TList<EmailMessage> list);
}
public abstract class ProcessFailureProvider : ProviderBase {
public abstract void Process(TList<EmailMessage> list);
}
Configuration information: As described in the Microsoft® Provider specification, after the concrete provider is implemented, it must be described in the configuration section. The beauty of the Provider Pattern is that the appropriate provider is instantiated at run time from information contained in the configuration file, and you may define an unlimited number of providers. <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>
In the above section, we have added the The use of Enterprise Library in the SME Framework: SME uses Enterprise Library January 2006 for data access, caching, logging, and exception handling. Email Queue and TemplateB: So the process starts with queuing A: Yes, you are. The client application needs to create an //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);
Here, you will notice that I simply created an 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("", message.EmailBody);
return dict;
}
The template used in this demo looks similar to the code below, where the [recievername], [body], and [sendername] will be replaced by the name-value pair, by matching the name and replacing it with the value. <html>
<body>
Hi <b>[recievername]</b>
This is a Generic Mail.
Regards,
[sendername]
</body>
</html>
And the following piece of code gets the templated message back. EmailMessage templatedmessage =
EmailTemplate.GetTemplatedMessage("GenericEmailTemplate",
message, namevalue);
What this does ‘under the hood’, is that it uses the //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;
}
The returned EmailQueue.Send(templatedmessage);
B: OK, I got you. Here, you have demonstrated how to turn an In this case, you have used A: Yes you are correct. The SME framework provides the ability to use a templated message, and the demo application implements the Email Dequeue, Dispatch, and Process FailuresB: OK, I understand. Let’s go back a bit. You mentioned previously that the demo application demonstrates ready-to-use mass emailing functionality for Microsoft® SQL Server 2000 and Microsoft® SQL Server 2005, can you please clarify this further? A: Sure. What I meant is I have written a couple of providers for SME. I have written providers for Microsoft SQL Server 2005, using Service Broker Technology. So, emails will be queued in Service Broker Queues asynchronously, and then the email dispatch framework will read from the queues and deliver the emails. On the other hand, I provided a solution for users who do not have access to Microsoft SQL Server 2005. They can use Microsoft SQL Server 2000, where the message is added as a row in the database table and later dispatched from there. Let me discuss both implementations one after another. SME Providers for Microsoft® SQL Server 2005To make the MessageType, Queue, and Services components ready, I had to run the following T-SQL script on the database. /****** 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])
Sql2005EmailQueueProviderThis provider queues email messages to the 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;
}
…..
}
As you have noticed, first of all the Sql2005EmailDequeueProviderThis provider dequeues email messages from the 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;
}
The above command is executed to receive the message from the queue. And after receiving the message, it is turned back into an string xml = reader["mgb"].ToString();
if (xml != null)
{
byte[] bytes = Encoding.Unicode.GetBytes(xml);
EmailMessage message = new EmailMessage();
message = (EmailMessage)LoadFromXml(message, bytes);
}
Dotnet2EmailDispatchProviderThis provider uses an 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;
}
Sql2005ProcessFailureProviderThis provider loops through each of the failed messages, and compares the public override void Process(TList
B: It is clear to me now how you have used the Microsoft® SQL Server 2005 Service Broker Messaging Framework to create a reliable mass emailing functionality, but how did you implement this in Microsoft® SQL Server 2000, as it lacks the messaging engine functionality? A: Yes, you are right. SQL Server 2000 does not have the messaging engine, and I had to simulate the messaging feature used in SQL Server 2005, using tables and stored procedures. SME Providers for MSSQL 2000Sql2000EmailQueueProviderThis provider simply adds a row to the table EmailMessage with a status = 0 (pending), to be picked up by the SQL2000EmailDeQueueProviderThis provider uses the following stored procedure to receive pending 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: I notice here that priority is included, which was not there in your Microsoft® SQL Server 2005 implementation. A: Yes, I was coming to that point; to keep the implementation of Microsoft® SQL Server 2005 simple, I ignored the message priority option. But this blog post, Message Priority, will point you towards the right direction, and some modification to the existing provider will do the trick. The Microsoft SQL Server 2000 providers of this demo are compatible with Message Priority, and as you have noticed in the stored procedure, the messages are received ordered by B: True, and I notice another property A: Sure. In this implementation when you queue a message, you can define when to dispatch the message. Also, on the failure of delivery of a message, you can define a new SMTPDotnet2EmailDispatchProviderI have used the same dispatch provider as the Microsoft® SQL Server 2005 implementation here, to dispatch the messages. SMTPDotnet1EmailDispatchProviderIn the demo application, you will also find this dispatch provider which uses SQL2000ProcessFailureProviderThis provider loops through the failed messages, and compares if 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: So, what I understand so far is, SME is an extensible framework for mass emailing, and you have already created providers for Microsoft® SQL Server 2005 and Microsoft® SQL Server 2000. However, end users can modify the existing providers, or implement their own providers if they wish. For example, if someone wants to queue the email messages in Microsoft ® Message Queuing (MSMQ), they simply have to write a provider for that, inheriting from the A: You are correct. The whole idea of using the Microsoft® Provider Pattern is to achieve this flexibility. Now, I'll discuss a bit on running SME as an ASP.NET application and as a Windows Service. Running the EmailDispatcher as an ASP.NET Application or as a Windows ServiceRunning SME as an ASP.NET ApplicationI am not going to explain in great detail how this works, as my friend, colleague, and mentor Omar Al Zabir describes this very elaborately in his article “Simulate a Windows Service using ASP.NET to run scheduled jobs”. To achieve this, we add a dummy page to the ASP.NET cache with a defined cache expiry time. When the cache expires, ASP.NET calls the 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);
}
Running SME as a Windows ServiceI have also provided examples of running SME as a Windows Service. I have used a timer component to dequeue the mail after certain interval. The following piece of code does the trick. 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;
}
}
}
The How to Use Smart Mass Email in your own ApplicationClient Application StepsThe client needs to create an The client application needs to add Server Application (EmailDeque and Dispatching) StepsThe The Service needs a reference to B: So what you meant is, anyone can add the required DLLs into their solution and tweak the app.config/web.config, and the mass emailing feature is ready. They can also use a choice of Microsoft® SQL Server 2000 or Microsoft® SQL Server 2005, depending on what they have access to. Also, you have provided A: That’s right. It is that easy to have mass emailing as part of your existing .NET application. ConclusionSME is a ready-to-use mass emailing feature that can be added to your existing .NET application. It has been architected keeping flexibility and extensibility in mind. SME ships with a couple of useful providers, but you can also write your own. Microsoft® SQL Server 2005 Service Broker is a very powerful technology for asynchronous processing, and SME takes full advantage of this. SME also can be used with existing SQL Server 2000 platforms, preserving compatibility with many previously implemented .NET 2.0 applications. This can also be used as a reference application of how you can send and receive messages by communicating with the Service Broker from your .NET application. SME uses cutting edge .NET technologies available today such as: Enterprise Library, Microsoft® Provider Pattern, and Microsoft® SQL Server 2005 Service Broker. Special thanks to Christopher Heale for proof reading this article.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||