Showing posts with label push notifications. Show all posts
Showing posts with label push notifications. Show all posts

Sunday, June 3, 2012

Windows 8 push notifications


This post is a brief overview of using push notifications in a Metro app. Push notifications let you create a web service that will be able to independently send tile, toast or raw notifications to your Metro app over Http using a preconfigured communication channel between the app and the service. So your app doesn’t need to poll the service regularly to check for updates, instead, the service can send updates to your app whenever new information is available. This is done through the Microsoft Windows Notification Service or WNS, which is an always-on Microsoft-managed service whose sole purpose is to channel billions of small messages from third-party callers to registered Metro apps in an efficient and dependable manner. Let’s see how this works in a nutshell.

image


1) Your Metro app requests from WNS that a channel be created between itself and WNS. WNS returns a unique channel Uri that represents that channel to the app.

2) The app forwards the channel Uri to your custom service – which you set up ahead of time. Your service stores the uri with the id of the device running the app. When the time comes, your service will use that uri to send a tile or toast notification to the registered devices through WNS.

3) Your service authenticates with WNS (one-time operation) by requesting and storing an authentication token returned by WNS. When the service is ready to send a toast or tile notifications to registered apps, it will send an HTTP  message to WNS including the stored auth token, so that WNS accepts the message.

4) Your service is now ready to send a notification to registered devices : it creates an XML message matching the tile or toast required schema, and sends it to each uris associated with the registered devices. WNS receives each authenticated message and forwards it to the corresponding device. As a result, the app’s tile is updated or a toast is raised on the device.

So first, you request a channel from your Metro app, using the PushNotificationChannelManager class :
   1: channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

Then you can forward the uri to your service, which has a register operation that accepts a Uri along with some unique identifier for the device running the Metro app :


   1: var data = new PushClientData
   2:             {
   3:                 CustomerId = App.Id,
   4:                 ChannelUri = channel.Uri.ToString()
   5:             };
   6:  
   7: // TODO : insert data into request content
   8:  
   9: //Send request
  10: var response = await client.PostAsync(uri, content);

The service operation may look something like this :


   1: public void RegisterApp(PushClientData app)
   2: {
   3:     //TODO : verify Uri is a valid WNS channel Uri
   4:  
   5:     registeredClientsDictionary[app.Id] = new Uri(app.ChannelUri);
   6: }

When your service runs for the first time, it needs to authenticate with WNS by retrieving an authentication token :


   1: protected void GetAccessToken()
   2: {
   3:     var urlEncodedSid = HttpUtility.UrlEncode(sid);
   4:     var urlEncodedSecret = HttpUtility.UrlEncode(secret);
   5:  
   6:     var body =
   7:       String.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=notify.windows.com",
   8:       urlEncodedSid, urlEncodedSecret);
   9:  
  10:     var client = new WebClient();
  11:     client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
  12:  
  13:     string response = client.UploadString(new Uri(authUri), body);
  14:     var oAuthToken = GetOAuthTokenFromJson(response);
  15:     this.accessToken = oAuthToken.AccessToken;
  16: }

The sid and secret key can currently be obtained by registering your Metro app in the Windows Push Notifications & Live Connect portal (although this may change later on in the Metro ecosystem). The GetOAuthTokenFromJson helper simply extracts the token from the JSON response returned by WNS.

Now your Metro app is all set up, and your cloud service is authenticated with WNS. The service has stored the channel Uris and Ids of each Metro device running your app and registered with WNS to receive notifications. The service can now send a tile or toast notification to the registered devices through WNS using the stored channel Uris. First, let’s create the notification message the service will send WNS :


   1: private string GetTilePayload(MyDataClass data, string tileId = "tile1")
   2: {
   3:    var wideTemplateName = "TileWidePeekImage01";
   4:  
   5:    tile = string.Format("<tile><visual lang=\"en-US\">" +
   6:                            "<binding template=\"{0}\">" +
   7:                            "<image id=\"1\" src=\"{1}\"/>" +
   8:                            "<text id=\"1\">{2}</text>" +
   9:                            "<text id=\"2\">{3}</text>" +
  10:                            "</binding>", 
  11:                            wideTemplateName, data.Photo, data.Fname + " " + data.Lname, data.Comments);
  12:  
  13:    return tile;
  14: }

