Notifications - Programming Windows Store Apps with C# (2014)

Programming Windows Store Apps with C# (2014)

Chapter 5. Notifications

Notifications are one of the areas of the Windows Store app UX that have been extremely well implemented. They are used in situations where you want to reach out and inform users about something when they are not using the app. In retail scenarios they are commonly used to indicate that new messages are available. In line-of-business (LOB) scenarios they can be used to proactively reach out to your user audience in innovative ways.

There are four types of notifications in Windows Store apps: toast, tiles, badges, and raw. (In this book we won’t be talking about raw notifications, as they are too niche.) Toast describes the messages that wind in from the right edge of the screen. (Prior to the implementation in Windows 8/Windows RT, these used to “pop up” from the bottom-right edge of the screen like toast from a toaster, hence the name.) Tiles are the most interesting way of handling notifications. Actually, tiles are perhaps the most significant UX feature in Windows Store apps. They are unique to the device-based/touch-centric Windows vision and allow apps to aggregate data in a single, easy-to-grasp view. First appearing in Windows Phone and brought over to Windows 8/Windows RT, they work like a “personal dashboard” for the user. In LOB scenarios they can be interesting, as they provide for new ways of interacting with your user base. Finally, badges apply to tiles. They allow you to attach numbers or a glyph to a tile. (A glyph, just in case the term is new to you, is basically an icon.)

The work in this chapter will be divided into two sections. We’ll start off by looking at local notifications. These are notifications where the app uses the notification subsystem to initiate toast, badge, and tile updates on the same device. We’re going to construct three “builder” classes, one for each type. As we’ll see, when we work with notifications we ask WinRT to give us an XML-based template, which we then populate with data and give back to WinRT. This XML-based approach is fine, but challenging from a code maintenance perspective unless you happen to remember the structure of the various XML templates—hence the builders, which make the maintenance easier.

In the second half of the chapter we’ll look at remote notifications. These are notifications that are initiated by a cloud-based server and that wend their way down to the device using the Windows Push Notification Service (WNS). This works by originating XML on the server in the same format as local and then passing it over to WNS. This requires an authentication step, followed by a transfer step.

Local Notifications

Notifications work in two ways: they are either generated locally from the device and shown on that same device, or they are generated by an external server and pushed to all subscribing devices. In this section we’re going to look at the former.

Turning Notifications On and Off

It’s possible to turn off toast notifications globally using the Change PC Settings option. You should make sure that notifications are globally available before you start this work to save frustration later.

XML Templates

All three of the notification formats use the same approach. Each has its own manager class: ToastNotifier, BadgeUpdater, and TileUpdater. Each of the manager classes has a method that returns an XML template that you fill in with your app-specific data before returning it back to the manager for display. Each format has a variety of templates to choose from. (I’ll go through some of the key templates as we look at each format, but MSDN should remain your definitive resource.) Here’s an example of some basic toast XML that displays a “Hello, world” message. For clarity, this is an example of a toast template (specifically ToastText01) that has been populated with some data:

<toast>

<visual>

<binding template="ToastText01">

<text id="1">Hello, world.</text>

</binding>

</visual>

</toast>

Easy, huh?

In fact, the only tricky thing about working with notifications is that from a code maintenance perspective, the XML-based approach is a bit of a pain. The code won’t self-document because all you do is ask the notification manager to return the XML template, then use the DOM to update values.

For our work here, I’m proposing that we create some “builder” classes that are able to populate the template for us. They will also be able to send the populated XML through to the “managers.” (The managers are the APIs supplied by WinRT that you give the notifications to.) This will let us create code that’s more expressive and self-describing, and thus more maintainable. Although the schema information for notifications is documented on MSDN (look for “toast schema” on the MSDN site), it’s much easier to abstract away the actual construction of the XML.

I’ll start by sketching out the proposed object model. Let’s look at the general structure of the templates first. (As we go through this I’m not going to create a detailed inventory of the templates, as these are all on MSDN. What I will do is call out some important templates with the expectation that you’ll need to go to MSDN for the definitive list.)

§ There are eight toast templates. Half of the eight templates allow for an image to be displayed on the left side of the notification. All of the templates have various text configurations—we’ll learn more about that as we go.

§ At the time of writing, there are 46 tile templates! (There are so many because Microsoft wants to provide maximum flexibility for this key feature. Plus, building your own tile formats is verboten.) There are two main types of tile template—one set that displays some static information, and another set that displays information that “revolves” between picture and text information. You can see this in action on the Start screen; the News app tile, for example, will display an image, and after a while it will be replaced with some text, and then it will go back to an image.

§ There are two badge templates. Badges are overlays for tiles, and can either display a number (e.g., the number of unread items) or a glyph.

One of the strange API design decisions at play here is that there isn’t a set of base classes in play. For example, WinRT provides ToastNotification, TileNotification, and BadgeNotification, but there is no base Notification class. Likewise, in the manager classes, there is no base implementation. While there will undoubtedly be good reason for this, our builder classes will be more obviously object-oriented. We’ll have NotificationBuilder<T> and NotificationWithTextBuilder<T> classes that we’ll specialize intoToastNotificationBuilder, TileNotificationBuilder, and BadgeNotificationBuilder classes.

Each of those classes will hold a static instance that keys into the notification system. For toast, this is called a notifier. For tiles and badges, this is called an updater. Regardless of the name, they do the same thing.

NOTE

The nomenclature inconsistency is because toast “notifies” the user something is happening, whereas tiles/badges are more passive. This inconsistency makes sense if you regard them in isolation, but seems strange if you regard them in terms of their construction and shared behavior. My advice? Don’t worry about it!

Figure 5-1 shows a sketch of what we’re looking to achieve.

Sketch of our notification builders

Figure 5-1. Sketch of our notification builders

Within our code, we’ll create an instance of whatever builder we want and use an object model to define the notifications. As discussed, this will be easier and more maintainable in the long run than hacking around with the XML.

We’ll start by looking at toast.

Toast

The basic toast template—ToastTemplateType.ToastText01— displays a single line of text wrapped on three lines. In this section we’re going to start with something a little more interesting by using ToastText02. This displays a single line of bold text, with the second and third lines being taken up with some wrapped content. Figure 5-2 illustrates.

A ToastText02 template example

Figure 5-2. A ToastText02 template example

Let’s look at how to create that notification.

Setting permissions

