Implementing Push Notifications - Learning Windows Azure Mobile Services for Windows 8 and Windows Phone 8 (2014)

Learning Windows Azure Mobile Services for Windows 8 and Windows Phone 8 (2014)

Chapter 5. Implementing Push Notifications

Push Notifications allow us to expand our application's user experience outside the bounds of the app with live tile updates, toast notifications, and badges in Windows 8. Windows Azure Mobile Services makes it very easy for us to trigger notifications via Windows Notifications Service (WNS) (for Store apps), Microsoft Push Notification Service (MPNS) (for Windows Phone 8 apps), Apple Notifications Service (ANS), and Google Notifications Service (GCM).We're going to discuss how to configure Windows 8 and Windows Phone applications to allow notifications, send different types of notifications using scripts, and create a list of devices to manage our user's notification channels.

Understanding Push Notification Service flow

The following procedure illustrates Push Notification Service (PNS) flow from establishing a channel to receiving a notification:

Understanding Push Notification Service flow

1. The mobile device establishes a channel with the PNS and retrieves its handle (URI).

2. The device registers its handle with a backend service (in our case, a table in our Mobile Service).

3. A notification request can be made by another service, an admin system, and so on, which calls the backend service (in our case, an API).

4. The service makes a request to the correct PNS for every device handle.

5. The PNS notifies the device.

Setting up Windows Store apps

Visual Studio 2013 has a new wizard, which associates the app with the store in order to obtain a push notifications URI. Code is added to the app to interact with the service that will be updated to have a Channels table. This table has an Insert script to insert thechannel and ping back a toast notification upon insert. The following procedure takes us through using the wizard to add a push channel to our app:

1. Right-click on the project, and then navigate to Add | Push Notification.

