El Hub de Notificaciones

Todo el mundo sabe que una de las características de Azure Mobile Services es la posibilidad de enviar notificaciones a los 3 servicios principales (Microsoft, tanto WNS para aplicaciones Windows Store y Windows Phone 8.1 como MPNS para Windows Phone 8.0 y anteriores. Notificaciones de Apple y notificaciones de Google Cloud para Android). Pero el ecosistema de Microsoft Azure, dispone también de un servicio denominado Service Bus, que contiene una característica llamada hubs de notificaciones y que en esencia tiene la misma capacidad.

Hace ya un tiempo que existe la posibilidad de enlazar nuestros servicios móviles con los hubs de notificaciones. Y ahora mismo con la ultima actualización realizada en Azure Mobile Services, cada vez que creamos un servicio con backend .NET, también se nos crea un hub de notificaciones.

El hub de notificaciones, ni mucho menos esta pensado solamente para ser usado con Mobile Services. Es un servicio pensado para que cualquier tipo de backend o de aplicación pueda gestionar y enviar notificaciones de una forma sencilla y sobre todo transparente.

Si nos ponemos ahora en el lado del uso de Mobile Services, es lógico que nos asalte una gran duda. ¿Cual de los dos sistemas es el que debemos de usar para enviar notificaciones? Bueno, pues en este post, voy a contar como se usa el hub de notificaciones, por lo que podremos ir viendo que características aporta este frente a Mobile Services y tomar así una decisión.

Registro de dispositivos.

En el hub de notificaciones, disponemos de la capacidad de registrar los dispositivos a los cuales vamos a enviar notificaciones en el futuro. Este registro se puede realizar, desde dos lugares distintos. Desde el propio dispositivo, conectándonos directamente al hub de notificaciones o desde nuestro servicio móvil, el cual enviará la información al hub de notificaciones. Realizando esto crearemos un PNS handle (Platform Notification Service handle), expresión o acrónimos que simplifican y agrupan los distintos tipos de handles o tokens de identificación usados en las diferentes plataformas soportadas. ChannelUri para dispositivos Windows, GCM para dispositivos Android, etc.…

Tenemos que tener en cuenta que al igual que expiran los PNS, también lo hacen las inserciones de dispositivos en este servicio. Disponemos de un máximo de 90 días de retención. Pasado ese tiempo serán eliminadas de forma automática. Por este motivo debemos de refrescar periódicamente desde el dispositivo cliente esta información.

Vamos a ver como podemos desde una aplicación de Windows Phone 8.1, registrarnos en el hub de notificaciones.

Nota: Existen librerías cliente de este servicio tanto para Windows Store (WNS), Windows Phone (MPNS), Objective-C (APNS de Apple) y Java (GCM para Android). Pero estas APIs o librerías de dispositivo solo nos dan la facultad de registrar dispositivos, nada más. Con estas librerías no podremos en ningún momento enviar notificaciones.

En el caso de Windows Phone 8.1, o de Windows Store, deberemos de descargarnos el paquete nuget correspondiente a “WindowsAzure.Messaging.Managed”. Ojo, este paquete, nos da acceso a todo lo relacionado con Service Bus. Nosotros solo veremos aquí, lo correspondiente al hub de notificaciones.

Aunque no es materia de este post, vamos a ver como registrar nuestra aplicación de Windows Phone 8.1 para que funcione y para obtener un canal de notificaciones.

PushNotificationChannelManager
  1. var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
  2.  
  3. string channelUri = channel.Uri.ToString();

Una vez que disponemos de ese canal / uri de notificaciones, este es el que pasaremos para registrar nuestro dispositivo en el hub de notificaciones.

Lo primero que debemos de hacer es crearnos una referencia al hub de notificaciones a través de la clase NotificactionHub. Para posteriormente registrar nuestro dispositivo a través del método asíncrono RegisterNativeAsync. Debemos de saber que este método, se usa tanto para registrar un dispositivo nuevo, como para la actualización de uno ya existente.

NotificationHub
  1. NotificationHub hub = new NotificationHub(            
  2.     "shaken3nethub",
  3.     "Endpoint=sb://shaken3nethub-ns.servicebus.windows.net/;SharedAccessKeyName=DefaultLis……..");
  4.  
  5. Registration registration = await hub.RegisterNativeAsync(channelUri);

Vemos que al crear la clase NotificationHub, debemos de especificar el nombre de nuestro hub de notificaciones, y como segundo parámetro la cadena de conexión segura al mismo. Cadena de conexión de DefaultListenSharedAccessSignature. Este dato está accesible en el portal de Azure, en la información de conexión, dentro de nuestro hub de notificaciones.

Si este proceso le quisiéramos realizar directamente en nuestro backend de Mobile Services, con tecnología en .Net deberíamos de realizar lo siguiente.

Primero debemos de comprobar que nuestro servicio tiene de forma correcta configurado el enlace con el hub de notificaciones. Como he comentado anteriormente al crear un servicio móvil con tecnología en .Net automáticamente se nos crea un hub de notificaciones, pero el lugar en donde este esta enlazado con el servicio es en la pestaña de inserción (Hub), en la sección de la base de datos central de notificaciones (xxxxxxxxx)

MobileService_NotificationHubs

Ya dentro de nuestro servicio, vamos a crear un ApiController para gestionar la inserción de nuestros dispositivos. En este controlador vamos a interactuar con el hub de notificaciones desde WebApi, por lo que deberemos de descargarnos el paquete de nuget correspondiente. “WindowsAzure.ServiceBus” es el que debemos de instalar.

NotificationController
  1. public async Task<RegistrationDescription> Post([FromBody]JObject registerCall)
  2. {
  3.     string notificationHubName = "shaken3nethub";
  4.     string connectionString = "Endpoint=sb://shaken3nethub-ns.servicebus.windows.net/;SharedAc……";
  5.  
  6.     var hubClient = NotificationHubClient.CreateClientFromConnectionString(connectionString, notificationHubName);
  7.  
  8.     var platform = registerCall["platform"] != null ? registerCall["platform"].ToString() : null;
  9.     var channelUri = registerCall["channelUri"] != null ? registerCall["channelUri"].ToString() : null;
  10.  
  11.     if ((platform == null) || (channelUri == null))
  12.     {
  13.         return null;
  14.     }
  15.  
  16.     RegistrationDescription registrationDescription = null;
  17.  
  18.     switch (platform)
  19.     {
  20.         case "windowsphone":
  21.             registrationDescription = await hubClient.CreateMpnsNativeRegistrationAsync(channelUri);
  22.             break;
  23.         case "windows":
  24.             registrationDescription = await hubClient.CreateWindowsNativeRegistrationAsync(channelUri);
  25.             break;
  26.         case "android":
  27.             registrationDescription = await hubClient.CreateGcmNativeRegistrationAsync(channelUri);
  28.             break;
  29.         case "ios":
  30.             registrationDescription = await hubClient.CreateAppleNativeRegistrationAsync(channelUri);
  31.             break;
  32.         case "kindle":
  33.             registrationDescription = await hubClient.CreateAdmNativeRegistrationAsync(channelUri);
  34.             break;
  35.     }
  36.  
  37.     return registrationDescription;
  38. }

Vamos por partes (como dijo el señor Jack). Lo primero de todo es que nuestro controlador va a recibir un objeto Json. Esto lo podemos hacer así para facilitar la llamada desde cualquier plataforma a este servicio y lo homogeneizamos. Pero vamos que cualquier otro sistema para enviar los datos de entrada, por supuesto que es valido.

Como vemos, debemos de crear un objeto NotificationHubClient, perteneciente al namespace Microsoft.ServiceBus.Notifications, indicando la cadena de conexión y el nombre de nuestro hub de notificaciones. La cadena de conexión debe de ser la misma que usamos si accedemos directamente desde un dispositivo cliente, DefaultListenSharedAccessSignature.

Extraemos los datos de nuestro objeto Json y dependiendo de la plataforma de la cual estemos realizando la llamada usaremos un método del objeto NotificationHubClient u otro. Como podemos ver disponemos de Mpns, para Windows Phone hasta la versión 8.0 (incluido 8.1 para Silverlight), Windows para aplicaciones Windows Store y Windows Phone 8.1 (Store). Gcm para dispositivos Android, Apple para dispositivos iOS y Adm (Amazon Device Message) para dispositivos Kindle. (sí, sí, has leído bien…. Kindle)

Una vez ejecutado el método CreateXXXXNativeRegistrationAsync nos devuelve un objeto del tipo RegistrationDescription, objeto de contenido similar que el objeto Registration que nos devuelve el método de registro en el hub cuando estamos usando el cliente en el propio dispositivo.

Nota: Aunque en el objeto Json que paso como parámetro al controlador disponga de un parámetro denominado ChannelUri, esto lo hago así por sencillez. Pero en cada caso introduzco en dicho parámetro lo necesario para el registro. El ChannelUri en el caso de Windows, el DeviceToken en el caso de Apple, etc…

A continuación muestro un ejemplo de como podríamos enviar la información desde una aplicación Windows Phone a través de nuestro servicio móvil.

NotificationHubByZuMo
  1. var registerCall = new Dictionary<string, string>() {
  2.                      {"platform", "windows"},
  3.                      {"channelUri", channelUri}
  4.                    };
  5.  
  6. string registerUri = "https://shaken3net.azure-mobile.net/api/Notification";
  7.  
  8. var client = new HttpClient();
  9. var request = new HttpRequestMessage(HttpMethod.Post, new Uri(registerUri));
  10.  
  11. request.Content = new StringContent(JsonConvert.SerializeObject(registerCall),
  12.                                     Encoding.UTF8, "application/json");
  13. string messageDescription;
  14.  
  15. try
  16. {
  17.     HttpResponseMessage response = await client.SendAsync(request);
  18.  
  19.     messageDescription = await response.Content.ReadAsStringAsync();
  20. }
  21. catch (Exception ex)
  22. {
  23.     messageDescription = ex.Message;
  24. }
  25.  
  26. return messageDescription;

Ya he comentado que el registro de los dispositivos es totalmente transparente para el programador. De todo se encarga azure. Dependiendo del canal de registro, ChannelUri, DeviceToken o el que corresponda en cada caso, el hub de notificaciones usa concurrencia optimista para realizar el control del registro. En algún caso nos podría dar algún tipo de excepción por este control, pero será el propio servicio el que se encarga de reintentar el registro o la actualización de forma también transparente.

En la llamada al método de registro, el objeto que nos devuelve, tanto en la clase Registration, como en la clase RegistrationDescription podemos ver una propiedad llamada ETag (entity tag). Esta propiedad es el identificador único que denomina el registro de cada uno de los dispositivos.

Hasta ahora hemos visto como poder registrar dispositivos directamente con las clases que nos proporciona el espacio de nombres de Services Bus. Pero si el registro lo realizamos a través de Azure Mobile Services, este, puede ser aun mucho mas sencillo y transparente. Basta con implementar la clase MobileServiceClient encargada de todas las operaciones entre nuestro dispositivo y Mobile Services.

Dentro de dicha clase, llamaremos a la extensión GetPush(), para finalmente invocar el método RegisterNativeAsync, pasándole como parámetro nuestra Uri de notificaciones.

Through ZuMo
  1. var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
  2.  
  3. string channelUri = channel.Uri.ToString();
  4.  
  5. MobileServiceClient zuMoClient = new MobileServiceClient("https://shaken3net.azure-mobile.net", "Password");
  6.  
  7. await zuMoClient.GetPush().RegisterNativeAsync(channelUri);

