Using C# and Azure to Build a SMS Representative Directory

June 23, 2016
Written by

Korea_President_Park_US_Congress_20130507_06

Earlier this month, you heard about Ian Webster, who built an app to call your federal representatives. Communication can be a powerful force for change. Today we’ll learn how to empower others by building an app similar to Ian’s with SMS.

Using C#, ASP.NET, and Azure, we will build a messaging application that responds to a ZIP code with a list of federal representatives for that area:

SMS (text) conversation showing the house representative and senators for ZIP code 95219

Tools

The tools we’ll be using are the staples of the Microsoft stack along with the Twilio API and Sunlight Foundation’s Congress API. Here’s what you’ll need to get right now:

  1. A Twilio Account
  2. Visual Studio 2015 – Free Community Edition is fine
  3. An Azure Account
  4. An API Key for Sunlight Foundation congress API

We’ll also be using ASP.NET MVC 5 and the Twilio C# Helper Library.

Getting Started

First, create a new, empty ASP.NET MVC project that can handle incoming SMS text messages from Twilio. For step-by-step instructions, please see our documentation on how to setup a new project.

Name your project TwilioCongressBot.Web  and the solution TwilioCongressBot . Once you see that your Azure Web App is ready, you can proceed.

zFQvq36cEm1jfDah69PweSGterrUmgowyPswbx_xR4ug_R69bEjRACok3aiCwDtwjJjDbnc8jzuBA6KtCqCe3g4i-jg_5q2y0Itk2yKIbK-BYhbfGO07un8_yMRVPB_NPgdEIHMj-2

NuGet Packages

To finish off the framing for our project, we need to install a couple dependencies via the NuGet Package Manager. In the menu, choose Tools > NuGet Package Manager > Package Manager Console. Then, run the following commands:

Install-Package Twilio.Mvc -DependencyVersion HighestMinor
Install-Package Newtonsoft.Json

Creating a Legislator Controller

When Twilio receives a SMS message at a phone number that you own it will call a special URL called a Webhook. Let’s create an empty MVC 5 Controller and name it LegislatorController . Our previously mentioned walkthrough has details on creating a new controller.

Update the namespaces that you import into your controller to be as follows:

using Newtonsoft.Json;
using RestSharp;
using System.Collections.Generic;
using System.Configuration;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using Twilio.Mvc;
using Twilio.TwiML;
using Twilio.TwiML.Mvc;
using TwilioCongressBot.Web.Models;

And then make sure your LegislatorController  inherits from TwilioController :

public class LegislatorController : TwilioController

Using the Congress API

If you don’t have your API key yet, head over to the Sunlight Foundation and register for a free one. Their website also contains documentation for the API.

Storing the API Key

We need somewhere to keep track of this API key, and the typical place for doing that in an ASP.NET app is the Web.config file, specifically the appSettings section:


<appSettings>
  <add key="webpages:Version" value="3.0.0.0" />
  <add key="webpages:Enabled" value="false" />
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  <add key="CongressApiKey" value="PASTE KEY HERE"/>
</appSettings>

Add the highlighted line to your web.config file and use the API key you obtained during the registration process.

Calling the API

We will be calling the /legislators/locate endpoint, passing it a ZIP code as well as our API key. The template for the URL we’ll be calling can be assigned to a private field on our controller like so:

private string _congressApiUrlTemplate = "https://congress.api.sunlightfoundation.com/legislators/locate?zip={0}&apikey={1}";

We’ll use C#’s String.Format() function to replace {0} with the ZIP code and {1} with our API key from the web.config file. Once we’ve built the URL, we can use RestSharp (already included as a dependency of the Twilio helper library) to make the call and Json.Net to parse the result:

private CongressApiResult getLegislators(string zip)
{
   var url = string.Format(_congressApiUrlTemplate, zip, ConfigurationManager.AppSettings["CongressApiKey"]);
   var client = new RestClient(url);
   var request = new RestRequest(Method.GET);
   var response = client.Execute(request);
   if (response.ResponseStatus != ResponseStatus.Completed || 
       response.StatusCode != System.Net.HttpStatusCode.OK)
   {
       return null;
   }
   return JsonConvert.DeserializeObject<CongressApiResult>(response.Content);
}

The CongressApiResult  class defines all of the expected properties of the API result. We need to create a source file to hold this class and dependent classes. Place it in the “Models” folder of our solution.

Pro-tip: This class was auto-generated by Visual Studio by copying the expected JSON result (obtained here) from the congress API and using: Edit… Paste Special… Paste JSON as Classes. The generated Result class was then manually renamed to Legislator:

public class CongressApiResult
{
   public Legislator[] results { get; set; }
   public int count { get; set; }
   public Page page { get; set; }
}

public class Page
{
   public int count { get; set; }
   public int per_page { get; set; }
   public int page { get; set; }
}

public class Legislator
{
   public string bioguide_id { get; set; }
   public string birthday { get; set; }
   public string chamber { get; set; }
   public string contact_form { get; set; }
   public string crp_id { get; set; }
   public int? district { get; set; }
   public string facebook_id { get; set; }
   public string fax { get; set; }
   public string[] fec_ids { get; set; }
   public string first_name { get; set; }
   public string gender { get; set; }
   public string govtrack_id { get; set; }
   public int icpsr_id { get; set; }
   public bool in_office { get; set; }
   public string last_name { get; set; }
   public object middle_name { get; set; }
   public object name_suffix { get; set; }
   public object nickname { get; set; }
   public string oc_email { get; set; }
   public string ocd_id { get; set; }
   public string office { get; set; }
   public string party { get; set; }
   public string phone { get; set; }
   public string state { get; set; }
   public string state_name { get; set; }
   public string term_end { get; set; }
   public string term_start { get; set; }
   public string thomas_id { get; set; }
   public string title { get; set; }
   public string twitter_id { get; set; }
   public int votesmart_id { get; set; }
   public string website { get; set; }
   public string youtube_id { get; set; }
   public string lis_id { get; set; }
   public int senate_class { get; set; }
   public string state_rank { get; set; }
}

Determining a ZIP Code

As you can see here, Twilio will pass us a Body parameter containing the contents of whatever was sent to us as a text message. It also may send us a FromZip parameter if it can identify geographic data based on the source phone number (not geolocation). For example if the text message was sent from the 415 area code, Twilio can tell you that area code is located in San Francisco, CA.

Let’s check the Body first, to see if the user specified a ZIP code and fallback to the FromZip:

private string getValidZip(SmsRequest request, TwilioResponse response)
{
    if (containsValidZip(request.Body)) return parseZip(request.Body);
    if (containsValidZip(request.FromZip))
    {
        var fromZip = parseZip(request.FromZip);
        response.Message("Text your ZIP code for more accurate results. We're guessing " + fromZip);
        return fromZip;
    }
    return null;
}

private Regex _zipValidator = new Regex(@"\d{5}");
private bool containsValidZip(string input)
{
    return _zipValidator.IsMatch(input ?? "");
}

private string parseZip(string input)
{
    return _zipValidator.Match(input).Value;
}

Notice we’re adding a help message if we fallback to the FromZip letting them know they may need to text us their specific ZIP code if the one associated with their phone number isn’t where they reside.

Putting it Together

Now we just need our controller action to use the code we’ve written to get the ZIP code, pass it to the congress API, and format the results:

