Relaying Twilio Requests using Windows Azure

If you’ve used Twilio before, you know that the way we notify you of events is by making a call to a URL you’ve configured for your Twilio phone number, but one question I often hear from Windows developers is “how can I debug the code in my application that handles these requests?”.  I hear this because developers run into one of a common set of problems which prevent them from being able to have Twilio make requests directly to their computer:

  • They have no way to give their computer a static public address. For example, you are behind a NAT device, have a dynamic IP address, or have inbound ports blocked
  • They are using the ASP.NET Development Server or IIS Express to test their applications on their development machine and these servers only accept connections from the localhost address

There are a variety of ways to work around these issues from dynamic DNS and port forwarding, to manually configuring IIS Express to allow external connections, but in this blog post I want to show you how use the Windows Azure Service Bus and a simple ASP.NET MVC website to create an easy to use solution that lets you work-around both problems.

Setting up the Service Bus

Since the core of my solution relies on the Windows Azure Service Bus, the first step to get started is to register for a free Windows Azure trial account and set up a new Service Bus Namespace. If you are not familiar with the Service Bus, it provides secure messaging and relay capabilities that enable building distributed and loosely-coupled applications in the cloud, as well hybrid application across both private and public clouds. In my solution, I will use the Service Bus to relay messages from Twilio to my local development machine, which normally sits behind a NAT device.

I used the How to Use the Service Bus Relay Service guide to learn how to create a new Service Bus Namespace, specifically the Create a Service Namespace and Obtain the Default Management Credentials for the Namespace sections.

Creating the Local WCF Service

Once I get the service bus namespace created, I need to create a new WCF service that will run on my development machine and receive the messages relayed from the service bus.

To do this, I created a new class library called AzureTunnel.Common that will contain my WCF Service Contract interfaces and be shared between the my WCF service and the client application, and a new console application called AzureTunnel.Service which will contain the concrete implementation of the WCF service.

In the Common class library I added the Windows Azure Service Bus NuGet package and then created two new interfaces, the first called IAzureTunnelService and the second called IAzureTunnelServiceChannel:

[code lang="csharp"][ServiceContract(Namespace = "urn:ps")]
public interface IAzureTunnelService
{
[OperationContract]
void Relay();
}

public interface IAzureTunnelServiceChannel : IAzureTunnelService, IClientChannel { }[/code]

The IAzureTunnelService interface is a basic WCF Service Contract interface, where I’ve defined a single Operation Contract method called Relay. This is the method that my service will implement to handle incoming messages from the Service Bus.

The IAzureTunnelServiceChannel interface defines a client channel, and is used by the client to initiate sending a message to the service bus.

Once I have the interfaces defined, I can create a concrete implementation of the IAzureTunnelService interface in my Service application. I start by adding a reference to the common class library and the Windows Azure Service Bus Nuget package to the project. I also changed the target framework of the project from the .NET Framework 4 Client Profile to the .NET Framework 4 profile since it includes the   System.ServiceModel.Activation assembly which the ??? needs.

Next I created a new class called AzureTunnelService that implements the IAzureTunnelService interface, including the Relay method, which for now simply writes out to the console:

[code lang="csharp"]public class AzureTunnelService : IAzureTunnelService
{
public void Relay()
{
Console.WriteLine("Service Bus Message Received");
}
}[/code]

Finally, in the console applications Main method, I wire up and start the service host, providing the URL I want the service to answer to, and the service bus namespace and secret which I grabbed from the Windows Azure Platform management website:

[code lang="csharp"]static void Main(string[] args)
{
ServiceHost sh = new ServiceHost(typeof(AzureTunnelService));

sh.AddServiceEndpoint(typeof(IAzureTunnelService), new NetTcpBinding(), "net.tcp://localhost:9385/relay");

sh.AddServiceEndpoint(typeof(IAzureTunnelService),
new NetTcpRelayBinding(),
ServiceBusEnvironment.CreateServiceUri("sb", "**namespace**", "relay"))
.Behaviors.Add(new TransportClientEndpointBehavior
{
TokenProvider = TokenProvider.CreateSharedSecretTokenProvider("owner", "**secret**")
});

sh.Open();

Console.WriteLine("Press ENTER to close.");
Console.ReadLine();

sh.Close();
}[/code]

Note in this code sample I’ve replaced the actual service bus namespace and secret values with **namespace** and **secret**. To use this code sample you would have to replace these with your own service bus namespace and secret.

Now I can run the console application and make sure that the service host is configured and correctly and starts:

When the console application starts, the service host begins to listen for messages from the service bus.  When a message is relayed it will run the Relay method and write out to the console.

Creating an Public Endpoint

Now that I have the local service created and listening for messages from the service bus, I need to create a way to take Twilio requests and put them as messages onto the service bus. To do this I’m going to start by creating a new empty ASP.NET MVC 3 application (and the associated Windows Azure project).  Next, I added a reference to the Common project and added the Azure Service Bus NuGet package.

Once the project references were set up, I created a new controller class named RelayController and in it, an action method named Index.  The action method created a new channel and then calls the Relay method:

[code lang="csharp"]public ActionResult Index()
{
var cf = new ChannelFactory<IAzureTunnelServiceChannel>("relay");
using (var ch = cf.CreateChannel())
{
ch.Relay();
return new EmptyResult();
}
}[/code]

For now the action method simply returns an EmptyResult, but we’ll change that later in the post.

Finally, I have to provide the configuration for the WCF service. For the ASP.NET MVC application I’ll do that by adding the configuration to thesection of the web.config file:

[code lang="xml"]<client>
<endpoint name="relay" contract="AzureTunnel.IAzureTunnelService" binding="netTcpRelayBinding" address="sb://**namespace**.servicebus.windows.net/relay" behaviorConfiguration="sbTokenProvider" />
</client>
<behaviors>
<endpointBehaviors>
<behavior name="sbTokenProvider">
<transportClientEndpointBehavior>
<tokenProvider>
<sharedSecret issuerName="owner" issuerSecret="**secret**" />
</tokenProvider>
</transportClientEndpointBehavior>
</behavior>
</endpointBehaviors>
</behaviors>[/code]

In the configuration sample shown, as I did earlier I’ve replaced the actual service bus namespace and secret values with **namespace** and **secret**. To use this configuration sample you would have to replace these with your own service bus namespace and secret.

Once the WCF service is configured I just need to publish the ASP.NET MVC project up to Azure and then I can test out my relay.

Testing the Relay

Now that I have a client ready to put messages onto the service bus, and a service waiting to receive them, its time to test. To do that, I simply set the URL of one of my Twilio phone numbers to the URL of my Azure hosted MVC application:

Then I sent an SMS message to the number.  What should happen when the SMS is sent is Twilio should make an HTTP request to my MVC app, which in turn calls the Relay method putting a message onto the service bus, which relays that to the service running on my local development machine:

You can see my service responded letting me know that it had received a message from the service bus.

Enhancing the Relayed Message

Now admittedly so far the solution is not super useful since all its doing is letting me know that a a request was sent from Twilio. What I really want to do is take the information from the Twilio request and relay that through the Service Bus to my service. Doing that is pretty easy.

First I create a new class in the Common project called TwilioRequestViewModel. This class contains properties for all of the parameters that are included in a Twilio request.

Once I’ve created this class I need to modify the IAzureTunnelService interface so that the Relay method accepts a TwilioRequestViewModel as a method parameter:

[code lang="csharp"][ServiceContract(Namespace = "urn:ps")]
public interface IAzureTunnelService
{
[OperationContract]
void Relay(TwilioRequestViewModel vm);
}[/code]

and update the AzureTunnelService class to reflect this change in the interface:

[code lang="csharp"]public void Relay(TwilioRequestViewModel vm)
{
Console.WriteLine("{0,13}|{1,13}", vm.From, vm.To);
}[/code]

Notice that I’ve also updated the implementation of the Relay method to output some additional details about from the message.

Finally, I need to change the action method in my MVC application to accept a TwilioRequestViewModel as an input parameter and pass that to the Relay method:

[code lang="csharp"]public ActionResult Index(TwilioRequestViewModel vm)
{
var cf = new ChannelFactory<IAzureTunnelServiceChannel>("relay");
using (var ch = cf.CreateChannel())
{
ch.Relay(vm);
return new EmptyResult();
}
}[/code]

ASP.NET MVC’s Model Binding feature will automatically map the parameters sent with the Twilio request to my View Model class for me, giving me a nice strongly types class to work with.

Now I can republish the MVC application to Azure, start my local service and send an SMS message to test the relay.

As you can see, this time instead of a generic message, the actual To and From phone numbers from the Twilio request were relayed from the MVC application to my local service and shown in the console.

Returning TwiML Responses

OK, so it’s good that I’m now capturing the parameters of incoming Twilio requests on my local development machine, but right now I’m still only able to look at what Twilio is sending me. What would be even more useful to me when debugging would be to not only capture the incoming request parameters, but actually relay them to the local version of my web application.  This would let me actually debug the app code directly and even respond by sending TwiML back to Twilio. I can do this by making a few simple changes to my relay application.

First, in order to send responses back to Twilio, I need to change the IAzureTunnelService interface to specify that a string should be returned from the Relay method:

[code lang="csharp"][OperationContract]
string Relay(TwilioRequestViewModel vm);[/code]

Now, in order to relay the incoming Twilio parameters to my local web app, I need to recreate the HTTP request on my local machine.  To do this, I’ll change the Relay method to create a new HttpWebRequest and set as its body the properties of the TwilioRequestViewModel.

In order to send the properties of the view model in the request, I need to convert the object into a string.

[code lang="csharp"]StringBuilder paramList = new StringBuilder();
var props = vm.GetType().GetProperties();
foreach (var prop in props)
{
string param =
string.Format("{0}={1}&", prop.Name, prop.GetValue(m, new object[] { }));
paramList.Append(param);
}[/code]

Next I create a the new HttpWebRequest using the URL of my local web application and set its content to the string I just created.

[code lang="csharp"]HttpWebRequest request =
(HttpWebRequest)HttpWebRequest.Create("http://localhost:1234/Sms/Index");
request.Method = vm.Method;

var requeststream = request.GetRequestStream();

byte[] bytes = System.Text.ASCIIEncoding.Default.GetBytes(paramList.ToString());
requeststream.Write(bytes, 0, bytes.Length);[/code]

Finally I take the response from the HttpWebRequest and return it from the Relay method:

[code lang="csharp"]var response = request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string responsestring = reader.ReadToEnd();

return responsestring;
}[/code]

The string I return from the Relay method, which I know will be a snippet of TwiML, is relayed back across the service bus to the controller where the Relay method was originally called. In that controller I take the string and return it to Twilio as Content, making sure to set the ContentType parameter to application/xml:

[code]public ActionResult Index(TwilioRequestViewModel vm)
{
var cf = new ChannelFactory<IAzureTunnelServiceChannel>("relay");
using (var ch = cf.CreateChannel())
{
string result = ch.Relay(vm);
return Content(result, "application/xml");
}
}[/code]

To test these changes I need to make sure I have a local copy of my web app running, and then start the relay service. Now I when I send a text message to my Twilio phone number the request is relayed to my development machine, which takes the request and calls my local web app. If I am debugging my local web app I can even insert breakpoints and step through its code.

Whatever content my app creates is returned through the relay service back to Twilio. For example, if I return TwiML containing the <sms> verb , when I send a text message to my Twilio phone number, I’ll get a response message back from my application.

Note that if you do insert breakpoints in your web app code, your TwiML response may not actually be executed by Twilio and you will probably start to see timeout errors showing up in your Twilio debugger. This is because the original Twilio request will timeout before a response can be returned to it.

Wrap Up

Service bus makes it easy to relay messages across boundaries to enable local debugging of Twilio applications. If you want to try running this yourself, the source code for the samples shown in this post are all online here. You’ll of course need to register for a new Windows Azure account, which you can do here.

If you have any feedback or comments, please email me at devin@twilio.com, or tweet at me @devinrader