2. Follow the wizard and sign in to your store account (if you haven't got one, you will need to create one).

3. Reserve an app name and select it. Then, continue by clicking on Next.

4. Click on Import Subscriptions... and the Import Windows Azure Subscriptions dialog box will appear.

5. Click on Download subscription file. Your default browser will be launched and the subscriptions file will be automatically downloaded. If you are logged into the portal, this will happen automatically; otherwise, you'll be prompted to log in.

6. Once the subscription file is downloaded, browse to the downloaded file in the Import Windows Azure Subscriptions dialog box and click on Import.

7. Select the subscription you wish to use, click on Next, and then click on Finish in the final dialog box. In the Output window in Visual Studio, you should see something like the following:

8. Attempting to install 'WindowsAzure.MobileServices'

9. Successfully installed NuGet Package 'WindowsAzure.MobileServices'

10.Successfully added 'push.register.cs' to the project

11.Added field to the App class successfully

12.Initialization code was added successfully

13.Updated ToastCapable in the app manifest

14.Client Secret and Package SID were updated successfully on the Windows Azure Mobile Services portal

15.The 'channels' table and 'insert.js' script file were created successfully

16.Successfully updated application redirect domain

Done

We will now see a few things have been done to our project and service:

· The Package.StoreAssociation.xml file is added to link the project with the app on the store.

· Package.appxmanifest is updated with the store application identity.

· Add a push.register.cs class in services\mobile services\[Your Service Name], which creates a push notifications channel and sends the details to our service.

· The server explorer launches and shows us our service with a newly created table named channels, with an Insert method that inserts or updates (if changed) our channel URI. Then, it sends us a toast notification to test that everything is working.

Run the app and check that the URI is inserted into the table. You will get a toast notification. Once you've done this, remove the sendNotifications(item.channelUri); call and function from the Insert method. You can do this in Visual Studio via the Server Explorerconsole. I've modified the script further to make sure the item is always updated, so when we send push notifications, we can send them to URIs that have been recently updated so that we are targeting users who are actually using the application (channels actually expire after 30 days too, so it would be a waste of time trying to push to them). The following code details these modifications:

function insert(item, user, request) {

var ct = tables.getTable("channels");

ct.where({ installationId: item.installationId }).read({

success: function (results) {

if (results.length > 0) {

// always update so we get the updated date

var existingItem = results[0];

existingItem.channelUri = item.channelUri;

ct.update(existingItem, {

success: function () {

request.respond(200, existingItem);

}

});

}

else {

// no matching installation, insert the record

request.execute();

}

}

})

}

I've also modified the UploadChannel method in the app so that it uses a Channel model that has a Platform property. Therefore, we can now work out which PNS provider to use when we have multiple platforms using the service. The UploadChannel method also uses a new InsertChannel method in our DataService method (you can see the full code in the sample app). The following code details these modifications:

public async static void UploadChannel() {

var channel = await Windows.Networking.PushNotifications.PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

var token = Windows.System.Profile.HardwareIdentification.GetPackageSpecificToken(null);

string installationId = Windows.Security.Cryptography.CryptographicBuffer.EncodeToBase64String(token.Id);

try {

var service = new DataService();

await service.InsertChannel(new Channel() {

ChannelUri = channel.Uri,

InstallationId = installationId,

Platform = "win8"

});

}

catch (Exception ex) {

System.Diagnostics.Debug.WriteLine(ex.ToString());

}

}

Setting up tiles

To implement wide or large square tiles, we need to create the necessary assets and define them in the Visual Assets tab of the Package.appxmanifest editor. This is shown in the following screenshot:

Setting up tiles

Setting up badges

Windows Store apps support badge notifications as well as tile and toast. However, this requires a slightly different configuration. To implement badge notifications, we perform the following steps:

1. Create a 24 x 24 pixel PNG badge that can have opacity, but must use only white color.

2. Define the badge in the Badge Logo section of the Visual Assets tab of the Package.appxmanifest editor.

3. Add a Background Tasks declaration in the Declarations tab of the Package.appxmanifest editor, select Push notification, and enter a Start page, as shown in the following screenshot:

Setting up badges

4. Finally, in the Notifications tab of the Package.appxmanifest editor, set Lock screen notifications to Badge. This is shown in the following screenshot:

Setting up badges

5. To see the badge notification working, you also need to add the app to the lock screen badge slots in Lock Screen Applications | Change PC Settings | Lock Screen.

Setting up Windows Phone 8 apps

Visual Studio 2012 Express for Windows Phone doesn't have a fancy wizard like Visual Studio 2013 Express for Windows Store. So, we need to configure the channel and register it with the service manually. The following procedure sets up the notifications in the app by using the table that we created in the preceding Setting up Windows Store apps section:

1. Edit the WMAppManifest.xml file to enable ID_CAP_IDENTITY_DEVICE, which allows us to get a unique device ID for registering in the Channels table, and ID_CAP_PUSH_NOTIFICATION, which allows push notifications in the app. These options are available in the Capabilities tab, as shown in the following screenshot:

Setting up Windows Phone 8 apps

2. To enable wide tiles, we need to check Support for large Tiles (you can't see the tick unless you hover over it, as there is apparently a theming issue in VS!) and pick the path of the wide tile we want to use (by default, there is one namedFlipCycleTileLarge.png under Tiles in the Assets folder). This is shown in the following screenshot:

Setting up Windows Phone 8 apps

3. Next, we need to add some code to get the push channel URI and send it to the service:

4. using Microsoft.Phone.Info;

5. using Microsoft.Phone.Notification;

6. using System;

7. using System.Collections.Generic;

8. using System.Linq;

9. using System.Net;

10.using System.Text;

11.using System.Threading.Tasks;

12.using TileTapper.DataServices;

13.using TileTapper.Models;

14.

15.namespace TileTapper.Helpers {

16. public class ChannelHelper {

17. // Singleton instance

18. public static readonly ChannelHelper Default = new ChannelHelper();

19. // Holds the push channel that is created or found

20. private HttpNotificationChannel _pushChannel;

21.

22. // The name of our push channel

23. private readonly string CHANNEL_NAME = "TileTapperPushChannel";

24.

25. private ChannelHelper() { }

26.

27. public void SetupChannel() {

28. try {

29. // Try to find the push channel

30. this._pushChannel = HttpNotificationChannel.Find(CHANNEL_NAME);

31.

32. // If the channel was not found, then create a new // connection to the push service

33. if (this._pushChannel == null ) {

34. this._pushChannel = new HttpNotificationChannel(CHANNEL_NAME);

35. this.AttachEvents();

36. this._pushChannel.Open();

37.

38. // Bind channel for Tile events

39. this._pushChannel.BindToShellTile();

40.

41. // Bind channel for Toast events

42. this._pushChannel.BindToShellToast();

43. }

44. else

45. this.AttachEvents();

46. }

47. catch (Exception ex) {

48. System.Diagnostics.Debug.WriteLine(ex.ToString());

49. }

50. }

51.

52. private void AttachEvents() {

53. // Register for all the events before attempting to // open the channel

54. this._pushChannel.ChannelUriUpdated + = async (s, e) => {

55. // Register URI with service

56. await this.Register();

57. };

58.

59. this._pushChannel.ErrorOccurred += (s, e) => {

60. System.Diagnostics.Debug.WriteLine(e.ToString());

61. };

62. }

63.

64. private async Task Register() {

65. try {

66. var service = new DataService();

67. await service.InsertChannel(new Channel() {

68. ChannelUri = this._pushChannel.ChannelUri.AbsoluteUri,

69. InstallationId = this.GetDeviceUniqueName(),

70. Platform = "wp8"

71. });

72. }

73. catch (Exception ex) {

74. System.Diagnostics.Debug.WriteLine(ex.ToString());

75. }

76. }

77.

78. // Note: to get a result requires // ID_CAP_IDENTITY_DEVICE

79. // to be added to the capabilities of the WMAppManifest

80. // this will then warn users in marketplace

81. private byte[] GetDeviceUniqueID() {

82. byte[] result = null;

83. object uniqueId;

84. if (DeviceExtendedProperties.TryGetValue("DeviceUniqueId", out uniqueId))

85. result = (byte[])uniqueId;

86.

87. return result;

88. }

89.

90. private string GetDeviceUniqueName() {

91. byte[] id = this.GetDeviceUniqueID();

92. string idEnc = Encoding.Unicode.GetString(id, 0, id.Length);

93. string deviceID = HttpUtility.UrlEncode(idEnc);

94.

95. return deviceID;

96. }

97. }

}

This is a singleton class that holds an instance of the HttpNotificationChannel object, so that channel URI changes can be captured and sent up to our service. The two methods at the end of the code snippet, GetDeviceUniqueID and GetDeviceUniqueName, will give a unique device identifier for the channels table.

98. Now that we have the code to manage the channel, we need to call the SetupChannel method in the App.xaml.cs launching method as shown in the following code snippet:

99.private void Application_Launching(object sender, LaunchingEventArgs e) {

100. TileTapper.Helpers.ChannelHelper.Default.SetupChannel();

}

Service scripts

In the TileTapper game, we send out notifications when a new level is created and when a new high score is submitted. We'll see how to send all the notification types (except raw; by all means do this if you need to in your application, but we're not going to discuss it now).

First, we’ll look at a set of scripts which gets all the URIs from the channels table, which have been updated in the last 30 days so we know they are likely to be active and then sends notifications out to the correct PNS services depending on the platform type.

The sendNotifications function gets the channels from the channels table. Then, it loops through them, calling the addToQueue method that queues PNS task functions for each channel. We don't call the PNS methods in the for loop as they run asynchronously and would try to execute simultaneously, which would lead to many failures as the server can only make a limited number of HTTP requests concurrently. The following code demonstrates this:

// Queue of PNS functions

var queue = [];

function sendNotifications(levelName) {

// Query channels updated in the last 30 days

var sql = "SELECT channelUri, platform FROM channels WHERE updated >= DATEADD(Day, -30, GETDATE())";

mssql.query(sql, {

success: function(results) {

// Because the PNS functions are asynchronous, we will loop // through channels

// and add a set of functions to a function queue for each // channel so we can

// process requests one at a time to save starving our // connections and failing

for(var i = 0; i < results.length; i++) {

addToQueue(results[i], levelName);

}

// Process first item

dequeue();

}

});

}

The addToQueue function determines which notification functions are required, based on the platform type; and pushes a task function into the queue so that they can be called one at a time as they complete, as shown in the following code snippet:

// Wrap functions and enqueue

function addToQueue(channel, levelName)

{

if(channel.platform == "win8") {

queue.push(function() { sendMultiTileWns(channel.channelUri, levelName); });

queue.push(function() { sendToastWns(channel.channelUri, levelName); });

queue.push(function() { sendBadgeWns(channel.channelUri, levelName); });

}

else if(channel.platform == "wp8") {

queue.push(function() { sendToastMpns(channel.channelUri, levelName); });

queue.push(function() { sendTileMpns(channel.channelUri, levelName); });

}

}

The dequeue method simply shifts a task function off the queue and calls it. As each function completes, it calls this function whether it succeeds or fails to empty the queue and process all PNS requests. The working of the dequeue method is shown in the following code snippet:

function dequeue(){

// Dequeue and execute

if(queue.length > 0)

(queue.shift())();

}

If a notification fails, we delete the channel registration from the table using the following function:

function deleteChannel(uri) {

var sql = "DELETE FROM channels WHERE channelUri = '" + uri + "'";

mssql.query(sql);

}

WNS scripts for Store apps

WNS supports the following notifications:

· sendTile

· sendToast

· sendBadge

· sendRaw

· send

Note

sendTile and sendToast have a template-specific suffix to define the payload type.

WNS doesn't support tile templates with multiple tile sizes. So, we can use the send method to stick multiple tile bindings together and update more than one tile.

There's a full reference available at http://msdn.microsoft.com/en-us/library/windowsazure/jj860484.aspx.

Sending toast notifications

The following function sends a toast notification using the sendToastText04 method:

function sendToastWns(uri, name) {

// Send wns push for store apps

push.wns.sendToastText04(uri, {

text1: "TileTapper",

text2: "New level available",

text3: name

}, {

success: function(pushResponse) {

console.log("Sent push toast WNS:", pushResponse);

dequeue();

},

error: function(error) {

console.error(error);

deleteChannel(uri);

dequeue();

}

});

}

Sending tile notifications

The following function sends a tile notification using the sendTileSquareText01 method:

function sendTileWns(uri, name) {

// Send wns push for store apps

push.wns.sendTileSquareText01(uri, {

text1: "TileTapper",

text2: "New level available",

text3: name

}, {

success: function(pushResponse) {

console.log("Sent push toast WNS:", pushResponse);

dequeue();

},

error: function(error) {

console.error(error);

deleteChannel(uri);

dequeue();

}

});

}

Sending multiple tiles

The following function sends a tile notification using multiple bindings that are defined using the raw XML templates. It gives us the benefit of sending multiple tile templates in one request, rather than sending them individually.

function sendMultiTileWns(uri, name) {

// Send wns push for store apps

push.wns.send(uri,

"<tile>" +

"<visual version='2'>" +

"<binding template = 'TileSquare150x150Text01' fallback='TileSquareText01'>" +

"<text id='1'>TileTapper</text>" +

"<text id='2'>New level available</text>" +

"<text id='3'>" + name + "</text>" +

"</binding>" +

"<binding template = 'TileWide310x150Text01' fallback='TileWideText01'>" +

"<text id='1'>TileTapper</text>" +

"<text id='2'>New level available</text>" +

"<text id='3'>" + name + "</text>" +

"</binding>" +

"</visual>" +

"</tile>",

"wns/tile", {

success: function(pushResponse) {

console.log("Sent push toast WNS:", pushResponse);

dequeue();

},

error: function(error) {

console.error(error);

deleteChannel(uri);

dequeue();

}

});

}

Sending badge notifications

The following function sends an alert badge notification using the sendBadge method:

function sendBadgeWns(uri, name) {

// Send wns push for store apps

push.wns.sendBadge(uri, "alert", {

success: function(pushResponse) {

console.log("Sent push toast WNS:", pushResponse);

dequeue();

},

error: function(error) {

console.error(error);

deleteChannel(uri);

dequeue();

}

});

}

MPNS scripts for Windows Phone apps

MPNS supports the following notifications:

· sendFlipTile

· sendTile

· sendToast

· sendRaw

There's a full reference available at http://msdn.microsoft.com/en-us/library/windowsazure/jj871025.aspx.

Sending toast notifications

The following function sends a toast notification using the sendToast method:

function sendToastMpns(uri, name) {

// Send wns push for store apps

// We can add a param object to pass params to a certain page: // param: "NewPage.xaml?item=5"

push.mpns.sendToast(uri, {

text1: "TileTapper - New level available",

text2: name

}, {

success: function(pushResponse) {

console.log("Sent push toast WNS:", pushResponse);

dequeue();

},

error: function(error) {

console.error(error);

deleteChannel(uri);

dequeue();

}

});

}

Sending tile notifications

The following function sends a tile notification using the sendFlipTile method:

function sendTileMpns(uri, name) {

// Send wns push for store apps

// We can add a param object to pass params to a certain page: // param: "NewPage.xaml?item=5"

push.mpns.sendFlipTile(uri, {

backTitle: "TileTapper - New level available",

backContent: name

}, {

success: function(pushResponse) {

console.log("Sent push toast WNS:", pushResponse);

dequeue();

},

error: function(error) {

console.error(error);

deleteChannel(uri);

dequeue();

}

});

}

Summary

In this chapter, we've covered setting up our Windows 8 and Windows Phone 8 applications to receive different notification types. We have also worked on the service to send different notifications from the WNS and MPNS notifications service.

Tiles and toast notifications are big subjects as there are a plethora of templates on each platform. So, it's worth having a good look at the documentation to help you choose the right templates.

In the next chapter, we're going to build on what we've learned here with the Notifications Hub, which provides us with a different, more scalable mechanism for managing push notifications.