The first thing we need to do to get toast working is to mark the app’s manifest as “toast capable.” If we don’t do this, we won’t see anything.

NOTE

You should note that users can turn off the toast notifications using the always-available Permissions option within the settings charm. You shouldn’t rely on toast to provide essential app functions—in fact, the store guidelines (which we’ll see more of in Chapter 15) forbid it.

To do this, double-click the Package.appxmanifest file to open the manifest editor. On the Application UI tab you’ll find an option called “toast capable” underneath the Notifications section. Change this to Yes. Figure 5-3 illustrates.

Setting the app to be toast capable

Figure 5-3. Setting the app to be toast capable

Toast without an image

The XML used to create the previously shown Figure 5-2 looks like this:

<toast>

<visual>

<binding template="ToastText02">

<text id="1">A bold line at the top...</text>

<text id="2">And then a longer string that's split over two lines

underneath.</text>

</binding>

</visual>

</toast>

We’ll start at the top of our hierarchy with NotificationBuilder<T>. This is—as you can see—a generic class that will take the type of the notification that it’s ultimately trying to create. (Specifically, we’ll be creating a Windows.UI.Notifications.ToastNotification.) As I mentioned before, the notification classes do not have a common base type, so we can’t constrain the type parameter. On a related note, because the manager classes don’t have a base type either, we can’t do much else than just add an abstract method to create a notification and another to update. (With more OO sophistication in the underlying API, we could make our builder base classes slicker.) Here’s the code:

public abstract class NotificationBuilder<T>

{

protected NotificationBuilder()

{

}

protected abstract T GetNotification();

public abstract T Update();

}

Toast and tiles have shared behavior in terms of being able to show text and images. I’m therefore proposing creating a NotificationWithTextBuilder<T> class that will hold a collection of strings to display. When we come to build the notification, our specializedToastNotificationBuilder and TileNotificationBuilder class will call up to us with the XML and expect us to populate any text elements that we find with the strings contained within the Texts property. This will be done via a method called UpdateTemplateText.

We haven’t spoken much about XML so far, which tells us something about how popular XML is in 2012. Going back five years, this book would have been full of XML. Now it’s far easier to work with JSON. However, we do need to talk about XML now.

The XML DOM API in the original version of .NET was a thing of beauty, especially compared to the way we used to work with XML in the days of COM. It was, and still is, easy to work with and expressive. With Windows Store apps we no longer use the System.Xml.XmlDocumentclass and its related types, but instead we use the WinRT version: Windows.Data.Xml.Dom.XmlDocument. This works in a very similar fashion to the “legacy” .NET version—in fact, I’d been using the new WinRT version for months before realizing that it had changed! Suffice it to say, if you’re used to using the .NET version you’re generally not going to come unstuck when working with the WinRT XML APIs.

Back to our UpdateTemplateText method: this just has to select out a list of all the text elements that are found in the DOM and replace the InnerText values with those stored in the Texts property. If we have more elements than strings, we’ll set the elements to empty strings. Here’s the code:

public abstract class NotificationWithTextBuilder<T> :

NotificationBuilder<T>

{

protected List<string> Texts { get; set; }

protected NotificationWithTextBuilder(IEnumerable<string> texts)

{

this.Texts = new List<string>(texts);

}

protected void UpdateTemplateText(XmlDocument xml)

{

// walk and combine elements...

var textElements = xml.SelectNodes("//text");

for (int index = 0; index < textElements.Count; index++)

{

if (index < this.Texts.Count)

textElements[index].InnerText = this.Texts[index];

else

textElements[index].InnerText = string.Empty;

}

}

}

With the base classes built, we can build ToastNotificationBuilder.

As discussed, we have these various template types to choose from when dealing with notifications. To add some color to our discussion, we’ll design them so that they’ll either infer the template type to use, or we can give it a specific template type if we want. In this case, we’ll infer the type from a possible/limited set of ToastText01 (one string, wrapped over three lines), ToastText02 (two strings, bold first line), and ToastText04 (three strings, bold first line, normal weight for the other two). In the next section we’ll look at images.

NOTE

How—or indeed if—you want to infer the template is up to you; this is just how I’ve done it based on how most readers of this book might end up using toast.

To start with, as we’ve also discussed, each notification type needs to be tied into its own manager. In the case of toast, we need to use the ToastNotificationManager. The first time that we want to use notifications, we need to ask this to provide a ToastNotifier instance to us. We can store this in a static variable and use the static constructor to trigger its creation.

Here’s the initial construction of the ToastNotificationBuilder class:

public class ToastNotificationBuilder : NotificationWithTextBuilder

<ToastNotification>

{

// what we're trying to show...

private ToastTemplateType _type;

private bool TypeSet { get; set; }

// the engine used to update it...

private static ToastNotifier Notifier { get; set; }

public ToastNotificationBuilder(string text)

: this(new string[] { text })

{

}

public ToastNotificationBuilder(IEnumerable<string> texts)

: base(texts)

{

}

static ToastNotificationBuilder()

{

Notifier = ToastNotificationManager.CreateToastNotifier();

}

}

You’ll notice that we have our template type stored in the _type field, and that we also have a TypeSet property. What we’re going to do is infer the type to use if it hasn’t been set (i.e., TypeSet is false), or use an explicit type if it has been set. Here’s the implementation of the Typeproperty:

// Add property to ToastNotificationBuilder...

public ToastTemplateType Type

{

get

{

if (this.TypeSet)

return _type;

else

{

if (this.Texts.Count <= 1)

return ToastTemplateType.ToastText01; // just 1 line...

else if (this.Texts.Count == 2)

return ToastTemplateType.ToastText02; // 1 - bold,

next normal

else

return ToastTemplateType.ToastText04; // 1 - bold,

2-3 normal

}

}

set

{

_type = value;

this.TypeSet = true;

}

}

Finally, we can implement the abstract methods defined on NotificationBuilder<T> that populate the template and send it through to the notifier for display. At this point, the only change that we need to make to the provided XML template is to populate the elements text, which we can do with the UpdateTemplateText method that we built earlier in NotificationWithTextBuilder<T>. Here’s the code:

// Add methods to ToastNotificationBuilder...

protected override ToastNotification GetNotification()

{

var xml = ToastNotificationManager.GetTemplateContent(this.Type);

UpdateTemplateText(xml);

// return...

return new ToastNotification(xml);

}

public override ToastNotification Update()

