Großflächiges Versenden von Push-Notifications auf mobile Endgeräte (Teil 1)

BriefkästenUm einzelne Push Notifications an die gängigen Betriebssysteme für mobile Endgeräte verschicken zu können, bot die Windows Azure Plattform bislang die Mobile Services als Lösung an.
Seit Ende Januar ist ein weiterer Dienst hinzugekommen, mit dem man das großflächigen Versenden von Push-Benachrichtigungen realisieren kann.

Die neuen Notification Hubs für Windows Azure, die vorerst in einer Preview zur Verfügung stehen, bieten eine hochskalierbare Infrastruktur zum großflächigen Versenden von Push-Notifications auf mobile Endgeräte.

Der ersten Teil dieser kleinen Blog Post Reihe befasst sich mit den Grundlagen, sowie der Anbindung einer Windows Store App.

 

Push Notification Services

Für alle mobile Endgeräte, funktionieren die Push-Notification-Services nach einem ähnlichen Prinzip:

Push Notification Services

Bevor die öffentliche Infrastruktur einer App Push-Benachrichtigungen verschicken kann, muss diese bei den jeweiligen Push-Notification-Services registriert werden.

Daraufhin können Apps sich für Push-Benachrichtigungen anmelden und diese anschließend erhalten:

  1. Dazu fordert die App bei dem im Betriebssystem eingebetteten Push-Notification-Client eine Art Token bzw. ID an.
    Bei Windows Store Apps ist dies eine Channel URI.
  2. Der lokale Push-Notification-Client fordert diesen Token beim Push-Notification-Service an …
    Bei Windows Store Apps heißt dieser Windows Push Notification Services (WNS).
    Bei Apple wird dieser als Apple Push Notification Service (APNS) bezeichnet.
  3. … und gibt die Antwort an die App zurück.
  4. Die App übermittelt den Token an die eigene, serverseitige Infrastruktur, welche diese für den Client abspeichert.
  5. Wenn jetzt eine App benachrichtigt werden soll, wird eine Nachricht inkl. des Empfänger-Tokens an den Push-Notification-Service gesendet …
  6. … welcher diese an den entsprechenden Push-Notification-Client weiterleitet.

Soweit zum klassischen Weg.
Aber wie sieht das Ganze jetzt mit den Windows Azure Service Bus Notification Hubs aus?

 

Erstellen eines Notification Hubs

Über "Create > App Services > Service Bus Notification Hub > Quick Create" kann im Windows Azure Management Portal ein neuer Notification Hub erstellt werden.
Neben der üblichen Region- und Subscription-Auswahl, besteht außerdem die Möglichkeit einen neuen Service Bus Namespace erstellen zu lassen oder einen bereits existierenden auszuwählen:

Einen Notification Hub erstellen

 

Wenige Minuten später ist der Notification Hub erstellt und kann im Configure-Bereich mit den Push-Notification-Services verbunden werden:

Notification Hub Configure-Bereich

 

Windows Store App reservieren

Um an die Package SID und das Client Secret zu gelangen, muss man zuerst im Dev Center für Windows Store Apps eine neue App anlegen (Dashboard > App übermitteln):

Dev Center - App übermitteln

 

Anschließend gelangt man über "Erweiterte Features" in den Bereich "Info zu Pushbenachrichtigungen und Live Connect-Diensten":

Dev Center - Erweiterte Features

 

Dort findet man unter "Authentifizieren des Diensts" die gewünschten Informationen:

Pushbenachrichtigungen - Authentifizieren des Diensts

 

Nachdem diese Informationen in den Configure-Bereich des Notification Hubs hinzugefügt und gespeichert wurden, können Push-Notifications an die Windows Store App versendet werden.

 

Die Windows Store App anpassen

Damit die Windows Store Apps die Push-Benachrichtigungen auch erhalten können, müssen diese eine Channel-URI bei den Windows Push Notification Services (WNS) anfordern und diese an den Notification Hub weiterleiten.