Here the service creates the string payload matching the required schema for a tile notification. See this post for more info. If instead, you want the service to send a toast, here’s an example :


   1: private string GetToastPayload(MyDataClass data)
   2: {
   3:     var templateName = "ToastImageAndText02";
   4:     var toast = string.Format("<toast><visual lang=\"en-US\">" +
   5:                             "<binding template=\"{3}\">" +
   6:                             "<image id=\"1\" src=\"{0}\"/>" +
   7:                             "<text id=\"1\">{1}</text>" +
   8:                             "<text id=\"2\">{2}</text>" +
   9:                             "</binding></visual></toast>",
  10:                             data.Photo, data.Fname + " " + data.Lname, data.Comments, templateName);
  11:     return toast;
  12: }

Once your service has built the notification payload, it needs to build the WNS request :


   1: protected string SendNotification(Uri uri, string payload, string type)
   2: {
   3:     byte[] content = Encoding.UTF8.GetBytes(payload);
   4:     string statusCode = "";
   5:  
   6:     var request = HttpWebRequest.Create(uri) as HttpWebRequest;
   7:     request.Method = "POST";
   8:  
   9:     request.Headers.Add("X-WNS-Type", type);
  10:     request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken));
  11:  
  12:     request.BeginGetRequestStream(result =>
  13:     {
  14:         var requestStream = request.EndGetRequestStream(result);
  15:         requestStream.Write(content, 0, content.Length);
  16:         request.BeginGetResponse(result2 =>
  17:         {
  18:             var response = request.EndGetResponse(result2) as HttpWebResponse;
  19:             statusCode = response.StatusCode.ToString();
  20:         }, null);
  21:     }, null);
  22:  
  23:     return statusCode;
  24: }

As you can see, the service builds a POST request that includes the authentication token in a header and the xml payload we just created in the body. In this example the service creates and sends the request asynchronously for better scalability, although yoy may choose to do so synchronously depending on your scenario.

Note that the authentication token has a time limited validity, so you should be prepared for the case where WNS returns an expired token message and rejects the notification message :


   1: try
   2: {
   3:     var response = request.EndGetResponse(result2) as HttpWebResponse;
   4:     statusCode = response.StatusCode.ToString();
   5: }
   6: catch (WebException ex)
   7: {
   8:     string authHeader = ex.Response.Headers["WWW-Authenticate"];
   9:     if (authHeader.Contains("Token expired"))
  10:     {
  11:         GetAccessToken();
  12:         statusCode = SendNotification(uri, xml, type);
  13:     }
  14:     else
  15:     {
  16:         //return ex.Message;
  17:     }
  18: }

Here I’ve wrapped the code that sends the request and fetches the WNS response in a try-catch block, and if the response indicates an expired token, the code goes out and fetches a new token and then resends the notification to WNS.

So when this method is invoked in your service by some external caller, such as an arbitrary service client, the notification request will get sent out to WNS, and WNS will forward it to all registered devices running your Metro app, which will in turn receive the message and consequently update the app tile or raise a toast on the device.

Note that the app may also want to run some code on the device when a push message is received :


   1: private void InterceptNotification()
   2:         {
   3:             channel.PushNotificationReceived += (s, e) =>
   4:             {
   5:                 switch (e.NotificationType)
   6:                 {
   7:                     case PushNotificationType.Toast:
   8:                         toast = e.ToastNotification; break;
   9:                     case PushNotificationType.Tile:
  10:                         tile = e.TileNotification; break;
  11:                     case PushNotificationType.Badge:
  12:                         badge = e.BadgeNotification; break;
  13:                 }
  14:                 //app-specific code ...
  15:             };
  16:         }



So to recap, you can use push notifications to have a custom cloud service send tile, toast or other kinds of messages to your Metro app. This requires quite a bit of setup involving Microsoft’s Windows Notification Service. You establish communication channels between your Metro app and WNS, between your app and your service, and between your service and WNS. As a result your app can benefit from the push model and from the efficiency and dependability of WNS to receive external notifications without polling or similar actions.

Hope you enjoyed this post!

Thursday, July 28, 2011

Push notifications in Windows Phone 7.5 (Mango) -part 2

This post follows up from PART 1. So far, we’ve created our custom web service and registered our phone app with the service. Now let’s implement the main operation in our web service.




Implementing PushToSubscribedPhones

