Windows 8 Notifications: Push Notifications via Windows Azure Web Sites (Part 2)

In this post, I’ll cover what’s required to outfit my Windows Store Boys of Summer application to receive toast notifications. If you haven’t already, I suggest reviewing the previous post for context .

To provide the underlying ‘glue’ for the Windows Notification Service, a Windows Store application must be associated with an application profile you’ve created under your Windows Store developer account. Prior to the official release of Windows 8, there was a temporary service that could be used, but going forward you will need to have a Windows Store developer account to develop and test Windows Store applications that leverage push notifications.

Step 1: Associate your application with the Windows Store to obtain credentials necessary for push notifications

Associate Windows Store application with the storeYou can access your account through the Dev center, or simply through Visual Studio via the Store>Associate App With the Store… context menu option, at which point you’ll be asked to login to the Dev center with your store account credentials.  For each app you declare on the Dev center, you’ll get a checklist of what’s needed to complete your app before submission, but for the purposes of this exercise, you’ll only need to configure one of the steps.

The screen shots below walk through the process to obtain a package security identifier (SID) and a client secret. Those will be needed for the implementation of the Cloud Service, which I’ll cover in the next post.

  1. After application name as been reserved:

  2. After selecting Advanced features optionabove:

  3. After selecting Push notifications and Live Connect services info.   The Package Security Identifier and Client Secret, should be treated as sensitive data; think of them as a user id and password allowing a service to push notifications to the registered application.

During this process, your application is also assigned a unique package name and publisher. You can obtain those values from the Identifying your app… page accessible via a link on the left sidebar above and populate it in your project’s Package.appxmanifest manually, or you can let Visual Studio associate it for you via the aforementioned context menu option.

Associate app with the Windows Store

Speaking of the application manifest don't forget to mark your application as Toast capable in the Application UI tab and select the Internet (Client) capability on the Capabilities tab of the manifest, or you'll be scratching your head for a while wondering why everything seems in place, but the notifications don't occur!

Step 2: Get a push notification channel within the application

A push notification channel is a URI that looks something like the following (and always points to the notify.windows.com domain)

https://bn1.notify.windows.com/?token=AgYAAADCM0ruyKKQnGeNHSWDfdqWh9aphe244WGh0%…

It serves to uniquely identify the current application running on the current device and as such is a potential destination for notifications issued from the Cloud Service in the notification workflow I described earlier.

The notification channel URIs are not persistent however, and have an expiration period of 30 days (though I’ve seen URIs change under other circumstances as well, such as new application deployments during testing). It’s up to your application to be aware of the expiration period and handle things accordingly. At the very least the application should renew its notification channel every time it runs. If the application may be dormant for more than 30 days, you may also want to register a background maintenance task that refreshes the channel, so the application continues to receive notifications even though it hasn’t been recently launched.