Um beispielsweise Toast-Benachrichtigungen erhalten zu können, müssen diese zuerst im Package.appmanifest freigeschaltet werden:

Package.appmanifest - Enable 'Toast capable'

 

Anschließend muss eine Referenz zum Service Bus WinRT Managed SDK (Microsoft.WindowsAzure.Messaging.Managed) dem Projekt hinzugefügt werden.

Hinweis:
Das Service Bus WinRT Managed SDK kann unter diesem Link heruntergeladen werden.

 

Für die Verbindung zum Notification Hub sollte man folgenden Codezeilen in der App.xaml.cs hinzufügen:

sealed partial class App
{
  private readonly NotificationHub _notificationHub;
  /// <summary>
  /// Initializes the singleton application object. 
  /// This is the first line of authored code
  /// executed, and as such is the logical equivalent of main() or WinMain().
  /// </summary>
  public App()
  {
    var cn = ConnectionString.CreateUsingSharedAccessSecretWithListenAccess(
             "sb://<Service Bus Namespace>.servicebus.windows.net/", 
             "<Passwort des DefaultListenSharedAccessSignature Kontos>");
    _notificationHub = new NotificationHub("myhub", cn);
 
    this.InitializeComponent();
    this.Suspending += OnSuspending;
  }

 

Das Passwort des DefaultListenSharedAccessSignature Kontos findet man im Notification Hub, wenn man auf "VIEW SAS KEY" in der unteren Leiste des Management Portals klickt:

Connect to your notification hub

 

Mit Hilfe dieses Verbindungsobjektes, lässt sich nun folgender Code ausführen:

private async Task InitializeNotificationsAsync()
{
  try
  {
    // Get latest Channel-URI and send it to the Notification Hub
    await _notificationHub.RefreshRegistrationsAsync();

    // Register for native Push-Notifications
    if (!await _notificationHub.RegistrationExistsForApplicationAsync())
    {
      await _notificationHub.CreateRegistrationForApplicationAsync();
    }
  }
  catch (Exception ex)
  {
    new MessageDialog(
      String.Format("An error occured: {0}", ex.Message),
      "Notification Hub Registration").ShowAsync();
  }
}

 

Diese Methode stellt 2 Dinge bereit:

  • Der Notification Hub wird mit einer aktuellen Channel-URI aktualisiert.
  • Die App kann jetzt native Push-Benachrichtigungen erhalten.

 

Vorlagen für Push-Benachrichtigungen

Bei der Verwendung von nativen Push-Benachrichtigungen besteht allerdings ein Problem:
Wie unterstütze ich Windows Store- UND iOS-Apps?

Hierzu hat sich das Service Bus Team etwas besonderes einfallen lassen.

Jede App kann bei der Registrierung am Notification Hub Vorlagen mitsenden, die für das Versenden der Nachrichten verwendet werden sollen.

Bei den Notification Hub-Vorlagen lassen sich dabei Platzhalter definieren, die später mit Hilfe von Schlüssel/Wert-Paaren gefüllt werden:

Expression Description

$(prop)

Platzhalter mit dem Schlüssel "prop".
Die Schlüsselbezeichnungen sind nicht case-sensitive.
Wenn bei der Nachricht kein passendes Schlüssel/Wert-Paar gefunden wird, wird ein Leerstring eingesetzt.

$(prop, n)

Wie bei $(prop), allerdings mit einer Maximallänge von n Zeichen.
Längere Texte werden gekürzt.

.(prop, n)

Wie bei $(prop, n), allerdings werden drei Punkte angehängt wenn der Text gekürzt werden muss. 
Auch durch die drei Punkte wird die Maximallänge nicht überschritten.

%(prop)

Wie bei $(prop), allerdings ist der Text URI encoded.

#(prop)

Wird bei den iOS Vorlagen benutzt, um den Zahlenwerte einzusetzen.
Es werden also die Anführungszeichen im verwendeten JSON-Format weggelassen.

$body

Setzt den "body" der Service Bus Nachricht ein.

expr1 + expr2

Mit dem + Operator können Strings miteinander verbunden werden.
Dabei können alle vorangegangenen Platzhalter benutzt werden.
Der Ausdruck muss mit geschweiften Klammern eingeschlossen sein.
Beispiel: {$(prop) + ‘ – ’ + $(prop2)}

 

Beispielsweise könnte aus der Vorlage für eine Toast-Nachricht mit einem Textblock …

<toast>
  <visual>
    <binding template="ToastText01">
      <text id="1">value</text>
    </binding>
  </visual>
</toast>

 

… folgende Notification Hub-Vorlage gemacht werden:

<toast>
  <visual>
    <binding template="ToastText01">
      <text id="1">$(msg)</text>
    </binding>
  </visual>
</toast>

 

Der Code der InitializeNotificationsAsync Methode wurde dann folgendermaßen aussehen:

private async Task InitializeNotificationsAsync()
{
  try
  {
    await _notificationHub.RefreshRegistrationsAsync();

    if (!await _notificationHub.RegistrationExistsForApplicationAsync("toast"))
    {
      await _notificationHub.CreateTemplateRegistrationForApplicationAsync(
        NotificationXmlBuilder.CreateToastText01Xml("$(msg)"),
        "toast");
    }
  }
  catch (Exception ex)
  {
    new MessageDialog(
      String.Format("An error occured: {0}", ex.Message),
      "Notification Hub Registration").ShowAsync();
  }
}

 

Nachdem die InitializeNotificationsAsync Methode erstellt wurde, muss diese zu den OnLaunched und OnActivated Methoden hinzugefügt werden:

protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
  await InitializeNotificationsAsync();

