Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Windows Push Notification Service, inviare notifiche all'utente

Link copiato negli appunti

In questo articolo, vedremo come sfruttare il Windows Push Notification Service (WNS) in modo da inviare toast e aggiornare tile e badge alla nostra applicazione Windows Store. Questa infrastruttura cloud gestisce i meccanismi della comunicazione con il dispositivo device in modo trasparente ed efficiente.

Richiedere e creare un notification channel

I passi necessari per poter sfruttare il WNS per inviare notifiche alla propria applicazione Windows Store sono sintetizzati nella prossima figura.



(fonte MSDN)

In primo luogo, l'applicazione client deve 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 alla Notification Client Platform una URI che rappresenta in modo univoco il canale assegnato a quello specifico client (sul punto vedi anche più avanti); l'URI verrà quindi restituita all'applicazione che l'ha richiesta, ed è a quest'ultima - e dunque in definitiva allo sviluppatore - a cui spetta il compito di inviare l'URI ricevuta al proprio servizio in back end, in modo che venga memorizzata 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.

È 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. Allo stesso modo, la stessa applicazione su due device diversi riceverà due differenti canali. Il servizio di back end deve saper gestire queste eventualità, ad esempio memorizzando più URI per lo stesso utente che ha installato l'applicazione meteo su più device.

Per poter lavorare con le API di push notification, raccolte 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 il metodo asincrono CreatePushNotificationChannelForApplicationAsync esposto dalla classe PushNotificationChannelManager.

Questo metodo provvede a richiedere 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 includere la chiamata a questo metodo all'interno di un try/catch, perché se la connessione a Internet non è disponibile al momento della chiamata al metodo CreatePushNotificationChannelForApplicationAsync, il sistema solleverà un'eccezione:

private async void CreateChannel_Click(object sender, RoutedEventArgs e)
{
    PushNotificationChannel channel = null;
    try
    {
        channel = await PushNotificationChannelManager.
                  CreatePushNotificationChannelForApplicationAsync();
        tbUri.Text = String.Format("Notification channel : {0}", channel.Uri);
    }
    catch (Exception ex)
    {
        tbUri.Text = String.Format("Impossibile creare un canale: {0}", ex.Message);
    }
}

Per testare questo semplicissimo codice d'esempio, utilizziamo la seguente definizione XAML come riferimento per la MainPage.xml dell'app.

<Page
    x:Class="Demo.Html.it.WNS.CS.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Demo.Html.it.WNS.CS"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button Click="CreateChannel_Click" Margin="20">Crea un canale</Button>
            </StackPanel>
            <TextBlock x:Name="tbUri" Width="Auto" Height="50" Margin="20" FontSize="20" TextWrapping="Wrap"></TextBlock>
        </StackPanel>
    </Grid>
</Page>

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 circa 30 giorni).

Quanto al servizio di back end, la soluzione migliore sarebbe quella di controllare lato client se l'URI è cambiata e, solo 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:

  1. Richiedere un canale al WNS tramite il metodo CreatePushNotificationChannelForApplicationAsync.
  2. Controllare nello storage locale se c'è già un'URI salvata in precedenza.
  3. 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 valido fino 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 notification channel, è 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:

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 alcune linee di codice :

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);
    }
    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(response)))
    {
        var ser = new DataContractJsonSerializer(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>
</toast>

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)

Occorre tener presente che per poter registrare l'app al WNS bisogna avere 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 OnPushReceived.

Il prossimo snippet mostra un esempio di utilizzo di questo evento:

public async void CreateChannel()
{
    PushNotificationChannel channel = null;
    try
    {
        channel = await PushNotificationChannelManager
            .CreatePushNotificationChannelForApplicationAsync();
        //...
        channel.PushNotificationReceived += OnPushReceived;
    }
    catch (Exception ex)
    {
    }
}

Il corrispondente event handler ispeziona la proprietà NotificationType, un enum che indica il tipo di notifica ricevuta (badge, tile, toast, raw) e, sulla base di una qualche logica applicativa, può manipolarne il contenuto ed eventualmente decidere di sopprimerla, come mostrato nel prossimo snippet.

private void OnPushReceived(PushNotificationChannel sender, PushNotificationReceivedEventArgs args)
{
     String notificationContent = String.Empty;
     switch (args.NotificationType)
     {
         case PushNotificationType.Badge:
             notificationContent = args.BadgeNotification.Content.GetXml();
             break;
         case PushNotificationType.Tile:
             notificationContent = args.TileNotification.Content.GetXml();
             break;
         case PushNotificationType.Toast:
             notificationContent = args.ToastNotification.Content.GetXml();
             // Logica per decidere se sopprimere o meno il toast
             args.Cancel = true;  // Sopprime il toast
             break;
         case PushNotificationType.Raw:
             notificationContent = args.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 logica interna, decide se e come utilizzare la notifica (ad esempio, sopprimendola, come nel caso di toast).

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 snippet mostra un esempio (per l'esempio completo si rinvia all'articolo dedicato alla creazione di un background task ):

if (!taskRegistered) {
   var builder = new BackgroundTaskBuilder();
   builder.Name = exampleTaskName;
   builder.TaskEntryPoint = "RawNotificationBackgroundTask.SampleTask";
   builder.SetTrigger(new Windows.ApplicationModel.Background.PushNotificationTrigger());
   BackgroundTaskRegistration task = builder.Register();
}

All'interno del background task è possibile usare la proprietà TriggerDetails (di tipo Object) per recuperare l'istanza della notifica ricevuta e accedere al relativo contenuto tramite la proprietà Content, come mostrato nel seguente esempio:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Networking.PushNotifications;
namespace RawNotificationBackgroundTask
{
    public sealed class SampleTask : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            RawNotification notification = taskInstance.TriggerDetails as RawNotification;
            String content = "";
            if (notification != null)
                content = notification.Content;
                // Analizza il contenuto della stringa
        }
    }
}

Come abbiamo visto, il Windows Push Notification Service di Microsoft semplifica notevolmente il lavoro, 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.


Ti consigliamo anche