{

var toast = this.GetNotification();

Notifier.Show(toast);

// return...

return toast;

}

To test this, we need to show some toast. I’m going to propose changing the behavior of the Refresh button that we put on the app bar in the last chapter such that when we explicitly choose this option, we’ll see some toast. You should note that this is not a proper use of toast. Toast is supposed to be used in situations where external imperatives are affecting a system—such as receipt of an email, or an IM friend logging on. They are not supposed to be used to replace message boxes. (Moreover, you can turn off notifications globally, or even mute them so you can’t rely on the user actually having seen them.)

Making this change is a matter of modifying the RefreshCommand handler on ReportsPageViewModel. Here is that change (I’ve omitted some code from the view-model’s constructor for brevity):

// Modify anonymous method in ReportsPageViewModel constructor...

public ReportsPageViewModel(IViewModelHost host)

: base(host)

{

// setup...

this.Items = new ObservableCollection<ReportItem>();

this.SelectedItems = new List<ReportItem>();

// commands...

this.RefreshCommand = new DelegateCommand(async (e) =>

{

this.Host.HideAppBar();

await this.DoRefresh(true);

// toast...

string message = "I found 1 report.";

if (this.Items.Count != 1)

message = string.Format("I found {0} reports.",

this.Items.Count);

var toast = new ToastNotificationBuilder(new string[] {

"Reports refreshed.", message });

toast.Update();

});

// code omitted for brevity...

}

You can now run this code. Navigate to the Reports page and explicitly refresh. You should see the toast appear as shown in Figure 5-4.

A successful toast notification

Figure 5-4. A successful toast notification

That’s great, but how do we display images?

Toast with an image

As mentioned, half of the templates that we can use with toast display images. When we work with one of those templates, we’ll get XML that’s essentially the same as before, but happens to include an image element. Here’s an example:

<toast>

<visual>

<binding template="ToastImageAndText02">

<image id="1" src=""/>

<text id="1">Reports refreshed.</text>

<text id="2">I found 50 reports.</text>

</binding>

</visual>

</toast>

To handle this, we need to get a value into the src attribute that references an image, and change the template type. The image reference will most likely be an ms-appx resource URI, like the one we met in Chapter 4. What we need to do is grab an image and put it into the Assets folder within our project. You can take any image you want, but it has to be less than 200KB in size and smaller than 1024×1024 pixels. In the code downloads for this book, you’ll find a Resources folder containing some images that are published under a Creative Commons license. I’ve called my image Toast.png. Do watch that 200KB size limit, however. If the image is too big you’ll still get the notification, just without the image. The same applies to the tile images we’ll see next.

NOTE

In the next chapter, we’re going to do more work with images. The strange nature of the images in that folder will become clear in Chapter 6 when we talk about the problem domain from which they were taken!

To begin, add a new read/write property to ToastNotificationBuilder of type string called ImageUri. Then, depending on whether we have one of these, we can rig the Type property to infer a different type. Here’s that change:

// Modify and add properties in ToastNotificationBuilder...

public ToastTemplateType Type

{

get

{

if (this.TypeSet)

return _type;

else

{

if (this.Texts.Count <= 1)

{

if (this.HasImageUri)

return ToastTemplateType.ToastImageAndText01;

else

return ToastTemplateType.ToastText01;

// just one line...

}

else if (this.Texts.Count == 2)

{

if (this.HasImageUri)

return ToastTemplateType.ToastImageAndText02;

else

return ToastTemplateType.ToastText02;

// 1 - bold, next normal

}

else

{

if (this.HasImageUri)

return ToastTemplateType.ToastImageAndText04;

else

return ToastTemplateType.ToastText04;

// 1 - bold, 2-3 normal

}

}

}

set

{

_type = value;

this.TypeSet = true;

}

}

private bool HasImageUri

{

get

{

return !(string.IsNullOrEmpty(this.ImageUri));

}

}

The final step is then to set the image element within the template XML. Here’s the code:

// Modify method in ToastNotificationBuilder...

protected override ToastNotification GetNotification()

{

var xml = ToastNotificationManager.GetTemplateContent(this.Type);

UpdateTemplateText(xml);

// do we have an image?

if (this.HasImageUri)

{

var imageElement = (XmlElement)xml.SelectSingleNode("//image");

imageElement.Attributes.GetNamedItem("src").NodeValue =

this.ImageUri;

}

// return...

return new ToastNotification(xml);

}

Finally we need to change the command that creates the ToastNotificationBuilder to include the image URL. Here’s a further change to the ReportsPageViewModel constructor. Again, I’ve omitted code for brevity:

// Modify constructor of ReportsPageViewModel...

public ReportsPageViewModel(IViewModelHost host)

: base(host)

{

// setup...

this.Items = new ObservableCollection<ReportItem>();

this.SelectedItems = new List<ReportItem>();

// commands...

this.RefreshCommand = new DelegateCommand(async (e) =>

{

this.Host.HideAppBar();

await this.DoRefresh(true);

// toast...

string message = "I found 1 report.";

if (this.Items.Count != 1)

message = string.Format("I found {0} reports.",

this.Items.Count);

var toast = new ToastNotificationBuilder(new string[]

{ "Reports refreshed.", message });

toast.ImageUri = "ms-appx:///Assets/Toast.jpg";

toast.Update();

});

// code omitted for brevity...

}

If you run that and do an explicit refresh, you’ll see toast with the image shown in Figure 5-5.

Toast with image

Figure 5-5. Toast with image

Asynchrony and notifications

Now that you’ve been through how notifications work, you may be wondering why when we hand an update over to Windows, the method we call is not marked as async. It would be typical to assume they would be; after all, they are calls that take some time to operate.

The reason they are synchronous is because they operate within the 50ms timescale rule. They pass over to the notification subsystem instantly and don’t block or delay the UI thread. It just happens that from that point they take more than 50ms to rattle through the system but, as they say, that’s not our problem.

Badges

In this section, we’ll quickly cover badge notifications. These are very straightforward. You can either have a number indicating the number of unread items, or you can have one of a small-but-decent number of glyphs.

You can choose from the following glyphs, and you can’t define your own. The value listed beside each glyph is the value that’s needed within the XML.

activity

alert

available

away

busy

newMessage

paused

playing

unavailable

error

attention

I’m not going to go through how to set the glyph because it’s so similar to setting a number (which we will do), and I’d rather move on to explaining tiles, which is far more interesting. In the code download, you will find glyphs supported on the BadgeNotificationBuilder class that we’re about to build.

