Chapter 13. Push Notification


In This Chapter

Benefits of push notification

Handling channel errors

Toast, tile, and raw notifications

Specifying notification delivery delay using notification classes

Shell tile anatomy

Using local and remote shell tile images

Updating an application tile using a tile schedule

Cloud service authentication

Sending CLR objects with raw notifications

Building a stock ticker app


Push notification is a server to client, or cloud to phone, messaging system that allows notifications to be sent from the cloud to the phone and displayed to the phone user. Notifications are one-way messages that are normally associated with a particular application and may be delivered both while the application is running and not running. The main purpose of the push notification system is to reduce the power consumption of the phone device, thereby increasing battery life.

This chapter begins with an overview of push notification, starting with the three different types of push notifications: toast, tile, and raw notifications. The chapter examines the various types of channel errors that can occur during push notification and how the phone’s battery level affects delivery of push notifications. Cloud service authentication using X.509 certificates is also discussed as well as how to update an application tile without tile notifications using shell tile schedules.

Finally, the chapter explores a custom stock ticker sample app that allows the user to enter a stock symbol into the phone, after which a cloud service periodically notifies the phone of stock price variations.

Push Notification Types

Windows Phone supports the following three types of push notifications:

Toast notification—A systemwide notification that is shown for a few seconds at the top of the phone’s display.

Tile notification—Displays information using a tile on the Start Experience (home screen).

Raw notification—Offers a completely flexible manner for delivering notifications to your running app. The OS does not display a raw notification, but rather the notification message is sent directly to your running app.

Benefits of Push Notification

The Microsoft Push Notification Service (MPNS) is designed to be resilient and to expose a persistent channel for sending notifications to Windows Phone devices.

Apart from some special cases, the Windows Phone platform does not allow multiple apps to be running at the same time.1 The main reason is that, in most cases, it is not efficient to have background apps consuming device resources, such as maintaining an open connection and sending data over the wire. Network activity results in high power consumption and decreased battery life.

1 Background tasks allow an app to run in the background periodically for short periods. For more information, see Chapter 27, “Scheduled Actions.”


Note

By battery life I mean the amount of time a device runs before it requires a recharge. Conversely, battery lifespan means the total amount of time a battery lasts before it is discarded and replaced with a new battery.


Push notification reduces power consumption in several ways; most notably it uses a single shared network connection, eliminating the need for private persistent open connections. If an app needs to be running merely to receive updates from the cloud, a better approach is to use push notification. The push notification model has the following benefits:

Offline capability—Windows Phone apps should be designed to be tolerant of interruptions as they may be deactivated or terminated at any time. Push notification allows the user to act on some information, even when your app is not running.

Notification aggregation—Multiple notifications for different apps can be pushed at the same time.

Notification prioritization—Notifications can be assigned a priority, which affects when the notification is delivered. This control allows a developer to assign lower priorities to those notifications that do not require immediate attention, thereby reducing the device’s network activity.

Connection pooling—It is more efficient to use a single shared network connection than to have each app owning its own connection.

Small message size—Notifications are designed to carry a small payload. This forces you to think carefully about what information is really necessary to be conveyed.

Deterministic behavior—Tile and toast notifications use a well-defined format with a fixed set of data properties, which allows the OS to present them automatically.

Eliminates polling—Apps that want to receive messages from a cloud service without the use of push notification must periodically poll the cloud service. Push notification eliminates the need for custom network communication code and for private persistent open connections.

Understanding Push Notification

Push notification relies on the MPNS to dispatch notifications to the phone. The process works like this:

1. A phone app requests subscription to the MPNS using a notification channel.

2. The MPNS returns a URI, where notifications can be sent.

3. The phone app forwards the URI to any third-party service (such as a web service created by you) that wants to send the phone app notifications (see Figure 13.1).

Image

Figure 13.1. An app subscribes to the MPNS, which is then forwarded to a third-party server.

The Push Notification URI is the address of the MPNS plus an identifier for phone device. Having received the push notification URI, a third-party service is then able to send notifications to the phone, via the MPNS, as shown in Figure 13.2.

Image

Figure 13.2. Your server sends a notification to the MPNS, which is then forwarded to the phone.


Note

The notification channel URI is not guaranteed to remain the same. Therefore, each time a phone app launches, it is expected to send its notification channel URI to its corresponding cloud service.


Notifications from the cloud service use a well-defined protocol for sending notifications to the MPNS. Subsequent sections of the chapter examine the protocol format for the various notification types.


Note

Microsoft does not offer a Service Level Agreement (SLA) for the delivery of push notifications.


Getting Started with Push Notification

The main CLR types used in push notification reside in the Microsoft.Phone.Notification namespace. To consume push notifications in an application, a reference to the Microsoft. Phone assembly is required (see Figure 13.3).

Image

Figure 13.3. Add a reference to Microsoft.Phone.

To enable an application to use push notification, the ID_CAP_PUSH_NOTIFICATION capability must be present in the WMAppManifest.xml file. While not explicitly required for push notification, the ID_CAP_NETWORKING capability is also required if you want to notify a cloud service of the existence of an MNPS URI. Push notification is useless in most cases without the latter capability.

The following excerpt shows the capabilities needed for push notification in the WMAppManifest.xml file:

<Capabilities>
  <Capability Name="ID_CAP_NETWORKING" />
  <Capability Name="ID_CAP_PUSH_NOTIFICATION" />
</Capabilities>

For more information on application capabilities, see Chapter 2, “Fundamental Concepts in Silverlight Development for Windows Phone.”


Note

To pass certification for the Windows Phone Marketplace, an app must provide the user with the ability to disable toast and tile notifications. The user must have the ability to perform this task from within the app.

Furthermore, before calling either the HttpNotificationChannel.BindtoShellToast or HttpNotificationChannel.BindToShellTile methods for the first time, an application must explicitly ask the user for permission.

These requirements give the user control over if and when push notification is used.


Subscribing to Push Notification

To subscribe to push notification within an app, we first attempt to retrieve an existing HttpNotificationChannel by name.

channel = HttpNotificationChannel.Find("<a channel name>");

The HttpNotificationChannel.Find method does not raise an Exception and returns null if the channel with the specified name is not found. If so, a new instance must be created.

The Push Notification sample in the downloadable code contains a class called PushNotificationSubscriber, which manages subscription to the MPNS by wrapping a notification channel. The main benefit of using a wrapper for the notification channel is that if the PushNotificationSubscriber instance is unable to perform the subscription to the MPNS, due to the absence of a network connection for example, it automatically retries.

If the channel cannot be found, a new channel is created as shown:

if (channel == null)
{
    channel = string.IsNullOrEmpty(serviceName)
        ? new HttpNotificationChannel(channelName)
        : new HttpNotificationChannel(channelName, serviceName);
}

The HttpNotificationChannel constructor has the following two overloads:

public HttpNotificationChannel(string channelName);
public HttpNotificationChannel(string channelName, string serviceName);

The channelName parameter is the name that the app uses to identify the notification channel instance. This allows an application to retrieve an existing channel by name using the static HttpNotificationChannel.Find method.

The serviceName parameter is the name of the cloud service to which the notification channel is associated. The serviceName parameter may be used to subscribe to an authenticated cloud service. For more information, see the section “Cloud Service Authentication” later in this chapter.

Once you have obtained a channel instance, it can be opened like so:

channel.Open();

Binding to the Shell

For the OS to display toast and tile notifications, the channel must call the BindToShellToast and BindToShellTile methods, as demonstrated in the following excerpt:

/* Toast Notifications. */
if (!channel.IsShellToastBound)
{
    channel.BindToShellToast();
}

/* Tile Notifications. */
if (!channel.IsShellTileBound)
{
    channel.BindToShellTile();
}

To stop receiving toast and tile notifications, the channel provides the UnbindToShellToast and UnbindToShellTile methods, respectively.


Best Practice

When creating an HttpNotificationChannel, avoid keeping it open if not subscribing to its events. It is not necessary for the channel to be kept open for the shell to receive either toast or tile notifications, and the channel itself consumes valuable resources.

HttpNotificationChannel implements IDisposable and can be safely disposed after binding it to the shell, as shown in the following excerpt:

using (HttpNotificationChannel channel
     = new  HttpNotificationChannel(channelName))
{
    channel.Open();
    channel.BindToShellTile();
    channel.BindToShellToast();
}



Note

Calling the Open method on a channel may immediately raise an Exception, most commonly an InvalidOperationException, if the channel fails to establish a connection with the MPNS.


After retrieving or creating a channel instance, you are able to subscribe to its various events.


Caution

Be mindful that each Windows Phone device is limited to a total of 15 apps registered for push notifications. If the user installs 15 apps that use push notifications and your app is the 16th one installed, an InvalidOperationException (channel quota exceeded) is raised if your app calls BindToShellTile or BindToShellToast.


HttpNotificationChannel Events

The following is a list of the HttpNotificationChannel events:

ChannelUriUpdated—This event occurs when the push notification URI changes, which can be at any time. If and when the URI changes, the new URI must be forwarded to any cloud services that send push notifications to your app.

HttpNotificationReceived—This event occurs when a raw notification is received.

ShellToastNotificationReceived—This event occurs when a toast notification is received.

ErrorOccurred—This event provides a handling mechanism for exceptions raised by the channel. The handler for this event receives a NotificationChannelErrorEventArgs object, which contains an ErrorCode property, an ErrorType property, and a Message property pertaining to the Exception that was raised.

Handling Channel Errors

When a channel error occurs, the NotificationChannelErrorEventArgs.ErrorType property can be used to determine the nature of the error, as the following excerpt demonstrates:

void HandleChannelErrorOccurred(
    object sender, NotificationChannelErrorEventArgs e)
{
    switch (e.ErrorType)
    {
        case ChannelErrorType.ChannelOpenFailed:
            // ...
            break;
        case ChannelErrorType.MessageBadContent:
            // ...
            break;
        case ChannelErrorType.NotificationRateTooHigh:
            // ...
            break;
        case ChannelErrorType.PayloadFormatError:
            // ...
            break;
        case ChannelErrorType.PowerLevelChanged:
            // ...
            break;
    }
}

The following list describes the NotificationChannelErrorEventArgs.ErrorType enumeration values:

ChannelOpenFailed—Occurs when the push client is unable to establish a connection with the MPNS. Note that exceptions resulting from, for example, a channel already being open, are raised at the call to the HttpNotificationChannel.Open method.

MessageBadContent—Occurs when using tile notifications and the BackgroundImage URI is pointing to a remote image, despite the HttpNotificationChannel not being bound to a list of URIs. Tile notifications are discussed in detail later in the chapter.

NotificationRateTooHigh—Occurs when the push notification client is unable to receive messages because the cloud service is sending too many messages at too high a rate.

PayloadFormatError—Occurs when the XML payload format, or the HTTP header of the push notification, is syntactically invalid. When this error occurs, the HttpNotificationChannel is closed and the channel must be reopened.

PowerLevelChanged—Occurs when the device’s battery level changes significantly enough to trigger a change in the push client’s power policy. This topic is discussed in the next section “Power Management and Push Notification.”

Additional error information can be obtained by examining the NotificationChannelErrorEventArgs.ErrorCode property, which identifies the HRESULT of the Exception. The HRESULT is a coded numerical value that is assigned to some specific exceptions.

Power Management and Push Notification

Even though push notification assists in reducing the power consumption of the phone, maintaining any kind of network connection unfortunately expends more power than most other device functions. Therefore, when the phone’s battery level is low, to help reduce power consumption, the phone progressively prevents certain types of notifications from being received by the device (see Figure 13.4).

Image

Figure 13.4. Device power states transition from a normal power level to low and to critically low.

When an HttpNotificationChannel raises its ErrorOccured event with an ErrorType value of PowerLevelChanged, it indicates that there is a change to the types of notifications that are to be delivered to the device.

The NotificationChannelErrorEventArgs.ErrorAdditionalData property can be used to obtain the ChannelPowerLevel enum value, as shown in the following excerpt:

void HandleChannelErrorOccurred(
        object sender, NotificationChannelErrorEventArgs e)
{
    switch (e.ErrorType)
    {
        case ChannelErrorType.PowerLevelChanged:
          if (e.ErrorAdditionalData == (int)ChannelPowerLevel.LowPowerLevel
                || e.ErrorAdditionalData
                           == (int)ChannelPowerLevel.CriticalLowPowerLevel)
            {
                /* Power level is too low. */
            }
            break;
    }
}

The following list describes each of the ChannelPowerLevel enum values:

NormalPowerLevel—The battery level is not low. All push notification types are sent to the device.

LowPowerLevel—The battery level is low. Only raw notifications are sent to the device.

CriticalLowPowerLevel—The battery level is critically low. No push notifications of any type are sent to the device.

Sending Push Notifications

Sending a toast or tile push notification is done by creating an XML envelope containing the notification information, converting it to a byte array, and then sending it via an HttpWebRequest to the MPNS.


Note

When sending a push notification, only the HTTP POST method is allowed. Using any other method such as GET, PUT, CREATE, or DELETE results in a 405 MethodNotAllowed response. Moreover, a ProtocolViolationException results when writing to the request Stream.

For testing purposes, however, the HTTP GET method can be used, and results in a 200 OK response regardless.

An example of a push notification URL is http://sn1.notify.live.net/throttledthirdparty/01.00/AAGvdLTQzLGqRZ0FRZRVT1GBAgoOs1kPAQAAAAQOMDAwAAAAAAAAAAAAAAA.

The notification URL subscriber identifier, which is located after the final forward slash, does not need to resolve to an active subscription. This prevents a third party from fishing for an active subscription URL.


Raw notifications have more flexibility in their format; however, the process of sending a raw notification remains the same as that of tile and toast notifications. The following sections explore in detail how to send toast, tile, and raw notifications.


Caution

When sending a notification, the maximum size of a toast, tile, or raw notification body should not exceed 1KB. If this size is exceeded, a System.Net.WebException is raised, resulting from a 400 (Bad Request) response code returned from the MPNS.


For a complete list of MPNS response codes, see http://bit.ly/wd6SXy.

Toast Notifications

Toast notifications are systemwide action requests primarily designed for use with peer-to-peer communications. Toast notifications do not disrupt the user’s workflow. They are, however, distracting and therefore should be used sparingly. Toast notifications are presented on the phone as a clickable overlay on the user’s current screen even if the associated app is not running (see Figure 13.5). Toast notifications are, however, not displayed if the current app is the app that initiated the push notification subscription. If a user clicks on a toast notification, the associated app is launched.

Image

Figure 13.5. Toast notifications are displayed as an overlay on the current application.


Best Practice

Toast notifications should be time critical and personally relevant to the user.

Assume that toast notifications are not enabled for your app, as they require the user to explicitly opt-in to receive them.


A toast notification consists of the application icon displayed at a reduced size and two text fields, one for a title, the other for the notification message.

Receiving a Toast Notification from Within an Application

The HttpNotificationChannel.ShellToastNotificationReceived event is used to receive toast notifications within your app, like so:

channel.ShellToastNotificationReceived +=
          channel_ShellToastNotificationReceived;


Note

Toast notifications are displayed only when your app is not running in the foreground. If your app is running in the foreground, the toast notification can still be received and handled in code.


When the ShellToastNotificationReceived event is raised, the handler receives a NotificationEventArgs object from which the relevant toast notification fields can be extracted, as shown:

void channel_ShellToastNotificationReceived(object sender,
                                            NotificationEventArgs e)
{
    string text1 = e.Collection["wp:Text1"];
    string text2 = e.Collection["wp:Text2"];
    //...
}

Sending a Toast Notification

Toast notifications require an XML envelope for the notification information. Listing 13.1 demonstrates how the creation of an XML envelope is constructed and sent to the MPNS. An HttpWebRequest is created using the push notification subscription URL. The notification XML is converted to a byte array, and then it is written to the outgoing request Stream. After sending the request, the response is examined to ensure that it was correctly received by the MPNS.


Note

For more information on the X-NotificationClass request header, and setting a notification’s priority level, see the upcoming section “Notification Classes.”


Listing 13.1. PushNotifier Class (excerpt)


public PushNotificationSendResult SendToastNotification(
    string title, string message, string url, PushNotificationPriority priority)
{
    string priorityHeader;
    if (priority == PushNotificationPriority.RealTime)
    {
        priorityHeader = "2";
    }
    else if (priority == PushNotificationPriority.Priority)
    {
        priorityHeader = "12";
    }
    else
    {
        priorityHeader = "22";
    }

    /* Create the http message that will be sent
     * to the Microsoft hosted server. */
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "text/xml";
    request.Headers = new WebHeaderCollection
                {
                    { "X-WindowsPhone-Target", "toast" },
                    { "X-NotificationClass", priorityHeader }
                };

    /* The XML envelope contains the notification text. */
    string messageFormat = "<?xml version='1.0' encoding='utf-8'?>" +
                        "<wp:Notification xmlns:wp='WPNotification'>" +
                            "<wp:Toast>" +
                                "<wp:Text1>{0}</wp:Text1>" +
                                "<wp:Text2>{1}</wp:Text2>" +
                            "</wp:Toast>" +
                        "</wp:Notification>";

    /* Insert the message string. */
    string messageFormatted = string.Format(messageFormat, title, message);
    /* The message will be sent as a byte array. */
    byte[] messageBytes = Encoding.Default.GetBytes(messageFormatted);

    request.ContentLength = messageBytes.Length;

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(messageBytes, 0, messageBytes.Length);
    }

    /* Get the response after the message is sent. */
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    string notificationStatus = response.Headers["X-NotificationStatus"];
    string deviceConnectionStatus
               = response.Headers["X-DeviceConnectionStatus"];
    string subscriptionStatus = response.Headers["X-SubscriptionStatus"];

    PushNotificationSendResult result = new PushNotificationSendResult(
        notificationStatus, deviceConnectionStatus, subscriptionStatus);

    Debug.WriteLine(result);
    return result;
}


An HttpRequestHeader named WindowsPhone-Target with the value toast is added to the request’s Headers collection, indicating to the MPNS that it is a toast notification.

The XML format for a toast notification is shown in the following fragment:

<?xml version='1.0' encoding='utf-8'?>
<wp:Notification xmlns:wp='WPNotification'>
  <wp:Toast>
    <wp:Text1>WP7 Unleashed</wp:Text1>
    <wp:Text2>Toast example</wp:Text2> " +
  </wp:Toast>
</wp:Notification>

When the notification is displayed on the phone, the content of the wp:Text1 element is placed in the title field of the notification overlay, while the content of the wp:Text2 element is placed in the body field (see Figure 13.6).

Image

Figure 13.6. Anatomy of a toast notification overlay

By default, the app icon is the ApplicationIcon.png file present in the root directory of the Windows Phone project. This can be customized from within the project’s properties editor (see Figure 13.7).

Image

Figure 13.7. The application icon image can be set via the Application tab on the properties editor, within Visual Studio.

Alternatively, the path to the image for the application icon can be modified manually within the WMAppManifest.xml file at the Deployment/App/IconPath element.

<IconPath IsRelative="true"
          IsResource="false">ApplicationIcon.png</IconPath>


Tip

As you can see in Figure 13.6, it is wise to tailor the application icon so that it displays clearly at a small size when presented in a toast notification.


Tile Notifications

A tile is a dynamic visual representation of your app that resides on the phone’s Start Experience. Each app on the phone can be associated with a single application tile and multiple secondary tiles. Tiles serve as a shortcut to an application.

Tile notifications allow the application tile to be modified periodically, using a data driven template model, with a fixed set of data properties. Each property corresponds to a UI element, with each UI element having a fixed position on the tile. A tile notification can be used to communicate information sent from the cloud, such as updates from an email service, a social networking service, or a weather service.

It is at the user’s discretion to pin an application to the Start Experience, and having the app pinned to the Start Experience is essential for receiving tile notifications.

When an application is pinned to the Start Experience, tile notifications are displayed regardless of whether the app is running.

The template for a tile notification consists of three assignable data properties: a title property, a count property (also called a badge), and a background image property (see Figure 13.8). A cloud service may modify the content of an associated tile using tile notifications.

Image

Figure 13.8. A tile notification consists of a count property, title property, and a background image property.

Tile Notification Anatomy

Tile notifications are comprised of three parts: a background, a title, and a count (or badge) field, as shown in Figure 13.9.

Image

Figure 13.9. Tile notifications consist of three assignable fields: the background, a title field, and a count field.

Background Image Property

The background image of a tile can be assigned a local resource URI or a remote URI to an image on the cloud. By using a remote image, you have the opportunity to dynamically generate it, giving you more control over information presented in the image but at the cost of bandwidth and power consumption.

The tile background image must be in either JPG or PNG format.


Note

When using a local image for a tile, the image must have its Build Action set to Content.


Title Property

The title is a text field located at the bottom of the tile.

Count Property

The Count element must be a positive integer value between 0 and 99. This value is displayed on the count property, or badge, region of the tile. If the value is 0 then the badge is not displayed.


Note

Tiles use sound and animation; however, these are determined by the OS and are not extensible.


Sending a Tile Notification

Sending a tile notification is done in the same manner as sending a toast notification, the differences being in the request headers and the format of the XML message (see Listing 13.2). As with toast notifications, we create an HttpWebRequest, build the message, convert the message to a byte array, and then write it to the request Stream. Finally, we verify that the notification was correctly received by the MPNS.

An HttpRequestHeader named WindowsPhone-Target with the value tile is added to the request’s Headers collection, indicating to the MPNS that it is a tile notification.

Listing 13.2. Sending a Tile Notification


public PushNotificationSendResult SendTileNotification(string title, int count,
    string imagePath, string url, PushNotificationPriority priority)
{
    string priorityHeader;
    if (priority == PushNotificationPriority.RealTime)
    {
        priorityHeader = "1";
    }
    else if (priority == PushNotificationPriority.Priority)
    {
        priorityHeader = "11";
    }
    else
    {
        priorityHeader = "21";
    }

    /* Create the http message that will be sent
     * to the Microsoft hosted server. */
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "text/xml";
    request.Headers = new WebHeaderCollection
                {
                    { "X-WindowsPhone-Target", "token" },
                    { "X-NotificationClass", priorityHeader }
                };

    /* The XML envelope contains the notification text. */
    string messageFormat = "<?xml version='1.0' encoding='utf-8'?>" +
                        "<wp:Notification xmlns:wp='WPNotification'>" +
                            "<wp:Tile>" +
                                "<wp:BackgroundImage>{0}</wp:BackgroundImage>" +
                                "<wp:Count>{1}</wp:Count>" +
                                "<wp:Title>{2}</wp:Title>" +
                            "</wp:Tile>" +
                        "</wp:Notification>";

    /* Insert the message string. */
    string messageFormatted = string.Format(
        messageFormat, imagePath, count, title);
    /* The message is sent as a byte array. */
    byte[] messageBytes = Encoding.Default.GetBytes(messageFormatted);

    request.ContentLength = messageBytes.Length;

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(messageBytes, 0, messageBytes.Length);
    }
    /* Get the response after the message is sent. */
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    string notificationStatus = response.Headers["X-NotificationStatus"];
    string subscriptionStatus = response.Headers["X-SubscriptionStatus"];
    string deviceConnectionStatus
                = response.Headers["X-DeviceConnectionStatus"];

    PushNotificationSendResult result = new PushNotificationSendResult(
        PushNotificationEnumConverter.ToNotificationStatus(notificationStatus),
        PushNotificationEnumConverter.ToDeviceConnectionStatus(
                                                       deviceConnectionStatus),
        PushNotificationEnumConverter.ToSubscriptionStatus(subscriptionStatus)
        );
    Debug.WriteLine(result);
    return result;
}


The format for a tile notification is shown in the following fragment:

<?xml version='1.0' encoding='utf-8'?>
<wp:Notification xmlns:wp='WPNotification'>
  <wp:Tile>
    <wp:BackgroundImage>
            ProjectSubdirectory/ExampleImage.png
    </wp:BackgroundImage>
    <wp:Count>
            An integer n, with 0 <= n <= 99
    </wp:Count>
    <wp:Title>Example Title</wp:Title>
  </wp:Tile>
</wp:Notification>

When the notification is displayed on the phone, the content of the wp:BackgroundImage element specifies the URL of the tile’s background image. This value can either be a local path within the project, or a remote URI such as http://www.example.com/ImageName.png.


Caution

A tile’s background image size must not exceed 80KB. In addition, the download time for an image must not exceed 1 minute. If the image size is too large, or the download takes too long, then the default image for the tile is used.


The wp:Title element denotes the title text to be displayed at the bottom of the tile.


Note

For information on how to create shell tiles or modify tile properties directly from your app, see Chapter 27.



Tip

Presenting too much information can slow down comprehension of a tile. Therefore, keep the style simple to improve clarity. When designing a background image for a tile, it is important to make different states easily recognizable.

Do not presume that a background image will be placed on a particular screen color. Themes allow the phone’s Start Experience color scheme to be modified. Therefore, when using PNG images as the tile image format, avoid using transparency if possible, as antialiasing may produce unintended discoloration around shape boundaries.


Updating an Application Tile Using a Shell Tile Schedule

You saw how to specify the background image of an application tile using push notification. There is, however, an alternative approach that lets you set the background image to a remote URI without using push notification, eliminating the need for a cloud service to issue tile notifications.

By using the ShellTileSchedule class the phone can be directed to periodically download an image from a remote server.

To create a schedule for a tile update, create a new instance of the ShellTileSchedule class and set the RemoteImageUri property, as shown in the following example:

string url = "http://www.example.com/ImageName.png";
ShellTileSchedule schedule = new ShellTileSchedule
{
    Interval = UpdateInterval.EveryHour,
    MaxUpdateCount = 10,
    Recurrence = UpdateRecurrence.Interval,
    RemoteImageUri = new Uri(url),
    StartTime = DateTime.Now
};
schedule.Start();

The following is a list of ShellTileSchedule properties that affect the download frequency:

Interval—This property specifies the download frequency rate and is of type Microsoft.Phone.Shell.UpdateInterval. UpdateInterval is an enumeration with the following values:

EveryHour

EveryDay

EveryWeek

EveryMonth

If the shortest frequency, EveryHour, does not offer a short enough interval, push notification or a background agent should be considered instead.

MaxUpdateCount—This property defines the number of times the schedule runs. If this value is not set or set to a number less than 1, then the schedule runs indefinitely. This value is ignored if Recurrence is set to OneTime.

Recurrence—This property specifies whether the image is to be fetched periodically, or if retrieval of the image is to occur only once. The available values are UpdateRecurrence.Interval or UpdateRecurrence.OneTime.

RemoteImageUri—This property is the fully qualified URI of the background image. It cannot point to a local image stored on the phone.

StartTime—This property specifies the duration from the initial ShellTileSchedule.Start method call until the first time that the image is fetched. This property is used to delay the initial download of an image.


Caution

The same restrictions apply to images retrieved using a ShellTileSchedule as those in tile notifications. In particular, image size must not exceed 80KB, and the download time must not exceed 1 minute. If the image size is too large, or the download takes too long, not only is the default image used for the tile, but the tile schedule is also removed. The only way to reinstate a schedule is to re-run the schedule generation code.


Raw Notifications

Raw notifications are not displayed by the OS and are delivered directly to your running app, via an event. If your app is not running when a raw notification is received, then the notification is discarded.

Sending a Raw Notification

Sending a raw notification differs from sending tile and toast notifications in that there is greater flexibility over the contents of the body of the notification.

Listing 13.3 demonstrates sending an object as part of a raw notification, using a DataContractJsonSerializer. As with tile and toast notifications, we create an HttpWebRequest, convert the content to a byte array, and then write it to the request Stream. Finally, we verify that the notification was correctly received by the MPNS.

Listing 13.3. PushNotifier Class (excerpt)—Sending a Raw Notification


public PushNotificationSendResult SendRawNotification(
    object content, string url, PushNotificationPriority priority)
{
    string priorityHeader;
    if (priority == PushNotificationPriority.RealTime)
    {
        priorityHeader = "5";
    }
    else if (priority == PushNotificationPriority.Priority)
    {
        priorityHeader = "16";
    }
    else
    {
        priorityHeader = "26";
    }

    /* Create the http message that will be sent
     * to the Microsoft hosted server. */
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    /* HTTP POST is the only allowed method to send the notification. */
    request.Method = "POST";
    request.ContentType = "application/json; charset=utf-8";
    request.Headers = new WebHeaderCollection {
   { "X-MessageID", Guid.NewGuid().ToString()},
   { "X-NotificationClass", priorityHeader } };

    /* The message will be sent as a byte array. */
    byte[] contentBytes;
    var serializer = new DataContractJsonSerializer(content.GetType());

    using (MemoryStream stream = new MemoryStream())
    {
        serializer.WriteObject(stream, content);
        contentBytes = stream.ToArray();
    }

    request.ContentLength = contentBytes.Length;

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(contentBytes, 0, contentBytes.Length);
    }

    /* Get the response after the message is sent. */
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    string notificationStatus = response.Headers["X-NotificationStatus"];
    string subscriptionStatus = response.Headers["X-SubscriptionStatus"];
    string deviceConnectionStatus
                = response.Headers["X-DeviceConnectionStatus"];

    PushNotificationSendResult result = CreateSendResult(
        notificationStatus, deviceConnectionStatus, subscriptionStatus);

    return result;
}


The use of the DataContractJsonSerializer is convenient because it allows you to serialize an entire object on the server in a format that is compatible with Silverlight. With little effort the object can be rehydrated on the client. You could, however, use another payload format, such as a string, as shown in the tile and toast notification examples.


Tip

The DataContractJsonSerializer type, as shown in this example, was chosen over the more commonly used DataContractSerializer because JSON serialization is more space efficient than XML, producing a payload of much smaller size. This is important because the maximum body size for a toast, tile, or raw notification is 1KB.


The DataContractJsonSerializer approach relies on the content object’s class being decorated with DataContract and DataMember attributes, as shown in the following example:

[DataContract]
public class StockQuote
{
    [DataMember]
    public string StockSymbol { get; set; }

    [DataMember]
    public double LastPrice { get; set; }

    [DataMember]
    public double ChangeInPrice { get; set; }
}

The DataContract and DataMember attributes provide the DataContractJsonSerializer with the information it needs to serialize the StockQuote object. Neglecting to decorate a property with a DataMember attribute causes that property to be ignored, and its value to be absent when the object is deserialized in the client application.

Receiving a Raw Notification

To receive a raw notification in an app, subscribe to the HttpNotificationChannel.HttpNotificationReceived event, as shown:

channel.HttpNotificationReceived += channel_HttpNotificationReceived;
...
void channel_HttpNotificationReceived(object sender,
                                             HttpNotificationEventArgs e)
{
...
}

When the HttpNotificationReceived event is raised, the handler receives an HttpNotificationEventArgs object that contains a Notification property of type HttpNotification. HttpNotification contains the following properties:

Body—The payload for the notification. This is the main property of interest. It is a Stream containing the raw notification data.

Channel—The channel to which the notification arrived.

Headers—The request headers that were supplied during the creation of the notification.

The following excerpt from the PushNotificationViewModel class in the downloadable sample code demonstrates how raw notification is handled on the phone, and in particular how the content object (in this case, a StockQuote) is rehydrated:

void subscriber_HttpNotificationReceived(
    object sender, HttpNotificationEventArgs e)
{
    Message = "Raw notification received.";
    Stream bodyStream = e.Notification.Body;

    if (bodyStream != null)
    {
        DataContractJsonSerializer serializer
                = new DataContractJsonSerializer(typeof(StockQuote));

        StockQuote = (StockQuote)serializer.ReadObject(bodyStream);
    }
}

A DataContractJsonSerializer reverses the serialization process by transforming the stream data back to a StockQuote object.

Identifying Notifications in an HttpWebResponse

Common to all push notification types is an optional custom header named X-MessageID, which can be added to the HttpWebRequest.Headers collection. Its purpose is to uniquely identify the notification message, and if present, the same value is able to be retrieved in the notification response. It must be a string that contains a universally unique identifier (UUID), as shown in the following excerpt:

request.Headers.Add("X-MessageID", "<UUID>");

At the time of writing, this header does not serve any purpose, because the resulting HttpWebResponse is retrieved using the original HttpWebRequest. It is, therefore, trivial to identify a notification based on an HttpWebResponse. In the future, however, this setting may be used in conjunction with an MPNS callback, to update the cloud service if a notification is impacted due to, for example, session expiry.

Notification Classes

Some kinds of notifications may be more time critical than others. For example, a notification indicating that tomorrow’s weather forecast has changed may be less time critical than a notification about a traffic jam on your planned route home. The MPNS allows notifications to be assigned a batching interval, which works as a guide for the MPNS in determining when to deliver one or more notifications in a batch.

To inform the MPNS of the batching interval of a notification, a header named X-NotificationClass is added to the outgoing request. The batching interval is a string representation of an integer value. The set of valid priority values is determined by the kind of notification, be it a toast, tile, or raw notification.

When notifications are sent to the MPNS, they are placed in queues (see Figure 13.10). Each queue is serviced at different intervals according to its notification class. Accordingly, a queue for a lower notification class may be serviced more frequently than one with a higher notification class. For example, a raw notification with an X-NotificationClass of 3 means that the message is delivered by the MPNS immediately, a value of 13 means that the message is delivered within 450 seconds, and a value of 23 means the message is delivered within 900 seconds.

Image

Figure 13.10. Notifications are queued and the notifications from each queue are periodically sent to the phone.

When sending a push notification, the X-NotificationClass may be omitted, in which case a default regular priority is used. Table 13.1 lists the push notification classes for each notification type.

Table 13.1. Push Notification Classes

Image

In the downloadable sample code, the notification class is represented as an enum called PushNotificationPriority and provides for strongly typed notification class values.

public enum PushNotificationPriority
{
    Regular,
    Priority,
    RealTime
}

Cloud Service Authentication

The Push Notification protocol enables a cloud service to send a notification to a phone application using a RESTful API. The cloud service can choose to operate as authenticated or unauthenticated by registering an X.509 certificate with the MPNS. An unauthenticated cloud service is limited to sending 500 notification requests a day for each subscription. While in most cases this will suffice, having an authenticated cloud service removes this limitation, and an unlimited number of push notifications can be sent for the life of a subscription.

Authenticating a Cloud Service

To authenticate a cloud service, obtain an X.509 certificate issued by a Microsoft trusted root certificate authority. The certificate can then be registered on the Windows Phone Marketplace. The certificate is used to establish a Secure Sockets Layer (SSL) connection between the cloud service and the MPNS.

Creating a Notification Channel for an Authenticated Cloud Service

As mentioned earlier in this chapter, the HttpNotificationChannel has a constructor that accepts the fully qualified domain name (FQDN) of the cloud service certificate. The FQDN must match the registered certificate’s subject name, which is the CN attribute. An example of an FQDN is www.example.com. Creating a channel using an FQDN is demonstrated in the following excerpt:

using (HttpNotificationChannel channel
         = new HttpNotificationChannel(channelName, "www.example.com"))
{
    channel.Open();
    channel.BindToShellEntryPoint();
    channel.BindToShellNotification();
}

Building a Stock Ticker Application

The main sample for this app demonstrates the three kinds of push notifications: tile, toast, and raw notifications. It is a stock ticker application that allows the user to enter a stock symbol into the phone, after which a cloud service periodically notifies the phone of stock price variations.


Note

When debugging using a phone device (not the emulator) a local WCF service is unreachable from the app. To allow a device to communicate with a local WCF service, configure Visual Studio to use IIS and use a machine name or external IP address in your WCF client configuration.


The server side component of the sample application consists of a WCF service that allows the phone app to register itself to receive stock price update notifications. The server side implementation retrieves stock price information periodically from Yahoo! Finance (see Figure 13.11).

Image

Figure 13.11. The sample Stock Quoter application monitors stock prices according to stock symbol.

Input controls for the application include a text box, which allows the user to enter a stock symbol to be monitored, a Register button, and an Unregister button. Clicking on the Register button causes the viewmodel’s RegisterForPushNotification method to be called. This causes a PushNotificationSubscriber to be initialized and its events subscribed to, as shown in the following excerpt.

void Subscribe()
{
    if (subscriber == null)
    {
        InitializeSubscriber();
    }
    subscriber.Subscribe();
}

void InitializeSubscriber()
{
    subscriber = new PushNotificationSubscriber(
                        channelName, null, RegisterWithCloudService);
    subscriber.HttpNotificationReceived
                                     += subscriber_HttpNotificationReceived;
    subscriber.ChannelUriUpdated += subscriber_ChannelUriUpdated;
    subscriber.ErrorOccurred += subscriber_ErrorOccurred;
}

A delegate is passed to the subscriber, which is used to notify the cloud service of the push notification URI once it is received from the MPNS.

As mentioned earlier in the chapter, the PushNotificationSubscriber handles the creation and opening of the HttpNotificationChannel (see Listing 13.4). When the Subscribe method is called, a channel is retrieved if it already exists; otherwise, a new channel is created. Subscription occurs to the channel’s various events and the channel is opened. The channel then nominates itself to receive tile and toast notifications.

Listing 13.4. PushNotificationSubscriber.Subscribe Method


public void Subscribe()
{
    try
    {
        channel = HttpNotificationChannel.Find(channelName);
        if (channel == null)
        {
            channel = string.IsNullOrEmpty(serviceName)
                ? new HttpNotificationChannel(channelName)
                : new HttpNotificationChannel(channelName, serviceName);
        }
        else
        {
            channel.UnbindToShellTile();
            channel.UnbindToShellToast();
            channel.Close();
            Subscribe();
            return;
        }

        UnsubscribeFromChannelEvents();
        SubscribeToChannelEvents();

        try
        {
            channel.Open();
        }
        catch (Exception ex)
        {
            channel.UnbindToShellTile();
            channel.UnbindToShellToast();
            channel.Close();
            WaitAndRetryChannelConnect();
            return;
        }

        /* Toast Notifications. */
        if (!channel.IsShellToastBound)
        {
            channel.BindToShellToast();
        }

        /* Tile Notifications. */
        if (!channel.IsShellTileBound)
        {
            channel.BindToShellTile();
        }

        if (channel.ChannelUri != null)
        {
            RegisterChannel(channel.ChannelUri);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Unable to subscribe. " + ex.ToString());
        WaitAndRetryChannelConnect();
    }
}


If something goes wrong during the subscription phase, the subscriber waits 30 seconds and then tries again, as the following excerpt shows:

void WaitAndRetryChannelConnect()
{
    if (retryTimer == null)
    {
        /* Create a timer and have it fire once. */
        retryTimer = new Timer(o => Subscribe(), null, retryDueTimeMs,
            Timeout.Infinite);
    }
    else
    {
        /* The timer is changed to fire once after the period expires. */
        retryTimer.Change(retryDueTimeMs, Timeout.Infinite);
    }
}

PushNotificationSubscriber contains a field of type Action<string> named registerWithServer, whose task is to send the URI of the MPNS subscription to the StockQuoteService cloud service, along with the stock symbol for the stock that the user wants to monitor. By plugging in this behavior you can test the app without relying on the cloud service being present.


Note

The StockQuoteService implementation is for demonstration purposes only and lacks the capability to support multiple concurrent users. In supporting multiple users, it would be advisable to use a database to record each user’s stock symbols, and to use a worker process to periodically query the database and to send notifications only when a price change occurs.


When the StockQuoteService.RegisterForStockQuoteNotifications method is called on the server, a Timer causes tile, toast, and raw notifications to be periodically sent to the subscriber (Listing 13.5).

The StockQuote result is provided by the StockQuoter class, discussed next.

Listing 13.5. StockQuoteService Class


[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class StockQuoteService : IStockQuoteService
{
    Timer timer;
    bool notify = true;
    int tilePushCount;
    string subscriptionUrl;
    string stockSymbol;
    StockQuote stockQuote;
    bool sendingNotifications;
    const int notificationPeriodMs = 30000;

    public void RegisterForStockQuoteNotifications(
        PushNotificationSubscriberId pushNotificationSubscriberId,
        string stockSymbol)
    {
        subscriptionUrl = pushNotificationSubscriberId.PushNotificationUrl;
        this.stockSymbol = stockSymbol;
        notify = true;

        if (timer == null)
        {
            timer = new Timer(delegate
                                {
                                    /* An exception must not be raised. */
                                      try
                                      {
                                        NotifyWithStockQuote();
                                      }
                                      catch (Exception ex)
                                      {
                                         Debug.WriteLine("Unable to notify.", ex);
                                      }
                                }, null,
                2000 /* 2000 MS = 5 seconds. */, notificationPeriodMs);
        }
    }

    void NotifyWithStockQuote()
    {
        if (!notify || sendingNotifications)
        {
            return;
        }

        if (tilePushCount > 98)
        {
            tilePushCount = 0;
        }

        try
        {
            sendingNotifications = true;
            try
            {
                StockQuoter stockQuoter = new StockQuoter();
                stockQuote = stockQuoter.GetStockQuote(this.stockSymbol);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to retrieve stock quote." + ex);
                return;
            }

            DateTime now = DateTime.Now;
            Debug.WriteLine(
                "Sending notifications at " + now.ToLongTimeString());
            PushNotifier notifier = new PushNotifier();

            string background;
            if (stockQuote.ChangeInPrice > 0)
            {
                background = "PushNotification/Images/StockUp.png";
            }
            else if (stockQuote.ChangeInPrice < 0)
            {
                background = "PushNotification/Images/StockDown.png";
            }
            else
            {
                background = "PushNotification/Images/StockNone.png";
            }

            try
            {
                notifier.SendTileNotification(
                    stockQuote.ToString(),
                    ++tilePushCount,
                    background,
                    subscriptionUrl,
                    PushNotificationPriority.RealTime);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to send tile notifications:" + ex);
            }
            try
            {
                notifier.SendToastNotification("Stock Quote",
                                       stockQuote.ToString(),
                                             subscriptionUrl,
                            PushNotificationPriority.RealTime);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to send toast notification:" + ex);
            }

            try
            {
                notifier.SendRawNotification(stockQuote,
                    subscriptionUrl, PushNotificationPriority.RealTime);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to send raw notification: " + ex);
            }
        }
        finally
        {
            sendingNotifications = false;
        }
    }

    public void UnregisterForStockQuoteNotifications(
        PushNotificationSubscriberId pushNotificationSubscriberId)
    {
        notify = false;
        tilePushCount = 0;
    }
}


The GetStockQuote method creates an HttpWebRequest that is sent to the Yahoo! Finance stock quote URL. This request produces a CSV (comma-separated values) file containing the stock information of interest, including the price of the stock and the change in price for the day. The following excerpt shows the GetStockQuote method:

public StockQuote GetStockQuote(string stockSymbol)
{
string queryString = string.Format(    "s={0}&f=sl1p2d1t1c1hgvba", stockSymbol);
    string url = "http://download.finance.yahoo.com/d/quotes.csv?"                     + queryString;
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

    string responseString;
    using (HttpWebResponse response
                            = (HttpWebResponse)request.GetResponse())
    {
        using (var reader = new StreamReader(
            response.GetResponseStream(), Encoding.ASCII))
        {
            responseString = reader.ReadLine();
        }
    }

    Debug.WriteLine("Stock Quote: " + responseString);

    var cells = responseString.Split(',');
    if (cells.Length < 3)
    {
        throw new Exception("Invalid response.");
    }
    if (cells[1].ToLower() == @" a")
    {
        throw new UnknownStockSymbolException(stockSymbol);
    }

    StockQuote stockQuote = new StockQuote();
    string changeInPrice = cells[2].Replace(""", "").Replace("%", "");
    stockQuote.ChangeInPrice = double.Parse(changeInPrice,
        NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint);
    stockQuote.LastPrice = double.Parse(cells[1].Replace(""", ""));
    stockQuote.StockSymbol = cells[0].Replace(""", "");
    return stockQuote;
}

The information that is sent back from Yahoo! Finance depends on the contents of the query string in the request URL. The format is like so:

http://finance.yahoo.com/d/quotes.csv?s=
                    <stock symbols separated by +>&f=<field identifiers>

For more information regarding the Yahoo! stock data format, see http://bit.ly/C5MPu.

The StockQuote class is a container for the information retrieved from Yahoo! Finance. It is decorated with DataContract and DataMember attributes so that it can be serialized for raw notifications, as shown in the following excerpt:

[Serializable
[DataContract]
public class StockQuote
{
    [DataMember]
    public string StockSymbol { get; set; }

    [DataMember]
    public double LastPrice { get; set; }

    [DataMember]
    public double ChangeInPrice { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1} {2}",
                         StockSymbol, LastPrice, ChangeInPrice);
    }
}

Sample App Notifications

The Stock Quoter app uses all three types of push notifications, which are described in the following sections.

Tile Notifications

Tile notifications are sent periodically from the StockQuoteService and are displayed as shown in Figure 13.8. The tile notification contains a Text1 data property equal to Stock Quote and a Text2 data property that is the result of calling the ToString method of the StockQuote object, which produces a combination of the stock symbol, the stock’s last price, and its change in price.

Toast Notifications

Toast notifications are sent periodically from the StockQuoteService and are displayed as shown in Figure 13.5. The sample uses the title property of the tile notification to display the stock symbol, its price, and its change in price for the day. The count property is populated with an incrementing value, which is just for demonstration purposes.

Sample Background Images

For tile and raw notifications, the sample makes use of three images: StockUp.png, StockNone.png, and StockDown.png, located in the PushNotification/Images directory of the WindowsPhone7Unleashed.Examples project.

The logic for determining the background image for a tile notification is located server-side, as this excerpt from the StockQuoteService shows:

string background;
if (stockQuote.ChangeInPrice > 0)
{
    background = "PushNotification/Images/StockUp.png";
}
else if (stockQuote.ChangeInPrice < 0)
{
    background = "PushNotification/Images/StockDown.png";
}
else
{
    background = "PushNotification/Images/StockNone.png";
}

Image paths are relative to the application project.

Raw Notifications

Raw notifications make use of the serialized StockQuote instance as the following excerpt from the StockQuoteService class shows:

notifier.SendRawNotification(
    stockQuote, subscriptionUrl, PushNotificationPriority.RealTime);

The StockQuote instance is serialized to a byte array, which is then used as the content for the raw notification. For more information on the serialization process see the previous section “Receiving a Raw Notification.”

When a raw notification is received by the phone app and the StockQuote object is rehydrated in the PushNotificationViewModel class, the object is assigned to the viewmodel’s StockQuote property.

The view displays the stock information via the viewmodel’s StockQuote property, as shown in the following excerpt from the PushNotificationView.xaml file (style attributes have been removed for clarity):

<TextBlock Text="Price:" />
<TextBlock Text="{Binding StockQuote.LastPrice}" />
<TextBlock Text="Change:" />
<TextBlock Text="{Binding StockQuote.ChangeInPrice}" />

The stock direction image is presented using the viewmodel’s StockPriceDirection property. When the StockQuote changes in the viewmodel, the StockPriceDirection property is reevaluated and a different image displayed in the view. The following excerpt shows the StockQuote property in the viewmodel:

StockQuote stockQuote;

public StockQuote StockQuote
{
    get
    {
        return stockQuote;
    }
    set
    {
        Assign("StockQuote", ref stockQuote, value);
        /* Set the direction so that the view
         * doesn't need to do any evaluation. */
        if (stockQuote == null || stockQuote.ChangeInPrice == 0)
        {
            StockPriceDirection = StockPriceDirection.None;
        }
        else if (stockQuote.ChangeInPrice > 0)
        {
            StockPriceDirection = StockPriceDirection.Up;
        }
        else
        {
            StockPriceDirection = StockPriceDirection.Down;
        }
    }
}

The view itself subscribes to the viewmodel’s PropertyChanged event. When the StockPriceDirection property changes, it determines what image should be displayed depending on its value.

void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "StockPriceDirection")
    {
        string url;
        switch (ViewModel.StockPriceDirection)
        {
            case StockPriceDirection.Down:
                url = "Images/StockDown.png";
                break;
            case StockPriceDirection.Up:
                url = "Images/StockUp.png";
                break;
            default:
                url = "Images/StockNone.png";
                break;
        }
        Image_StockQuote.Source = new BitmapImage(
            new Uri(url, UriKind.Relative));
    }
}


Note

Updating the UI based on a viewmodel property could be better orchestrated using the page’s VisualStateManager. In Chapter 16, “Bing Maps,” we discuss a custom VisualStateUtility class that allows you to bind the visual state of a UI element to a viewmodel property.


Opting Out of Stock Price Updates

The Stock Quoter application allows a user to unregister from receiving stock updates. By clicking the Unregister button, the viewmodel’s UnregisterPushNotification method is called, which calls the PushNotificationSubscriber object’s Unsubscribe method, as shown:

public void UnregisterForPushNotification()
{
    try
    {
        Message = "Unregistering for notifications.";
        if (subscriber != null)
        {
            subscriber.Unsubscribe();
            subscriber.Dispose();
            subscriber = null;
        }
        Message = "Unregistered for notifications.";
    }
    catch (Exception ex)
    {
        Message = "Problem unregistering: " + ex.ToString();
    }

    if (channelUri != null)
    {
        try
        {
            UnregisterWithServer(channelUri.ToString());
        }
        catch (Exception ex)
        {
            Message = "Problem unregistering with server: " + ex.ToString();
        }
    }
}

When the PushNotificationSubscriber is disposed, its Timer and HttpNotificationChannel are also disposed. The cloud service is then notified that push notifications should no longer be sent.

Summary

This chapter looked at the advantages of push notification, and how it can be used to extend the battery life of the device.

The chapter began with an overview of push notification, starting with the three different types of push notifications: toast, tile, and raw notifications. The various types of channel errors that can occur during push notification were examined, and you saw how the phone’s battery level affects delivery of push notifications. The chapter also looked at cloud service authentication using X.509 certificates, and at how to update an application tile without tile notifications using shell tile schedules.

Finally, the chapter explored a stock ticker sample app that allows the user to enter a stock symbol into the phone, after which a cloud service periodically notifies the phone of stock price variations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.238.226