Azure and Phone … Better Together

We had an excellent time presenting today’s Windows Phone Camp in Charlotte. Thank you to everyone who attended. Here are some resources and major points of today’s “To the cloud…” session.

First, here is the slide deck for the presentation. 

To The Cloud...

Next, download the Windows Azure Toolkit for Windows Phone. This contains both the sending notifications sample, and the Babelcam application. Note that there are quite a few setup steps – using the Web Platform Installer is a great way to make all of this easier.

The key takeaway that I really wanted to convey: while the cloud is most often demonstrating massive scale scenarios, it’s also incredibly efficient at micro scale. The first scenario we looked at was using Azure Blob Storage as a simple (yet robust) way to host assets. Think of Blob Storage as a scalable file system with optional built in CDN support. Regardless of where your applications of hosted (shared host, dedicated hosting, or your own datacenter), and regardless of the type of application (client, phone, web, etc.) the CDN offers a tremendously valuable way to distribute those resources.

For MSDN subscribers, you already have access so there’s no excuse to not use this benefit. But even if you had to go out of pocket, hosting assets in Azure is $0.15/GB per month, + $0.01/10,000 transactions, + $0.15/GB outbound bandwidth (inbound is free). For small applications, it’s almost free. Obviously you need to do the math for your app, but consider hosting 200MB in assets (images, JS files, XAPs, etc.), a million transactions a month with several GB of data transfers: it’s very economical at the cost of a few dollars / month.

In the second demo, we looked at using Azure Queues to enhance the push notification service on the phone. The idea being that we’ll queue failed notifications, and retry them for some specified period of time. For the demo, I only modified the raw notifications. In PushNotificationsController.cs (in toolkit demo above), I modified SendMicrosoftRaw slightly:

 [HttpPost]
public ActionResult SendMicrosoftRaw(string userId, string message)
{
   if (string.IsNullOrWhiteSpace(message))
   {
       return this.Json("The notification message cannot be null, empty nor white space.", 
        JsonRequestBehavior.AllowGet);
   }

   var resultList = new List<MessageSendResultLight>();
   var uris = this.pushUserEndpointsRepository.GetPushUsersByName(userId).Select(u => u.ChannelUri);
   var pushUserEndpoint = this.pushUserEndpointsRepository.GetPushUsersByName(userId).FirstOrDefault();

   var raw = new RawPushNotificationMessage
   {
       SendPriority = MessageSendPriority.High,
       RawData = Encoding.UTF8.GetBytes(message)
   };

   foreach (var uri in uris)
   {              
       var messageResult = raw.SendAndHandleErrors(new Uri(uri));
       resultList.Add(messageResult);
      
       if (messageResult.Status.Equals(MessageSendResultLight.Error))
       {
           this.QueueError(pushUserEndpoint, message);
       }
   }

   return this.Json(resultList, JsonRequestBehavior.AllowGet);
}

Really the only major change is that if the messageResult comes back with an error, we’ll log the error. QueueError looks like this:

 private void QueueError(PushUserEndpoint pushUser, string message)
{            
    var queue = this.cloudQueueClient.GetQueueReference("notificationerror");

    queue.CreateIfNotExist();
    queue.AddMessage(new CloudQueueMessage(
        string.Format("{0}|{1}", pushUser.ChannelUri.ToString(), message)
        ));
}

We’re simply placing the message on the queue with the data we want: you need to get used to string parsing with queues. In this case, we’ll delimit the data (which is the channel URI and the message of the notification) with a pipe character. While the channel URI is not likely to change, it’s a better approach to store the username and not the URI in the message, and instead do a lookup of the current URI before sending (much like the top of SendMicrosoftRaw does), but for the purposes of the demo is fine.

When we try sending a raw notification when the application isn’t running, we’ll get the following error:

image_thumb3

Typically, without a queue, you’re stuck. Using a tool like Cloud Storage Studio, we can see the notification is written to the failure queue, including the channel URI and the message:

image_thumb5

So, now we need a simple mechanism to poll for messages in the queue, and try to send them again. Because this is an Azure webrole, there’s a way to get a “free” thread to do some processing. I say free because it’s invoked by the Azure runtime automatically, so it’s a perfect place to do some background processing outside of the main site. In Webrole.cs, you’ll see there is no Run() method. The base WebRole Run() method does nothing (it does an indefinite sleep), but we can override that. The caveat is, we never want to exit this method. If an exception bubbles out of this method, or we forget to loop, the role will recycle when the method exits:

 public override void Run()
{
    this.cloudQueueClient = cloudQueueClient ?? 
        GetStorageAccountFromConfigurationSetting().CreateCloudQueueClient();
    var queue = this.cloudQueueClient.GetQueueReference("notificationerror");
    queue.CreateIfNotExist();

    while (true)
    {
        Thread.Sleep(200);
       
        CloudQueueMessage message = queue.GetMessage(TimeSpan.FromSeconds(60));

        if (message == null) continue;              

        if (message.DequeueCount > 60)
        {
            queue.DeleteMessage(message);
            continue;
        }

        string[] messageParameters = message.AsString.Split('|');

        var raw = new RawPushNotificationMessage
        {
            SendPriority = MessageSendPriority.High,
            RawData = Encoding.UTF8.GetBytes(messageParameters[1])
        };

        var messageResult = raw.SendAndHandleErrors(new Uri(messageParameters[0]));

        if (messageResult.Status.Equals(MessageSendResultLight.Success))
        {
            queue.DeleteMessage(message);
        }
    }
}

What this code is doing, every 200 milliseconds, is looking for a message on the failure queue. Messages are marked with a 60 second timeout – this will act as our “retry” window. Also, if we’ve tried to send the message more than 60 times, we’ll quit trying. Got to stop somewhere, right?

 

We’ll then grab the message from the queue, and parse it based on the pipe character we put in there. We’ll then send another raw notification to that channel URI. If the message was sent successfully, we’ll delete the message. Otherwise, do nothing and it will reappear in 60 seconds.

 

While this code is running in an Azure Web Role, it’s just as easy to run in a client app, service app, or anything else. Pretty straightforward, right? No database calls, stored procedures, locking or flags to update. Download the completed project (which is the base solution in the toolkit plus these changes) here (note: you’ll still need the toolkit): 

VS2010 Solution

The final demo was putting it all together using the Babelcam demo – Azure queues, tables, notifications, and ACS.

Questions or comments? Let me know.