Intro to Building Twilio Apps with ASP.NET MVC

UPDATE Since this post was originally published we’ve released updated Twilio helper libraries for .NET that simplify the TwiML generation process. You can install the new library using the ‘Twilio.Mvc’ NuGet package and find complete documentation on the project wiki.

Last week I had the privilege of presenting “Intro to Building Twilio Apps with ASP.NET MVC” at mvcConf 2, a virtual conference for ASP.NET MVC developers with over 3000 registered attendees. The talk covered some ways that you can leverage features of the ASP.NET MVC framework to make it easier to build voice and SMS apps. You can watch a recording of the session below. If you prefer text over video, read on for more information about the topics covered in the session.

Download high-res (1280×720) version.

Overview

If you’re new to Twilio, here’s a brief overview of the types of apps you can build with our API and how it works:


This post assumes a basic knowledge of how ASP.NET MVC and TwiML work. I’m using the MVC Music Store as a starting point for my examples. You can find the source for the store on CodePlex.

We’ll first build an order status inquiry feature where customers can call in to get updates on an order. Then I’ll demonstrate how to secure your controller actions using request validation. Lastly we’ll put all the pieces together to build the same order status feature but using an SMS interface instead of a phone call.

Handling Incoming Calls

When a call arrives to your Twilio number, a POST request is made to a URL you specify in your account dashboard. Your URL responds back with TwiML to control the call. Let’s create a new CallController.cs and add our first action method:

[csharp]public ActionResult Incoming(string CallSid, string From, string To)
{
var doc = new XDocument();
var response = new XElement("Response");
var gather = new XElement("Gather",
new XAttribute("action", Url.Action("Input")),
new XElement("Say", "Thank you for calling. Please enter your order number followed by the pound key.")
);

response.Add(gather);
doc.Add(response);

return new ContentResult
{
Content = doc.ToString(),
ContentType = "text/xml"
};
}[/csharp]

The first thing you’ll notice in the code above are the method parameters. These are being bound to a few of the POST parameters Twilio sends along with every request using the automatic parameter binding ASP.NET MVC provides.  Next we build up an XML document that prompts the caller to enter their order number and then return the output. I’m starting with the “long way” to demonstrate the concepts. We’ll refactor it later to be more concise and idiomatic ASP.NET MVC.

At this point you could upload this to a public URL, set your phone number’s voice URL to http://example.com/Incoming and call your number to hear the prompt. If you enter a key, you’ll get an error because we haven’t written that part yet. So let’s do that now.

The <Gather> element we use includes an action parameter that points to a ‘Input’ action method. When the digits are collected, a POST request will be made to that action with a ‘Digits’ parameter tagging along with the standard data.

[csharp]public ActionResult Input(string Digits, string CallSid, string From, string To)
{
var id = Convert.ToInt32(Digits.Replace("#", ""));
var order = storeDB.Orders.Find(id);
var doc = new XDocument();
var response = new XElement("Response");
if (order != null)
{
var orderStatus = order.Status;
var orderNum = string.Join(". ", id.ToString().ToCharArray());
var say = new XElement("Say", "The status of order " + orderNum + ". is " + orderStatus + ".");
say.Add(new XAttribute("voice", "woman"));
say.Add(new XAttribute("loop", 3));
response.Add(say);
}
else
{
response.Add(new XElement("Say", "We could not locate your order. Please try again."));
response.Add(new XElement("Redirect", Url.Action("Incoming")));
}

doc.Add(response);
return new ContentResult
{
Content = doc.ToString(),
ContentType = "text/xml"
};
}[/csharp]

We start off by removing the # character from the Digits parameter since we need just the order ID. Once we have the sanitized input, we query our database for the order ID entered. If we find the order, the status is read back to the caller three times in a female voice. If no order is found, we notify the caller and redirect them back to the /Incoming action method to start the process over.

Refactoring to Use Custom ActionResult

The return statements at the end of both methods work, but they’re not pretty. To clean them up we’ll create a custom TwimlResult that inherits from ActionResult.

[csharp]public class TwimlResult : ActionResult
{
public XDocument Response { get; set; }
public TwimlResult(XDocument response)
{
Response = response;
}

public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/xml";
Response.Save(context.HttpContext.Response.Output);
}
}[/csharp]

This class takes in an XDocument, sets the content type header to the proper value and writes out the content to the output stream. Now we can update our controller actions to simple do this instead:

[csharp]return new TwimlResult(doc);[/csharp]

We can shorten that up even further, as you’ll see next.

Moving Common Functionality into a TwilioController

If you’re going to be returning a lot of TwiML, you’ll soon find yourself repeating a lot of common steps in your controllers. We can move common logic into a TwilioController base class that all of our controllers will inherit from.

The first thing we can do is add a method to our base controller to make it easier to return TwimlResults.

[csharp]public class TwimlController : Controller
{
public TwimlResult Twiml(XDocument response)
{
return new TwimlResult(response);
}
}[/csharp]

Now in our controller actions we can send back TwiML with return Twiml(doc);

I mentioned earlier that there are common parameters that are sent along with every request. Instead of using parameter binding to grab them on each request, we can move this logic into the base controller. Note that if you’re heavily testing your controllers this method can make them harder to test so it’s not for everyone. If you’re OK with the caveats, add the following code to TwilioController:

[csharp]public string CallSid { get; set; }
public string To { get; set; }
public string From { get; set; }
public string AccountSid { get; set; }
public string CallStatus { get; set; }
public string FromCity { get; set; }
public string FromState { get; set; }
public string FromZip { get; set; }
public string FromCountry { get; set; }
public string ToCity { get; set; }
public string ToState { get; set; }
public string ToZip { get; set; }
public string ToCountry { get; set; }
public string Digits { get; set; }
public string SmsSid { get; set; }
public string Body { get; set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
CallSid = filterContext.HttpContext.Request.Params["CallSid"];
To = filterContext.HttpContext.Request.Params["To"];
From = filterContext.HttpContext.Request.Params["From"];
AccountSid = filterContext.HttpContext.Request.Params["AccountSid"];
CallStatus = filterContext.HttpContext.Request.Params["CallStatus"];
FromCity = filterContext.HttpContext.Request.Params["FromCity"];
FromState = filterContext.HttpContext.Request.Params["FromState"];
FromZip = filterContext.HttpContext.Request.Params["FromZip"];
FromCountry = filterContext.HttpContext.Request.Params["FromCountry"];
ToCity = filterContext.HttpContext.Request.Params["ToCity"];
ToState = filterContext.HttpContext.Request.Params["ToState"];
ToZip = filterContext.HttpContext.Request.Params["ToZip"];
ToCountry = filterContext.HttpContext.Request.Params["ToCountry"];
Digits = filterContext.HttpContext.Request.Params["Digits"];
SmsSid = filterContext.HttpContext.Request.Params["SmsSid"];
Body = filterContext.HttpContext.Request.Params["Body"];
base.OnActionExecuting(filterContext);
}[/csharp]

This code will pull the POST parameters and populate the properties so they’re available in a strongly-typed manor on every request. Back in our controller actions, you can now write code like this:

[csharp]public ActionResult Input()
{
var id = Convert.ToInt32(Digits.Replace("#", "")); // Digits pre-populated by TwilioController
var order = storeDB.Orders.Find(id);
// and so on
}[/csharp]

I’ve also included helper methods for generating XML elements in my TwilioController.

[csharp]public XElement Verb(string name, object value)
{
return Verb(name, value, new { });
}

public XElement Verb(string name, object value, object attributes)
{
var element = new XElement(name, value);
foreach (var prop in attributes.GetType().GetProperties())
{
element.Add(new XAttribute(prop.Name, prop.GetValue(attributes, null)));
}
return element;
}[/csharp]

You can use this to more easily build up your TwiML responses like so:

[csharp]public ActionResult Incoming()
{
var doc = new XDocument();
var response = new XElement("Response");
var gather = Verb("Gather",
Verb("Say", "Thank you for calling. Please enter your order number followed by the pound key."),
new { action = Url.Action("Input") }
);

response.Add(gather);
doc.Add(response);

return Twiml(doc);
}[/csharp]

Securing Your Controller Actions

Since the URLs you use to handle Twilio requests need to be publicly available, that leaves them open for anyone that knows the URL to request them. We use a system called ‘Request Validation‘ to allow you to verify that the request is coming from us. In short, we combine all the elements of the request, hash them against your AuthToken and send the signature in a header. You can generate the same value on your server and compare the values to make sure the request is legit.

I’ve encompassed all the logic to do this into a ValidateRequestAttribute which you can attach to specific controller actions, an entire controller, or your TwilioController base class. The class is a little long for the post, but you can find it attached at the end. Using it is simple:

[csharp][ValidateRequest("AuthToken")] // replace with your AuthToken from your dashboard
public class TwimlController : Controller
{

}[/csharp]

Now if someone tries to access your URL that isn’t Twilio they’ll get an error. Note that requests that originate and end on your local machine are excluded from validation to make it easier to test while developing locally.

Putting it All Together: Order Status via SMS

We can use some of the same logic and our new conveniences in TwilioController to write a SmsController to look up order status based on the phone number of the sender:

[csharp][ValidateRequest("AuthToken")]
public class SmsController : TwimlController
{
MusicStoreEntities storeDB = new MusicStoreEntities();
public ActionResult Incoming()
{
var doc = new XDocument();
var response = new XElement("Response");

// get most recent order by phone number
var order = from o in storeDB.Orders
where o.Phone == From
orderby o.OrderDate descending
select o;
if (order.Any())
{
var mostRecent = order.First();
response.Add(Verb("Sms",
"Order " + mostRecent.OrderId + " is " + mostRecent.Status + ".")
);
}
else
{
response.Add(Verb("Sms", "No order found."));
}

doc.Add(response);
return Twiml(doc);
}
}[/csharp]

Next Steps

I’ve spent a lot of time on incoming use cases, but you can easily use Twilio to send SMS text messages and make outbound phone calls as well. I have a helper library I’ve put together to make that easy in .NET that you can find on GitHub. We’ll be combining that library and some of the ideas from this post into a new official .NET library that will be available soon.

You can download the code from this post here. To use them, drop these files into your MVC project and update the namespaces as needed.

If you have any questions about anything discussed in this post, feel free to comment below, post in our forums or email our support staff who will be happy to help assist you.