I’m assuming my Windows Store application will be compelling enough to be opened at least once a month, so I am foregoing the background task. To refresh the channel on each launch, I’ve included the following single line of code at the end of the OnLaunched and OnActivated methods (this is the XAML/C# implementation; for JavaScript you’d use the activated event listener)

 this.CurrentChannel = await PushNotificationChannelManager
                              .CreatePushNotificationChannelForApplicationAsync();

I defined CurrentChannel as a public read property of the Application class instance, so I could easily refer to it within other areas of the application.

Step 3: Register the notification channel with the Cloud Service

The notification channel is merely an web address to which the Cloud Service pushes the notification data. It’s up to the service to decide what that data is and to what subset of users that information should be sent.

Notification toggle in the Boys of Summer user interfaceA toast notification, which is the scenario here, typically targets a subset of users based on some combination of criteria (metadata, if you will) associated with their notification channel. In the Boys of Summer application, that metadata is fairly obvious – it’s the team or teams that a user is interested in keeping abreast of. And the user signifies his or her interest (or disinterest!) by toggling the Notification switch (right) associated with a given team in the application’s user interface.

The code (below) behind the ToggleSwitch’s toggled event directly interfaces with the Cloud Service (via a RESTful API). I’ll describe that service in more detail later, but at the high level, the code really does one of two things:

  • Records the current user’s interest in notifications for the associated team (Lines 9 – 25) by POSTing the notification channel and the team name to the Cloud Service, or
  • Issues a DELETE HTTP request (Lines 29 –36) to signal that same service to no longer inform this client about events related to the given team.
  1: private async void notificationsToggled(object sender, RoutedEventArgs e)
  2: {
  3:     var toggleSwitch = e.OriginalSource as ToggleSwitch;
  4:     var selectedTeam = ((FrameworkElement)sender).DataContext as SampleDataItem;
  5:  
  6:     var channelUri = ((App)Application.Current).CurrentChannel.Uri;
  7:  
  8:     HttpClient clientRequest = new HttpClient();
  9:     if (toggleSwitch.IsOn)
  10:     {
  11:         string postUri = String.Format("{0}/api/registrations/", 
  12:                                         App.Current.Resources["ServiceUri"]);
  13:  
  14:         var JsonPayload = new StringContent(
  15:                 Newtonsoft.Json.JsonConvert.SerializeObject(
  16:                     new
  17:                     {
  18:                         TeamId = selectedTeam.UniqueId,
  19:                         Uri = ((App)Application.Current).CurrentChannel.Uri
  20:                     }
  21:                 )
  22:         );
  23:         JsonPayload.Headers.ContentType = 
                                       new MediaTypeHeaderValue("application/json");
  24:  
  25:         await clientRequest.PostAsync(postUri, JsonPayload);
  26:     }
  27:     else
  28:     {
  29:         // format DELETE request
  30:         string deleteUri = String.Format("{0}/api/registrations/{1}/{2}",
  31:             App.Current.Resources["ServiceUri"],
  32:             selectedTeam.UniqueId,
  33:             channelUri.ToBase64ish()
  34:         );
  35:  
  36:         await clientRequest.DeleteAsync(deleteUri);
  37:     }
  38: }

What's this ToBase64ish call on Line 33? This was part of an interesting adventure that really is tangential to Windows 8 and push notifications, so feel free to skim or ignore this callout.

Since I am using the ASP.NET Web API to implement the Cloud Service, I wanted to do things in a very RESTful way. The DELETE HTTP request, therefore, should identify the resource on the server that is to be deleted as part of the URI itself (versus within the HTTP request body). In this application, that resource is uniquely identified by the combination of the notification channel URI and the team name. 

Now, notification channel URIs have some rather unfriendly characters (like the forward slash and the colon) when it comes to including them as a path element of a larger URI. That’s not a huge shock, and that’s one reason why there’s UrlEncode functions (or as I found out multiple variants thereof!). It get’s uglier when you discover there’s also settings like DontUnescapePathDotsAndSlashes and, oh, if you have multiple forward slashes they’ll collapse into one.

So, I gave in and decided to Base64 encode the notification URI. Base64 uses the 52 Latin alphabetic characters (lower and upper case) and the digits (0 through 9), but also supplements with two other characters: the forward slash (/) and the plus sign (+). Both of those are also somewhat ‘special’ in terms of URIs, but the hyphen and underscore are not, so I created a simple String extension method (named ToBase64ish) that Base64 encodes and then replaces every / with a hyphen and every + with an underscore.  (Of course, there’s a matching FromBase64ish method as well that the Cloud Service uses on the other end of the pipe).

Step 2.5: Update previously recorded notification channel URI registrations

While the three steps above would seem to do the trick, let’s take a step (or half-a-step) back and consider what happens when the notification channel for the application changes (as it will at least every 30 days). When the client’s notification channel URI has changed, all of those channel URI/team combinations previously sent to the server for safe keeping are obsolete. The user will assume she’ll still get notifications about her favorite team, but when team news items are processed by the Cloud Service, it will forward them to a URI that’s no longer valid!

It’s up to the application to make sure the channel URIs that are registered on the server accurately reflect the current channel URI that the application has secured. There are a few ways to handle this, and how you do it depends somewhat on your application’s requirements. The guidance for Windows Store applications is to provide as optimal an off-line experience as possible, so for the Boys of Summer application this is what I came up with:

  1. If the application is off-line, the notification ToggleSwitches will appear disabled, but retain their last-known settings. That means that I need to save some application state for the notification setting (on or off) of each of the teams. Local application storage is a good spot for that, and so my application pulls that data into the ViewModel that’s associated with each division’s team list.
  2. The application also needs to retain the value of the last URI it used to register notifications with the Cloud Service; otherwise, it wouldn’t know that that URI had changed! Local application storage to the rescue again.
  3. Lastly, the application needs to check if the notification URI it just obtained (on launch or activation) is different from the last one it used to register teams of interest with the Cloud Service. If so then the Cloud Service needs to update all of the notification records containing the previous URI to reflect the new one.

To handle this last step, I included another method in the App class, and invoke that method immediately after securing the push notification channel - that one line of code above that calls CreatePushNotificationChannelForApplicationAsync. As you might expect, if the channel URI has changed, I need to invoke another method on the Cloud Service, this time via an HTTP PUT indicating that the previous URI should be updated on the server with the new one.

 private async void RefreshChannelUri(String newUri)
 {
     // get last known notification channel id
     String previousUri = 
         (ApplicationData.Current.LocalSettings.Values["ChannelUri"] 
            ?? String.Empty).ToString();
     ApplicationData.Current.LocalSettings.Values["ChannelUri"] = newUri;
  
     // if no previous channel id, nothing to do
     if (String.IsNullOrEmpty(previousUri)) return;
  
     // if channel id hasn't changed, nothing to do
     if (previousUri == newUri) return;
  
     // update current registrations to new URI
     HttpClient clientRequest = new HttpClient();
     string putUri = String.Format("{0}/api/registrations/{1}", 
         App.Current.Resources["ServiceUri"], previousUri.ToBase64ish());
  
     var JsonPayload = new StringContent(
             Newtonsoft.Json.JsonConvert.SerializeObject(
                 new
                 {
                     Uri = newUri.ToBase64ish()
                 }
             )
     );
  
     JsonPayload.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  
     await clientRequest.PutAsync(putUri, JsonPayload);
 }

Next step: implementing the cloud service and deploying it as a Windows Azure Web Site