As mentioned a few times, the notifications all work in the same way, so to set a badge value we need to get the template XML, populate it, and pass it back. Specifically, we’ll get a template like this:

<badge value=""/>

We can set value to be any positive integer that we like. (Or we can set it to a glyph, as per the preceding table.) There is a high limit on that number of 99. If you specify 100 or greater, “99” will be rendered with a small plus sign next to it.

Seeing as BadgeNotificationBuilder is just a specialization of the NotificationBuilder<T> that we’ve already created, I’ll simply present the code. Here’s BadgeNotificationBuilder:

public class BadgeNotificationBuilder : NotificationBuilder

<BadgeNotification>

{

// what we're trying to show...

public int Number { get; private set; }

// the engine used to update it...

private static BadgeUpdater Updater { get; set; }

public BadgeNotificationBuilder(int number)

{

this.Number = number;

}

static BadgeNotificationBuilder()

{

Updater = BadgeUpdateManager.CreateBadgeUpdaterForApplication();

}

public override BadgeNotification Update()

{

// create the notification and send it...

var badge = GetNotification();

Updater.Update(badge);

// return...

return badge;

}

protected override BadgeNotification GetNotification()

{

var xml = BadgeUpdateManager.GetTemplateContent

(BadgeTemplateType.BadgeNumber);

// glyph?

var attr = xml.FirstChild.Attributes.GetNamedItem("value");

attr.NodeValue = this.Number.ToString();

return new BadgeNotification(xml);

}

}

To test that this works, we can again change ReportsPageViewModel. In this case, whenever we update the view we’ll set the badge. Like toast, this is not a proper use of badges in production information. They should be used solely to draw the user’s attention to new information. Here’s the change to ReloadReportsFromCacheAsync:

private async Task ReloadReportsFromCacheAsync()

{

// set up a load operation to populate the collection from

// the cache...

using (this.EnterBusy())

{

var reports = await ReportItem.GetAllFromCacheAsync();

// update the model...

this.Items.Clear();

foreach (ReportItem report in reports)

this.Items.Add(report);

// update the badge...

var badge = new BadgeNotificationBuilder(this.Items.Count);

badge.Update();

}

}

Run the code, and the badge value will change as illustrated in Figure 5-6.

Our tile with a badge showing the number of reports

Figure 5-6. Our tile with a badge showing the number of reports

Now that we can add a badge to a tile, let’s see if we can make the tile even more interesting.

Tiles

As mentioned, tiles are a big deal within the Windows Store app experience and are a great way for you to provide extra value to your user. While engagement is one of the things tiles offer, the most important thing they do is allow users to create a dashboard of all the apps they use, which puts timely information front and center. In LOB apps—especially when the enterprise has published many—this can be very helpful.

However, one thing you need to know about tiles is that the user can turn off the updates, in which case the tile just becomes a static presentation of whatever image you initially provide. This means that you cannot rely on presentation through a tile—or, to put it another way, a tile cannot be the only place where certain information is represented. It must bring forward information otherwise available in the app.

Another thing to consider with tiles is that they come in two varieties: normal and wide. They can also rotate between images and text (called peeking in the API). Figure 5-7 shows the News app tile in normal and wide mode, and I’ve presented both the text view and image view for all of these.

All four possible views provided by the News app’s tile

Figure 5-7. All four possible views provided by the News app’s tile

By default you get a normal width tile, but you can provide a wide tile. The wrinkle to this arrangement is that the user can select whether to view the narrow or wide tile, and you have no control over that. (Users do this via an option on the app bar within the Start screen.) Thus, when we’re using wide tiles we have to provide both every time we do an update—that is, we need to tell the tile manager to change both the narrow tile and the wide tile. This is unlikely to be a problem, however, as in order to change one you likely have the information loaded and available to update the other. (Or, more specifically, there isn’t going to be much horsepower wasted updating the one that the user doesn’t want to see.)

Tile template types

As mentioned, there are 46 tile template types. I won’t repeat the MSDN documentation here, but in summary:

§ Some of the templates are normal width, and some are wide.

§ Virtually all of the templates display text information.

§ Some templates are peek templates, which means they rotate between an image display and text display.

§ Some templates show image collections. The People app tile does this—it’ll display one large image and four smaller images next to it.

§ Some templates include block text. For example, the Calendar app shows the current day of the month in large text, and upcoming calendar information in small text.

The tiles that we’re going to use in this example are:

TileWidePeekImage01

This is a wide, rotating template with an image on one side, and two lines of text on the other.

TileSquarePeekImageAndText02

This is a normal-width, rotating template again with an image on one side and two lines of text on the other.

Creating a wide tile

Before we update the tile with live information, we need to configure the wide tile. This is just a matter of creating a wide image and specifying it in the manifest.

Normal image files are 150×150 pixels. Wide images are—perhaps bizarrely—310×150 pixels in size. You can create an image in your favorite image editor.

NOTE

There’s a wrinkle to working with images in that you need to provide a set of images to cover scaling on displays with a higher density. This is discussed more in Chapter 13.

We haven’t spoken much about design of images for our apps (although you may have noticed that in the code downloads I’ve provided images based on the pica featured on the cover of this book). There are two approaches to designing them. The basic way of doing it is to create a transparent image and add design elements that are white aliased onto transparency. This allows you to change the background color for the app in the manifest and not have to change all of the images.

This isn’t a design book, and I’m not expecting most readers to be design savvy, but this can be a little hard to get your head around. My recommendation is to use Adobe Fireworks or a higher-end design package. Although you can do this with free packages, it is much easier to buy the right tool for the job. In the code download for this chapter, you’ll find my wide logo that was created using Fireworks.

NOTE

The built-in apps work using this transparency method. A lot of the apps currently in the Store, however, don’t do this—they have more a designed, graphical look.

When you’re done, add the logo to the ~/Assets folder within your project and remember to include it within the project. As a final step, open the manifest editor and set the “Wide logo” value on the Application UI tab. Figure 5-8 illustrates.

Setting the “Wide logo” value in the manifest

Figure 5-8. Setting the “Wide logo” value in the manifest

With the wide logo in place, find the tile on the Start screen, right-click on it, and choose the Larger option from the app bar. Figure 5-9 illustrates the wide logo.

The StreetFoo wide logo

Figure 5-9. The StreetFoo wide logo