Con esta simple llamada estaremos registrando el dispositivo, sin necesidad de usar ninguna Api de Service Bus directamente.

Dentro del namespace de ServiceBus disponemos de métodos para consultar todos los registros de los diferentes dispositivos, comprobar que estos se han registrado de forma correcta, el numero de ellos, etc., etc.… Pero como muchas otras cosas, existen atajos que nos pueden ayudar mucho. Paolo Salvatori, Principal Program Manager de Windows Azure, a desarrollado una aplicación denominada Service Bus Explorer. Con ella, entre otras muchas cosas podremos visualizar de forma rápida los dispositivos registrados en nuestro hub de notificaciones.

ServiceBusExplorer

El registro de dispositivos es una gran diferencia entre Notification Hubs y Mobile Services. En Mobile Services, somos nosotros los que nos tenemos que encargar de crear una tabla para guardar el registro de los canales de notificación, mantenerle en condiciones (actualizar las uris de conexión o tokens de direcciones, borrarles cuando estos estén caducados, …..)  Con el hub de notificaciones, esto es totalmente transparente para nosotros. Tiene todo lo necesario para realizar estas operaciones con garantías.

Offtopic: en muchos lugares os podéis encontrar la denominación de Azure Mobile Services como ZuMo.

Tags.

Mobile Services esta pensado para realizar el envío de notificaciones de forma individualizada. Notificación a notificación. Sin embargo, el hub de notificaciones esta diseñado para ser un transmisor a gran escala, un broadcasting. Lo veremos mas adelante pero si quisiéramos realizar el envío de una notificación, por defecto el sistema no discriminaría  y realizaría el envío a todos los dispositivos registrados que fueran de la plataforma indicada. Para evitar esto y poder de alguna forma “categorizar” estos envíos se usan los tags.

Un tag no es mas que como digo una categorización. Por ejemplo, si nuestra aplicación muestra un listado de noticias podemos registrarnos para que nos lleguen notificaciones de cambios en las noticias de deportes y / o en las de economía. 

Por supuesto que otra categorización podría ser nuestro propio usuario. Al registrar un dispositivo en ningún momento estamos diciendo a que usuario pertenece, simplemente registramos el token de conexión. Pero ¿y si necesitamos enviar una notificación a un usuario en concreto?. No tendríamos forma de identificarlo dentro de todos los dispositivos registrados (no de una manera directa, al menos). Podemos usar un tag para este fin.

Existe una sobrecarga en el método de creación del registro de un dispositivo, tanto si este le realizamos a través del cliente directamente o a través de nuestro servicio móvil. Esta sobrecarga acepta como segundo parámetro una lista de string, que son los tags que identifican y categorizan a ese dispositivo. Obviamente un dispositivo puede tener asociado uno o n tags.

NotificationHub with tags
  1. string userName = zumoClient.CurrentUser.UserId.Replace(":", "_");
  2.  
  3. List<string> tags = new List<string>();
  4. tags.Add("News:Economics");
  5. tags.Add("News:Sports");
  6. tags.Add(string.Format("User:{0}", userName));
  7.  
  8. Registration registration = await hub.RegisterNativeAsync(channelUri, tags);

En el código anterior, realizamos el registro directamente desde una aplicación de Windows Phone 8.1, añadiendo nuestro usuario (una vez autenticados a través de mobile services) y un par de categorías de noticias a las cuales queremos estar “subscritos”.

Como se puede ver no es mas que una lista de strings. En el caso del nombre de usuario, me permito la licencia de convertirlo de “provider:userId” a “provider_userId” básicamente por tener controlado en todo momento el signo de ‘:’ para futuros usos.

Tenemos que tener en cuenta que al registrar o actualizar un dispositivo debemos de pasar en todo momento todos los tags que queramos asociarle. Al actualizar un dispositivo este no conserva los tags que tuviera y añade los nuevos que le pasemos. Borra toda la información y la substituye por la nueva que le pasamos, por lo que siempre es necesario pasar todos los tags.

