Inherited Message Distributing
- Posted: Sep 11, 2008 at 11:35 AM
- 459 Views
- 14 Comments
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
| Given a week off of school I found the time to start designing a system that had been thought up in a school class. The idea was simple, a webapp that lets a user reach the widest possible audience by distributing messages across many mediums. One message could be delivered to each recipient as an email, text message, instant message, or phone call to name a few possibilities. With this system, an already overworked teacher could take 30 seconds to send one message that was delivered to each student and parent instantly across multiple mediums. A sports coach could finally reach everyone on the team, without trying to post news on a site that was rarely checked and poorly maintained. | |
|
http://www.chalk2me.com/
Difficulty: Intermediate
Time Required:
3-6 hours
Cost: Free
Hardware:
Download: Download
Disclaimer: Currently this article is C# only. If you want it in Visual Basic.Net, please comment and tell us so we can transcode it to VB.
|
|
Starting Out:
We thought we had come up with a cool concept, and I knew it wouldn't be that difficult to build. After calling our dev-team into the office I started on it, by myself in my apartment. My goal was to have a single method to send any type of message that would then be picked up by an appropriate thread to deliver the message. As the whole idea here was to create and deliver messages, a logical place to start was a struct to hold our message fields. It's always good to keep other services in mind while you're building your own. I started off with the first 4 fields in any email. This covered message content and we only needed a bit more info to deliver the message. The medium type, along with an identifier field for your address (or screen name etc.), would cover this. Just in case a service we end up using needs a second identifier, I added one more. Check it out:
public struct ctMessage { public ctMessage(string sendToUser, string fromUser, string subj, string msg, string mediumType, string mediumArg1, string mediumArg) { this.toUser = sendToUser; this.fromUser = fromUser; this.subject = subj; this.message = msg; this.mediumType = mediumType; this.mediumArg1 = mediumArg1; this.mediumArg2 = mediumArg2; } public string toUser; //name of recipient public string fromUser; //name of sender public string subject; public string message; public string mediumType; //which node should deliver this public string mediumArg1; //varies, to identify the recipient public string mediumArg2; //varies, to identify the recipient }
Once we had a message designed I, of course, wanted to be able to send one! Eventually I wanted a bunch of different types of messages being sent, so a solid base class would save a ton of time later. Although all of the messaging services, or ‘nodes' would undoubtedly send the messages completely different, they would all need to run in a similar thread that sent messages coming from a single location. By doing a good job writing an abstract base class, we could add new services by only overriding the 'send' method later.
This abstract class would have a field identifying the type of messages it will handle, and call an external send method whenever messages of that type existed. A continuous loop to poll for messages, given a modest polling frequency would do the trick. Notice the protected methods and fields that we'll want to override.
public abstract class ctNode { public ctNode() { this.maxQueueSize = 100; //Set the size according to how long it will take to reach the Nth message this.msgQueue = new Queue(maxQueueSize); this.msPauseAfterRun = 10000; //If using a DB, polling it with no pause b/w queries is an unnecessary load this.runnerThread = new Thread(new ThreadStart(this.run)); } private Thread runnerThread; protected int msPauseAfterRun; protected Queue msgQueue; public int maxQueueSize; void run() { while (true) { while (this.msgQueue.Count > 0) { //pop a message and send it ctMessage ctm = (ctMessage)msgQueue.Dequeue(); try { sendSingleMessage(ctm); } catch (Exception e) { //Write a more detailed error log here Console.writeLine("the message could not be sent!" + e.toString()); } } //get new messages ctNode ctn = this; ctMessage[] newMessages = messageSender.getMessages(ref ctn, getRoomInQueue()); //Wait some time before checking for more messages! if (newMessages.Length == 0) Thread.Sleep(msPauseAfterRun); else this.EnqueueMessages(newMessages); } } public virtual void EnqueueMessages(ctMessage[] messagesToSend) { int roomInQueue = maxQueueSize - msgQueue.Count; if (messagesToSend.Length > roomInQueue) throw new Exception("Too many Messages have been inserted"); for (int i = 0; i < messagesToSend.Length; i++) { msgQueue.Enqueue(messagesToSend[i]); } } public virtual void EnqueueMessages(ctMessage messageToSend) { ctMessage[] ctm = new ctMessage[1]; ctm[0] = messageToSend; this.EnqueueMessages(ctm); } protected virtual void sendSingleMessage(ctMessage ctm) { //code to send an individual message } public void startThread() { runnerThread.Name = this.nodeType; runnerThread.Start(); } protected string nodeType; public string getNodeType() { return this.nodeType; } }
You might have noticed the big thing missing from these classes, a place to store, and retrieve messages! I initially put our message storage into an external static class. This made them centralized, easily accessible from any other objects that might need to access them, simple to ensure thread safety, and gave us room to move to a smarter mechanism later (or database. Notice the simplicity:
public static class messageSender { private static List<ctMessage> messagesToSend = new List<ctMessage>(); public static ctMessage[] getMessages(ref ctNode nodeRef, int numberOfMessages) { //We'll do this in a database later, but for now this works similarly List<ctMessage> returnMessages = new List<ctMessage>(); lock (messagesToSend) for (int i = 0; i < messagesToSend.Count; i++) if (messagesToSend[i].mediumArgs == nodeRef.getNodeType()) { returnMessages.Add(messagesToSend[i]); messagesToSend.RemoveAt(i); i--; } return returnMessages.ToArray(); } public static void sendMessage(ctMessage message) { lock (messagesToSend) messagesToSend.Add(message); } }
Now that the groundwork was done, I grabbed some eye drops and an energy drink to refresh for the best part, function! I started simple with an email node, and if you've played with email before in .net, you've definitely seen and loved the SmtpClient class in the System.Net.Mail Namespace. If you don't already have a local SMTP client set up, you can easily use a Gmail account as an SMTP server. Check out the following implementation, notice we only had to set the node type, initialize the SmtpClient and override the send method. Send a message through our message sender, and watch the node pick up the message and deliver it. Inheritance rocks!
ctMessage ctm = new ctMessge("Friend", "Developer", "testing", "My first message", "email", "yourEmail@yourDomain.com", ""); messageSender.sendMessage(ctm); public class ctNodeEmail : ctNode { public ctNodeEmail() : base() { this.nodeType = "email"; // //SET UP SMTP CLIENT // //GMAIL SERVER this.mSmtpClient = new SmtpClient("gmail.com/mail", 25); this.mSmtpClient.Host = "smtp.gmail.com"; this.mSmtpClient.Port = 25; this.mSmtpClient.EnableSsl = true; this.mSmtpClient.Credentials = new System.Net.NetworkCredential("YOURADDRESS@gmail.com", "YOUR_PASSWORD"); //LOCAL SERVER //this.mSmtpClient = new SmtpClient("localhost", 25); } SmtpClient mSmtpClient; protected override void sendSingleMessage(ctMessage ctm) { MailMessage msgMail = new MailMessage(new MailAddress(ctm.fromUser + "@ChalkTalkNow.com"), new MailAddress(ctm.mediumArgs)); msgMail.Subject = ctm.subject; string msg = ctm.message; msgMail.Body = msg; // Send the mail message mSmtpClient.Send(msgMail); } }
After writing this so quickly I wanted something a bit more than email, and knew text messages were only a step away. It's no secret that all major cell companies give your phone an email address, you just need to append the proper domain to your number. Building off of our email node, we used the first mediumArg to hold a phone number, and put the carrier into the second mediumArg.
public class ctNodeTextMessage : ctNode { public ctNodeTextMessage() : base() { this.nodeType = "textmessage"; // //SET UP SMTP CLIENT // this.mSmtpClient = new SmtpClient("gmail.com/mail", 25); this.mSmtpClient.Host = "smtp.gmail.com"; this.mSmtpClient.Port = 25; this.mSmtpClient.EnableSsl = true; this.mSmtpClient.Credentials = new System.Net.NetworkCredential("GMAIL@gmail.com", "PASSWORD"); } SmtpClient mSmtpClient; private string getEmailAddress(string phoneNumber, string provider) { switch (provider) { case "t-mobile": return phoneNumber + "@tmomail.net"; case "virginmobile": return phoneNumber + "@vmobl.com"; case "cingular": return phoneNumber + "@cingularme.com"; case "att": return phoneNumber + "@cingularme.com"; case "sprint": return phoneNumber + "@messaging.sprintpcs.com"; case "verizon": return phoneNumber + "@vtext.com"; case "nextel": return phoneNumber + "@messaging.nextel.com"; default: //assume cingular/att is the most popular return phoneNumber + "@cingularme.com"; } } protected override void sendSingleMessage(ctMessage ctm) { //send a single message string emailAddress = getEmailAddress(ctm.mediumArg1, ctm.mediumArg2); MailMessage msgMail = new MailMessage(new MailAddress(ctm.fromUser + "@chalktalk.net", ctm.fromUser), new MailAddress(emailAddress)); msgMail.Subject = ctm.subject; msgMail.Body = ctm.message; sendEmail(msgMail); } private void sendEmail(MailMessage msgMail) { // Send the mail message this.mSmtpClient.Send(msgMail); } }
Now to test this out, just like we sent the email we'll send a text. Cool!
ctMessage ctm = new ctMessge("Friend", "Developer", "testing", "hello world via text", "textmessage", "#########", "verizon"); messageSender.sendMessage(ctm);
When you're working on a project with a small team, it's especially important to build a good foundation like this so you can focus on easily added functionality without getting bogged down in complexity. This portion of the project was written over the course of a week and later on allowed us to focus almost exclusively on adding new services like Instant Messengers and interactive phone calling. Having functionality so quickly was crucial, along with a UI built by my brother, in getting the attention and support of others to build a business around this.
Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation,
please create a new thread in our Forums,
or
Contact Us and let us know.
Follow the Discussion
Oops, something didn't work.
What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in. You need to be signed in to Channel 9 to use this feature.What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in and view them all on your notifications page.sign up for email notifications?
Yes a VB.net version would be nice.
@Bob Thanks, I will get to this then.
cool can you trancode it to vb language
Hi Robert,
An alternative would be to have a base abstract "Message" class and a sub-type for each message type, e.g. TextMessage, EmailMessage etc. To create a message you just call new TextMessage(args) and you have your TextMessage object.
The Message base class defines a method "send()". Each message sub-class should know how to send itself.
To send your messages all you have to do is go through your queue of Message objects calling "send()" on each.
I'm curious as to why you chose to sub-class the sender rather than the message, as "send()" seems like something a message should be able to do?
Cheers,
GC
GC,
You bring up a couple of interesting points, that are not well addressed in this piece. For the simple examples shown, you could certainly encapsulate sending functionality within the message struct, with perhaps a statically maintained connection.
When you're designing a web app with a lot done behind the scenes it's often best to decouple the interface and the processing. For the larger project here this meant an interface needed a message struct solely to hold fields. A second server, running the nodes might be actually doing the sending. This design allows you to not expose the node overhead to the UI developers that won't be it anyways.
The second point to consider is that a node may require a persistent singleton connection that must talk to another server. In this case it is easier to maintain sending in a separate class/thread that always runs.
-Rob
Yes Please give a VB version!!!
hey, the idea is cool but im not farmiliar with C# PLS CAN U TRASCODE IN VB
@rmehta looking into this
I could not find the downloable code on codeplex.
@Oron: Working on it, just have other things on my plate.
http://converter.telerik.com/ can do most of the heavy lifting.
I could not find the downloable code on codeplex.
Just wanted to followup if you manged to get the code. I could not find the code at the downloadable link
@rmehta working on getting it back up.
Hey thanks, I know how it gets!!!
Remove this comment
Remove this thread
close