In Part 1 of this series we explored what “aliveness” means to a user and how apps participate in creating that experience. In Part 2 we looked at how to write and debug web services to support periodic updates for live tiles. Here in Part 3 we’ll complete the story by understanding how to deliver tile updates, toasts, and raw notifications to specific client devices on demand through the Windows Push Notification Service (WNS), and how Windows Azure Mobile Services simplifies the whole process.
Push notifications
Periodic updates, as we saw in Part 2, are initiated from the client side and provide a pollingor “pull” method of updating a tile or badge. “Push” notifications happen when a service directly sends an update to a device, where that update can be specific to a user, an app, and even a secondary tile.
Unlike polling, push notifications can happen at any time with much greater frequency, though be aware that Windows will throttle the amount of push notification traffic on a device if it’s on battery power, in connected standby mode, or if notification traffic becomes excessive. This means there is no guarantee that all notifications will be delivered (especially if the device is turned off).
So avoid thinking that you can use push notifications to implement a clock tile or other tile gadgets with a similar kind of frequency or time resolution. Instead, think about how you can use push notifications to connect tiles and notifications to a back-end service that has interesting and meaningful information to convey to the user, which invites them to re-engage with your app.
Before we dive into the details, understand that there are two distinct kinds of push notifications:
- XML updates that contain tile or badge updates, or toast notification payloads: Windows can process such push notifications and issue the update or toast on behalf of an app. An app can also handle these notifications directly, if desired.
- Binary or raw notifications that contain whatever data the service wants to send: these must be handled by app-specific code because Windows won’t know what to do with the data otherwise. See Guidelines and checklist for raw notifications for details such as size limits (5KB) and encoding (base64).
In both cases, a running (foreground) app can handle push notifications directly through the PushNotificationChannel class and its PushNotificationReceived event. For XML payloads, the app can modify the contents, change tags, and so forth, before issuing it locally (or choosing to ignore it). For raw notifications, the app processes the contents and then decides which notifications to issue, if any.
If the app is suspended or not running, it can also provide a background task for this same purpose. The app must request and be granted lock screen access, which then allows such app-specific code to run when a notification is received.
A background task typically does one or two things when the notification arrives. First, it might save some relevant information from the notification into local app data, where the app can retrieve it when it’s next activated or resumed. Second, the background task can issue local tile and badge updates and/or toast notifications.
To understand raw notifications better, consider a typical email app. When its back-end service detects new messages for a user, it sends a raw push notification to WNS that contains a number of email message headers. The service does so using a channel URI that’s connected to the specific app on that user’s specific device.
WNS then attempts to push that notification to the client. Assuming it succeeds, Windows receives that notification and looks for the app that’s associated with the channel URI in question. If it cannot find a suitable app, the notification is ignored. If an app exists and it’s running, Windows fires the PushNotificationReceived event, otherwise Windows looks for and invokes an available background task for that app.
Either way the raw notification ends up in the hands of some app code, which then processes the data, issues a badge update to the app tile to indicate the number of new messages, and issues up to five cycling tile updates with message headers. The app can also issue toast notifications for each new message that’s arrived, or at least one that indicates there’s new mail.
As a result, toasts tell the user that new email has arrived, and the app’s tile on the Start screen provides a quick and immediate view of new mail activity.
For more info about these client-side event handlers and background tasks, see the Raw notifications sample, the Being productive in the background—background tasks post on this blog, and the Background networking whitepaper. For our purposes here, let’s turn now to the service side of the story.
Working with Windows Push Notification Service (WNS)
Through the cooperation of Windows, apps, services, and WNS, it’s possible to deliver user-specific data to a specific app tile (or toast or raw notification handler) on a specific device for a specific user. The relationships between all these pieces is shown below.
Clearly, some wiring is necessary to make all this work harmoniously:
- You (the developer) register the app in the Windows Store to use push notifications. This provides an SID and client secret that the service uses to authenticate with WNS (these bits should never be stored on client devices for security purposes).
- At runtime, the app requests a WNS channel URI from Windows for each of its live tiles (primary and secondary), or one for raw notifications. An app must also refresh these channel URIs every 30 days, for which you can use another background task.
- The app’s service provides a URI through which the app can upload those channel URIs along with any data that describes its use (such as location for weather updates or a specific user account and activity). Upon receipt, the service stores those channel URIs and associated data for later use.
- The service monitors its backend for changes that apply to each particular user/device/app/tile combination. When the service detects a condition that triggers a notification for a particular channel, it builds the content of that notification (XML or raw), authenticates itself with WNS using the SID and client secret, and then sends the notification to WNS along with the channel URI.
Let’s look at each step in detail. (And just as a preview, if dealing with HTTP requests makes you nervous, Windows Azure Mobile Services relieves you from many of the details, as we’ll see.)
App registration with the Windows Store
To obtain the SID and client secret for your service, refer to How to authenticate with the Windows Push Notification Service (WNS) on the Windows Developer Center. The SID is what identifies your app with WNS, and the client secret is what your service uses to tell WNS that it’s allowed to send notifications for your app. Again, these should only be stored in the service.
Note that Step 4 in How to authenticate with the Windows Push Notification Service (WNS), “Send the cloud server's credentials to WNS,” is something you do only when your service sends a push notification. We’ll come back to that shortly, because at this stage your service lacks the key piece that it needs to send a notification, namely a channel URI.
Obtaining and refreshing channel URIs
The client app obtains channel URIs at runtime through the Windows.Networking.PushNotifications.PushNotificationChannelManager object. This object has only two methods:
- createPushNotificationChannelForApplicationAsync: creates a channel URI for the app’s primary tile as well as toasts and raw notifications.
- createPushNotificationChannelForSecondaryTileAsync: creates a channel URI for a specific secondary tile identified by a tileId argument.
The result of both async operations is a PushNotificationChannel object. This object contains the channel URI in its Uri property, along with an ExpirationTime to indicate the deadline for refreshing that channel. A Close method specifically terminates the channel if needed, and most importantly is the PushNotificationReceived event, which is again what’s fired when the app is in the foreground and a push notification is received through this channel.
The lifetime of a channel URI is 30 days, after which WNS rejects any requests made for that channel. App code thus needs to refresh those URIs with the create methods above at least once every 30 days and send those URIs to its service. Here’s a good strategy:
- On first launch, request a channel URI and save the string in the Uri property in your local app data. Because channel URIs are specific to a device, do not store them in your roaming app data.
- On subsequent launches, request a channel URI again and compare it to the one previously saved. If it’s different, send it to the service, or send it and let the service replace an older one if necessary.
- Also perform the previous step in your app’s Resuming handler (see Launching, resuming, and multitasking in the docs), because it’s possible that the app might have been suspended for more than 30 days.
- If you’re concerned the app won’t be run within 30 days, implement a background task with a maintenance trigger to run every few days or once a week. For details, refer again to Being productive in the background—background tasks; the background task in this case will just execute the same code as the app to request a channel and send it to your service.
Sending channel URIs to the service
Typically, push notification channels work with user-specific updates like email status, instant messages, and other personalized information. It’s unlikely that your service will need to broadcast the same notification to every user and/or every tile. For this reason, the service needs to associate each channel URI with more specific information. For an email app, the user’s id is paramount, as that would specify the account to check. A weather app, on the other hand, would likely associate each channel URI with a specific latitude and longitude, such that each tile (primary and secondary) would reflect a distinct location.
The app, then, must include these details when it sends a channel URI to its service, and the service must store them for later use.
Where user identity is concerned, it’s best for the app to authenticate the user with the service separately, using service-specific credentials or through an OAuth provider such as Facebook, Twitter, Google, or the user’s Microsoft Account (using OAuth is helpful with Windows Azure Mobile Services, as we’ll see later). If for some reason that’s not possible, be sure to encrypt any user id you send to the service or make sure to send it over HTTPS.
Whatever the case, how you send all this information to your service (in headers, through data in the message body, or as parameters on the service URI) is up to you. This part of the communication is strictly between the app and its service.
As a simple example, let’s say we have a service with a page called receiveuri.aspx (as we’ll see in the next section), the full address of which is in a variable called url. The following code requests a primary channel URI from Windows for the app and posts it to that page via HTTP. (This code derived and simplified from the Push and periodic notifications client side sample where the itemId variable, defined elsewhere, is used to identify secondary tiles; the sample also has a C++ variant, not shown here):
JavaScript:
Windows.Networking.PushNotifications.PushNotificationChannelManager
.createPushNotificationChannelForApplicationAsync()
.done(function (channel) {
//Typically save the channel URI to appdata here.
WinJS.xhr({ type: "POST", url:url,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
data: "channelUri=" + encodeURIComponent(channel.uri)
+ "&itemId=" + encodeURIComponent(itemId)
}).done(function (request) {
//Typically update the channel URI in app data here.
}, function (e) {
//Error handler
});
});
The following ASP.NET code is a basic implementation of a receiveuri.aspx page that processes this HTTP POST, making sure it received a valid channel URI, a user, and some identifier for the item.using Windows.Networking.PushNotifications;
PushNotificationChannel channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
byte[] channelUriInBytes = Encoding.UTF8.GetBytes("ChannelUri=" + WebUtility.UrlEncode(newChannel.Uri) + "&ItemId=" + WebUtility.UrlEncode(itemId));
TaskrequestTask = webRequest.GetRequestStreamAsync();
using (Stream requestStream = requestTask.Result)
{
requestStream.Write(channelUriInBytes, 0, channelUriInBytes.Length);
}
I emphasize the “basic” nature of this code because, as you can see, the SaveChannel function simply writes the contents of the request into a fixed text file, which clearly won’t scale beyond a single user! A real service, of course, would employ a database for this purpose, but the structure here will be similar.
<%@ Page Language="C#" AutoEventWireup="true" %>