En el caso de realizar el registro a través de una Api dentro de Mobile Services el sistema seria similar. Por cada uno de los métodos de cada tipo de dispositivo también disponemos de las sobrecargas correspondientes para añadir tags al registro de nuestro dispositivo.

Controller with tags
  1. switch (platform)
  2. {
  3.     case "windowsphone":
  4.         registrationDescription = await hubClient.CreateMpnsNativeRegistrationAsync(channelUri, tags);
  5.         break;
  6.     case "windows":
  7.         registrationDescription = await hubClient.CreateWindowsNativeRegistrationAsync(channelUri, tags);
  8.         break;
  9.     case "android":
  10.         registrationDescription = await hubClient.CreateGcmNativeRegistrationAsync(channelUri, tags);
  11.         break;
  12.     case "ios":
  13.         registrationDescription = await hubClient.CreateAppleNativeRegistrationAsync(channelUri, tags);
  14.         break;
  15.     case "kindle":
  16.         registrationDescription = await hubClient.CreateAdmNativeRegistrationAsync(channelUri, tags);
  17.         break;
  18. }

Por ultimo si el registro le realizamos directamente a través de la clase MobleServiceClient, también existe dicha sobrecarga para indicar los tags asociados al dispositivo.

Through ZuMo with tags
  1. List<string> tags = new List<string>();
  2. tags.Add("News:Economics");
  3. tags.Add("News:Sports");
  4.  
  5. await zuMoClient.GetPush().RegisterNativeAsync(channelUri, tags);

En este ultimo caso existe una consideración a tener muy en cuenta. Cuando es el propio servicio móvil el que registra el dispositivo a través de la clase cliente en cada plataforma podemos “subscribirnos” a dicho proceso de registro y modificar, añadir o incluso cancelar dicho registro desde el propio servicio. Para ello deberemos de crear una clase en nuestro Mobile Service que implemente el interfaz INotificationHandler.

NotificationHandler
  1. public class NotificationHandler : INotificationHandler
  2. {
  3.     public Task Register(ApiServices services, HttpRequestContext context, NotificationRegistration registration)
  4.     {
  5.         registration.Tags.Add("SomeTag");
  6.         return Task.FromResult(true);
  7.     }
  8.  
  9.     public Task Unregister(ApiServices services, HttpRequestContext context, string deviceId)
  10.     {
  11.         return Task.FromResult(true);
  12.     }
  13. }

Al implementar este interfaz, nuestra clase dispone de 2 métodos. Register el cual es invocado al registrar un dispositivo y Unregister que se invoca justamente en el caso contrario. En el ejemplo anterior simplemente lo que estamos realizando es a todo dispositivo que se registre añadirle a las tags que ya incorpora en el proceso de registro una nueva (“someTag”).

Como podemos ver el objeto que nos interesa es el parámetro registration del tipo NotificationRegistration. En este parámetro es donde podemos comprobar por ejemplo la plataforma con la propiedad Platform, los Tags como hemos visto, etc.. También disponemos de la propiedad services que nos proporciona acceso al entorno de Mobile Services desde esta clase.

Nota: El backend en .NET para Azure Mobile Services, dispone de una manera peculiar, bueno o mas bien fija, de registrar los componentes que le conforman. Entre ellos esta la clase que he comentado anteriormente. Nuestro backend en el momento que encuentra una clase publica, no estática que implemente el interfaz INotificationHandler, la registra de forma automática para que sea invocada cuando corresponda. Vamos, todo un poco mágico.

Bueno hasta aquí la forma de registrar nuestros dispositivos al Hub de notificaciones. En el siguiente post mostrare como se realizan los envíos de estas notificaciones a los dispositivos registrados.

Aquí os deja el código fuente de una solución en la que implemento todo lo que hemos visto hasta ahora.

Skydrive Folder

Nos vemos!!!