  // ...
}

protected async override void OnActivated(IActivatedEventArgs args)
{
  base.OnActivated(args);
  await InitializeNotificationsAsync();
}

 

Verschicken von Nachrichten

Damit jetzt Push-Benachrichtigungen verschickt werden, muss in ein beliebiges Projekt, wie beispielsweise eine Konsolenapplikation oder auch eine Windows Azure Web Role, das NuGet Paket ServiceBus.Preview hinzugefügt und folgende Methode aufgerufen werden:

public void SendMessage(string message)
{
  var connectionString = ServiceBusConnectionStringBuilder
    .CreateUsingSharedAccessSecretWithFullAccess("<Service Bus Namespace>", 
    "<Passwort des DefaultFullSharedAccessSignature Kontos>");
  var hubClient = NotificationHubClient
    .CreateClientFromConnectionString(connectionString, "myhub");
  hubClient.SendTemplateNotification(new Dictionary
    {
      {"msg", message}
    });
}

 

Mehrere Vorlagen registrieren

In den Apps können auch mehrere Vorlagen registriert werden, um beispielsweise mit nur einer Notification Hub-Nachricht gleichzeitig eine Toast-Nachricht zu erzeugen und das Live-Tile zu aktualisieren:

private async Task InitializeNotificationsAsync()
{
  try
  {
    await _notificationHub.RefreshRegistrationsAsync();

    if (!await _notificationHub.RegistrationExistsForApplicationAsync("toast"))
    {
      await _notificationHub.CreateTemplateRegistrationForApplicationAsync(
        NotificationXmlBuilder.CreateToastText01Xml("$(msg)"),
        "toast");
    }

    if (!await _notificationHub.RegistrationExistsForApplicationAsync("tile"))
    {
      await _notificationHub.CreateTemplateRegistrationForApplicationAsync(
        BuildTileTemplate(),
        "tile");
    }
  }
  catch (Exception ex)
  {
    new MessageDialog(
      String.Format("An error occured: {0}", ex.Message),
      "Notification Hub Registration").ShowAsync();
  }
}

