Click here to Skip to main content
14,034,985 members
Click here to Skip to main content
Add your own
alternative version

Stats

8.2K views
58 downloads
7 bookmarked
Posted 5 Apr 2019
Licenced CPOL

Implementing SMS API using Azure Serverless Functions

, 8 Apr 2019
Rate this:
Please Sign up or sign in to vote.
Implementing serverless free SMS web API using Azure functions

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 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 methd,

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);//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.

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 programatically 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);//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

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 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 intiated either by HTTP GET of POST method, so we read the posted mobile number and message using 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:

cors in Azure function - click to enlarge image

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

//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 debuggerFiddler 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)

Share

About the Author

Nitin Sawant
Software Developer (Senior)
India India
I'm a full stack web developer, having 8 years of experience in web development. I love coding so much, I do it in my spare time too. I work with C#, and all the other things that usually go with it, like:
• ASP.NET MVC
• Angular & Typescript
• SQL Server
• HTML, Javascript & CSS
• Entity Framework

You may also be interested in...

Pro

Comments and Discussions

 
GeneralFor India only Pin
Gustav Brock8-Apr-19 23:21
professionalGustav Brock8-Apr-19 23:21 
GeneralRe: For India only Pin
Nitin Sawant15-Apr-19 21:55
professionalNitin Sawant15-Apr-19 21:55 
QuestionGood Pin
Arlene Kaddy8-Apr-19 3:46
memberArlene Kaddy8-Apr-19 3:46 
QuestionCode Dump Pin
CodeLancer8-Apr-19 1:07
memberCodeLancer8-Apr-19 1:07 
AnswerRe: Code Dump Pin
Nitin Sawant8-Apr-19 2:25
professionalNitin Sawant8-Apr-19 2:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06 | 2.8.190424.1 | Last Updated 8 Apr 2019
Article Copyright 2019 by Nitin Sawant
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid