Push Notifications в Windows Phone 7 (raw)

Я уже рассказывал о том, что представляют собой уведомления в Windows Phone 7. Используя этот механизм можно доставлять уведомление из сервиса в облаке непосредственно на мобильное устройство.

Я не буду рассматривать устройство механизма уведомлений в целом (ознакомиться с ним можно здесь), повторю только отдельные типы уведомлений:

  • Tile Notifications — при отправке уведомления указывается URL некоторого изображения, расположенного в сети. Когда устройство получает уведомление оно самостоятельно загружает изображение из сети и обновляет его на рабочем столе устройства. Поскольку в Windows Phone OS CTP, доступной на данный момент размещение своих элементов на рабочем столе недоступно, то мы пока не будем рассматривать этот тип уведомлений и вернемся к нему позже.
  • Toast Notifications — при получении этого типа уведомления на экране устройства всплывает небольшое сообщение, содержащее текст уведомления. При этом приложение в данный момент может быть неактивным. При нажатии на это сообщение пользователь может быстро переключиться к соответствующему уведомлению.
  • Raw Notifications — при получении этого типа уведомления его содержимое передается непосредственно приложению. Приложение в праве обработать содержимое уведомление так, как считает нужным по своему собственному алгоритму. Это наиболее гибкий способ получения уведомлений от внешних сервисов.

На этот раз давайте поговорим о Raw Notifications.

Raw Notifications

Raw Notifications – это наиболее гибкий тип уведомлений. Обрабатывать данный тип уведомлений можно по своему усмотрению, используя нужный для приложения алгоритм. Если в случае с Toast Notifications и Tile Notifications мы должны соблюдать определенный формат и тип передаваемых данных (для Toast – строка, для Tile – адрес до изображения). то в случае с Raw Notifications мы можем передавать абсолютно любые данные, которые нам необходимы.

Основная идея отправки уведомления остается той же самой – мобильное приложение подключается к сервису в облаке, получает уникальный URI и каким-либо образом передает его приложению, которое должно уведомлять наш сервис.

private HttpNotificationChannel notifications;
private const string notificationServiceName = "Some test";

private void InitializePushChannel()
{
    if (notifications != null)
    {
        ShowUri(notifications.ChannelUri);
    }
    else
    {
        try
        {
            notifications = new HttpNotificationChannel(Application.Current.ToString(), notificationServiceName);
            notifications.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);
            notifications.Open();
            ShowUri(notifications.ChannelUri);

            notifications.BindToShellNotification();
        }
        catch (NotificationChannelExistsException)
        {
            notifications = HttpNotificationChannel.Find(Application.Current.ToString());
            notifications.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);
            ShowUri(notifications.ChannelUri);
        }
    }
}

Как видно, для работы с механизмом уведомлений используется все тот же объект HttpNotificationChannel. Аналогичным образом следует обрабатывать ситуацию, когда сервис уже был зарегистрирован (в этом случае следует использовать метод Find). Также следует обрабатывать событие ChannelUriUpdated на случай, если сервис в облаке решит изменить адрес для отправки уведомлений. Для этого мы создадим специальный обработчик, который в случае изменения адреса будет отображать его пользователю.

private void ShowUri(Uri uri)
{
    if (uri != null)
    {
        Dispatcher.BeginInvoke(() => ChannelUri.Text = uri.ToString());
        Debug.WriteLine(uri.ToString());
    }
}

Для того, чтобы обработать данные, которые были получены с помощью Raw-уведомлений следует подписаться на событие HttpNotificationReceived. Это событие будет срабатывать всякий раз, когда поступает новое Raw-уведомление.

private HttpNotificationChannel notifications;
private const string notificationServiceName = "Some test";

private void InitializePushChannel()
{
    if (notifications != null)
    {
        ShowUri(notifications.ChannelUri);
    }
    else
    {
        try
        {
            notifications = new HttpNotificationChannel(Application.Current.ToString(), notificationServiceName);
            notifications.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);
            notifications.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(notifications_HttpNotificationReceived);
            notifications.Open();
            ShowUri(notifications.ChannelUri);

            notifications.BindToShellNotification();
        }
        catch (NotificationChannelExistsException)
        {
            notifications = HttpNotificationChannel.Find(Application.Current.ToString());
            notifications.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);
            notifications.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(notifications_HttpNotificationReceived);
            ShowUri(notifications.ChannelUri);
        }
    }
}

Теперь создадим обработчик, который будет срабатывать в момент получения уведомлений. Для этого мы уже подписались на событие HttpNotificationReceived. В параметрах обработчика события передается объект HttpNotificationEventArgs, позволяющий получить доступ к телу HTTP-запроса, который был получен от приложения, создавшего уведомление. В простейшем случае давайте отобразим это сообщение пользователю. Тело сообщения доступно как поток (Stream). Для его чтения мы будем использовать объект StreamReader.

void notifications_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
{
    using (var r = new StreamReader(e.Notification.Body))
    {
        var data = r.ReadToEnd();
        Debug.WriteLine(data);
        Dispatcher.BeginInvoke(() => ReceivedData.Text = data);
    }
}