private static XmlDocument BuildTileTemplate()
{
  var tileXml = NotificationXmlBuilder.CreateTileWideText04Xml("$(msg)");

  var visualNode = tileXml.SelectSingleNode("//visual") as XmlElement;
  if (visualNode != null)
  {
    var squareTileXml = 
      NotificationXmlBuilder.CreateTileSquareText04Xml("$(msg)");
    var bindingNode = tileXml.ImportNode(
      squareTileXml.GetElementsByTagName("binding").Item(0), true);
    visualNode.AppendChild(bindingNode);
  }

  return tileXml;
}

 

Gezieltes Versenden von Nachrichten

Wenn gezielt kleinere Gruppen oder auch einzelne Empfänger angesprochen werden sollen, muss man bei der Registrierung der Vorlagen eine Liste von Schlagwörtern (Tags) mit angeben.

Dies könnte beispielsweise wie folgt aussehen, um

  • mit dem "All"-Tag beide Nachrichten an alle Empfänger zu senden
  • mit den "Toast"- bzw. "Tile"-Tags nur Toastnachrichten oder nur Live-Tile Updates zu verschicken
  • mit der App Specific Hardware ID (ASHWID) ein einzelnes Gerät ansprechen zu können
  • mit der UserID alle Geräte eines einzelnen Benutzers erreichen zu können
private async Task InitializeNotificationsAsync()
{
  try
  {
    await _notificationHub.RefreshRegistrationsAsync();

    var ashwid = GetAppSpecificHardwareId();
    var userid = GetUserId();

    if (!await _notificationHub.RegistrationExistsForApplicationAsync("toast"))
    {
      await _notificationHub.CreateTemplateRegistrationForApplicationAsync(
        NotificationXmlBuilder.CreateToastText01Xml("$(msg)"),
        "toast", 
        new[] { "All", "Toast", ashwid, userid });
    }

    if (!await _notificationHub.RegistrationExistsForApplicationAsync("tile"))
    {
      await _notificationHub.CreateTemplateRegistrationForApplicationAsync(
        BuildTileTemplate(),
        "tile",
        new[] { "All", "Tile", ashwid, userid });
    }
  }
  catch (Exception ex)
  {
    new MessageDialog(
      String.Format("An error occured: {0}", ex.Message),
      "Notification Hub Registration").ShowAsync();
  }
}

private static string GetAppSpecificHardwareId()
{
  var token = Windows.System.Profile
    .HardwareIdentification.GetPackageSpecificToken(null);
  var hardwareId = token.Id;

  var bytes = new byte[hardwareId.Length];
  using (var dataReader = Windows.Storage.Streams
    .DataReader.FromBuffer(hardwareId))
  {
    dataReader.ReadBytes(bytes);
  }

  return BitConverter.ToString(bytes);
}

private static string GetUserId()
{
  // TODO: Implement logic to retrieve the user id.
  return "UserID";
}

 

Zum Senden der Push-Benachrichtigungen mit Hilfe der eines "Tags" sähe der Code wie folgt aus:

public void SendMessage(string message, string tag)
{
  var connectionString = ServiceBusConnectionStringBuilder
    .CreateUsingSharedAccessSecretWithFullAccess("<Service Bus Namespace>", 
    "<Passwort des DefaultFullSharedAccessSignature Kontos>");
  var hubClient = NotificationHubClient
    .CreateClientFromConnectionString(connectionString, "myhub");
  hubClient.SendTemplateNotification(new Dictionary
    {
      {"msg", message}
    }, 
    null, // Message-Body 
    tag);
}

 



Verwendete Bildquellen:
© Rainer Sturm / PIXELIO

Check Also

Time Machine Backups nach Microsoft Azure

Seit einigen Jahren verwende ich eine Apple Time Capsule, um meine Time Machine Backups an einem zentralen Ort speichern zu können. Bislang hatte das für mich auch vollkommen ausgereicht. Seitdem ich jedoch immer mehr unterwegs bin, habe ich nach einer Lösung gesucht, die ich auch von unterwegs nutzen kann. In diesem Blog Post zeige ich deshalb, wie man Time Machine Backups nach Microsoft Azure machen kann.

One comment

  1. Sorry hier nochmal der korrekte Link 😉 http://www.littlepostman.com/de/index.html