As a side note, you should be aware that Microsoft’s guidelines are that if you have a wide tile you must support tile updating. Specifically, Microsoft says that “content must always be fresh” when you’re using wide tiles.

Creating TileNotificationBuilder

TileNotificationBuilder will extend NotificationWithTextBuilder<T> and will be essentially the same as the others built so far. The only difference is that we’re going to create a way to streamline the companion tile—specifically, we want to get to a position where we can create one and automatically create the other. We’ll call this companion tile a replica.

TileNotificationBuilder will contain a collection of image URIs that can be patched into the template. If we were using a template that shows image collections (which we’re not), we’ll get multiple image elements in the XML in exactly the same way as we had a single imagetemplate in the toast XML. This, for example, is the template for TileWidePeekImageCollection04, which contains a collection of images:

<tile>

<visual>

<binding template="TileWidePeekImageCollection04">

<image id="1" src=""/>

<image id="2" src=""/>

<image id="3" src=""/>

<image id="4" src=""/>

<image id="5" src=""/>

<text id="1"></text>

</binding>

</visual>

</tile>

We’re not going to be using that template type because we have only one image, but we’ll make TileNotificationBuilder capable of populating any number of image elements in exactly the same way that NotificationWithTextBuilder<T> was able to populate any number oftext elements.

Here’s the basic implementation that doesn’t take creating a replica into consideration. (We’ll do the replica part next.)

public class TileNotificationBuilder : NotificationWithTextBuilder

<TileNotification>

{

public TileTemplateType Type { get; private set; }

public List<string> ImageUris { get; set; }

private static TileUpdater Updater { get; set; }

public TileNotificationBuilder(IEnumerable<string> texts,

TileTemplateType type)

: base(texts)

{

this.ImageUris = new List<string>();

this.Type = type;

}

static TileNotificationBuilder()

{

Updater = TileUpdateManager.CreateTileUpdaterForApplication();

}

public override TileNotification Update()

{

var tile = this.GetNotification();

Updater.Update(tile);

return tile;

}

protected override TileNotification GetNotification()

{

var xml = TileUpdateManager.GetTemplateContent(this.Type);

this.UpdateTemplateText(xml);

// images...

if (this.ImageUrls.Any())

{

var imageElements = xml.SelectNodes("//image");

for (int index = 0; index < imageElements.Count; index++)

{

var attr = imageElements[index].Attributes.

GetNamedItem("src");

// set...

if (index < this.ImageUris.Count)

attr.NodeValue = this.ImageUris[index];

else

attr.NodeValue = string.Empty;

}

}

// return...

return new TileNotification(xml);

}

}

The magic happens there with the population of the image. Like the text population we did earlier, we take any image elements that we can find and replace their src values with any image URIs that we’ve been supplied.

To handle creating the replica we’ll add two methods. Replicate will create a new TileNotificationBuilder with a newly supplied template type. This would typically work by having you create the wide tile in the first instance with populated text and images and then creating a replica with a normal-width tile. By way of a shortcut, we’ll create UpdateAndReplicate, which will do the first type and then the second type in turn. Here are the new methods to add to TileNotificationBuilder:

// Add methods to TileNotificationBuilder...

private TileNotificationBuilder Replicate(TileTemplateType newType)

{

var newBuilder = new TileNotificationBuilder(this.Texts, newType);

newBuilder.ImageUrls = new List<string>(this.ImageUrls);

return newBuilder;

}

public TileNotification UpdateAndReplicate(TileTemplateType replicaType)

{

// update this one...

var result = Update();

// then copy...

var replica = this.Replicate(replicaType);

replica.Update();

// return...

return result;

}

To get this to work, we’ll modify the ReloadReportsFromCacheAsync method like we did when working with the badge builder to update the tiles. We’ll add some text and an image URI. In this example implementation, just to show that we support multiple lines, I’ve repeated the word StreetFoo. In a production app you would not do this, because the name of the app always appears in the bottom-left corner of the tile.

Here’s the change to ReloadReportsFromCacheAsync. For simplicity I’ve reused the same Toast.jpg image that we saw when we were working with toast.

// Modify ReloadReportsFromCacheAsync...

private async Task ReloadReportsFromCacheAsync()

{

// set up a load operation to populate the collection

// from the cache...

using (this.EnterBusy())

{

var reports = await ReportItem.GetAllFromCacheAsync();

// update the model...

this.Items.Clear();

foreach (ReportItem report in reports)

this.Items.Add(report);

// update the badge...

var badge = new BadgeNotificationBuilder(this.Items.Count);

badge.Update();

// update the tile...

string message = "1 report";

if (this.Items.Count != 1)

message = string.Format("{0} reports", this.Items.Count);

var tile = new TileNotificationBuilder(new string[]

{ "StreetFoo", message },

TileTemplateType.TileWidePeekImage01);

tile.ImageUris.Add("ms-appx:///Assets/Toast.jpg");

tile.UpdateAndReplicate(TileTemplateType.

TileSquarePeekImageAndText02);

}

}

Run the code and the tile will update. Wait long enough and the initial image will be replaced with text. Wait longer still and the text will rotate back. Right-click on the tile and use the app bar to toggle between the smaller and larger versions. Figure 5-10 shows the wide tile in picture mode.

The wide tile showing a single picture (and badge)

Figure 5-10. The wide tile showing a single picture (and badge)

Other Notification Features

There are some tile features that we haven’t had space to talk about in this chapter. Here’s a summary:

§ Tiles and badges can both update themselves periodically by automatically calling up to a remote web server. The idea here is that this server will return the populated XML notification document that would be set on the tile. (This idea of servers creating notification XML is something that we talk about in the next section.) Some of the built-in Bing apps (News, Travel, and so on) do this. The periods are fairly conservative, ranging from 30 minutes to 24 hours. This sort of thing is useful for retail apps that want to keep content fresh, but it’s unclear to me how this is useful in LOB apps.

§ Tiles have a notification queue, which can be up to five items long. The idea here is that you can pile in a whole load of updates that will cycle through on the Start screen. The documentation describes the scheduling of the updates as being subject to “internal factors” and that they “cannot be controlled by applications.” For retail apps, this is helpful for creating dynamic and interesting content. For LOB apps, I can’t see how helpful this is, as the information would surely tend to become overwhelming.

