usingSystem;usingSystem.Net;usingSystem.Text;usingSystem.Net.Mail;usingiKnode.Applications;usingiKnode.Applications.Data;namespaceApplications{/// <summary>/// Defines the Main entry point of the Notifications application./// </summary>[Application]publicclassNotifications{privateconststringServer="myServer";privateconstintPort=587;privateconstboolUseSsl=true;privateconststringUserName="notifications@myemail.com";privateconststringPassword="myPassword";/// <summary>/// Sends a notifications message through an SMTP Server./// </summary>/// <returns>True if the message was sent, false otherwise.</returns>publicboolSend(stringrecipient,stringsubject,stringbody,boolisBodyHtml){using(MailMessageemailMessage=CreateMailMessage(UserName,recipient,subject,body)){SmtpClientsmtpClient=newSmtpClient(Server,Port);smtpClient.UseDefaultCredentials=false;smtpClient.EnableSsl=UseSsl;smtpClient.Credentials=newNetworkCredential(UserName,Password);smtpClient.Send(emailMessage);}returntrue;}/// <summary>/// Creates an email message from a notification message./// </summary>/// <returns>Mail Message.</returns>privatestaticMailMessageCreateMailMessage(stringsender,stringrecipient,stringsubject,stringbody,boolisBodyHtml=false){MailMessageemailmsg=newMailMessage();emailmsg.To.Add(newMailAddress(recipient));emailmsg.From=newMailAddress(sender);emailmsg.Body=System.Web.HttpUtility.HtmlDecode(body);emailmsg.Subject=subject;emailmsg.IsBodyHtml=isBodyHtml;emailmsg.BodyEncoding=Encoding.UTF8;returnemailmsg;}}}
This is a very simple application that receives a recipient, subject, body and a flag if the message’s body is HTML. You can create and publish this application in iKnode; and then test it in the console with the following command:
You should get a True result and receive an email for the address set in the recipient field.
This application fulfills the first requirement, and it works fine, but it doesn’t support the other 3 requirements.
For example, if the email server is down, the Notifications application will return false, the message will be lost completely. In order to send the message, the client needs to send it again. This breaks the requirement #2: The system must not loose any notification messages. This also breaks requirement #3, since the message will never be sent, unless the clients creates the message and sends it again.
Another problem with this design is that if the email server is saturated or very far from the iKnode server, the Notifications Application will take a long time to process.
The problem with this application, in a nutshell, is that it puts all the burden of the Notification Sending process in the client. If the email server is down, then the user needs to keep track of its own notifications so that it can try again. Additionally, if the email server is down, the system puts the burden on the client to recreate and try again. And finally if the email server is too far or there are connectivity problems, the burden of the connection time is put on the client again, making the client slow as well.
Another thing to think about, is that if there ever is a requirement to send notifications through multiple channels not just email(e.g. Twitter, SMS, or Facebook notifications), this Application would need to be redesign in a big way.
After understanding the problems that plague this design, let’s revise it to meet the rest of the requirements:
What we need to do in order to support the other 3 requirements is to separate the ‘Email Sending’ functionality from the ‘Notifications Send Request’. This separation will create a buffer between the Notifications Request Application and the Email Application.
The Notifications Application then receives the request, stores it in the database and returns to the client, which happens to be very fast. Then an scheduled Emailer Application, runs every 20 seconds to pick up any unsent emails and sends them. Once the email has been sent successfully, the Emailer will removes it from the database, and continues with the next.
If, for ever reason, the email is not sent, the Emailer sends the other emails in the database without removing the failed message. The Emailer will then try again the next time.Any email that was not successfully sent, remains in the database until it is possible to so.
The new design has two application classes and one Collection called Notifications:
The first thing we need to do is create the ‘Notifications’ collection in iKnode by just using the UI. The structure should like the following:
Once the Collection is created, let’s update the Notifications Application to follow the diagram described before:
usingSystem;usingiKnode.Applications;usingiKnode.Applications.Data;namespaceApplications{/// <summary>/// Defines the Notifications Application./// </summary>[Application]publicclassNotifications{/// <summary>/// Executing User Identifier./// </summary>privatestaticreadonlyGuidUserId=newGuid("");// Put your userId here./// <summary>/// Sends a notifications message./// </summary>/// <returns>Send Result.</returns>publicstringSend(stringrecipient,stringsubject,stringbody,boolisBodyHtml){Documentdoc=newDocument("Notifications");doc.Fields.Add("recipient",recipient);doc.Fields.Add("subject",subject);doc.Fields.Add("body",body);doc.Fields.Add("isBodyHtml",isBodyHtml.ToString());DocumentRepositoryrepository=newDocumentRepository(UserId);stringdocId=repository.Save(doc);stringresult=String.Format("Notification could not be placed in the queue for {0}",recipient);if(!String.IsNullOrEmpty(docId)){result=String.Format("The Notifications has been successfully added to the queue for {0} with confirmation id {1}",recipient,docId);}returnresult;}}}
Once the application is published, test it with the same command we used before:
Now it is time to move to the Emailer; the code for the emailer is displayed below, which is pretty much the same Notifications Application that we had initially with the exception of a new method called RunAsScheduled.
usingSystem;usingSystem.Net;usingSystem.Text;usingSystem.Net.Mail;usingiKnode.Applications;usingiKnode.Applications.Data;namespaceApplications{/// <summary>/// Defines the Main entry point of the Emailer application./// </summary>[Application]publicclassEmailer{privateconststringServer="myServer";privateconstintPort=587;privateconstboolUseSsl=true;privateconststringUserName="notifications@myEmail";privateconststringPassword="myPassword";privateconstintBatchSize=10;/// <summary>/// Executing User Identifier./// </summary>privatestaticreadonlyGuidUserId=newGuid("");// Put your userId here./// <summary>/// Run the emailer as an scheduled task using the "Notifications" collections./// </summary>publicstringRunAsScheduled(){stringcollectionName="Notifications";DocumentRepositoryrepository=newDocumentRepository(UserId);stringquery="{}";PagedList<Document>notificationsToSend=repository.QueryCollection(collectionName,query,"","ASC",0,BatchSize);if(notificationsToSend!=null&¬ificationsToSend.List.Count>0){foreach(DocumentdocinnotificationsToSend.List){if(this.Send(doc.Fields["recipient"],doc.Fields["subject"],doc.Fields["body"],ConvertToBool(doc.Fields["isBodyHtml"]))){repository.Delete(doc);}}}returnstring.Format("Notifications Sent {0}",notificationsToSend.List.Count);}/// <summary>/// Sends a message through an SMTP Server./// </summary>/// <returns>True if the message was sent, false otherwise.</returns>publicboolSend(stringrecipient,stringsubject,stringbody,boolisBodyHtml){using(MailMessageemailMessage=CreateMailMessage(UserName,recipient,subject,body)){SmtpClientsmtpClient=newSmtpClient(Server,Port);smtpClient.UseDefaultCredentials=false;smtpClient.EnableSsl=UseSsl;smtpClient.Credentials=newNetworkCredential(UserName,Password);smtpClient.Send(emailMessage);}returntrue;}/// <summary>/// Creates an email message from a notification message./// </summary>/// <returns>Mail Message.</returns>privatestaticMailMessageCreateMailMessage(stringsender,stringrecipient,stringsubject,stringbody,boolisBodyHtml=false){MailMessageemailmsg=newMailMessage();emailmsg.To.Add(newMailAddress(recipient));emailmsg.From=newMailAddress(sender);emailmsg.Body=System.Web.HttpUtility.HtmlDecode(body);emailmsg.Subject=subject;emailmsg.IsBodyHtml=isBodyHtml;emailmsg.BodyEncoding=Encoding.UTF8;returnemailmsg;}/// <summary>/// Converts the selected string to Boolean./// </summary>privatestaticboolConvertToBool(stringval){return(val!=null&&val.Trim().ToLower().Equals("true"));}}}
The RunAsScheduled method is a convenience method running as an scheduled application. What it does is to get a batch of 10 notifications every time time it runs and tries to send them via email. If it is sent successfully then it deletes it from the collection.
You can test this method in the console by running the following command:
Code
1
Emailer:RunAsScheduled
You may notice that the method Send is still there. The reason is that the Emailer is really an iKnode Application. Even if an instance of it is being ran in an schedule, nothing stops you from calling the emailer directly like this: