Il Windows Push Notification Service (WNS) è un'infrastruttura cloud di Microsoft che permette di inviare toast e aggiornare tile e badge dal proprio servizio cloud verso un'applicazione Windows Store, gestendo i meccanismi della comunicazione con il dispositivo device in modo trasparente ed efficiente.
Richiedere e creare un notification channel
Il primo passo necessario per poter sfruttare il WNS per inviare notifiche alla nostra applicazione Windows Store consiste nel richiedere, tramite le API di WinRT, la creazione di un notification channel alla Notification Client Platform di Windows 8, la quale provvede a girare la richiesta al WNS.
Il WNS restituisce all'applicazione, sempre per il tramite la Notification Client Platform, una URI che rappresenta in modo univoco l'applicazione stessa (sul punto vedi anche più avanti), ed è a quest'ultima che spetta il compito di inviare l'URI così ricevuta al proprio servizio di back end, in modo che venga salvata in uno storage persistente (indipendente dal WNS).
A questo punto, ogni volta che il servizio di back end dovrà notificare qualcosa all'utente, dovrà semplicemente effettuare una richiesta HTTP POST
su SSL (Secure Sockets Layer) al WNS usando l'URI che identifica quella particolare applicazione client.
Il WNS provvederà quindi a girare la notifica al device e, tramite sempre le librerie della Notification Client Platform di Windows 8, potrà essere utilizzata dall'applicazione per notificare un toast, per aggiornare il tile e/o il badge applicativo o inviare una raw notification (ossia un breve messaggio il cui payload è definito a livello applicativo).
Ad esempio, un'applicazione meteo potrebbe richiedere un notification channel al WNS e inviare al servizio in back end la URI ricevuta, assieme ad alcune informazioni aggiuntive come ad esempio l'elenco delle località preferite dell'utente. Questi dati possono essere quindi processati dalla logica di back end, ad esempio per avvertire l'utente tramite toast che sta per piovere o che la temperatura è scesa sotto una certa soglia, oppure semplicemente per aggiornare il tile applicativo con la temperatura attuale.
La prossima immagine sintetizza il flusso complessivo.
(fonte MSDN)
È importante sottolineare che un notification channel rappresenta un singolo utente su un singolo device per una specifica applicazione. Questo significa che due applicazioni sullo stesso device non riceveranno mai la stessa URI, così come la stessa applicazione su due device diversi riceverà due differenti canali. Il servizio di back end deve saper gestire queste eventualità (ad esempio serializzando più URI per lo stesso utente che ha installato la stessa applicazione su più device).
Per poter lavorare con le API di push notification, inserite nel namespace Windows.Networking.PushNotification
, non è necessario aggiungere alcuna reference al progetto Windows Store.
La prima cosa da fare, come si è visto, è richiedere al WNS la creazione di un notification channel. Per far questo, è sufficiente utilizzare la funzione asincrona CreatePushNotificationChannelForApplicationAsync
esposto dall'oggetto di tipo PushNotificationChannelManager
. Questo metodo provvede a creare un notification channel e a restituire un oggetto di tipo PushNotificationChannel
, la cui proprietà Uri
espone l'identificativo univoco del canale. Il prossimo snippet ne mostra un esempio.
Occorre sempre ricordare di gestire l'eventuale fallimento dell'operazione, perché se la connessione a Internet non è disponibile al momento della chiamata della funzione CreatePushNotificationChannelForApplicationAsync
, il sistema solleverà un'eccezione:
app.onloaded = function () {
btnCreateChannel.addEventListener("click", createChannel_click);
}
function createChannel_click(args) {
var pushNotifications = Windows.Networking.PushNotifications;
var channelOperation = pushNotifications.PushNotificationChannelManager
.createPushNotificationChannelForApplicationAsync()
.then(function (reqChannel) {
message.innerText = "Notification channel: " + reqChannel.uri;
},
function (error) {
// Impossibile recuperare il notification channel
});
}
Per testare questo semplicissimo codice d'esempio, puoi usare la seguente definizione HTML come riferimento per la pagina di default della tua app.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Demo.Html.it.WNS.JS</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
<!-- Demo.Html.it.WNS.JS references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<button id="btnCreateChannel" class="button">Crea un canale</button>
<div id="message"></div>
</body>
</html>
La prossima immagine mostra l'URI ricevuta dal WNS:
A questo punto l'applicazione ha bisogno di inviare l'URI al back end applicativo in modo sicuro. Ad esempio, se l'URI deve essere comunicata a un servizio web, sarebbe meglio usare un algoritmo di cifratura del messaggio e usare HTTPS come protocollo di trasporto.
È importante sottolineare che l'applicazione dovrebbe richiedere un canale al WNS a ogni nuovo lancio. Non c'è infatti alcuna garanzia che l'URI rimanga la stessa ad ogni successiva richiesta (secondo la documentazione ufficiale MSDN, attualmente un notification channel rimane attivo per 30 giorni). Quanto al servizio di back end, la soluzione migliore sarebbe quella di controllare lato client se l'URI è cambiata e, in caso affermativo, aggiornare il servizio di back end con la nuova URI. Ma per poter effettuare questo controllo, occorre salvare l'URI corrente nello storage locale, in modo da poterla confrontare con la nuova URI ricevuta al successivo lancio dall'applicazione. In particolare, a ogni lancio l'applicazione deve:
- Richiedere un canale al WNS tramite il metodo
CreatePushNotificationChannelForApplicationAsync
. - Controllare nello storage locale se c'è già un'URI salvata in precedenza.
- In caso positivo, confrontare la vecchia URI con la nuova e, se diverse, inviare la nuova URI al servizio di back-end e contemporaneamente persisterla nello storage locale.
Vale la pena precisare che un'app può avere più canali validi allo stesso tempo senza nessun problema. Un canale rimane attivo sino a quando non arriva a scadenza: fino a quel momento, l'applicazione potrà continuare a usare il canale.
Qualora l'app non abbia più necessità di usare il canale, è possibile invocare il metodo Close
dell'oggetto PushNotification
per chiudere esplicitamente il canale. Qualunque notifica inviata successivamente su questo canale non verrà "recapitata". Se il canale è usato per aggiornare il tile applicativo, è buona norma, dopo aver chiuso il canale, ripristinare il tile allo stato iniziale tramite il metodo Clear
della classe TileUpdater
.
Inviare una notifica al client dal servizio in back end
Ricevuto l'URI che identifica il notification channel, il back end ha il compito di salvare questa informazione, per poi usarla per inviare notifiche all'utente. Ad esempio, il servizio potrebbe salvare l'URI del canale (magari assieme alle preferenze dell'utente) in un database SQL Azure, o in una tabella del Windows Azure Storage Account. La logica di business del back end potrebbe poi utilizzare le preferenze dell'utente per decidere se e quando avvisare l'utente, inviando le notifiche al WNS tramite l'URI salvata in precedenza.
In particolare, il back end può inviare all'utente aggiornamenti da visualizzare tramite tile, badge o toast, semplicemente utilizzando la sintassi XML appropriata. Questa sintassi è identica a quella usata dall'applicazione client per compiere le medesime operazioni. Ad esempio, per inviare un toast testuale il payload XML sarà simile al seguente (lo stesso vale per tile e badge):
<toast launch="">
<visual lang="it-IT">
<binding template="ToastText01">
<text id="1">Ricordati di prendere l'ombrello, sta per piovere!</text>
</binding>
</visual>
</toast>
Il frammento XML deve essere inviato tramite HTTPS al WNS assieme al token di autenticazione (authentication token) fornito dal servizio Windows Live. Per ottenere il token, è possibile utilizzare la classe HttpClient
per effettuare un roundtrip verso il servizio di autenticazione.
Il prossimo snippet mostra un esempio di richiesta (nell'esempio proposto, il back end è in C#, ma ovvimanente il servizio può essere sviluppato in qualunque linguaggio e su qualunque piattaforma server-side):
private OAuthToken GetOAuthToken()
{
String secret = "la_secret_key_ottenuta_dal_Windows_Store";
String sid = "sid_ottenuto_dal_Windows_Store";
var encSecret = HttpUtility.UrlEncode(secret);
var encSid = HttpUtility.UrlEncode(sid);
var body = String.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=notify.windows.com", encSid, encSecret);
String response;
using (var client = new WebClient())
{
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
response = client.UploadString("https://login.live.com/accesstoken.srf", body);
}
}
Questo snippet mostra come costruire la richiesta da inviare al servizio Windows Live: in primo luogo, l'URL del servizio di autenticazione (https://login.live.com/accesstoken.srf), il content-type del messaggio ("application/x-www-form-urlencoded"), nonché una serie di parametri obbligatori che devono essere contenuti nel body del messaggio:
Prametro | Descrizione |
---|---|
grant_type | deve essere impostato su "client_credentials". |
client_id | identifica il Package security identifier (SID) assegnato al servizio cloud al momento della registrazione dell'app sul Windows Store. |
client_secret | la secret key per il servizio cloud assegnata al momento della registrazione dell'app sul Windows Store. |
scope | deve essere impostato su "notify.windows.com". |
Per autenticare la richiesta al Windows Live Service, è dunque necessario inviare il SID e la secret key ricevuti durante il processo di registrazione al servizio. Nel prossimo paragrafo vedremo come recuperare questi dati. Per il momento, è importante precisare che, proprio perché confidenziali, il back end dovrebbe conservare queste due informazioni in un posto sicuro.
La risposta del Windows Live Services utilizza il formato JavaScript Object Notation (JSON) e contiene direttamente il token di autorizzazione. Il seguente snippet ne mostra un esempio:
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Length: 422
Content-Type: application/json
{
"access_token":"EgAcAQMAAAAALYAAY/c+Huwi3Fv4Ck10UrKNmtxRO6Njk2MgA=",
"token_type":"bearer"
}
Il primo parametro, access_token
, rappresenta, come il nome suggerisce, il token che il servizio cloud dovrà utilizzare per inviare le notifiche. Il secondo parametro, token_type
, presenta un valore costante, ossia bearer
.
La risposta può quindi essere deserializzata aggiungendo le seguenti linee di codice:
private OAuthToken GetOAuthToken()
{
// ...
// parte da aggiungere
using (var ms = newMemoryStream(Encoding.Unicode.GetBytes(response)))
{
var ser = newDataContractJsonSerializer(typeof(OAuthToken));
var oAuthToken = (OAuthToken)ser.ReadObject(ms);
return oAuthToken;
}
}
Il codice utilizza la classe MemoryStream
per incapsulare la stringa JSON contenuta nella risposta, quindi istanzia un oggetto di tipo DataContractJsonSerializer
per leggere lo stream e trasformarlo in un token di tipo OAuth
. La classe OAuthToken
è definita come segue:
[DataContract]
public class OAuthToken
{
[DataMember(Name = "access_token")]
public string AccessToken { get; set; }
[DataMember(Name = "token_type")]
public string TokenType { get; set; }
}
La classe OAuthToken
espone una proprietà denominata AccessToken
che rappresenta l'header di autenticazione che il back end dovrà utilizzare assieme alla richiesta di notifica.
In particolare, la richiesta da parte del servizio di back end deve includere i seguenti header obbligatori:
Header | Descrizione |
---|---|
Authorization | si tratta del classico header HTTP standard. Nel caso del servizio cloud, questo header contiene il token di autorizzazione. |
Content-Type | anche questo è il tradizionale header HTTP standard. Nel caso di toast, tile e badge, questo header deve essere impostato a "text/xml", mentre nel caso di raw notification, il valore dev'essere "application/octet-stream". |
Content-Length | header HTTP standard che indica le dimensioni del payload. |
X-WNS-Type | header custom che definisce il tipo di payload: tile, toast, badge o raw. |
Oltre agli header obbligatori, la richiesta di notifica può includere uno dei seguenti header (opzionali):
Header | Descrizione |
---|---|
X-WNS-Cache-Policy | abilita il caching della notifica. Non si applica ai toast. |
X-WNS-RequestForStatus | richiede che nella risposta siano indicati lo status del device e quello della connessione con il WNS. |
X-WNS-Tag | il tag da associare a quella particolare notifica, per i tile che supportano il meccanismo di notification queue (vedi la relativa documentazione su MSDN). |
X-WNS-TTL | un intero che specifica, in secondi, il "time to live" (TTL). |
Qui di seguito è mostrato un esempio di possibile richiesta dal servizio di back end al WNS contenente tutti gli elementi necessari:
POST https://db3.notify.windows.com/?token=AfUAABBCQmGg7OMlCg%2fK0K8rBPcBqHuy%2b1rTSNPMuIzF6BtvpRdT7DM4j%2fs%2bNNm8z5l1QKZMtyjByKW5uXqb9V7hIAeA3i8FoKR%2f49ZnGgyUkAhzix%2fuSuasL3jalk7562F4Bpw%3d HTTP/1.1
Authorization: Bearer agFaAQDAAAAEgAAACoAAPzCGedIbQb9vRfPF2Lxy3K//QZB78mLTgK
X-WNS-RequestForStatus: true
X-WNS-Type: wns/toast
ContentType: text/xml
Host: db3.notify.windows.com
Content-Length: 189
<toast launch="">
<visual lang="it-IT">
<binding template="ToastText01">
<text id="1">Ricordati di prendere l'ombrello, sta per piovere!</text>
</binding>
</visual>
Una volta ottenuto il token di autorizzazione e preparato l'XML per il payload (in questo caso, un toast), possiamo inviare al WNS la richiesta di notifica:
private void PushNotification()
{
OAuthToken token = this.GetOAuthToken();
String uri = "Uri_ricevuta_dal_WNS";
var toastInBytes = File.ReadAllBytes(@"toast.xml");
HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
request.Method = "POST";
request.Headers.Add("X-WNS-Type", "wns / toast");
request.ContentType = "text/xml";
request.Headers.Add("Authorization", String.Format("Bearer {0}", token.AccessToken));
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(toastInBytes, 0, toastInBytes.Length);
using (HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse())
Console.WriteLine(webResponse.StatusCode.ToString());
}
Una volta ottenuto il token di autorizzazione, il codice lo utilizza per creare l'authentication header per la richiesta al WNS. Quindi, dopo aver costruito il resto del messaggio, il codice effettua una POST verso il servizio, passando come payload il frammento di XML che definisce il toast.
Se la richiesta ha avuto successo, il servizio otterrà come risposta l'HTTP status code 200 OK, assieme al token di autorizzazione. Se invece l'autenticazione fallisce, riceverà un 400 Bad Request (gli altri codici di errore sono descritti qui, assieme alla descrizione delle possibili cause).
Per evitare roundtrip, puoi tenere in cache il token di autorizzazione ricevuto, ma in questo caso accertati di intercettare l'eccezione derivante da un token scaduto. Il prossimo snippet mostra come gestire questa eventualità:
catch (WebException webException)
{
string exceptionDetails = webException.Response.Headers["WWW-Authenticate"];
if (exceptionDetails.Contains("Token expired"))
{
var token = GetOAuthToken(secret, sid);
// Impostare politica di retry
}
}
Registrare la propria app presso il WNS
Per poter ottenere il SID e la secret key, è necessario registrare la propria applicazione con il servizio tramite la dashboard del Windows Store, come illustrato nella prossima immagine, presa dalla documentazione ufficiale su MSDN (alla quale si rinvia per i dettagli della procedura di registrazione):
(fonte MSDN)
Tieni presente che per poter registrare l'app al WNS occorre aver precedentemente riservato sul Windows Store il nome dell'applicazione (non è invece necessario caricare i pacchetti applicativi).
Nella pagina Push notification and Live Connect services info, per visualizzare il SID e la chiave segreta è sufficiente cliccare sul link Authenticating Your Service, come mostrato nella prossima immagine:
(fonte MSDN)
Intercettare le notifiche
Quando l'applicazione è in esecuzione e una notifica è inviata al device, è possibile per l'applicazione intercettare e gestire il messaggio prima che il toast sia mostrato, o il tile e il badge aggiornati. In questo modo, l'app è in grado di modificare o eventualmente sopprimere la notifica. Riprendendo l'esempio dell'applicazione meteo, se l'app riceve un toast con l'ultima temperatura rilevata nella località preferita dall'utente, ma questi già visualizzando relativa pagina sull'app, il messaggio potrebbe risultare fastidioso e superfluo. Per questo, la logica applicativa potrebbe decidere di intercettare e semplicemente sopprimere il toast sfruttando l'evento PushNotificationReceived
.
Il prossimo snippet mostra un esempio di utilizzo di questo evento:
function createChannel_click(args) {
var pushNotifications = Windows.Networking.PushNotifications;
var channelOperation = pushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync()
.then(function (reqChannel) {
// riga da aggiungere
reqChannel.addEventListener("pushnotificationreceived", onNotificationReceived, false);
message.InnerText = reqChannel.uri;
},
function (error) {
// Impossibile recuperare il notification channel
});
}
Il corrispondente event handler ispeziona la proprietà NotificationType
, un enum che indica il tipo di notifica ricevuta (badge, tile, toast, raw) e, sulla base della propria logica applicativa, può eventualmente manipolarne il contenuto, come mostrato nel prossimo snippet.
function onNotificationReceived(e) {
var notification;
switch (e.notificationType) {
case pushNotifications.PushNotificationType.toast:
notification = e.toastNotification.content.getXml();
break;
case pushNotifications.PushNotificationType.tile:
notification = e.tileNotification.content.getXml();
break;
case pushNotifications.PushNotificationType.badge:
notification = e.badgeNotification.content.getXml();
break;
case pushNotifications.PushNotificationType.raw:
notification = e.rawNotification.content;
break;
}
}
Dopo aver ispezionato il tipo di notifica ricevuto tramite la proprietà NotificationType
dell'oggetto PushNotificationEventArgs
ricevuto come parametro dall'event hanlder, il codice recupera il frammento XML corrispondente e, sulla base della propria logica interna, decide se e come utilizzare la notifica.
Finora abbiamo visto come intercettare le notifiche quando l'app è in esecuzione. Tuttavia è anche possibile creare e registrare un background task per eseguire codice in background quando l'app non è in foreground. Questo codice verrà eseguito in risposta ad una raw notification (per quanto riguarda la creazione e la registrazione di un background task si rinvia agli articoli di questa guida espressamente dedicati a questo argomento). Il seguente estratto mostra la sezione dell'application manifest contenente la definzione del background task: come si può notare, il task è di tipo pushNotification.
<Extensions>
<Extension Category="windows.backgroundTasks">
<BackgroundTasks>
<Task Type="pushNotification" />
</BackgroundTasks>
</Extension>
</Extensions>
Quindi l'applicazione registra il task tramite un PushNotificationTrigger
, il quale determina l'esecuzione del task all'arrivo di una nuova raw notification da parte del WNS. Il seguente estratto ne mostra un esempio (per l'esempio completo si rinvia all'articolo dedicato alla creazione di un background task [[LINK]]):
var builder = new Windows.ApplicationModel.Background.BackgroundTaskBuilder();
var trigger = new Windows.ApplicationModel.Background.PushNotificationTrigger();
builder.name = "sampleBackgroundTask";
builder.taskEntryPoint = "js\\sampleTask.js";
builder.setTrigger(trigger);
All'interno del background task è possibile recuperare, tramite la proprietà TriggerDetails
, l'istanza della notifica ricevuta, nonché accedere al relativo contenuto tramite la proprietà Content
, come mostrato nel seguente esempio:
var bgTaskInstance = Windows.UI.WebUI.WebUIBackgroundTaskInstance.current;
function doSomeWork() {
var notificationContent = bgTaskInstance.triggerDetails.content;
// analizza il contenuto della stringa
close();
}
doSomeWork();
Come abbiamo visto in queste pagine, il Windows Push Notification Service di Microsoft semplifica notevolmente il lavoro dello sviluppatore, consentendo l'invio di toast, badge, tile e raw notification senza doversi preoccupare dei dettagli relativi alla comunicazione tra il servizio di backend, che racchiude la logica che presiede all'invio di messaggi all'utente, e l'applicazione Windows Store che riceve le comunicazioni provenienti dal cloud.