В данном обработчике мы используем элемент управления ReceivedData. Для того, чтобы это стало возможным его необходимо добавить на форму. Сделаем это легким движением руки.

<phoneNavigation:PhoneApplicationPage 
    x:Class="ToastNotification.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}">

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.Background>
            <ImageBrush ImageSource="FormBackground.jpg" Stretch="UniformToFill"/>
        </Grid.Background>

        <!--TitleGrid is the name of the application and page title-->
        <Grid x:Name="TitleGrid" Grid.Row="0">
            <TextBlock Text="MY APPLICATION" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
            <TextBlock Text="Notifications" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
        </Grid>

        <!--ContentGrid is empty. Place new content here-->
        <Grid x:Name="ContentGrid" Grid.Row="1">
            <Border VerticalAlignment="Top" HorizontalAlignment="Center" Margin="20" Padding="20" CornerRadius="5">
                <Border.Background>
                    <SolidColorBrush Color="Black" Opacity="0.2"/>
                </Border.Background>
                <TextBlock Name="ReceivedData"
                           TextWrapping="Wrap"
                           FontSize="30"/>
            </Border>

            <Border VerticalAlignment="Center" HorizontalAlignment="Center" Margin="20" Padding="20" CornerRadius="5">
                <Border.Background>
                    <SolidColorBrush Color="Black" Opacity="0.2"/>
                </Border.Background>
                <TextBlock Name="ChannelUri"
                           TextWrapping="Wrap"
                           FontSize="30"/>
            </Border>
            
        </Grid>
    </Grid>
    
</phoneNavigation:PhoneApplicationPage>

Мобильное приложение готово, теперь, используя полученный URI для отправки уведомлений отправить HTTP-запрос по этому адресу. Для этого как раньше создадим небольшое консольное приложение, способное выполнить эти несложные действия. Для этих целей мы все так же будем использовать объект WebClient. Однако, в отличие от Toast Notifications, при отправке Raw Notifications не нужно соблюдать какой-либо формат сообщения – можно просто записать в тело сообщения те данные, которые необходимо отправить мобильному устройству. Также стоит изменить заголовок “X-NotificationClass” на значение “3”, что будет означать наивысший приоритет отправки для Raw-уведомлений.

При отправке уведомления, в ответ мы также получим два заголовка “X-DeviceConnectionStatus” и “X-NotificationStatus”, которые позволяют определить состояние подключения устройства к сети и статус доставки этого сообщения. Таким образом, код нашего простого приложения будет выглядеть следующим образом.

class Program
{
    private const string Url = @"http://sn1.notify.live.net/throttledthirdparty/01.00/AAFTkavjFpAuT4i1fIKvueDiAgoOs1ADAgAAAAQOMDAwAAAAAAAAAAAAAAA";

    static void Main(string[] args)
    {
        Console.Write("Text: ");
        string text = Console.ReadLine();
        SendToast(Url, text);
        Console.ReadLine();
    }

    static void SendToast(string uri, string text)
    {
        var client = new WebClient();

        client.Headers.Add("Content-Type", "text/html");
        client.Headers.Add("X-NotificationClass", "3");

        var result = client.UploadString(uri, "POST", text);
        Console.WriteLine(result);
        Console.WriteLine("Device status is {0}", client.ResponseHeaders["X-DeviceConnectionStatus"]);
        Console.WriteLine("Notification status is {0}", client.ResponseHeaders["X-NotificationStatus"]);
    }
}

Теперь при запуске приложения и отправке текста, этот текст будет отображен на экране мобильного устройства.

Мы создали с вами тривиальное приложение, которое получая строку просто отображает её на экране. Однако, вся мощь Raw Notifications состоит в том, что мы можем определить совершенно любое поведение для обработки уведомлений. Например, давайте изменим приложение так, чтобы при получении названий некоторых цветов оно изменяло цвет букв, отображающих сообщение уведомления.

void notifications_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
{
    using (var r = new StreamReader(e.Notification.Body))
    {
        var data = r.ReadToEnd().Trim('\0');
        Debug.WriteLine(data);

        switch (data)
        {
            case "red":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Red));
                break;
            case "green":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Green));
                break;
            case "blue":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Blue));
                break;
            case "white":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.White));
                break;
            case "black":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Black));
                break;
            case "yellow":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Yellow));
                break;
            case "orange":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Orange));
                break;
            case "gray":
                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Gray));
                break;
            default:
                Dispatcher.BeginInvoke(() => ReceivedData.Text = data);
                break;
        }
    }
}

Теперь, при отправке уведомлений, содержащих имена цветов, цвет текста в приложении будет изменяться.

Понятно, что это тоже очень простой алгоритм обработки. Однако, он хорошо демонстрирует, что здесь никто не ограничивает нас тем, каким образом построить обработку уведомлений. В общем случае мы можем создавать совершенно любые типы манипуляций и изменять поведение приложения.