This operation will be invoked from an external app that needs to have push notifications send out to registered devices. Here we need to create a web request, and depending on the type of notification we want to send out, create the payload in accordance with the required schema for that type. Then we send the request out to MPNS to each registered device, through the channelUri we stored for each device in the subscribedPhones dictionary :
public void PushToSubscribedPhones(NotificationData data)
{
foreach (var entry in subscribedPhones)
{
var uri = entry.Value;
byte[] payload;
 
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "POST";
request.ContentType = "text/xml";
 
if (data.PushType == "toast")
{
payload = GetToastPayload(data.Title, data.JobId);
request.Headers.Add("X-WindowsPhone-Target", "toast");
request.Headers.Add("X-NotificationClass", "2");
}
 
else if (data.PushType == "tile")
{
payload = GetTilePayload(data);
 
request.Headers.Add("X-WindowsPhone-Target", "token");
request.Headers.Add("X-NotificationClass", "1");
}
 
else //raw
{
payload = Encoding.UTF8.GetBytes(myMessageData);
request.Headers.Add("X-NotificationClass", "3");
}
 
request.ContentLength = payload.Length;
 
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(payload, 0, payload.Length);
}
 
var response = (HttpWebResponse)request.GetResponse();
}
}
}

Here’s an example of the GetTilePayload method, that generates the message to be sent within a tile notification :


private byte[] GetTilePayload(NotificationData data)
{
string payload = string.Format(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<wp:Notification xmlns:wp=\"WPNotification\">" +
"<wp:Tile Id=\"/Views/JobDetailsView.xaml?id=" + data.JobId + "\">") +
"<wp:BackgroundImage>{2}</wp:BackgroundImage>" +
"<wp:Count>{1}</wp:Count>" +
"<wp:Title>{0}</wp:Title>" +
"<wp:BackBackgroundImage>{3}</wp:BackBackgroundImage>" +
"<wp:BackTitle>{4}</wp:BackTitle>" +
"<wp:BackContent>{5}</wp:BackContent>" +
"</wp:Tile> " +
"</wp:Notification>",
text, data.Count, data.TileUri, data.BackTileUri, data.BackTitle, data.BackContent);
 
return Encoding.UTF8.GetBytes(payload);
}

Note a couple things : first, the message’s components are provided by the calling app through the data parameter. Second, many of these elements are actually values that will be set on the properties of the target live tile, both for the front and back side of the tile. And third, notice the Id attribute that identifies the tile targeted by the notification – remember there may be more than one tiles pinned to the phone’s start menu.

When GetResponse() is called in the previous snippet, the notification is actually sent out to MPNS, which sends back a response. Depending on network, device and other factors, MPNS will then send the notification to the registered phones.



Invoke the web service from an external app

Now that your setup is complete, you can have an external caller invoke the operation to send out information to registered devices in the form of either a push, tile or raw notification. For example, within a WPF app you may write :


pushClient.PushToSubscribedPhones(
new NotificationData
{
PushType = "toast",
JobId = job.Id,
DriverId = driverId,
Title = "Job updated : "
});

This uses an instance of the web service client proxy class, which can be generated for you by Visual Studio using the Add Service Reference command. You call the PushSubscribedPhones operation in our service (you may want to do so asynchronously).  You create a NotificationData instance to specify the information you need to pass to the phones in the notification. Note this example sends a Toast, but you can also send a Tile notification and set the properties of the target tile :


var data = new NotificationData
{
PushType = "tile",
JobId = jobId,
Title = "Cancelled",
TileUri = "/Images/TileJobCancel.png",
BackTileUri = "/Images/TileJobCancelBack.png",
Count = 0,
BackTitle = "CANCELLED",
BackContent = "Unpin this job"
};


When the phone app receives the tile notification, any pinned tile will be updated with the values passed in the message.


So we’ve gone briefly through the push notification setup process involving our phone app, a custom service, MPNS, and possibly an external caller. We could also implement the service part in the cloud, as well as the calling app if we chose to.

In a later post, I’ll discuss possible failures and how to handle them.

If you’d like me to cover certain topics, feel free to send a comment.


Happy notifications!

Push notifications in Windows Phone 7.5 (Mango) -part 1

With push notifications, you can have your website or service send messages to an app running on a bunch of phone devices registered to receive the messages. These messages may trigger a toast popup on the phone, update a specific tile, or simply pass some raw data  to the phone app.

