Apr 13, 2019

Implementing SMS API using Azure Serverless Functions

my article published at codeproject.com

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

Toptal Talent pool

Working as a freelance developer gives a lot of flexibility, Going to office by travelling through heavy traffic is thing of past, The Toptal network allows software engineers to work remotely from home.

Toptal is a giant talent aggregator where freelance web developers, designers, writers and marketing professionals create their profile and the clients hire only the top freelancers

Jun 24, 2018

Javascript: Not allow user to open website in multiple windows

jQuery plugin to check if current window is duplicate window,
I createdd this plugin based on timkellypa's answer on stackoverflow
"Stop people having my website loaded on multiple tabs"
https://stackoverflow.com/a/45717724/223752

This plugin works across all browsers IE, Firefox, Microsoft Edge & Google chrome

It is basically checking GUID of the window against value in cookie


Sample usage:




    
    Multi window
    
    


    This is test page,
Click here to open another window

Jan 10, 2018

Vpnbook Dialer application for Windows 10

Created Vpnbook dialer windows application which auto retrieves vpnbook.com password and connects PPTP vpn connection using the retrieved password.

vpnbook.com dialer v1.0
Released the application on github.com

Please note before using the application a PPTP VPN connection needs to be set-up using Windows wizard in control panel.

Source Code: https://github.com/nitinjs/Vpnbook-Dialer

C# - Programatically download file from Dropbox

To download file from dropbox use following code:

We are just appending the "dl=1" querystring parameter to the shared dropbox user shared url and directly accessing the file as HTTPS stream

Dec 8, 2017

C# generic function to export list of objects into excel file

C# generic function to export list of objects into excel file using EPPLUS library, if the file already contains the tab then data will be appended to the excel tab in worksheet.

Nov 8, 2017

Visual Studio 2017 Installer stuck at Applying Microsoft VisualStudio Debugger JustInTime

While installing Visual Studio 2017 Installer may stuck at Applying Microsoft VisualStudio Debugger JustInTime

if you are using older version of the installer. To solve the issue:
Solution 1
1. Click On Cancel, after few minutes it will ask to update VS installer.
2. Update the Visual Studio Installer. 
3. Restart your PC. Installer will auto-continue.

Solution 2
1. Download Process Explorer
2. Kill the process PowerShell.exe under vs_installershell.exe
3. Installer will resume skipping JustInTime
4. After VS installation completes you have to manually install JustInTime  

Solution3

Package ‘Microsoft.VisualStudio.Debugger.JustInTime,version=15.0.26621.2’ failed to install


SOLUTION:

The solution was as simple as updating the System Variable __PSLockDownPolicy value to 1:
This is related to the PowerShell Constrained Language Mode, see the following article for more details: