In this article, you will learn how to implement a serverless free SMS web API using Azure function.
Introduction
The SMS API has three main pieces which fit together as follows:
- SMS Providers: There are various third party SMS providers which allow sending of SMS to any mobile device without charging anything, for this piece of Azure function, we are choosing 160by2.com SMS provider which allows sending SMS to any mobile for free.
- Screen Scraping: The SMS providers do not allow sending SMS without actually visiting the site. Using C# code, we will be submitting login form and then programmatically submit the web form which sends SMS.
- Serverless function on Azure: The serverless function hosted on Azure will allow us to set up web API which will be consumable by any REST client.
Background
Before you go through this article, have a look at the following:
- What is serverless
- Introduction to serverless functions on Azure
- Web scraping with C#
- CsQuery: jQuery like DOM manipulation using .NET
Using the Code
Before we dive into writing our SMS web API using Azure function, we need to understand the sequence of actions which will be carried out:
The SmsWebClient
class inherits from C# WebClient
class which will allow us to programmatically send HTTP
POST
/GET
methods.
We will implement the programmatic execution of HTTP POST
and GET
methods to the 160by2.com SMS provider. 160by2.com is a free SMS provider, you need obtain a username and password to send SMS. The SMS provider class has Login()
and SendSms()
functions to handle the main job. We are using the CsQuery
library to perform HTML DOM manipulations.
On the 160by2.com website, we have login form containing the username and password.
When we inspect the HTML of the web form, we can see that there are two input fields with keys id=username
and id=password
.
When we click on Login button, the form data is posted using HTTP POST
method.
To programatically do this, we will create a C# NameValueCollection
and add the values for web form keys and then submit the form using the UploadValues
method of WebClient
.
var Client = new WebClient();
string loginPage = "http://www.160by2.com/re-login";
NameValueCollection data = new NameValueCollection();
data.Add("username", UserName);
data.Add("password", Password);
Client.UploadValues(loginPage, "POST", data);
The Send SMS form simply has the mobile number and message in UI.
To inspect what form values are submitted when we submit this web form, we will use the Google Chrome console, you can use Fiddler too.
Click F12 to open the Chrome developer console, then go to network tab and in UI, submit the Send SMS form by clicking Send now. The submitted request appears as follows:
Just like the login form, we need to programmatically submit these form data keys along with values.
var base_url = "http://www.160by2.com/";
var recipient = "8888YOURNUMBER";
var message = "This is test SMS message";
string cookieVal = CookieJar.GetCookies(new Uri(base_url))["JSESSIONID"].Value.Substring
(cookieVal.IndexOf('~') + 1);
CQ sendSmsPage = Client.DownloadString(base_url + "SendSMS?id=" + cookieVal);
NameValueCollection data = new NameValueCollection();
CQ form = sendSmsPage.Find("form[id=frm_sendsms]");
CQ inputs = form.Find("input[type=hidden]");
foreach (var input in inputs)
{
CQ inp = input.Cq();
data.Add(inp.Attr("name"), inp.Attr("value"));
}
CQ mobileNumberBox = form.Find("input[placeholder='Enter Mobile Number or Name']")[0].Cq();
data.Add(mobileNumberBox.Attr("name"), recipient);
data.Add("sendSMSMsg", message);
string sendSmsPost = base_url + data["fkapps"];
data["hid_exists"] = "no";
data["maxwellapps"] = cookieVal;
data.Add("messid_0", "");
data.Add("messid_1", "");
data.Add("messid_2", "");
data.Add("messid_3", "");
data.Add("messid_4", "");
data.Add("newsExtnUrl", "");
data.Add("reminderDate", DateTime.Now.ToString("dd-MM-yyyy"));
data.Add("sel_hour", "");
data.Add("sel_minute", "");
data.Add("ulCategories", "29");
Client.UploadValues(sendSmsPost, data);
The final class is as follows:
using CsQuery;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Text;
using System.Linq;
namespace azuresmsapp
{
public class OneSixtybyTwo
{
public string UserName { get; set; }
public string Password { get; set; }
private CookieContainer CookieJar { get; set; }
private SmsWebClient Client { get; set; }
private string base_url = "http://www.160by2.com/";
private bool IsLoggedIn = false;
public OneSixtybyTwo(string username, string password)
{
UserName = username;
Password = password;
CookieJar = new CookieContainer();
Client = new SmsWebClient(CookieJar, false);
}
public bool Login()
{
string loginPage = base_url + "re-login";
NameValueCollection data = new NameValueCollection();
data.Add("rssData", "");
data.Add("username", UserName);
data.Add("password", Password);
byte[] loginResponseBytes = Client.UploadValues(loginPage, "POST", data);
CQ loginResponse = System.Text.Encoding.UTF8.GetString(loginResponseBytes);
IsLoggedIn = loginResponse.Find("[type=password]").Count() == 0;
return IsLoggedIn;
}
public bool SendSms(string recipient, string message)
{
if (IsLoggedIn == false)
throw new Exception("Not logged in");
string cookieVal = CookieJar.GetCookies(new Uri(base_url))["JSESSIONID"].Value;
cookieVal = cookieVal.Substring(cookieVal.IndexOf('~') + 1);
CQ sendSmsPage = Client.DownloadString(base_url + "SendSMS?id=" + cookieVal);
NameValueCollection data = new NameValueCollection();
CQ form = sendSmsPage.Find("form[id=frm_sendsms]");
CQ inputs = form.Find("input[type=hidden]");
foreach (var input in inputs)
{
CQ inp = input.Cq();
data.Add(inp.Attr("name"), inp.Attr("value"));
}
CQ mobileNumberBox =
form.Find("input[placeholder='Enter Mobile Number or Name']")[0].Cq();
data.Add(mobileNumberBox.Attr("name"), recipient);
data.Add("sendSMSMsg", message);
string sendSmsPost = base_url + data["fkapps"];
data["hid_exists"] = "no";
data["maxwellapps"] = cookieVal;
data.Add("messid_0", "");
data.Add("messid_1", "");
data.Add("messid_2", "");
data.Add("messid_3", "");
data.Add("messid_4", "");
data.Add("newsExtnUrl", "");
data.Add("reminderDate", DateTime.Now.ToString("dd-MM-yyyy"));
data.Add("sel_hour", "");
data.Add("sel_minute", "");
data.Add("ulCategories", "29");
Client.UploadValues(sendSmsPost, data);
return true;
}
}
}
Now our main piece of cake containing the cherry...
To send SMS, we create an instance of OneSixtybyTwo
class, call the Login
function and the call the SendSMS
function.
OneSixtybyTwo objSender = new OneSixtybyTwo ("160BY2.COM_USERNAME", "160BY2.COM_PASSWORD");
if (objSender.Login()) {
var sendResult = objSender.SendSms(number, message);
}
Let's dive into the Azure serverless function having HTTP trigger:
The function can be initiated either by HTTP GET
of POST
method, so we read the posted mobile number and message using the following code:
string number = req.Query["number"];
string message = req.Query["message"];
string requestBody = new StreamReader(req.Body).ReadToEnd();
dynamic data = JsonConvert.DeserializeObject(requestBody);
number = number ?? data?.number;
message = message ?? data?.message;
The final function is as follows:
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
using System;
namespace azuresmsapp
{
public static class SendSMS
{
[FunctionName("SendSMS")]
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function,
"get", "post", Route = null)]HttpRequest req, TraceWriter log)
{
try
{
log.Info("C# HTTP trigger function processed a request.");
string number = req.Query["number"];
string message = req.Query["message"];
string requestBody = new StreamReader(req.Body).ReadToEnd();
dynamic data = JsonConvert.DeserializeObject(requestBody);
number = number ?? data?.number;
message = message ?? data?.message;
OneSixtybyTwo objSender = new OneSixtybyTwo
("160BY2.COM_USERNAME", "160BY2.COM_PASSWORD");
if (objSender.Login())
{
var sendResult = objSender.SendSms(number, message);
if (sendResult)
{
return (ActionResult)new OkObjectResult($"Message sent");
}
else
{
throw new Exception($"Sending failed");
}
}
else
{
throw new Exception("Login failed");
}
}
catch (System.Exception ex)
{
return new BadRequestObjectResult("Unexpected error, " + ex.Message);
}
}
}
}
Angular Client for API
I will be using Angular 7 app as client for the web API. You can use any desired client.
Before we consume the API, we need to allow requests to the API from all origins.
To do this, navigate to the Azure function => Click on Platform features => Click on CORS.
Delete existing entries and add new entry '*
' as shown below:
Now in the Angular 7 client to send the SMS, we write the following code:
import { Component } from '@angular/core';
import { Message } from './dtos/message';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public message:Message;
private baseUrl = "https://YOUR_FUNCTION_URL_HERE";
constructor(private httpClient: HttpClient){
this.message = {
message: "",
number: ""
};
}
Send(){
alert("Sending sms...");
this.httpClient.get(this.baseUrl + '&number=' + this.message.number +
'&message=' + this.message.message).subscribe((x)=>{}, (y)=>{},()=>{
alert("Message sent successfully!");
this.message = {
message: "",
number: ""
};
});
}
}
The user interface simply contains mobile number and message:
The pseudo Angular7 client demo app is available at: Stackblitz & Github
Also, you can download the attached source code files.
Points of Interest
- CsQuery: The
CsQuery
library allows us to make jquery like DOM manipulations. - SMS Providers: There are many SMS providers which allow sending SMS for free, I have implemented few of them using screen scraping, the project is available on github.
- Fiddler web debugger: Fiddler allows inspecting submitted web foms.
History
- 5th April, 2019: Initial draft
- 6th April, 2019: Angular client code added
- 8th April, 2019: More details about code