Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

Implementing SMS API using Azure Serverless Functions

2.82/5 (9 votes)
1 Sep 2020CPOL3 min read 33.5K   173  
Implementing serverless free SMS web API using Azure functions
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:

  1. 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.
  2. 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.
  3. 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:

  1. What is serverless
  2. Introduction to serverless functions on Azure
  3. Web scraping with C#
  4. 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:

Sequence diagram for Azure serverless function - click to enlarge image

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.

Image 2

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.

Image 3

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.

C#
var Client = new WebClient();
string loginPage = "http://www.160by2.com/re-login";
NameValueCollection data = new NameValueCollection();
data.Add("username", UserName);              //username input field
data.Add("password", Password);              //password input field
Client.UploadValues(loginPage, "POST", data);//submit the form

The Send SMS form simply has the mobile number and message in UI.

Image 4

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:

Image 5

Just like the login form, we need to programmatically submit these form data keys along with values.

C#
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);  //we need to read the session id value from cookies 
                                 //send by the server while logging in
//load the send sms web form
CQ sendSmsPage = Client.DownloadString(base_url + "SendSMS?id=" + cookieVal);
NameValueCollection data = new NameValueCollection();

//find keys for all inputs in the form
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"));
}

//mobile number input
CQ mobileNumberBox = form.Find("input[placeholder='Enter Mobile Number or Name']")[0].Cq();
data.Add(mobileNumberBox.Attr("name"), recipient);

//textarea for message input
data.Add("sendSMSMsg", message);
string sendSmsPost = base_url + data["fkapps"];

data["hid_exists"] = "no";
data["maxwellapps"] = cookieVal;

//additional vals
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);//submit the send sms form  

The final class is as follows:

C#
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();
            //all inputs
            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"));
            }

            //sms input
            CQ mobileNumberBox = 
               form.Find("input[placeholder='Enter Mobile Number or Name']")[0].Cq();
            data.Add(mobileNumberBox.Attr("name"), recipient);

            //textarea
            data.Add("sendSMSMsg", message);
            string sendSmsPost = base_url + data["fkapps"];

            data["hid_exists"] = "no";
            data["maxwellapps"] = cookieVal;

            //additional vsls
            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.

C#
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:

C#
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:

C#
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:

cors in Azure function - click to enlarge image

Now in the Angular 7 client to send the SMS, we write the following code:

C#
//pseudo 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:

sms ui

 

The pseudo Angular7 client demo app is available at: Stackblitz & Github

Also, you can download the attached source code files.

Points of Interest

  1. CsQuery: The CsQuery library allows us to make jquery like DOM manipulations.
  2. 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.
  3. 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)