Share via


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

It’s finally time to get to the “Azure” piece of this three-parter! Those of you who haven't read Part 1 and Part 2 may want to at lease peruse those posts for context. Those of you who have know that I created a Windows Store application (Boys of Summer) that features push notifications to inform its end users of news related to their favorite major league teams. The Windows Store application works in concert with a Cloud Service (hosted as a Windows Azure Web Site) to support the notification workflow, and this post tackles that last piece of the puzzle, as highlighted below.

Push notification workflow, highlighting Cloud Service

There are two main components to this cloud service: an ASP.NET MVC site that heavily leverages the Web API and backing storage in the form of a MySQL database. The deployment mechanism is a Windows Azure Web Site, with MySQL being selected (over Windows Azure SQL Database) since a 20MB instance of MySQL is included free of charge in the already free-of-charge entry level offering of Web Sites. I don’t anticipate my application needing more horsepower than already included in that free tier of Windows Azure Web Sites, but it’s nice to know I can step up to the shared or reserved model should Boys of Summer become wildly successful!

What follows is a rather lengthy but comprehensive post on how I set this service up inside of Windows Azure. I’ve split it into seven sections so you can navigate to the parts of specific interest to you:

Step 1. Get your Windows Azure account

Step 2. Create a new Windows Azure Web Site

Step 3. Set up the MySQL database

Step 4. Build the APIs for the cloud service

Step 5. Implement the ASP.NET MVC page that issues the notifications

Step 6. Implement interface with the Windows Notification Service (WNS)

Step 7. Deploy the completed solution to Windows Azure

Step 1: Get Your Windows Azure Account

Windows AzureThere are a number of ways to get a Windows Azure account, and one of the easiest for ‘kicking-the-tires’ is the 90-day Free Trial. If you’re an MSDN subscriber, you already have a monthly allotment of hours as well, and all you need to do is activate the benefit.

Likewise, WebsiteSpark and BizSpark participants get monthly benefits by virtue of the MSDN subscriptions that come with those programs. Of course, you can also opt for one of the paid plans as well or upgrade after trying out one of the free tiers.

Step 2: Create a New Windows Azure Web Site

New option for creating Windows Azure assetsOnce you’ve logged into the Windows Azure portal with your Microsoft account, you’ll be able to provision any of the services available, via the NEW option at the lower left.

For Boys of Summer, I needed a Web Site along with a database.

Creating a new Web Site with Database

If you don’t see Windows Azure Web Sites listed on the left sidebar, or it’s disabled, you’ll need to visit your account page and select the preview features menu option to enable Web Sites (and other features that are not yet fully released). The activation happens in a matter of minutes, if not faster.

To create the Web Site, only a few bits of information need be entered on the two screens that appear next:

New Windows Azure Web Site properties

  • the URL for the Web Site, which must be a unique URL (in the azurewebsites.net domain). It’s this URL that will be target of the RESTful service calls made from my Windows Store application.
  • the location of the data center that will host the service.
  • what type of database to create (MySQL here, but SQL Database is also an option).
  • a database connection string name.
  • the name of the database server.
  • the data center location hosting the MySQL database, which should be the same data center housing the Web Site itself; otherwise, you’ll incur unnecessary latency as well as potential bandwidth cost penalties.

The last checkbox on the page confirms agreement with ClearDB’s legal and privacy policy as they are managing the MySQL database instances.

Once the necessary details have been entered, it takes only a minute or two to provision the site and the database, during which the status is reflected in the portal. When the site is available, it can be selected from the list of Web Sites in the Azure portal to access the ‘getting started’ screen below:

New Web Site 'getting started' page

The site (with default content) is actually live at this point, which you can confirm by pressing the BROWSE button on the menu bar at the bottom of the screen.

To get the information I need to develop and deploy the service, I next visit the DASHBOARD option which provides access to a number configuration settings. I specifically need the first two listed in the quick glance sidebar on the right of the dashboard page:

  1. I’ll use the MySQL connection string to create the tables needed to store the notification URIs for the various clients, and
  2. I’ll save the publish profile (as a local file) to later be imported into Visual Studio 2012. That will allow me to deploy my ASP.NET application directly to Windows Azure. Do be aware that this file contains sensitive information enabling deployment to the cloud, so treat publish settings file judiciously.

Key information for building the Web API service

At this point, I won’t even need to revisit the Windows Azure portal, but of course there are a number of great monitoring and scaling features I may want to consult there once my site is up and running.

Step 3. Set up the MySQL Database

In the Web API service implementation, I’m using Entity Framework (EF) Code First along with a basic Repository pattern on a VERY simple data model that abstracts the two tables with the column definitions shown below.

Table Name Column Name Column Type Column Usage
registrations Id INT(11) autoincrement value (unique and primary key)
Uri VARCHAR(1024) a notification channel URI
TeamId VARCHAR(10) short name for a baseball team associated with the notification channel
teams Id VARCHAR(10) short name of a baseball team (unique and primary key)
Name VARCHAR(63) friendly name of a baseball team
Logo MEDIUMBLOB image of team logo (less than 200KB and 1024x1024 pixels)

I used the open source MySQL Workbench (with the connection string data from the Windows Azure portal) to create the tables and populate data into the teams table. There’s also a very simple stored procedure that is invoked by one of the service methods that I’ll discuss a bit later in this post:

 CREATE PROCEDURE updatechanneluri 
          (IN olduri VARCHAR(2048), IN newuri VARCHAR(1024))
BEGIN
   UPDATE registrations SET Uri = newuri WHERE Uri = olduri;
END

Since I opted for MySQL, I needed to install a provider, and specifically I installed Connector/Net 6.6.4, which supports Entity Framework and Visual Studio 2012. As of this writing, the version of the Connector/Net available via NuGet did not.

Step 4. Build the APIs for the Cloud Service

There are numerous options for building a cloud service in Windows Azure Web Sites – .NET, PHP, Node.js – and a number of tools as well like Visual Studio and Web Matrix. I opted to use ASP.NET MVC and the Web API in .NET 4.5 within Visual Studio 2012 (as you can see below).

Creating an ASP.NET Web API site

If you’re new to the ASP.NET MVC Web API, I highly recommend checking out Jon Galloway’s short screencast ASP.NET Web API, Part 1: Your First Web API to give you an overview of the project components and overall architectural approach.

My service includes three controllers: the default Home controller, a Registrations controller, and a Teams controller. Home Controller provides a default web page to send out the toast notifications; I’ll look at that one in Step 6.

The other two controllers specifically extend ApiController and are used to implement various RESTful service calls. Not surprisingly, each corresponds directly to one of the two Entity Framework data model classes (and by extension, the MySQL tables). These controllers more-or-less carry out CRUD operations on those two tables.

Step 4.1  Coding TeamsController

The application doesn’t modify any of the team information, so there are only two read operations needed:

 public Team Get(String id) 

retrieves information about a given team given its id;theTeam class is one of the two entity classes in my Entity Framework model.

This method is invoked via an HTTP GET request matching a route of https://boysofsummer.azurewebsites.net/api/teams/{id}, where id is the short name for the team, like redsox or orioles.

 public HttpResponseMessage GetLogo(String id, String fmt)

retrieves the raw bytes of the image file for the team logo. Returning an HttpResponseMessage value (versus the actual data) allows for setting response headers (like Content-type to “image/png”). The implementation of this method ultimately reaches in to the teams table in MySQL to get the logo and then writes the raw bytes of that logo as StreamContent in the returned HttpResponseMessage. If a team has no logo file, an HTTP status code of 404 is returned.

This method responds to a HTTP GET request matching the route https://boysofsummer.azurewebsites.net/api/logos/{id}/png, where id is again the short name for a given team.

Why didn't I use a MediaTypeFormatter? If you've worked with the Web API you know it leverages the concept of content negotiation to return different representations of the same resource by using the Accept: header in the request to determine the content type that should be sent in reply. For instance, the resource https://boysofsummer.azurewebsites.net/api/teams/redsox certainly refers to the Red Sox, but does it return the information about that team as XML? as JSON? or something else?

The approach I'd hoped for was to create a MediaTypeFormatter, which works in conjunction with content negotiation to determine how to format the result. So I created a formatter that would return the raw image bytes and a content type of image/png whenever a request for the team came in with an Accept header specifying image/png. Any other requested type would just default to the JSON representation of that team's fields.