§ Secondary tiles are the final cool feature in the API that we haven’t discussed. Each app has at least one tile (the “primary”), but apps can create any number of secondary tiles that allow shortcuts into the app. For example, say you were building an order-taking LOB app—you can let users create secondary tiles for their key customers. When this tile is clicked, the app could be rigged to view the order summary for that customer. Similarly, the app could update the tile with key information about that customer.

Now that we’ve been through local notifications in detail, let’s look at how we can initiate notifications from the cloud.

Push Notifications

A really fantastic piece of design in the Windows Store app APIs is that if you want your notifications to originate from a server in the cloud rather than from the local device, you take the same XML that we’ve just seen and hand it over to the Windows Push Notification Service (WNS), and eventually it will get routed to the device and displayed. The only complexity lies in the process of communicating with WNS, and it’s these steps that we’ll see in this section.

WNS Process

To work with WNS, you need to have an account registered, and you need to tell it about your apps. There are two WNS systems—one production system and one test/sandbox system. In this section we’re going to use the test system. A word of warning: these types of systems operated by Microsoft tend to change a lot, so the screenshots and steps I’ll take you through here are for illustration only. Your mileage will undoubtedly vary.

Back to the steps: the principle is that on the client you need to obtain a notification URI and then pass that URI to your own cloud server. This process is called opening a channel. It’s easier to think of this URI as an arbitrary “token,” but we’re going to talk about something else called anauthentication token, and it will get confusing. When I talk about the “notification URI,” it’s just a string, nothing else.

Your cloud server will take that notification URI and attach it to some customer/account data in its database. When something happens that you want to tell that user about, you give the notification XML and the notification URI to WNS, and it deals with the problem of actually driving the notification down to whatever client it is applicable to. Note that’s “client” singular—you’ll have to handle multiple notification URIs per customer account, as each device’s channel is unique. If one customer has three devices, that’s three channels you need to record.

Sending the notification XML to WNS involves authenticating yourself using OAuth.

Figure 5-11 illustrates how this works. In the setup phase, the local Windows Store app calls an API to receive a notification URI specific to that application instance. That is then passed to the server. In the invocation phase, the cloud server calls through to WNS with that same notification URI. WNS then dereferences the client using that URI, and the call is passed to the client whereupon the notification is displayed.

Setup and signaling phases of the Windows Push Notification Service

Figure 5-11. Setup and signaling phases of the Windows Push Notification Service

In this chapter, we’re not going to build the server, as it’s way out of scope for the book. What we’re going to do instead is build a Windows Forms client that acts as a simulator for a server. We’ll plug the various values we need into this, it’ll call WNS, and WNS will call down to Windows in our device, and from there into the Windows Store app client. The reason I wanted to do this in Windows Forms is that Windows Forms is notionally more similar to ASP.NET, which is the technology you’d most likely use for initiating the connections into WNS.

Because I felt it would be generally valuable, I wrote a small standalone library that I’ve put on GitHub for sending notification XML over to WNS and handling the authentication. You can find a version of this library in the download for this chapter within the FakeCloud folder. If you’re following along, you should use the code in this folder.

NOTE

You should be aware that as of the time of writing, you are only allowed to use WNS with apps that are distributed through the Windows Store. This means that you cannot use WNS with LOB apps that are sideloaded.

This is explicitly written into the certification requirements—we talk about this and sideloading in Chapter 15.

Handling User Accounts

One big complication that we need to deal with here is that the WNS notifications know nothing about our user account model. Thus far, we’ve allowed users to register their own accounts and log on. The Windows notification system is based on the assumption that your app authentication is tied into the Windows authentication in some way. In a LOB app with single-sign-on, this makes plenty of sense, but generally for retail apps where you’re going to want customer accounts, not so much.

I need to be clear on this—if you have one app where the user logs into Windows using two accounts, you’re fine. You’ll get two separate URIs and hence when you notify on those URIs it’ll find its way to the correct user. But if you have one app where the user logs into the app on two separate accounts and you’re only using one Windows account, this can be a real problem—if you’re building an IM app and start surfacing updates to the wrong user, it could end up being extremely embarrassing.

NOTE

There is an unwritten rule that when working in the Windows world you shouldn’t necessarily allow this separate “subaccount” approach—that is, user accounts should be tied one-to-one with the actual Windows account. Philosophically I don’t agree with this, which is why the design of the app here provides an alternative view. It’s easy for me to show you a more complex approach and allow you to simplify if you feel it necessary.

Even though we don’t have the bandwidth to build a sample server within the book, I will take you through its design, as most of you will need to do this and some design elements are quite subtle.

§ A notification URI is bound to a device, not to a user account.

§ Your database will have a list of user accounts. As a user could have multiple devices, you need to hold multiple notification URIs per user.

§ The Windows Store app on the client will from time to time send up a logon token and a notification URI. You can use the logon token to dereference the user.

§ If the notification URI is not anywhere in your database, attach it to the user.

§ If the notification URI is within your database, remove it from the old user, then attach it to the new user.

This last step is key. Imagine you have Device X that User A and User B log on to. User B also logs on to Device Y. (The notification URIs we’re about to see bear no resemblance to how real notification URIs look. You should treat the real ones as arbitrary in any case.)

§ User A logs on to Device X and gets a URI like https://myUriForDeviceX. The server is updated with {UserA: https://myUriForDeviceX}.

§ User B logs on to Device Y and gets a URI like https://myUriForDeviceY. The server is updated with {UserB: https://myUriForDeviceY}.

