I recently started working on integrating Zeep Mobile into an app. The documentation provided by Zeep is not the best or accurate but they do provide source code examples in…python, pearl, java. I asked about c# and the response was “i’m sure someone has posted something, search the group”. Well, no-one posted a solution so I went about coding and iteratively developing the code to send an SMS. Running into an issue, I even tried StackOverflow but that wasn’t too helpful either.
Well I finally managed to get my code working. Here is some sample c# code to send messages using the zeep framework.
I hope you all looking for c# code find this helpful.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Web;
using System.Web.Handlers;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace ConsoleApplication1
{
class Program
{
public static string API_KEY = “YOUR_API_KEY_GOES_HERE! INCLUDE DASHES!”;
public static string SECRET_ACCESS_KEY = “YOUR_SECRET_KEY_GOES_HERE!”;
static void Main(string[] args)
{
Console.WriteLine(“BLAST – \r\n\r\n”);
BlastTcpPost();
Console.WriteLine(“SEND – \r\n\r\n”);
SendTcpPost();
}
///
///
public static void BlastTcpPost()
{
SendSMS(
“https://api.zeepmobile.com/messaging/2008-07-14/blast_message”, // URL for Send_Message
“You are on blast”, // Message to send
string.Empty // No UserId to send.
);
}
///
///
public static void SendTcpPost()
{
// Note:- 22 I use for the UserId is just a user I have signed up. Yours may be different and you
// might want to pass that in as a parameter.
SendSMS(
“https://api.zeepmobile.com/messaging/2008-07-14/send_message”, // URL for Send_Message
“You are a user…good job!”, // Message to send
“22” // User Id in your system.
);
}
///
///
/// URL that the POST must be directed to.
/// Message that is to be sent.
/// UserId in your Zeep System. Only required if your sending a Single Message to a User.
/// Otherwise, just send a string.Empty.
///
public static string SendSMS(string requestUrl, string body, string user)
{
string parameters = “”;
string requestHeaders = “”;
string responseData = “”;
// FORMAT must be Sun, 06 Nov 1994 08:49:37 GMT
string http_date = DateTime.UtcNow.ToString(“r”);
// Clean the text to send
body = HttpUtility.UrlEncode(body, System.Text.Encoding.UTF8);
if (user.Length > 0) parameters += “user_id=” + user + “&”;
if (body.Length > 0) parameters += “body=” + body;
// String that will be converted into a signature.
string canonicalString = API_KEY + http_date + parameters;
//————START HASH COMPUTATION———————
// Compute the Base64 HMACSHA1 value
HMACSHA1 hmacsha1 = new HMACSHA1(SECRET_ACCESS_KEY.ToByteArray());
// Compute the hash of the input file.
byte[] hashValue = hmacsha1.ComputeHash(canonicalString.ToByteArray());
String b64Mac = hashValue.ToBase64String();
String authentication = String.Format(“Zeep {0}:{1}”, API_KEY, b64Mac);
//———–END HASH COMPUTATION————————
string auth = String.Format(“Zeep {0}:{1}”, API_KEY, b64Mac);
System.Uri uri = new Uri(requestUrl);
System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(uri.Host, uri.Port);
string requestMethod = “POST ” + uri.LocalPath + ” HTTP/1.1\r\n”;
// Set Headers for the POST message
requestHeaders += “Host: api.zeepmobile.com\r\n”;
requestHeaders += “Authorization: ” + auth + “\r\n”;
requestHeaders += “Date: ” + http_date + “\r\n”;
requestHeaders += “Content-Type: application/x-www-form-urlencoded\r\n”;
requestHeaders += “Content-Length: ” + parameters.ToByteArray().Length + “\r\n”;
requestHeaders += “\r\n”;
// Get the data to be sent as a byte array.
Byte[] data = System.Text.Encoding.UTF8.GetBytes(requestMethod + requestHeaders + parameters + “\r\n”);
// Send the message to the connected TcpServer.
NetworkStream stream = client.GetStream();
// SSL Authentication is used because the Server requires https.
System.Net.Security.SslStream sslStream = new System.Net.Security.SslStream(
stream,
false,
new System.Net.Security.RemoteCertificateValidationCallback(ValidateServerCertificate));
sslStream.AuthenticateAsClient(uri.Host);
// Send the data over the SSL stream.
sslStream.Write(data, 0, data.Length);
sslStream.Flush();
// Receive the TcpServer.response.
for (int i = 0; i < 100; i++)
{
if (stream.DataAvailable)
{
break;
}
System.Threading.Thread.Sleep(100);
}
Byte[] bytes = new byte[1024];
System.Text.StringBuilder sb = new System.Text.StringBuilder();
while (stream.DataAvailable)
{
int count = sslStream.Read(bytes, 0, 1024);
if (count == 0)
{
break;
}
sb.Append(System.Text.Encoding.UTF8.GetString(bytes, 0, count));
}
responseData = sb.ToString();
Console.WriteLine(responseData);
// Close everything.
client.Close();
return responseData;
}
// The following method is invoked by the RemoteCertificateValidationDelegate.
// We want to make sure the SSL has no Policy errors and is safe.
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// Somehow the cert always has PolicyErrors so I am returning true regardless.
return true;
//if (sslPolicyErrors == SslPolicyErrors.None)
// return true;
//Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
//// Do not allow this client to communicate with unauthenticated servers.
//return false;
}
}
public static class Extensions
{
public static byte[] ToByteArray(this string input)
{
UTF8Encoding encoding = new UTF8Encoding();
return encoding.GetBytes(input);
}
public static string ToBase64String(this byte[] input)
{
return Convert.ToBase64String(input);
}
}
}
[/sourcecode]
First, THANK YOU SO MUCH FOR SHARING THIS!
Second, I think there is one change that should be made:
On line 105:
requestHeaders += “Date: ” + DateTime.UtcNow.ToString(“r”) + “\r\n”;
I think it would be better if it said:
requestHeaders += “Date: ” + http_date + “\r\n”;
Third, I’m getting:
400 InvalidSubscription The subscrtiption specified is not active or is blocking access from your application.
Which subscription would this be? The user_id? My developer subscription?
Please help if you can 🙂
Hi Ed. Glad to help with the code example. You are absolutely correct. It should be changed to http_date instead of recalculating it. Zeep rejects messages that have a date different to what they expect the UTC date to be so its important to keep dates uniform. I will make the change to the code. Thanks for the feedback. 🙂
Answering my own question, it’s the subscription referred to by the user_id 😀
Aboutdev, you rock. This saves me an unbelievable amount of time.
Also, I am guessing that the reason I am getting the same “InvalidSubscription” message received by Ed is due to Zeep blocking requests from anything but the domain registered for the specific API/secret key. For obvious reasons, the registered domain is not my dev machine. 🙂
Thanks Daryll. Yes the request must originate from the domain you registered. Also timestamps used must also be up to date so ensure that your server is updating time regularly.
You are a life saver! Now let me tell you why… I’m creating this ASP.Net website that has some texting features, and I’ve created the whole thing, only to realize stupid .Net does not allow manual manipulation of the Date header, without which Zeep wouldn’t work! It was really driving me nuts, till I finally found your entry. Zeep must post your article on their homepage!
However, I caught a little problem in your code, and that is the user_id must also be URL Encoded (according to Simon Wex, here: http://groups.google.com/group/zeep-mobile/browse_thread/thread/a15e54affab3d570) and so, your line:
// Clean the text to send
body = HttpUtility.UrlEncode(body, System.Text.Encoding.UTF8);
I also added this underneath:
user = HttpUtility.UrlEncode(user, System.Text.Encoding.UTF8);
And it works great. This takes care of the situation where the user_id is specified with the preceding plus sign (i.e. +1xxxyyyzzzz)
Well, thank you so much for this great post, and I hope it becomes a bit more visible in search engines, coz I googled, bang! (past tense for bing I guess!), and yahooed the crap out of the web and didn’t find any solution, until I saw a link to this in some pretty hidden place on Zeep website.
Cheers,
Thanks for the feedback Arash. Happy coding.
Thanks for the post!
Just wondering if you had any idea on how to actually receive the messages that come in from the users? Zeep’s website does a bad job of explaining this stuff
Thanks!!!
Leyla, see Adil’s post on how to receive messages.
[…] https://aboutdev.wordpress.com/2009/02/09/zeep-mobile-sms-with-ads/ talks about sending messages to your subscribed users. I made some modifications to the code given there. Since this is originally AboutDev’s code, you’ll have to head over to that site and follow my modifications below. […]
Thank you for this post!
I referred to your code to give a complete C#/ASP.NET example for ZeepMobile – I hope you don’t mind!
Here’s the post link: http://elektron9.wordpress.com/2010/03/23/zeepmobile-sending-and-receiving-messages-in-asp-net-c/
Thanks for writing that follow up article Adil. Its great to see Zeep being used and growing in a community. Keep up the good work.
Thanks for forging the trail! Good article
Hi,
I just need your help, getting this problem in
System.Net.Security.SslStream sslStream = new System.Net.Security.SslStream(
stream,false,new System.Net.Security.RemoteCertificateValidationCallback(ValidateServerCertificate));
sslStream.AuthenticateAsClient(uri.Host);
“Error: No overload for ‘ValidateServerCertificate’ matches delegate ‘System.Net.Security.RemoteCertificateValidationCallback’
Thank you in advance.
CLN, do you have the method ValidateServerCertificate in your code? If not you should add it from my code above. The error means that the delegate that is passed into the RemoteCertificateValidationCallback is not correct or could not be found.