Windows Phone 7 approche doucement mais surement et ces derniers jours nous avons eu le droit de toucher à la version beta du SDK.
Depuis l’annonce, je suis de plus ou moins près ce nouveau OS mobile et j’en suis plutôt content dans l’ensemble, certaines limitations techniques me laisse un peu perplexe en ma qualité de dev malheureusement…
Les vidéos des devices sont sympas et…la réalité l’est tout autant
donc je voulais m’y intéresser d’un peu plus près après les classiques petites applications de rss reader et autre meteo reader, j’ai voulu regarder le système de notifications.
Ces notifications permettent de s’abonner à un channel de diffusion et ensuite d’être informé par le biais d’event un peu près n’importe quand (et donc même quand l’application “dort” en background).
Bon le concept…rien de bien nouveau et de magique mais comme d’habitude, le SDK est vraiment bien fait et assez souple pour permettre une intégration rapide et surtout facile au sein de nos applications mais egalement de n’importe quelles applications client/desktop/serveur.
Je ne vais pas m’étendre sur la création d’appli simple sur WP7, il commence a y en avoir un peu partout sur le net mais plus vous montrer comment en quelques lignes, on peut recevoir des notifications sur son application.
Nous allons découper cela en deux étapes, la première sera bien entendu de préparer son application à recevoir des notifications et réagir en conséquence.
La seconde sera la mise en place d’une plateforme d’envois, cela peut-être un peu près n’importe quoi…ah non enfaite n’importe quoi capable d’envoyer une requête HTTP (oui autant faire simple).
Comme évoqué, nous devons passer par un channel, il suffit d’imaginer cela comme un tuyau ou des messages peuvent se mettre en attente d’être consommé par l’application.
public HttpNotificationChannel myChannel;
A savoir que le channel pour une application peut changer entre chaque lancement mais pas obligatoirement donc il faut prendre en compte ce cas:
myChannel = HttpNotificationChannel.Find("MyChan"); //ou MyChan est un nom permettant de le retrouver par la suite a coup de ce .Find
Les deux cas:
1. Le channel existe déjà, il nous suffit de nous y abonner (if myChannel != null)
2. Le channel n’existe pas et/ou plus, on va donc l’ouvrir puis s’y abonner
Pour ouvrir notre channel, deux instructions:
myChannel = new HttpNotificationChannel("MyChan");
myChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(myChannel_ChannelUriUpdated);
myChannel.Open();
Bon d’accord…trois instructions car l’event ChannelUriUpdated est là pour nous informer que l’adresse de notre channel, assez pratique pour la communiquer aux autres (les “senders”).
En version simple, voici la méthode en question:
void myChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
Debug.WriteLine("Notification channel URI:" + e.ChannelUri.ToString());
}
La, bien sûr il faut imaginer implémenter l’envoi de cette uri vers un server ou un autre client pour qu’ils sachent par ou envoyer les notifications.
Je vous laisse mettre en place cette partie, en fonction de vos besoins.
Il existe trois types de notifications possibles, chacune ayant un rôle particulier et un “champs d’action”
1. Les Raw notifications, système le plus “ouvert” car permettant d’envoyer un peu près ce que l’on veut, c’est donc la façon pour communiquer avec son application tout ce qui concerne la logique de l’application.
2. Les Tile notifications, sont celle pour mettre à jour la tile de l’application si celle-ci est “pin to start” (comprendre par la, mise en avant sur la page d’accueil du téléphone), on peut faire varier l’image mais également un compteur (prenons exemple du nombre de mail non lu) et le titre de la tile
3. Les Toast notifications, elles permettent d’afficher un petit bandeau en haut de l’écran qui contient l’icône de l’application, un titre et un sous-titre. Utile pour par exemple informer qu’une chose est arrivée dans l’application et si on clique dessus, l’application s’ouvre.
Voici une illustrations pour les Tile et une pour les Toasts, les autres n’ayant pas de visuel ( sauf si choix du développeur)
Un exemple de tile:

Un exemple de Toast:

Ensuite il suffit de brancher notre channel au shell du telephone, pour cela:
myChannel.BindToShellTile(); //pour s’abonner aux Tiles
Suffit pour cela.
Nous allons pour le reste ajouter le bind des toasts:
myChannel.BindToShellToast(); //pour s’abonner aux Toasts
/* pour recevoir les Raw */
myChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(myChannel_HttpNotificationReceived);
/* gestion des erreurs */
myChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(myChannel_ErrorOccurred);
Et un event nous permettant de gérer les erreurs.
Celui du milieu étant pour gérer les Raw
.
Je ne vais pas m’étendre sur les raw, il faut savoir que l’on envoie un buffer dans ce type de notification donc ensuite le protocole de communication dépend entièrement de l’application cible, il faut mettre en place une API que l’envoyeur comme le destinataire connait.
Une fois l’event levé, il vous suffit de lire le contenu de e.Notification.Body pour y entrainer vos informations.
Rien de plus n’est nécessaire, nous somme déjà en mesure de recevoir tout ce qui nous intéresse!
Passons donc à l’envois de notification, on part du principe que vous avez communiqué l’adresse du channel a un serveur qui par exemple va de temps en temps envoyer une notification vers les clients abonnés.
Dans notre exemple nous simulerons cela par une adresse superbement gérer au sein d’un copier/coller car c’est bien plus classe au sein d’un tuto
.
Etudions déjà la structure des messages à envoyer dans le cas d’un Toast ou d’un Tile, à savoir que c’est encore sujet a modification donc dans le cas d’une mise à jour, si cela ne marche plus, pas de panique, il suffit d’aller voir sur la page MSDN la nouvelle version: http://msdn.microsoft.com/en-us/library/ff402545%28v=VS.92%29.aspx
Dans le cas d’un Tile, on va donc envoyer:
string tileMessage = String.Format("Content-Type: text/xml\r\nX-WindowsPhone-Target: token\r\n\r\n" +
"<?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>", this.Image.Text, this.Count.Text, this.Message.Text);
On voit donc trois paramètres servant à:
1. Indiquez l’image à mettre en background de Tile, doit être contenu dans l’application ou à télécharger sur un serveur “autorisé” (lors du BindToShellTile, une surcharge permet de lister les serveurs “autorisés”, image qui doit prendre moins d’une minute à se télécharger!)
2. Un compteur, par exemple le nombre de mail non lu
3. Un titre, je vous laisse deviner
Et dans le cas d’un Toast:
string toastMessage = String.Format("Content-Type: text/xml\r\nX-WindowsPhone-Target: toast\r\n\r\n" +
"<?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>", this.Message.Text, this.SubMessage.Text);
Cette fois ci, deux paramètres:
1. Un titre
2. Un sous-titre
Et là on se demande ou l’envoyer? Et bien tout simplement vers notre adresse de channel donne par l’application et que l’on va donc récupérer sur notre serveur ou…dans le buffer magique du copier/coller
.
Pour cela, je ne vais pas m’étendre sur l’explication de ce code, rien de bien mystérieux:
HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(subscriptionUri);
sendNotificationRequest.Method = "POST";
sendNotificationRequest.Headers = new WebHeaderCollection();
sendNotificationRequest.Headers.Add("X-NotificationClass", codeHeader);
byte[] notificationMessage = Encoding.Default.GetBytes(msg);
sendNotificationRequest.ContentLength = notificationMessage.Length;
using (Stream requestStream = sendNotificationRequest.GetRequestStream())
{
requestStream.Write(notificationMessage, 0, notificationMessage.Length);
}
response = (HttpWebResponse)sendNotificationRequest.GetResponse();
string notificationStatus = response.Headers["X-NotificationStatus"];
string notificationChannelStatus = response.Headers["X-SubscriptionStatus"];
string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];
Il nous suffit donc d’appeler ce code avec subscriptionUri correspondant a notre adresse de channel et petite astuce, codeHeader doit prendre la valeur de “1” dans le cas d’un Tile et “2” pour un Toast.
msg etant bien sur le flux xml de la notification.
Je dois admettre que cette petite différence de code header est casse pied mais la doc est très clair la dessus
.
Dans le cas d’un envoi d’information en Raw, une fois votre protocole défini, il vous suffit d’envoyer l’information sans mettre de code header particulier.
Si on prend l’exemple MSDN :
new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
Rien de nous empêche bien sûr d’envoyer des textes en clair ou autre information, quand que les deux parties savent le lire et l’écrire correctement
.
L’idée est de permettre un transfert simple d’information même quand l’application n’est pas active (car si active, un simple webservice suffit amplement).
Pour les toast, je vous laisse vous reporter a l’image plus haut pour voir a quoi cela ressemble, concernant le changement de tile et l’ajout d’information, voici une illustration:

Je n’ai mis que de petit bout de code pour me concentrer sur l’essentiel mais pour ceux n’ayant pas réussi à remettre cela en ordre, voici une classe complète permettant de gérer les abonnements.
public class Notification
{
public HttpNotificationChannel myChannel;
public void CreatingANotificationChannel()
{
myChannel = HttpNotificationChannel.Find("MyChan");
if (myChannel == null)
{
myChannel = new HttpNotificationChannel("MyChan");
myChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(myChannel_ChannelUriUpdated);
myChannel.Open();
}
else
{
Debug.WriteLine("Notification channel URI:" + myChannel.ChannelUri.ToString());
}
myChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(myChannel_HttpNotificationReceived);
myChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(myChannel_ErrorOccurred);
myChannel.BindToShellTile();
myChannel.BindToShellToast();
}
void myChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
Debug.WriteLine("Notification channel URI:" + e.ChannelUri.ToString());
}
void myChannel_ErrorOccurred(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;
}
}
void myChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
{
if (e.Notification.Body != null && e.Notification.Headers != null)
{
System.IO.StreamReader reader = new System.IO.StreamReader(e.Notification.Body);
}
}
}
J’ai également mis en place une page web vous permettant d’envoyer des notifications sur un channel que vous lui donnez en paramètre
.
http://wp7.fr/push/