HttpPost]
public ActionResult Index(SmsRequest request)
{
   var response = new TwilioResponse();

   var zip = getValidZip(request

There’s two things in the formatting code above we haven’t covered yet. First, we create the following dictionary as a private field:

private IDictionary<string, string> labelDict = new Dictionary<string, string>()
{
    { "house", "House Representative" },
    { "senate", "Senator" }
};

This helps us translate the “chamber” property from the API to a more friendly text description. Also, we have a simple helper function to concatenate the first, middle, and last name of the representative:

private string getFullName(Legislator legislator)
{
    return (legislator.first_name + " " +
            legislator.middle_name + " " +
            legislator.last_name).Replace(" ", " ");
}

You can find the full source code for the controller and model on GitHub.

Testing and Debugging Locally

Run your application from within Visual Studio to view your application in the browser. In the case of an empty ASP.NET project, we don’t have a default home page for our app:

IgKsiyuDVjAaEYXHToPOSMsLu_tggXebmjhN8tPimkVbbcMS8zrCPepZW43uDOdr7cIZkkLBchhUI22OLTS11iT34FJEh1x5gR3BgQSU7POR5QAcMOYq6NeGgwSc2-vOrFylYr3O-2

Instead of using the browser to test, open a new PowerShell window and run the following command:

Invoke-WebRequest http://localhost:XXXXX/Legislator -Method POST -Body @{Body="Hello World 95219-4444";FromZip="95209"}

Replace the XXXXX with the random port number that Visual Studio assigned to your web app. You can see this in the browser URL when Visual Studio first launched your app.

This will return a PowerShell object with the response from your controller:

StatusCode : 200
StatusDescription : OK
Content : <?xml version="1.0" encoding="utf-8"?>
          <Response>
            <Message>Your House Representative: Jerry McNerney - 202-225-1947 - Rep.Mcnerney@opencongress.org</Message>
            <Message>Your Senator: Barbara Boxe...
RawContent : ...

You should be able to see the raw XML response we are returning in the Content property. Pro-tip: you can see just the Content property with a single command:

(Invoke-WebRequest http://localhost:XXXXX/Legislator -Method POST -Body @{Body="Hello World 95219-4444";FromZip="95209"}).Content

Deploying and Configuring the App

Publish to Azure

To publish your app to Azure, find the “Azure App Service Activity” tab in Visual Studio and click the Publish button:

yFSmmSC2uNboRCcMhmt1hDQmNnOcGsMqXLfRG-KhHv6JJ1RbsPQTcCwHVOmd-Vvezp4cTFI0zYw1hDul8eRCjZRtQ2BP4qFUY6SAGPx9zo-BHM5imgtt07IrjcRI5sRtz0zCdUIb-2

Once the publish is complete, you can test your app from PowerShell again:

Invoke-WebRequest https://congressbot01.azurewebsites.net/Legislator -Method POST -Body @{Body="Hello World 95219-4444";FromZip="95209"}

Replace congressbot01  with the name you selected for your Azure Web App.

Assuming that works, we can move on to attaching your Webhook to a phone number in Twilio.

Configure a Twilio Number

If you haven’t already purchased a Twilio phone number, do so now. Once that is done, you can configure the Messaging Webhook as follows:

P7H6PI1sQraynbnZSlDGDWyGvqen-Wx37dPHnMHZJtAznKxM6L8N1nTwkmj-pKbjGEYapqZ7QV0etnh5AW5AJTdQs3z2ZT0SPww_BsB-4HCLIT5J6eOqq-QBtIEtBC8dHWhl8tSF-2

Use the same URL for your Azure Web App that you tested in PowerShell earlier. Be sure to click the SAVE button to save your configuration for the phone number.

Taking the Final Test Drive

With your Twilio phone number confirmed, you can simply send a text to test it out:

65RvKADoXAwig_u9ulklhCpix2gGUcRn9pywsNfG7oh6x7he_6fkDZurZPnTrM-CLsj31KJDBW9-3an3b6r0-gjOHKfNEtO_ReMcFmM27eKtmJPL4TLpSGO4f4BOP6cIKqA0ypsD-1

Wrapping Up

You can download the entire solution from GitHub. If you want to publish this completed app to Azure, right-click on the CongressBot web app in the Solution Explorer and choose “Publish.”

Now all you need to do is give your Twilio phone number to your colleagues, friends, family, etc. and allow their voices to be heard! Or, have them text (646) 8USA-REP (+1-646-887-2737), which is powered by this app.

I’m David Prothero and I work on the Twilio Developer Education team. If you have any questions or feedback on this hack, let me know in the comments below, hit me up on Twitter at @dprothero or shoot me an email at dprothero at twilio.com.