Push notifications work by creating a dedicated, one-on-one communication channel between the phone app and the Microsoft Push Notification Service (MPNS). Then, when your service or web app wants to send a notification to the phone, it sends it to the MPNS through a pre-determined URI, and MPNS will forward it to the phone as soon as it can.


image


In Mango, push notifications have gained in robustness. They are now more reliable, efficient, and performant. An example of reliability improvement is the revamped so-called TDET detection mechanism, which is the timeout period on a mobile network after which the data connection to your device is dropped for network efficiency. There is a wide range of mobile network configurations in this regard, which used to cause problems for push notifications delivery – push notification require a persistent channel to exist between MPNS and the device. Mango brings broader network compability in this respect.

As a result of the deep improvements, you now have up to 30 MPNS channels on a single device, vs. 5 previously (I think that was the number). In any case, each app can still have only one MPNS channel.
Here’s the basic process to implement push notifications in your app :

- Create a web app or service to let devices register and to send out notifications to them
- In the phone app, create an MPNS channel and then register the phone with web app
- In the phone app, bind to the channel to receive toast, tile and/or raw notifications
- In the web app, implement the operation to create and send notifications to the devices
- From an external client, call the web app’s send method, passing notification data to it

Let's look at each of these steps.




Create the web app or service
In your solution, create a new WCF application. In your service definition interface you can define 2 methods, one that phones will call to register with the service, and the other to send notifications to registered devices. For example  :
[ServiceContract]
public interface IMyPushService
{
 
[OperationContract]
void RegisterPhone(int driverId, string channelUri);
 
[OperationContract]
void PushToSubscribedPhones(NotificationData data);
 
}

Note that RegisterPhones takes a unique Id for the device and the URI that the channel URI that the device obtained from MPNS, as we’ll see. Its implementation can be as simple as storing the device’s channel Uri in a static dictionary :


static Dictionary<int, Uri> subscribedPhones = new Dictionary<int, Uri>();
 
public void RegisterPhone(int driverId, string channelUri)
{
subscribedPhones[driverId] = new Uri(channelUri, UriKind.Absolute);
}

PushToSubscribedPhones accepts a custom object that will contain the data we need to include in our notification messages. So NotificationData can be any class that exposes properties we need to send that data back and forth. Of course it needs to be decorated with the [DataContract] attribute.


[DataContract]
public class NotificationData
{
[DataMember] public string PushType;
[DataMember] public string Title;
[DataMember] public int Count;
[DataMember] public string TileUri;
[DataMember] public string BackTitle { get; set; }
[DataMember] public string BackContent { get; set; }
... }

We’ll come back to PushToSubscribedPhones in a moment.


Register the phone app with MPNS

When your phone application starts it needs to establish a communication channel with MPNS. It looks for an existing one from a previous execution (channels are persisted on the phone), and if none is found, it creates one  :


channel = HttpNotificationChannel.Find(channelName);
if (channel == null)
{
channel = new HttpNotificationChannel(channelName);
}

You hook up a handler for the ChannelUriUpdated event, and in the handler you can retrieve the channelUri asynchronously returned by MPNS and then register with the custom service through the RegisterPhone operation :


channel.ChannelUriUpdated += OnChannelUriUpdated;
 
void OnChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
pushClient.RegisterPhoneAsync(App.MyDriverId, e.ChannelUri.ToString());
}

Once you’ve hooked up the handler, you can call the Open() method on Channel if the channel is new, which will asynchronously request a channel Uri.

Bind to the channel to receive toast and/or tile notifications

If you want your phone app to receive toast and/or tile notifications, in the above handler you also need to call the BindToShellXXX methods accordingly :


if (!channel.IsShellTileBound)
channel.BindToShellTile();
 
if (!channel.IsShellToastBound)
channel.BindToShellToast();

Note that the channel you create will only be persisted on the device across app instances if you bind to it for toast or tile. If you don’t, it will be destroyed and you’ll need to create a new channel the next time you run the app.

In addition to the ChannelUriUpdated event, you may want to handle the ShellToastNotificationReceived event if you want your app to receive toasts even when the app is running – in which case toasts normally get discarded. You can also handle HttpNotificationReceived if you want your app to receive and handle raw notifications, which are pieces of custom data send through MPNS.

So at this point, we have our web service sketched out and our phone app registered with both MPNS and our service. The next step is to implement the PushToSubscribedPhones() operation in the service. I’m running out of time so I’ll do that in Part2.

Stay tuned !

Part 2 is now live