In our last article, we created the basis of a messaging system, allowing us to receive inbound messages using keywords and shortcodes. We’ve also previously seen how we can send SMS messages. It’s time to bring everything together and create a simple messaging system that can process inbound SMS messages and send back responses to those messages.
The Current System
Our inbound messaging system (see the Receiving SMS Messages via Shortcodes & Keywords article) currently consists of:
- A simple SQL Server database
- A “listener” ASP.NET Web page
- An Inbound SMS Message class
As it stands, the system can receive inbound SMS messages and delivery receipts. The key part we are missing is the ability to send a response message back to the person who sent in the inbound response. That’s what we’re going to add here. This will cause the code to change quite a bit, so we won’t show the previous code here; please refer to the other articles if you want to see this.
Why Do We Need To Respond?
You might ask yourself why bother sending a response? Well, courtesy first of all. If you’ve advertised a keyword mechanism and one of your customers (or potential customers) has made the effort to use it, the least you can do is acknowledge their message. Secondly, perception; most people expect to receive a reply of some sort when they send in a SMS message to a keyword/shortcode. Imagine you entered a competition via SMS. You would expect to receive a message back confirming your entry, wouldn’t you?
It’s always best to respond to user queries. This lets the user know the system is working and can also be used to direct them to a Web site, which can possibly be used to entice them to use more of your services or products. Don’t leave the user sitting around wondering whether their SMS message arrived!
Planning Our System
We need to change both parts of the system to improve our functionality. We must:
- Change the database structure
- Change the ASP.NET listener and classes
What You Need
This example is developed using a SQL Server database (any version from 2000 onwards) and a simple C# ASP.NET Web site. So you’ll need a version of SQL Server (the free SQL Server Express Edition will do the trick) and a tool which allows you to create an ASP.NET Web site (Visual Studio Express could be used here, this is also free).
If you want to test the system in anger, you’ll need an account with an aggregator or SMS provider (see here for more information on this), and at least one keyword with that aggregator. We’re using iTagg in this example, but there are lots more aggregators and SMS providers – Mdev, Oxygen8, and Tanla are just a couple of examples.
The Database
We’ll start off by creating the database. In our previous articles, this consisted of one table, which we used to store messages. It also contained a couple of stored procedures, which inserted messages into the table and updated messages with delivery receipt information.
To be able to support a proper inbound/outbound SMS mechanism, we need to add:
- The ability to send back a response message to inbound messages
- The ability to identify the keyword to which the inbound message was received, and then send back a response specifically for that keyword
Assume we had two keywords – mobiforge and deviceatlas. If our system only supported one response, the message sent back would always be the same. If the response was set up as follows:
Thanks for contacting mobiForge! To see great articles discussing all aspects of mobile development, visit http://mobiforge.com now!
Then we would be confusing users who have texted in to the deviceatlas keyword. It’s natural to assume they would expect to receive a response along these lines:
DeviceAtlas is the premier mobile device detection database. To find out more, visit us at http://deviceatlas.com today.
To support this functionality, we’ll need to amend the database as follows:
- Add a KeywordResponses table, which will store keyword details and details of the response message to send
- Modify the Messages table, so it is linked to the KeywordResponses table
- Modify the usp_InsertMessage stored procedure, so it can identify the keyword to which the message has been sent, and returns the response to send
- Add a stored procedure to return keyword details when an inbound message is received
Here’s an updated script, with all of those changes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
-- MobiforgeMessageDemo database -- Supports storage of response message details -- Supports storage and retrieval of messages sent/received USE master -- Delete the database if it already exists so we can recreate IF EXISTS(SELECT [name] FROM sys.databases WHERE [name] = 'MobiforgeMessageDemo') BEGIN DROP DATABASE MobiforgeMessageDemo END GO -- Create the database CREATE DATABASE MobiforgeMessageDemo GO USE MobiforgeMessageDemo -- The keyword responses table stores keyword details, and -- the contents of the message we will send back CREATE TABLE KeywordResponses ( -- Identity just automatically assigns a unique number to a new keyword, starting from 1 KeywordID INT IDENTITY(1,1) PRIMARY KEY, Keyword VARCHAR(25), Shortcode VARCHAR(12), SendResponse BIT, -- An originator can be 11 characters in length, or 12 if using a mobile number ResponseOriginator VARCHAR(12), -- An SMS message can contain up to 160 characters ResponseMessageBody VARCHAR(160) ) -- Add the Messages table CREATE TABLE [Messages] ( -- Added so we can link the KeywordResponse and Messages tables together KeywordID INT, AggregatorMessageID VARCHAR(40), InboundMessage BIT, Originator VARCHAR(12), Recipient VARCHAR(12), MessageDate DATETIME DEFAULT GETDATE(), MessageBody VARCHAR(160), MessageDelivered BIT DEFAULT 0, CONSTRAINT pk_messages PRIMARY KEY (KeywordID, AggregatorMessageID) ) GO -- Create relationship between KeywordResponses and Messages ALTER TABLE [Messages] ADD CONSTRAINT fk_KeywordResponses_Messages FOREIGN KEY (KeywordID) REFERENCES KeywordResponses (KeywordID) ON UPDATE CASCADE ON DELETE CASCADE GO -- Returns details for the specified keyword, including -- whether we should send a message back and the message details if so CREATE PROCEDURE usp_SelectKeywordDetails @Keyword VARCHAR(25), @Shortcode VARCHAR(12) AS BEGIN SET NOCOUNT ON SELECT KeywordID, Keyword, Shortcode, SendResponse, ResponseOriginator, ResponseMessageBody FROM KeywordResponses WHERE Keyword = @Keyword AND Shortcode = @Shortcode SET NOCOUNT OFF END GO -- Stored procedure to insert a message CREATE PROCEDURE usp_InsertMessage @KeywordID INT, @AggregatorMessageID VARCHAR(40), @InboundMessage BIT, @Originator VARCHAR(12), @Recipient VARCHAR(12), @MessageBody VARCHAR(160), @MessageDate DATETIME = NULL AS BEGIN SET NOCOUNT ON -- Set message date to NOW if we don't have a date/time IF (@MessageDate IS NULL) BEGIN SET @MessageDate = GETDATE(); END -- Insert the message with the keyword ID INSERT INTO [Messages] (KeywordID, AggregatorMessageID, InboundMessage, Originator, Recipient, MessageBody) VALUES (@KeywordID, @AggregatorMessageID, @InboundMessage, @Originator, @Recipient, @MessageBody) SET NOCOUNT OFF END GO -- Stored procedure to update a message's delivery status CREATE PROCEDURE usp_MarkMessageAsDelivered @AggregatorMessageID VARCHAR(40) AS BEGIN SET NOCOUNT ON -- iTagg pass message IDs with a -3 when sending messages, -- but do not pass the -3 in delivery receipts. So we check -- if we can find the message first, and if we can't we append -- a -3 to the end of the ID IF NOT EXISTS (SELECT AggregatorMessageID FROM [Messages] WHERE AggregatorMessageID = @AggregatorMessageID) BEGIN SET @AggregatorMessageID += '-3' END UPDATE [Messages] SET MessageDelivered = 1 WHERE AggregatorMessageID = @AggregatorMessageID END |
Run this script in SQL Server Management Studio to create the database. In the screenshot below, I’ve inserted two records into the KeywordResponses table to support the mobiforge and deviceatlas keywords I was discussing earlier.
You can see that each record stores the name of the keyword, along with the shortcode on which the keyword is located. There’s a SendResponse flag, which denotes whether a response should be sent or not; and finally there’s a ResponseOriginator and a ResponseMessageBody. These two values determine what message is sent, and who it appears to be sent from.
We now have support for multiple keywords in our database. Hurrah! Now we just need to update the ASP.NET side of things.
ASP.NET Changes
On the ASP.NET side of things, we’d previously created a site that had:
- An ASPX page that acts as a listener, waiting for messages to be received from the aggregator (the intermediary company we use to send and receive SMS messages through – see here for more information)
- A simple iTaggDeliveryReceipt class, to handle delivery receipts
- A simple InboundSmsMessage class, to handle inbound SMS messages
We’ll change the site so it has the following:
- An ASPX page that acts as a listener. This will handle both the inbound messages and the delivery receipt notifications
- A simple iTaggDeliveryReceipt class
- A KeywordResponse class, to represent the keywords and their associated response
- A SmsMessage class, which represents both inbound and outbound SMS messages
We’ll assume we’re starting from scratch, and no previous project exists.
Creating the ASP.NET Web Site
Open up Visual Studio. From the File menu, choose New, then Web Site. Call the Web site MobiforgeDemo and click OK. You’ll have a variety of options depending upon the version of Visual Studio used; ensure you create an ASP.NET Empty Web Application.
Once the project is created, your Solution Explorer (this is usually displayed on the right-hand side of the screen) will look something like this (if you’re using Visual Studio 2010):
Let’s add in the classes first. Right-click on MobiforgeDemo and choose Add -> New Item from the pop-up menu. Choose Class, and set the file name as iTaggDeliveryReceipt.cs. Then click Add to create the class.
Repeat the process two more times, setting the file name as:
- KeywordResponse.cs
- SmsMessage.cs
Finally, we need to add the listener Web page. Select the Add -> New Item option from the pop-up menu again, but this time choose Web Form. Name the form listener.aspx and click Add.
Solution Explorer will now look like this:
We now have all the building blocks in place, and we can start adding code.
iTaggDeliveryReceipt Class
Let’s add the code for the iTaggDeliveryReceipt class first. This processes delivery receipt XML sent to our system from iTagg and updates the database accordingly. Double-click on iTaggDeliveryReceipt.cs in Solution Explorer, and replace the code there with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
using System; using System.Collections.Generic; using System.Xml; using System.Web; /// <summary> /// Represents an iTagg delivery receipt /// </summary> public class iTaggDeliveryReceipt { private string _version = string.Empty; // The iTagg delivery receipt version private string _msisdn = string.Empty; // The mobile number the message was delivered to private string _submissionRef = string.Empty; // The iTagg message ID private string _status = string.Empty; // Message delivery status private string _reason = string.Empty; // Message success/failure reason private string _timestamp = string.Empty; // Time/date message was delivered private string _retry = string.Empty; // Denotes whether message should be retried // Public accessors public string Version { get { return _version; } set { _version = value; } } public string Msisdn { get { return _msisdn; } set { _msisdn = value; } } public string SubmissionRef { get { return _submissionRef; } set { _submissionRef = value; } } public string Status { get { return _status; } set { _status = value; } } public string Reason { get { return _reason; } set { _reason = value; } } public string Timestamp { get { return _timestamp; } set { _timestamp = value; } } public string Retry { get { return _retry; } set { _retry = value; } } /// <summary> /// Accepts XML parameter as string and populates class properties /// </summary> /// <param name="XmlInput"></param> public iTaggDeliveryReceipt(string XmlInput) { try { // Create XML document XmlDocument doc = new XmlDocument(); doc.LoadXml(XmlInput); // Use XPATH to obtain values _version = _returnNodeValue(doc, "/itagg_delivery_receipt/version"); _msisdn = _returnNodeValue(doc, "/itagg_delivery_receipt/msisdn"); _submissionRef = _returnNodeValue(doc, "/itagg_delivery_receipt/submission_ref"); _status = _returnNodeValue(doc, "/itagg_delivery_receipt/status"); _reason = _returnNodeValue(doc, "/itagg_delivery_receipt/reason"); _timestamp = _returnNodeValue(doc, "/itagg_delivery_receipt/timestamp"); _retry = _returnNodeValue(doc, "/itagg_delivery_receipt/retry"); } catch (Exception ex) { HttpContext.Current.Trace.Warn("Error processing receipt: " + ex.Message); } } /// <summary> /// Returns the actual value for the specified node /// </summary> /// <param name="document"></param> /// <param name="xpathQuery"></param> /// <returns></returns> private string _returnNodeValue(XmlDocument document, string xpathQuery) { string nodeValue = string.Empty; if (document != null) { XmlNode node = document.SelectSingleNode(xpathQuery); if (node != null && !string.IsNullOrEmpty(node.InnerText)) { nodeValue = node.InnerText; } } return nodeValue; } } |
The code is pretty simple. We start off by defining a bunch of private properties and public accessors. Once this is done we define a constructor to accept and process the XML sent to us by iTagg. This constructor calls a private method that returns a value for the specified XPATH query.
SmsMessage Class
We need a mechanism that allows us to represent SMS messages in the database. This mechanism should allow us to:
- Represent the properties of an SMS message
- Store messages in the database, whether they are inbound or outbound
We’ll create the SmsMessage class for this very purpose. Open SmsMessage.cs and enter this code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
using System; using System.Collections.Generic; using System.Web; using System.Data; using System.Data.SqlClient; /// <summary> /// Summary description for InboundSmsMessage /// </summary> public class SmsMessage { private string _aggregatorMessageID = string.Empty; // The aggregator message ID private string _originator = string.Empty; // The message originator private string _recipient = string.Empty; // The message recipient private DateTime _messageDate = DateTime.MinValue; // Date/time the message was received private string _messageBody = string.Empty; // The message body private int _keywordID = 0; // Stores the keyword ID the message was sent via or received on private bool _inboundMessage = false; // Determines whether a message is an inbound message or not (true if inbound) // Public accessors public string AggregatorMessageID { get { return _aggregatorMessageID; } set { _aggregatorMessageID = value; } } public string Originator { get { return _originator; } set { _originator = value; } } public string Recipient { get { return _recipient; } set { _recipient = value; } } public DateTime MessageDate { get { return _messageDate; } set { _messageDate = value; } } public string MessageBody { get { return _messageBody; } set { _messageBody = value; } } public int KeywordID { get { return _keywordID; } set { _keywordID = value; } } public bool InboundMessage { get { return _inboundMessage; } set { _inboundMessage = value; } } /// <summary> /// Accepts Input Stream parameter as a string and populates class properties /// This is used for inbound messages /// </summary> /// <param name="InputStream"></param> public SmsMessage(string InputStream) { // Added to process responses // Set the inbound message property to true _inboundMessage = true; // Obtain the parameter values by splitting on the ampersand string[] parameters = InputStream.Split(new char[] { '&' }); // And now we can loop around the parameters and process each value // Note that we don't process every value iTagg send to us as we don't need them all for (int i = 0; i < parameters.Length; i++) { // Separate parameter name and value string[] parameterValues = parameters[i].Split(new char[] { '=' }); // Should have two values if (parameterValues.Length == 2) { // Assign the value to the appropriate class property switch (parameterValues[0]) { case "source": _originator = parameterValues[1]; break; case "dest": _recipient = parameterValues[1]; break; case "dtime": // Length should be 12 // 4 digits for YYYY // 2 digits for MM // 2 digits for DD // 2 digits for HH // 2 digits for MM if (parameterValues[1].Length == 12) { int year = Convert.ToInt32(parameterValues[1].Substring(0, 4)); int month = Convert.ToInt32(parameterValues[1].Substring(4, 2)); int day = Convert.ToInt32(parameterValues[1].Substring(6, 2)); int hour = Convert.ToInt32(parameterValues[1].Substring(8, 2)); int minute = Convert.ToInt32(parameterValues[1].Substring(10, 2)); DateTime dateToProcess = new DateTime(year, month, day, hour, minute, 0); _messageDate = dateToProcess; } break; case "message": _messageBody = parameterValues[1]; break; case "message_id": _aggregatorMessageID = parameterValues[1]; break; } } } } /// <summary> /// Used by outbound messages - constructs a message from specific values /// </summary> /// <param name="AggregatorMessageID"></param> /// <param name="Originator"></param> /// <param name="Recipient"></param> /// <param name="MessageBody"></param> public SmsMessage(int KeywordID, string AggregatorMessageID, string Originator, string Recipient, string MessageBody) { _keywordID = KeywordID; _aggregatorMessageID = AggregatorMessageID; _originator = Originator; _recipient = Recipient; _messageBody = MessageBody; } /// <summary> /// Inserts the message into the database /// </summary> public void WriteMessageToDatabase() { // Only process if we have valid data if (_aggregatorMessageID != string.Empty && _keywordID > 0) { // Ensure current date is set if no value specified if (_messageDate == DateTime.MinValue) { _messageDate = DateTime.Now; } SqlConnection conn = new SqlConnection( "Data Source=DBSERVER;Initial Catalog=MobiforgeMessageDemo;Integrated Security=true"); SqlCommand cmd = new SqlCommand("usp_InsertMessage", conn); cmd.CommandType = CommandType.StoredProcedure; // Set the parameters cmd.Parameters.Add(new SqlParameter("@KeywordID", _keywordID)); cmd.Parameters.Add(new SqlParameter("@AggregatorMessageID", _aggregatorMessageID)); cmd.Parameters.Add(new SqlParameter("@InboundMessage", _inboundMessage)); cmd.Parameters.Add(new SqlParameter("@Originator", _originator)); cmd.Parameters.Add(new SqlParameter("@Recipient", _recipient)); cmd.Parameters.Add(new SqlParameter("@MessageBody", _messageBody)); cmd.Parameters.Add(new SqlParameter("@MessageDate", _messageDate)); conn.Open(); cmd.ExecuteNonQuery(); conn.Close(); } } } |
The first part of the code listing defines the properties – message body, originator, recipient etc. Then we have two constructors. The first constructs a message from an Input Stream. This is used to create inbound message objects, as iTagg will deliver an input stream to our listener page when they receive an inbound SMS message on our behalf. The second is used to construct outbound messages and accepts a defined set of parameters that we can use to create the SmsMessage object.
The last method in the listing is WriteMessageToDatabase. This calls the usp_InsertMessage stored procedure to insert the message into the database. When instantiating the connection, ensure you replace DBSERVER with the name of your SQL Server.
KeywordResponse Class
The last class we’ll create is KeywordResponse. This is another simple class that will:
- Define a set of properties to define a keyword response
- Create a keyword response from an inbound message, by reading details from the database
- Have the ability to send the response SMS message
Open up the KeywordResponse.cs file, and add in the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
using System; using System.Collections.Generic; using System.Web; using System.Data.SqlClient; using System.Data; /// <summary> /// Represents an outbound keyword response /// </summary> public class KeywordResponse { private int _keywordID; private bool _sendResponse; private string _responseOriginator; private string _responseMessageBody; // Public accessors public int KeywordID { get { return _keywordID; } set { _keywordID = value; } } public bool SendResponse { get { return _sendResponse; } set { _sendResponse = value; } } public string ResponseOriginator { get { return _responseOriginator; } set { _responseOriginator = value; } } public string ResponseMessageBody { get { return _responseMessageBody; } set { _responseMessageBody = value; } } /// <summary> /// Accepts Input Stream parameter as a string and populates class properties /// </summary> /// <param name="InputStream"></param> public KeywordResponse(string InputStream) { // Obtain the parameter values by splitting on the ampersand string[] parameters = InputStream.Split(new char[] { '&' }); // Temporary placeholders string keyword = string.Empty; string shortcode = string.Empty; // And now we can loop around the parameters and process each value // Note that we don't process every value iTagg send to us as we don't need them all for (int i = 0; i < parameters.Length; i++) { // Separate parameter name and value string[] parameterValues = parameters[i].Split(new char[] { '=' }); // Should have two values if (parameterValues.Length == 2) { // Assign the value to the appropriate class property switch (parameterValues[0]) { case "dest": shortcode = parameterValues[1]; break; case "matched_keyword": keyword = parameterValues[1]; break; } } } // We now check if we have the inbound values - if we do, we call the database to // obtain the keyword properties if (!string.IsNullOrEmpty(shortcode) && !string.IsNullOrEmpty(keyword)) { _readKeywordFromDatabase(shortcode, keyword); } } /// <summary> /// Returns keyword message data from the database /// </summary> /// <param name="shortcode"></param> /// <param name="keyword"></param> private void _readKeywordFromDatabase(string shortcode, string keyword) { SqlConnection conn = new SqlConnection( "Data Source=DBSERVER;Initial Catalog=MobiforgeMessageDemo;Integrated Security=true"); SqlCommand cmd = new SqlCommand("usp_SelectKeywordDetails", conn); cmd.CommandType = CommandType.StoredProcedure; // Set the parameters cmd.Parameters.Add(new SqlParameter(@"Keyword", keyword)); cmd.Parameters.Add(new SqlParameter("@Shortcode", shortcode)); conn.Open(); SqlDataReader dr = cmd.ExecuteReader(); while (dr.Read()) { _keywordID = (int)dr["KeywordID"]; _sendResponse = (bool)dr["sendResponse"]; _responseOriginator = (string)dr["ResponseOriginator"]; _responseMessageBody = (string)dr["ResponseMessageBody"]; } conn.Close(); } // Sends the keyword response message to the specified recipient public void SendSmsMessage(string Recipient) { // Initialise the Web request System.Net.WebClient webClient = new System.Net.WebClient(); // Prepare the HTTP POST data // Change the user name as appropriate // Change the password as appropriate // Type is set to text for SMS - could also be set to binary // Route is set to 4 - this is the iTagg budget route. Other routes are 7 (for UK) and 8 (for Global) string postData = "usr=USERNAME&pwd=PASSWORD&from= " + _responseOriginator + "&to=" + Recipient + "&type=text&route=4&txt=" + _responseMessageBody; webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); // Make the request to iTagg string result = webClient.UploadString("https://secure.itagg.com/smsg/sms.mes", postData); //// Parse the response returned by iTagg to obtain message ID string[] iTaggResponse = result.Split(new char[] { '|' }); string iTaggMessageID = string.Empty; // If a valid response was received, store the message ID // In a real system we'd implement a much more robust mechanism for parsing responses if (iTaggResponse.Length == 5 && iTaggResponse[2] == "submission referencen0") { // Remove new line tag iTaggMessageID = iTaggResponse[4].Replace("n", string.Empty); } else { // We would add code to handle errors here } if (!string.IsNullOrEmpty(iTaggMessageID)) { // Create a message and write it to the database SmsMessage message = new SmsMessage(_keywordID, iTaggMessageID, _responseOriginator, Recipient, _responseMessageBody); message.WriteMessageToDatabase(); } } } |
We begin by defining four properties:
- Keyword ID – The internal keyword identifier; we use this to easily identify keywords within the database.
- Send Response – A Boolean value, which determines whether we send a response back when an inbound message is received. If this is true, we send the response.
- Response Originator – Response Message Body
These are the values sent back to the person who sent the inbound SMS message. The Response Originator determines who the message was sent from; the Response Message Body represents the content of the message.
Next up we define our constructor. This accepts an Input Stream, which in this case is the HTTP POST string passed to us by iTagg. We then pull out the parts of this Input Stream that allow us to identify the keyword to which the message was sent.
Our next method is _readKeywordFromDatabase. This is called by the constructor to pull out the keyword response details from the database we created earlier. This is a private method and cannot be called outside of the class.
Last up we have SendSmsMessage. This is a public method and is used to send the response SMS message back to the original sender via iTagg. You’ll note at the end of this method we create a SmsMessage object and write it to the database – this creates a record of the message we’ve just sent in the database.
When instantiating the connection in readKeywordFromDatabase, ensure you replace DBSERVER with the name of your SQL Server. Also, ensure you set the correct user name and password for your iTagg account in the SendSmsMessage method.
Listener.aspx
The last piece of the puzzle is the listener page. This page will be called when an inbound message or delivery receipt arrives, and will contain code to process the inbound message/delivery receipt. This page will perform the following processing:
- Determine whether the data being passed by iTagg is a delivery receipt or an inbound message
- If the data represents a delivery receipt, the database is updated
- If the data represents an inbound message, we obtain the keyword details, write the message to the database, and then send back a response SMS message (if the keyword is configured to do so)
There are four key methods defined here to do our work:
- Page_Load – This is the standard ASP.NET page load event. This is the first event called. It calls DeliveryReceiptReceived to determine what type of message has been received, and then calls the appropriate processing method.
- DeliveryReceiptReceived – Returns true if a delivery receipt has been received. If an inbound SMS message was received, it returns false.
- ProcessDeliveryReceipt – Processes a delivery receipt. The message ID is identified and the appropriate message in the database is updated.
- ProcessInboundSmsMessage – Processes an inbound SMS message. The sending of the outbound response SMS message is performed here.
Here’s the code, type or paste it into listener.aspx.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Data; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.IO; public partial class listener : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { try { // Check if some POST data was passed if (Request.InputStream != null) { StreamReader sr = new StreamReader(Request.InputStream); // Convert the POST stream to a string // We remove the xml= tag at the front, otherwise the XML will not parse // NOTE - "xml" is only passed if we receive a delivery receipt string inputStream = sr.ReadToEnd().Replace("xml=", string.Empty); if (deliveryReceiptReceived(inputStream)) { processDeliveryReceipt(inputStream); } else { processInboundSmsMessage(inputStream); } } } catch (Exception ex) // Output the error details { Response.Write("Error occurred whilst processing: " + ex.Message); Trace.Write("Error occurred whilst processing: " + ex.Message); } } /// <summary> /// Determines whether we should process a delivery receipt or an inbound SMS message /// </summary> /// <param name="inputStream">The data to check</param> /// <returns></returns> private bool deliveryReceiptReceived(string inputStream) { // Assume we're processing an inbound SMS message bool deliveryReceiptReceived = false; // If the input stream contains a delivery receipt XML element, // return delivery receipt if (inputStream.Contains("itagg_delivery_receipt")) { deliveryReceiptReceived = true; } return deliveryReceiptReceived; } /// <summary> /// Process the inbound delivery receipt and add it to the database /// </summary> /// <param name="inputStream"></param> private void processDeliveryReceipt(string inputStream) { // Create a delivery receipt iTaggDeliveryReceipt dr = new iTaggDeliveryReceipt(inputStream); // Write out the delivery receipt details to the trace Trace.Write("iTagg DR data: " + inputStream); Trace.Write("DR submission ref: " + dr.SubmissionRef); Trace.Write("DR status: " + dr.Status); // Only process if the status is "delivered" if (dr.Status == "Delivered") { // Try and update the database SqlConnection conn = new SqlConnection( "Data Source=DBSERVER;Initial Catalog=MobiforgeMessageDemo;Integrated Security=true"); SqlCommand cmd = new SqlCommand("usp_MarkMessageAsDelivered", conn); cmd.CommandType = CommandType.StoredProcedure; // Make sure we set the aggregator message ID cmd.Parameters.Add(new SqlParameter("@AggregatorMessageID", dr.SubmissionRef)); conn.Open(); cmd.ExecuteNonQuery(); conn.Close(); // Show we updated the database in the trace Trace.Write("Successfully updated database."); } else // A different status has been received, so we output a message to show this { Response.Write("Message was not delivered. Status: " + dr.Status + "<BR>"); } // Write out a confirmation message at the end Response.Write("OK"); } /// <summary> /// Processes an inbound SMS message and adds it to the database /// </summary> /// <param name="inputStream"></param> private void processInboundSmsMessage(string inputStream) { // Create a keyword response // Create an inbound SMS message SmsMessage message = new SmsMessage(inputStream); // Write out the SMS details to the trace Trace.Write("iTagg SMS data: " + inputStream); // Only process if we have valid data if (message.AggregatorMessageID != string.Empty) { // Obtain keyword details KeywordResponse keyword = new KeywordResponse(inputStream); Trace.Write("Message body received: " + message.InboundMessage); Trace.Write("Keyword message received on: " + keyword.KeywordID); // If we found the keyword, process the inbound message if (keyword.KeywordID > 0) { SmsMessage inboundMessage = new SmsMessage(inputStream); inboundMessage.KeywordID = keyword.KeywordID; inboundMessage.WriteMessageToDatabase(); // And now, we send the response - if required if (keyword.SendResponse) { Trace.Warn("Send response is true - about to send response"); keyword.SendSmsMessage(inboundMessage.Originator); } } } // Write out a confirmation message at the end Response.Write("OK"); } } |
Compile the application – it should build without any issues (make sure you change DBSERVER to the name of your SQL Server). Deploy it to IIS as you would any other application. We’re almost ready to roll!
Configuring iTagg
Assuming you have set up a keyword with iTagg, you now need to tell iTagg to direct any messages sent to this keyword to your new Web site. Let’s assume the Web site URL is:
http://mobiforge.com/mobiforgedemo/listener.aspx
You need to log on to the ITagg control panel, and set up the keyword URL. We also need to set up the delivery receipt URL.
To set these URLs up, point your browser at www.itagg.com. Log on to the control panel. After doing this, a screen should appear listing your keywords.
Click on the keyword and set the URL. This should be set to the URL you have published the Web site to. Make sure you add the listener.aspx part. Click OK to save.
Nearly ready – we just need to set up delivery receipts. In the menu on the left, click on the Delivery Receipts option. When the screen appears, set the URL to the same as that you’ve just specified for the keyword.
Click Update to save. You’re done!
Testing The System
All that is left to do now is test the system. So grab your phone and type in your keyword! If your keyword is mobiforge, send mobiforge to 60300. Please don’t try this – it isn’t a live example! If you’d like to try out a real company that’s using iTagg, try texting thebestof to 60300.
Once you’ve sent your message, wait for a few seconds and you should receive a response back:
Let’s check out the database. We should have two messages in there; the inbound message we’ve just sent, and the outbound message the system has just sent.
Note the Delivered column in the table; the outbound message is showing true in the column, which tells us our delivery receipt processing is working too (the inbound messages will always display false). Hurrah!
Summary
So there we are – a complete messaging system in a few easy steps! Remember that you don’t have to use iTagg – you are free to use any messaging provider you wish. The system we’ve just built isn’t robust – it needs more error handling, for example – but it gives you the basis to go on and create a fully automated, inbound and outbound SMS messaging system. Have fun sending – and receiving!