§ If User B logs on to Device X, User A has to be logged off. The URI will be the same as the one User A got though, so you will have {UserA:(null)} and {UserB: https://myUriForDeviceY, https://myUriForDeviceY}.

What this last set of values tells you is that if you need to notify User A, you have no URIs, so you do nothing. If you need to notify User B, you have two URIs and need to call them both.

Obtaining a Notification URI

You open a channel by calling the CreatePushNotificationChannelForApplicationAsync method on PushNotificationChannelManager. This will return a PushNotificationChannel object containing your URI.

You should ask for the URI whenever the app starts. The first time you get a URI, this needs to be sent to the server. On subsequent calls, you may or may not get the same URI back. If the URI changes, you should send it to the server, as this indicates that the one that you had is invalid.

Notification URIs have a natural life, which as of the time of writing was 30 days. However, various error conditions may cancel off a token, causing it to be replaced. Moreover, the 30 days isn’t guaranteed; thus, this rule of “check whether it’s changed and resend” will always apply. It’s going to be better practice to only send up a new URI when it’s changed for improved efficiency at both the client and server ends. While we’re talking about best practice, you should transmit URIs over SSL, as anyone with both the URIs and the logon credentials for WNS can spam your users.

A wrinkle here is that your app may well end up running for more than 30 days, plus there’s a chance it may end up being in a suspended state for more than 30 days. We’re a little ahead of ourselves here, as we talk more about app lifetime in Chapter 14, but I need to explain some of this now.

If the user logs on explicitly, we need to ensure the channel is created, and transmit the notification URI up to the server if necessary. If the user opens the app and the logon is done implicitly through “remember me,” we need to go through the same process. If the user resumes the app and we are logged on, we again need to go through that same process.

We can sort out the first two cases of explicit and implicit logon by calling our channel setup from within our existing LogonAsync method. For the other case we’ll have to make a change to App to handle the Resumed event.

First, add this method to StreetFooRuntime. This will create the channel and use the SQLite persistent settings code that we built in Chapter 3 to track the state of various pieces of user settings data.

// Add method to StreetFooRuntime...

public static async Task SetupNotificationChannelAsync()

{

// get the notification channel...

var manager = await PushNotificationChannelManager.

CreatePushNotificationChannelForApplicationAsync();

Debug.WriteLine("Channel: " + manager.Uri);

// if either the URI we sent or the username we sent has changed,

// resend it...

var lastUri = await SettingItem.GetValueAsync("NotificationUri");

var lastToken =

await SettingItem.GetValueAsync("NotificationToken");

if (lastUri != manager.Uri || lastToken != LogonToken)

{

// send it...

Debug.WriteLine("*** This is where you asynchronously send it!

***");

// store it...

await SettingItem.SetValueAsync("NotificationUri", manager.Uri);

await SettingItem.SetValueAsync("NotificationToken", LogonToken);

}

else

Debug.WriteLine("URI not changed.");

}

NOTE

We’re writing the URI out to the debug console because we’ll need to copy and paste it into our server simulator later.

Within App, we need to add a handler to the Resuming event and, if we have a logon token, call into SetupNotificationChannelAsync. Here’s the code:

// Modify constructor and add method to App...

public App()

{

this.InitializeComponent();

this.Suspending += OnSuspending;

this.Resuming += App_Resuming;

}

async void App_Resuming(object sender, object e)

{

if (StreetFooRuntime.HasLogonToken)

await StreetFooRuntime.SetupNotificationChannelAsync();

}

That’s really it as far as setting up the channel goes. Of course, there would be some additional complexity in transmitting the changes to the server, but we know that we can do that easily enough.

Sending to WNS

Now we can turn our attention to the step where we transmit the notification to WNS, whereupon it would hopefully find its way down to our device.

As mentioned, I have built a Windows Forms client that we can use in this chapter. I’m not going to go through the construction of that at all. I will go through the construction of the MetroWnsPush library that we’re using, as it illustrates some common patterns that you see when talking to third-party endpoints over HTTP, not least of all some basic OAuth stuff.

Figure 5-12 shows the client app. What we need to do is fill in the package security identifier (SID), security secret, and channel URI fields. The package SID and client secret fields will come from the Microsoft portal that you use to manage your apps. The URI comes from the Windows Store app itself when it registers the channel.

We’ll now look at registering your app with the test/sandbox portal that Microsoft provides. As mentioned before, these screens are likely to change, but the steps should retain this general shape.

Our cloud simulation app

Figure 5-12. Our cloud simulation app

Registering the app

To register the app, start by visiting https://manage.dev.live.com/build. You’ll be asked to log in to your Microsoft Account. As of the time of writing, you will be shown a screenshot of the package editor within Visual Studio. You’ll also be shown a form to complete asking for the “Package display name” and “Publisher.” You’ll find these within the Packaging tab of the manifest editor. Figure 5-13 illustrates.

“Package display name” and “Publisher” fields

Figure 5-13. “Package display name” and “Publisher” fields

Copy and paste these into the form on the portal. Although it’s obvious, Figure 5-14 illustrates clearly what to do.

Filling out the form on the portal

Figure 5-14. Filling out the form on the portal

Click Accept, and you’ll be given three pieces of information: the package name, the client secret, and the package SID. Figure 5-15 illustrates.

The values needed to hook into WNS

Figure 5-15. The values needed to hook into WNS

NOTE

Don’t use these magic numbers—they’re specific to my app and my account. Make sure you obtain your own using the steps detailed here.

The package name gets baked into the app. The client secret and package SID will get baked into your cloud server. These two values are used to authenticate you with WNS. The first value is used as part of the channel setup initiated by the Windows Store app and uniquely identifies your app out of the total universe of all Window Store apps.

Copy and paste the package name back into the Packaging tab of the manifest editor. Copy and paste the other two values into Notepad and keep them safe. Figure 5-16 shows the former.

Pasting the package name back into the manifest

Figure 5-16. Pasting the package name back into the manifest

Now you can run the app. The channel will be created and the notification URI written to the debug console. Figure 5-17 illustrates. When you get the URI, copy and paste that into Notepad too.

Our new notification URI

Figure 5-17. Our new notification URI

Now that we have all the data we need, we can make the call, starting with authentication.

Authenticating

Authentication with WNS is done over OAuth. We use the client secret and the package SID to authenticate ourselves.

In this part, we’re going to use the MetroWnsPush package that I described earlier; however, I will present code from it so that you can get a feel for how the actual communication works.

During authentication, a GET request is sent to https://login.live.com/accesstoken.srf, passing in the secret package SID, and some special values using the application/x-www-form-unencoded content type. The AuthenticateAsync method within WnsAuthenticator does this in the MetroWnsPush package. This uses the new HttpClient implementation that we’ve been using thus far in StreetFoo client. Ultimately, we’ll receive JSON that contains token_type and access_token. Here’s the code:

public async Task<WnsAuthentication> AuthenticateAsync(string sid,

string secret)

{

// create some content...

var body = string.Format("grant_type=client_credentials&client_id=

{0}&client_secret={1}&scope=notify.windows.com",

HttpUtility.UrlEncode(sid).Trim(), HttpUtility.UrlEncode

(secret).Trim());

var content = new StringContent(body);

// set the type...

content.Headers.ContentType.MediaType =

"application/x-www-form-urlencoded";

// send it...

var client = new HttpClient();

var response = await client.PostAsync

("https://login.live.com/accesstoken.srf", content);

// check...

if (response.StatusCode == HttpStatusCode.OK)

{

// get it...

string json = await response.Content.ReadAsStringAsync();

var asObject = JObject.Parse(json);

// get...

string scheme = (string)asObject["token_type"];

string token = (string)asObject["access_token"];

return new WnsAuthentication(scheme, token);

}

else

throw await CreateRequestException(string.Format("Invalid status

code received: {0}.", response.StatusCode), response);

}

In terms of results, we’ll get something like this:

{

"token_type":"bearer","access_token":"EgAcAQMAAAAEgAAACoAAMgSg6vFTbLshYEo5VGPU

i+ph+uZ6G0w6bH+MFYwKIuGFHfeId77U4saX6DrfJ6GbzWBHcXPfPEo0P9OgywvqEhQjpduh7r7mJITC

5QLinGyE/FPJNTRNF9Lc5UuKOKyGQeBA61m1zU/KiEbt69dXXyJbrLRe+X6WZ95A/5in80GLAFoAiwAA

AAAAMeMMRBabEVAWmxFQ60gEAA8AODEuMTQ5LjI0Ny4yMzIAAAAAAFwAbXMtYXBwOi8vcy0xLTE1LTIt

Mzc4ODI2MTEwMi0xOTE3MzYxNTMxLTI4Njc3MTY1NDktMzYwMDI2MTMxNS0yNTM5OTU0MDQtMTk2ODc1

ODgyLTgzMTc4MzU3MwA=",

"expires_in":86400

}

The token_type of bearer simply tells you what sort of OAuth request it is. At the time of writing, bearer is essentially the only OAuth methodology in use. It basically means that the “person who is bearing the token” has been authenticated.

To try this, run the Windows Forms client and paste in the secret and SID that you captured earlier. Click Authenticate, and you should see a token appear in the box. Figure 5-18 illustrates.

The token appearing after successful authentication

Figure 5-18. The token appearing after successful authentication

Sending

Now that we’ve authenticated, we can push through our notification. We do so by creating a new HttpClient instance and providing it with an authorization header. In MetroWnsPush, the result of the call to AuthenticateAsync is a WnsAuthentication instance. The class of this instance contains the logic to do this. One wrinkle is that although token_type is provided as bearer, it has to be passed back to the server as Bearer.

public class WnsAuthentication

{

public string Scheme { get; private set; }

public string Token { get; private set; }

internal WnsAuthentication(string scheme, string json)

{

// atm, this is fixed to understand bundle only...

if(scheme != "bearer")

throw new NotSupportedException(string.Format("Cannot handle

'{0}'.", scheme));

this.Scheme = "Bearer";

this.Token = json;

}

internal HttpClient GetHttpClient()

{

// create a client and pass in the authentication...

var client = new HttpClient();

client.DefaultRequestHeaders.Authorization = new

AuthenticationHeaderValue(this.Scheme, this.Token);

return client;

}

}

Passing up the XML is very easy. We just have to set the content type to text/xml, and make sure that we include special headers to indicate the notification type. In MetroWnsPush we do this with the WnsPusher class.

One oddity of WNS is that the results come back in the response headers, not in the body. Specifically, we have to look in X-WNS-NOTIFICATIONSTATUS to see what happened if we get HTTP 200 back. If we don’t get HTTP 200 back, there are other headers to look for. You can see these in the MetroWnsPush package within the CreateRequestException method.

If what we send up works, we’ll get back one of these:

Received

This means it looks OK, and it’ll be queued for transmission. There is no guarantee of success given the vagaries of whether the device will ever be turned on, whether the user has uninstalled the app, etc.

Dropped

This means there is an “error condition,” or that you’ve sent a toast notification and the device is offline. This is a tricky one to deal with, because one of those is obviously an error condition, and the other (the toast one) isn’t.

ChannelThrottled

This means you are sending too many messages over too short a period of time.

With that covered, here’s the code for sending the notification:

public async Task<WnsPushResult> PushAsync(WnsAuthentication

authentication, string uri, XmlDocument doc, NotificationType type)

{

// create...

var content = new StringContent(doc.OuterXml);

content.Headers.ContentType.MediaType = "text/xml";

// if...

if(type == NotificationType.Toast)

content.Headers.Add("X-WNS-Type", "wns/toast");

else if (type == NotificationType.Tile)

content.Headers.Add("X-WNS-Type", "wns/tile");

else if (type == NotificationType.Badge)

content.Headers.Add("X-WNS-Type", "wns/badge");

else

throw new NotSupportedException(string.Format("Cannot handle

'{0}'.", type));

// ok...

var client = authentication.GetHttpClient();

var response = await client.PostAsync(uri, content);

// what happened?

if (response.StatusCode == HttpStatusCode.OK)

{

// what happened?

var all = response.Headers.Where(v => v.Key ==

"X-WNS-NOTIFICATIONSTATUS").FirstOrDefault();

if(string.IsNullOrEmpty(all.Key))

throw new InvalidOperationException("'X-WNS-

NOTIFICATIONSTATUS' header not returned.");

return (WnsPushResult)Enum.Parse(typeof(WnsPushResult),

all.Value.First(), true);

}

else

throw await WnsAuthenticator.CreateRequestException("Failed to

post notification.", response);

}

You can test this by running the Windows Forms client, authenticating, and then setting up the XML. Figure 5-19 shows a successful toast notification.

A successful notification sent through WNS

Figure 5-19. A successful notification sent through WNS

Troubleshooting Tips

There are a few things to try if your notifications do not seem to be working.

§ If you’re sending toast notifications, make sure the app is marked as “Toast capable.” (This may involve uninstalling and redeploying the app—see the last bullet in this list.) Also check the Permissions option in the settings charm to make sure you haven’t switched off notifications.

§ WNS will accept any XML, but the client side will decline to present XML if it doesn’t fit the schema. Don’t put additional XML in assuming you can use it as a channel for additional stuff. You can’t.

§ At the time of writing, the Dropped notification seemed flaky. Sometimes things that should be dropped (e.g., invalid XML) would return as Dropped or Received. The Received notification, however, was always correct.

§ If you’re struggling, try uninstalling and redeploying the app. The easiest way to do this is with PowerShell. Chapter 15 has more details on this. That can particularly affect the “Toast capable” flag, because if you create the channel with this flag off, the channel will be set not to support it and you’ll need to uninstall.