For this to work though, the incoming GET request for the URI must set the Accept header appropriately. Unfortunately, when creating the toast template request (in XML) you only get to specify the URI itself, and the request that is issued for the image (by the inner workings of the Windows 8 notification engine) includes a catch-all header of Accept: */*

My chosen path of least resistance was to create a separate GET method (GetLogo above) with an additional path parameter that ostensibly indicates the representation format desired (although in my simplistic case, the format is always PNG).

Step 4.2  Coding RegistrationsController

The Registrations controller includes three methods which manage the channel notification registrations that associate the application users’ devices with the teams they are interested in tracking. As you would probably expect, those methods manipulate the registrations table in MySQL via my EF model and repository implementation.

 public HttpResponseMessage Post(Registration newReg)

inserts a new registration record into the database. Each registration record reflects the fact that a client (currently identified by a given notification channel URI) is interested in tracking a given team, identified the team id (or short name). The combination of channel URI and team should be unique, so if there is an attempt to insert a duplicate, the HTTP code of 409 (Conflict) is returned; a successful insertion yields a 201 (Created).

This POST method is invoked by the Windows Store application whenever the user moves a given team’s notification toggle switch to the On position. The URI pattern it matches is https://boysofsummer.azurewebsites.net/api/registrations, and the request body contains the channel URI and the team name formatted as JSON.

 public HttpResponseMessage Delete(String id, String uri)

deletes an existing record from the registrations database, in response to a user turning off notifications for a given team via the Windows Store application interface. A successful deletion results in a HTTP code of 204 (No Content), while an attempt to remove a non-existent record returns a 404 (Not Found).

The DELETE HTTP method here utilizes a URI pattern of https://boysofsummer.azurewebsites.net/api/registrations/{id}/{uri} where id is the team’s short name and uri is the notification channel URI encoded in my modification of Base64.

 public HttpResponseMessage Put(String id, [FromBody] UriWrapper u)

modifies all existing records matching the notification channel URI recorded in the database with a new channel URI provided via the message body. This is used when a given client’s notification channel URI ‘expires’ and a new one is provided, so that the client can continue to receive notifications for teams to which he previously subscribed. This method, by the way, is the one that leverages the MySQL stored procedure mentioned earlier to more efficiently update potentially multiple rows.

This PUT method matches the URI pattern https://boysofsummer.azurewebsites.net/api/registrations/{id} where id is the previous notification channel URI as recorded in the MySQL database. The replacement URI is passed via the message body as a simple JSON object. Both URIs are Base64(ish) encoded. The update returns a 204 (No Content) regardless of whether any rows in the Registration table are a match.

Step 5. Implement the ASP.NET MVC Page That Issue the Notifications

For the sample I’m presenting here, I adapted the default ASP.NET MVC home page to provide a way to trigger notifications to the end users of the Boys of Summer Windows Store app. At the moment, it’s quite a manual process and assumes that someone is sitting in front of a browser – perhaps scanning sports news outlets for interesting tidbits to send. That’s not an incredibly scalable scenario, so ‘in real life’ there might be a (semi-)automated process that taps into various RSS or other feeds and automatically generates relevant notifications.

ASP.NET MVC View for sending notifications

 

The controller here has two methods, a GET and a POST. The GET is a simple one-liner that returns the View with the list of teams populated from the model via my team repository implementation.

The POST, which is fired off when the Send Notification button is pressed, is the more interesting of the two methods, and appears in its entirety below with a line-by-line commentary.

    1:  [HttpPost]
    2:  public async Task<ActionResult> Index
    3:         (String notificationHeader, String notificationBody, String teamName)
    4:  {
    5:      // set XML for toast
    6:      String toast = SetToastTemplate(notificationHeader, notificationBody,             
    7:          String.Format("{0}api/logos/{1}", Request.Url, teamName));
    8:   
    9:      // send notifications to subscribers for given team
   10:      List<NotificationResult> results = new List<NotificationResult>();
   11:      foreach (Registration reg in RegistrationRepository.GetRegistrations(teamName))
   12:      {
   13:          NotificationResult result = 
   14:                           await WNSHelper.PushNotificationAsync(reg.Uri, toast);
   15:          results.Add(result);
   16:   
   17:          if (result.RequiresChannelUpdate())
   18:              RegistrationRepository.RemoveRegistration(teamName, reg.Uri);
   19:      }
   20:   
   21:      // show results of sending 0, 1 or multiple notifications
   22:      ViewBag.Message = FormatNotificationResults(results);
   23:    
   24:      return View(TeamRepository.GetTeams().ToList());
   25:  }
Lines 2-3 The POST request pulls the three form fields from the view, namely the Header Text, Message, and Team
Lines 6-7 A helper method is called to format the XML template; it’s simply a string formatting exercise to populate the appropriate parts of the template. This implementation only supports the ToastImageAndText02Template.
Line 11 A list of all the registrations for the desired team is returned from the database
Lines 13-14 A notification is pushed for each registration retrieved using a helper class that will be explained shortly.
Line 15 A list of NotificationResult instances records the outcome of each notification, including error information if present. Note though that you can never know if a notification has successfully arrived at its destination.
Lines 17-18 If the specific error detected when sending the notification indicates that the targeted notification channel is no longer valid, that registration is removed from the database. For instance, it may correspond to a user who has uninstalled the application in which case it doesn’t make sense to continue sending notifications there and, if unchecked, could raise a red flag with the administrators of WNS.
Line 22 A brief summary of the notification results is included on the reloaded web page. If a single notification was sent, a few details are provided; if multiple notifications were attempted, only the number of successes and failures is shown. For a production application, you may want to record each attempt in a log file or database for review to ensure the application is healthy. Additionally, failed attempts can give some high level insight to the usage patterns of your applications – who’s on line when and perhaps how many have uninstalled your app.
Line 24 As with the GET request, the model (the list of baseball teams) is reloaded.

Step 6. Implement the Interface with the Windows Notification Service (WNS)

This is the fun part: the exchange between my cloud service and WNS, which does all the heavy lifting in terms of actually delivering the toast to the devices - via the implementation behind Line 14 above:

 await WNSHelper.PushNotificationAsync(reg.Uri, toast)

WNSHelper is a class I wrote to abstract the message flow between the service and WNS and as such is a simpler (but less robust) version of the Windows 8 WNS Recipe code available in the Windows Azure Toolkit for Windows 8 (the source for which is located in the /Libraries/WnsRecipe of the extracted toolkit). The code for WNSHelper is available a Gist (if you want to dive deeper), but here it is in pictures:

OAuth flow

First, my service (via the RefreshTokenAsync method) initiates a request for an access token via the OAuth 2.0 protocol. It uses Live Services to authenticate the package SID and client secret I obtained from the Windows Store when registering my application. The package SID and client secret do need to be stored securely as part of the Cloud Service, since they are the credentials that enable any agent to send push notifications to the associated application.

The HTTP request looks something like the following. Note the SID and the client secret are part of the URL-encoded form parameters along with grant_type and scope, which are always set to the values you see here.

 POST https://login.live.com/accesstoken.srf HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: login.live.com
Content-Length: 210

grant_type=client_credentials&scope=notify.windows.com&
client_id=REDACTED_SID&client_secret=REDACTED_SECRET

Assuming the authentication succeeds, the response message will include an access token, specifically a bearer token , which grants the presenter (or ‘bearer’) of that token the privileges to carry out some operation in future HTTP requests. The HTTP response message providing that token has the form:

 HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token" : "mF_9.B5f-4.1JqM",
  "token_type" : "Bearer",
  "expires_in" : 86400
}

An access token is nothing more than a hard-to-guess string, but note there is also an expires_in parameter, which means that this token can be used only for a day (86400 seconds), after which point additional requests using that token will result in an HTTP status code of 401 (Unauthorized). This is important, and I’ll revisit that shortly.

Once the Cloud Service has the bearer token, it can now send multiple push notifications to WNS by presenting that token in the Authorization header of push notification HTTP requests. The actual request is sent to the notification channel URI, and the message body is the XML toast template.

A number of additional HTTP headers are also supported including X-WNS-Type, which is used to indicate the type of notification (toast, tile, badge, or raw) and X-WNS-RequestForStatus, which I employ to get additional information about the disposition of the notification.

Here’s a sample notification request as sent from my cloud service:

 POST https://bn1.notify.windows.com/?token=AgYAAADCM0ruyKK… HTTP/1.1
Content-Type: application/xml
Host: bn1.notify.windows.com
Authorization: Bearer mF_9.B5f-4.1JqM
X-WNS-Type: wns/toast
X-WNS-RequestForStatus: true
Content-Length: 311

<toast>
   <visual>
      <binding template="ToastImageAndText02">
          <image id="1" src="https://boysofsummer.azurewebsites.net/api/logos/tigers/png" />
          <text id="1">Breaking News!!!</text>
          <text id="2">The Detroit Tigers have won the ALCS!</text>
      </binding>
   </visual>
</toast>

Assuming all is in order, WNS takes over from there and delivers the toast to the appropriate client device. For toast notifications, if the client is off-line, the notification is dropped and not cached; however, for tiles and badges the last notification is cached for later delivery (and when the notification queue is in play, up to five notifications may be cached).

If WNS returns a success code, it merely means it was able to process the request, not that the request reached the destination. That bit of information is impossible to verify. Depending on the additional X-WNS headers provided in the request, some headers will appear in the reply providing diagnostic and optional status information.

Now if the request fails, there are some specific actions you may need to take. The comprehensive list of HTTP response codes is well documented, but I wanted to reiterate a few of the critical ones that you may see as part of ‘normal’ operations.

200 Success – this is a good thing
401 Unauthorized – this can occur normally when the OAuth bearer token expires. The service sending notifications should continue to reuse the same OAuth token until receiving a 401. That’s the signal to reissue the original OAuth request (with the SID and client secret) to get a new token. This means of course, that you should check for a 401 response when POSTing to the notification channel, because you’ll want to retry that operation after a new access token has been secured.
406 Not Acceptable – you’ve been throttled and need to reduce the rate at which you are sending notifications. Unfortunately, there’s no documentation of how far you can push the limits before receiving this response.
410 Gone – the requested notification channel no longer exists, meaning you should purge it from the registry being maintained by the cloud service. It could correspond to a client that has uninstalled your application or one for which the notification channel has expired and a new one has not been secured and recorded.

My implementation checks for all but the throttling scenario. If a 401 response is received, the code automatically reissues the request for an OAuth token (but with limited additional error checking). Similarly, a 406 response (or a 404 for that matter) results in the removal of that particular channel URI/team combination from the MySQL Registrations table.

Step 7. Deploy it all

Only one thing left! SHIP IT! During the development of this sample I was able to do a lot of debugging locally, relying on Fiddler quite a bit to learn and diagnose problems with my RESTful API calls, so you don’t have to deploy to the cloud right away – though with the free Windows Azure Web Site tier there’s no real cost impact for doing so. In fact, my development was somewhat of a hybrid architecture, since I always accessed the MySQL database I’d provisioned in the cloud, even when I was running my ASP.NET site locally.

When it comes time to move to production, it’s incredibly simple due to the ability to use Web Deploy to Windows Azure Web Sites. When I provisioned the site at the beginning of this post, I downloaded the publication settings file from the Windows Azure Portal. Now via the Publish… context menu option of the ASP.NET MVC project, I’m able in Visual Studio 2012 (and 2010 for that matter) to import the settings from that file and simply hit publish to push the site to Windows Azure.

Truth be told, there was one additional step – involving MySQL. Windows Azure doesn’t have the Connector / NET assemblies installed, nor does it know that it’s an available provider. That’s easily remedied by including the provider declaration in the web.config file of the project:

<system.data>
<DbProviderFactories>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient"
description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data" />
</DbProviderFactories>
</system.data>MySQL references need to have Copy Local set to true

and making sure that each of the MySQL referenced assemblies has its Copy Local property set to true. This is to ensure the binaries are copied over along with the rest of the ASP.NET site code and resource assets.

With the service deployed, the only small bit of housekeeping remaining is ensuring that the Windows Store application routes its traffic to the new endpoint, versus localhost or whatever was being used in testing. I just made the service URI an application level resource in my Windows Store C#/XAML app so I could easily switch between servers.

 <x:String x:Key="ServiceUri">https://boysofsummer.azurewebsites.net:12072</x:String>

Finis!

With the service deployed, the workflow I introduced two posts ago with the picture below has been realized!  Hopefully, this deep-dive has given you a better understanding of the push notification workflow, as well as a greater appreciation for Windows Azure Mobile Services, which abstracts much of this lower-level code into a Backend-as-a-Service offering that is quite appropriate for a large number of similar scenarios.

 

Boys of Summer push notification workflow

Comments

  • Anonymous
    April 02, 2013
    Hi Jim, first of all good work in putting this implementation together, Secondly do you have any source code you can share, I'm particularly interesting in the registration or linking of Teams with Channels, as I have similar "use cases",but in my case I actually need to associate multiple topics or in your case "teams" to a particular Channel. Thanks Robert