Cover

Architecting WP7 - Part 8 of 10: Toast Push Notifications

In this eighth part of my series on architecting Windows Phone 7 (WP7) applications, i’ll show you how to deal with Toast Push Notifications.  If you’ve missed the past parts of the series, you you can visit them here:

When I say Toast notifications, I am specifically talking about being able to send the phone (e.g. the user) a push notfication that something is important and that the user might possibly want to launch the application. Its a near-time notification to the phone (if connectivity is there).  This notification can either alert the application when its running or give the user a notification window at the top of the phone like so:

Toast Notification

Toast push notifications are one type of notifications that Windows Phone 7 supports. The Push notification system for Windows Phone 7 is a service that Microsoft provides to allow you to communication with the phone without worrying about whether the phone is available or to queue up the notification for when the phone is available.

WP7 Notificaiton System Overview
(click for full size image)

Typical this requires some sort of service to push the notifications to the “Microsoft Push Notification Service”.  The “Microsoft Push Notification Service” then is responsible for alerting the phone when the phone is available.

To use Push Notifications, your application must warn your user that it uses Push Notifications and allow them to disable those notifications.

How It Works

The workflow for using notifications in general (and specifically for Toast notifications) is:

  • Your Phone App will register a “Channel” with Microsoft’s service.
  • Your Phone App will get a URI for that “Channel” that it will send to a service you own.
  • Your Phone App will bind it for specific uses (e.g. Toast).
  • When ready, your service will call Microsoft’s Push Notification Service with a Notication using the URI received by your Phone App.
  • Phone will receive the Notification.

First you will need to register your channel during your application’s lifetime (I usually do this in an Options dialog when the user enables the feature):

const string CHANNELNAME = "FunWithToast";
private HttpNotificationChannel _myChannel;

public void CreateChannel()
{
  try
  {
    if (_myChannel == null)
    {
      // Attempt to get the channel
      _myChannel = HttpNotificationChannel.Find(CHANNELNAME);

      // Can't Find the Channel, so create it
      if (_myChannel == null)
      {
        _myChannel = new HttpNotificationChannel(CHANNELNAME);
        WireChannel(_myChannel);
        _myChannel.Open();
      }
      else
      {
        WireChannel(_myChannel);
      }

      // Make sure that you can accept toast notifications
      if (!_myChannel.IsShellToastBound) _myChannel.BindToShellToast();

      // If the channel uri is valid now, then send it to the server
      // Otherwise it'll be registered when the URI is updated
      if (_myChannel.ChannelUri != null)
      {
        SendChannelUriToService(_myChannel.ChannelUri.ToString());
      }
    }

    return;
  }
  catch (Exception)
  {
    // NOOP
  }
}

To walkthrough the code:

  • I first check to see if the registration has happened already (by checking whether the _myChannel is null).
  • Then I use the HttpNotificationChannel class’ Find method to find a channel that was created (on a prior invocation of my application on this particular phone).
  • If I can’t find the channel, I create one and open it.  (NOTE: The WireChannel method just wires up some events).
  • Since I want a Toast Push Notification, I take the channel and bind it to the shell (via BindToShellToast method) to be sure that notifications are shown to the user.
  • Finally, I call my own Web Service with the URI of the channel so that I can push a notificaiton to this phone later.  If the ChannelUri is null, that means i’ll get an event thrown when the Uri is valid and I can let my web service know about that later.
void WireChannel(HttpNotificationChannel channel)
{
  channel.ChannelUriUpdated += myChannel_ChannelUriUpdated;
  channel.ShellToastNotificationReceived += 
    channel_ShellToastNotificationReceived;
  channel.ErrorOccurred += channel_ErrorOccurred;
}

When I wire up the channel, I can wire up events for:

  • When the channel URI is updated (so I can notify my web service).
  • When a Toast is received (Toasts aren’t shown if the application is already running, so you can show any information you need here).
  • And when an error occurs.  This event is important as it’ll help you debug if your message type is correct as we’ll see later.

In my web service (WCF but it could be REST, ASMX or MVC too), I am simply getting the URI and saving it to the database for use later:

public class MyPhoneService : IMyPhoneService
{

  public bool RegisterPhoneApp(string toastUrl)
  {
    // Add to list of URI's to notify
    using (var ctx = new PhoneEntities())
    {
      if (ctx.Phones.Where(p => p.PhoneUrl == toastUrl).Count() == 0)
      {
        var phone = Phone.CreatePhone(0, toastUrl, DateTime.Now);
        ctx.Phones.AddObject(phone);
        ctx.SaveChanges();
      }
    }

    // Always return true; 
    // should send better status, but this is an example ;)
    return true;
  }
}

The last part of the puzzle is the actual sending of the message. The messaging service that the Microsoft Push Notification Service uses is not SOAP based but simple HTTP messages. For Toast Push Notifications you will need to POST (e.g. HTTP message) an XML message that looks like this:

<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
   <wp:Toast>
      <wp:Text1>First Line</wp:Text1>
      <wp:Text2>Second Line</wp:Text2>
   </wp:Toast>
</wp:Notification>

This means the notifications are a one-off network call:

HttpWebRequest sendNotificationRequest =
  (HttpWebRequest)WebRequest.Create(pushUri);

// For Toast Update
sendNotificationRequest.ContentType = "text/xml";
sendNotificationRequest.Headers.Add("X-WindowsPhone-Target", "toast");
sendNotificationRequest.Headers.Add("X-NotificationClass", "2");

sendNotificationRequest.Method = "POST";

// Unique ID to help not repeat msg (Optional)
sendNotificationRequest.Headers.Add("X-MessageID", 
                                     Guid.NewGuid().ToString());

// Send it
var msg = string.Format(toastMessage, 
                        "Toast Message", 
                        "This is from the server");
byte[] notificationMessage = Encoding.UTF8.GetBytes(msg);
sendNotificationRequest.ContentLength = notificationMessage.Length;
using (Stream requestStream = sendNotificationRequest.GetRequestStream())
{
  requestStream.Write(notificationMessage, 
                      0, 
                      notificationMessage.Length);
}

You’ll note that in the I am using headers when I make the call.  The first two headers (“X-WindowsPhone-Target” and “X-NotificationClass”) specify that this is a toast notification. The third header is specifically to help you limit duplication of a message. You can download the example here: FunWithToast.zip

Architectural Considerations

So you now know how its done, but what are the implications?  I was chatting on Twitter today about why so few apps and @justinangel reminded me of one compelling problem with the service…it its a one call per message API. NOw if you’re building a modest application, especially if you have an existing, scalable web-tier, its not too bad.  But imagine if you need to send 25,000 toast notifications?  It would require you be able to scale up your service pretty quickly (e.g. something like EC2/Azure would help). It also requires that you have more than just the Phone application, but have a web presence to handle the incoming and outgoing http communication. So you have the extra expense which makes it hard for small-scale application development.

I am not as cynical as Justin on this fact, but I do know that when you build your application, that using Notification services (especially Toast since they are typically time-sensitive) means you have to take scale into account.