Tutorial: Envío de notificaciones push a usuarios concretos mediante Azure Notification Hubs

Este tutorial muestra cómo puede utilizar los Centros de notificaciones de Azure para enviar notificaciones de inserción a un usuario de aplicaciones determinado en un dispositivo concreto. Un back-end de ASP.NET WebAPI se usa para autenticar clientes y generar notificaciones, tal como se muestra en el tema de referencia Registering from your app backend (Registro desde el back-end de la aplicación).

En este tutorial, realizará los siguientes pasos:

  • Creación del proyecto de API web
  • Autenticar clientes en el back-end de WebAPI
  • Registrar notificaciones mediante el back-end de WebAPI
  • Enviar notificaciones desde el back-end de WebAPI
  • Publicación del nuevo back-end de WebAPI
  • Modificación de la aplicación iOS
  • Prueba de la aplicación

Requisitos previos

Este tutorial asume que creó y configuró el centro de notificaciones tal como se describe en Envío de notificaciones push a aplicaciones iOS mediante Azure Notification Hubs. Este tutorial también es el requisito previo para el tutorial Inserción segura (iOS) . Si quiere utilizar Aplicaciones móviles como su servicio back-end, consulte Incorporación de notificaciones push a la aplicación iOS.

Creación del proyecto de API web

En las secciones siguientes se describe la creación de un nuevo back-end de ASP.NET WebAPI. Este proceso tiene tres objetivos principales:

  • Autenticación de clientes: se agrega un controlador de mensajes para autenticar las solicitudes de cliente y asociar el usuario a la solicitud.
  • Registro para recibir notificaciones mediante el back-end de WebAPI: se agrega un controlador para administrar los registros nuevos de un dispositivo cliente para que reciba notificaciones. El nombre de usuario autenticado se agrega automáticamente al registro como etiqueta.
  • Envío de notificaciones a los clientes: se agrega un controlador para que los usuarios puedan desencadenar una inserción segura en los dispositivos y clientes asociados con la etiqueta.

Realice las siguientes acciones para crear el nuevo back-end de API web de ASP.NET Core 6.0:

Para comprobarlo, inicie Visual Studio. En el menú Herramientas, seleccione Extensiones y actualizaciones. Busque el Administrador de paquetes NuGet correspondiente a su versión de Visual Studio y asegúrese de que tiene la versión más reciente. Si no es la más reciente, se debe desinstalar y volver a instalar el Administrador de paquetes NuGet.

Captura de pantalla del cuadro de diálogo Extensiones y actualizaciones con el paquete NuGet de administración para Visual Studio resaltado.

Nota

Asegúrese de que ha instalado el SDK de Azure para Visual Studio para la implementación de sitios web.

  1. Inicie Visual Studio o Visual Studio Express.

  2. Seleccione el Explorador de servidores e inicie sesión en su cuenta de Azure. Para crear los recursos del sitio web en su cuenta, tiene que iniciar sesión.

  3. En el menú Archivo de Visual Studio, seleccione Nuevo>Proyecto.

  4. Escriba API web en el cuadro de búsqueda.

  5. Seleccione la plantilla de proyecto ASP.NET Core Web API y, a continuación, Siguiente.

  6. En el cuadro de diálogo Configurar el nuevo proyecto, asigne al proyecto el nombre AppBackend y seleccione Siguiente.

  7. En el cuadro de diálogo Información adicional:

    • Confirme que el Marco es .NET 6.0 (Compatibilidad a largo plazo).
    • Confirme que la casilla Use controllers(uncheck to use minimal APIs) [Usar controladores (desactivar para usar API mínimas)] está activada.
    • Desactive Habilitar compatibilidad con OpenAPI.
    • Seleccione Crear.

Eliminación de los archivos de plantilla WeatherForecast

  1. Quite los archivos de ejemplo WeatherForecast.cs y Controllers/WeatherForecastController.cs del nuevo proyecto AppBackend.
  2. Abra Properties\launchSettings.json.
  3. Cambie las propiedades launchUrl de weatherforcast a appbackend.

En la ventana Configurar aplicación web de Microsoft Azure, seleccione una suscripción y, en la listaPlan de App Service, realice una de las siguientes acciones:

  • Seleccione un plan de Azure App Service que ya haya creado.
  • Seleccione Crear un nuevo plan de App Service para crear uno.

No es necesario una base de datos para este tutorial. Una vez seleccionado el plan de App Service, seleccione Aceptar para crear el proyecto.

Ventana Configurar aplicación web de Microsoft Azure

Si no ve esta página para configurar el plan de App Service, continúe con el tutorial. Se puede configurar al publicar la aplicación más adelante.

Autenticar clientes en el back-end de WebAPI

En esta sección se crea una clase de controlador de mensajes llamada AuthenticationTestHandler para el nuevo back-end. Esta clase deriva de DelegatingHandler y se agrega como controlador de mensajes para que procese todas las solicitudes que lleguen al back-end.

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto AppBackend, y seleccione Agregar y Clase.

  2. Asigne a la nueva clase el nombre AuthenticationTestHandler.cs y seleccione Agregar para generar la clase. Para simplificar, esta clase autentica a los usuarios con Autenticación básica. La aplicación puede utilizar cualquier esquema de autenticación.

  3. En AuthenticationTestHandler.cs, agregue las siguientes instrucciones using :

    using System.Net.Http;
    using System.Threading;
    using System.Security.Principal;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    
  4. En AuthenticationTestHandler.cs, reemplace la definición de clase AuthenticationTestHandler con el código siguiente:

    Este controlador autoriza la solicitud cuando se cumplen las tres condiciones siguientes:

    • La solicitud incluya un encabezado Autorización.
    • La solicitud utiliza la autenticación básica .
    • La cadena de nombre de usuario y la cadena de contraseña son la misma cadena.

    En caso contrario, la solicitud se rechaza. Esta autenticación no es un enfoque de autorización y autenticación real. Es solo un ejemplo sencillo para este tutorial.

    Si AuthenticationTestHandler autentica y autoriza el mensaje de solicitud, el usuario de autenticación básica se adjuntará a la solicitud actual en HttpContext. Otro controlador (RegisterController) usará después la información de usuario de HttpContext para agregar una etiqueta a la solicitud de registro de notificación.

    public class AuthenticationTestHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var authorizationHeader = request.Headers.GetValues("Authorization").First();
    
            if (authorizationHeader != null && authorizationHeader
                .StartsWith("Basic ", StringComparison.InvariantCultureIgnoreCase))
            {
                string authorizationUserAndPwdBase64 =
                    authorizationHeader.Substring("Basic ".Length);
                string authorizationUserAndPwd = Encoding.Default
                    .GetString(Convert.FromBase64String(authorizationUserAndPwdBase64));
                string user = authorizationUserAndPwd.Split(':')[0];
                string password = authorizationUserAndPwd.Split(':')[1];
    
                if (VerifyUserAndPwd(user, password))
                {
                    // Attach the new principal object to the current HttpContext object
                    HttpContext.Current.User =
                        new GenericPrincipal(new GenericIdentity(user), new string[0]);
                    System.Threading.Thread.CurrentPrincipal =
                        System.Web.HttpContext.Current.User;
                }
                else return Unauthorized();
            }
            else return Unauthorized();
    
            return base.SendAsync(request, cancellationToken);
        }
    
        private bool VerifyUserAndPwd(string user, string password)
        {
            // This is not a real authentication scheme.
            return user == password;
        }
    
        private Task<HttpResponseMessage> Unauthorized()
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);
            return tsc.Task;
        }
    }
    

    Nota

    Nota de seguridad: la clase AuthenticationTestHandler no proporciona una autenticación verdadera. Se utiliza únicamente para simular una autenticación básica y no es segura. Debe implementar un mecanismo de autenticación seguro en las aplicaciones y servicios de producción.

  5. Para registrar el controlador de mensajes, agregue el siguiente código al final del método Register en el archivo Program.cs:

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. Guarde los cambios.

Registrar notificaciones mediante el back-end de WebAPI

En esta sección, agregaremos un nuevo controlador al back-end de WebAPI para administrar las solicitudes de registro de un usuario y un dispositivo para que reciban notificaciones mediante la biblioteca cliente de los centros de notificaciones. El controlador agrega una etiqueta de usuario al usuario que el AuthenticationTestHandlerautenticó y adjuntó a HttpContext. La etiqueta tiene el formato de cadena, "username:<actual username>".

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto AppBackend y seleccione Administrar paquetes NuGet.

  2. En el panel izquierdo, seleccione En línea y en el cuadro Buscar, escriba Microsoft.Azure.NotificationHubs.

  3. En la lista de resultados, seleccione Microsoft Azure Notification Hubs e Instalar. Complete la instalación y cierre la ventana del Administrador de paquetes NuGet.

    Esta acción agrega una referencia al SDK de Azure Notification Hubs mediante el paquete NuGet Microsoft.Azure.NotificationHubs.

  4. Cree un archivo de clase que represente la conexión con el centro de notificaciones de envío. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Modelos, seleccione Agregar y Clase. Asigne el nombre a la nueva clase Notifications.cs y seleccione Agregar para generar la clase.

    Ventana Agregar nuevo elemento

  5. En Notifications.cs, agregue la siguiente instrucción using en la parte superior del archivo:

    using Microsoft.Azure.NotificationHubs;
    
  6. Reemplace la definición de clase Notifications con el código siguiente y los dos marcadores de posición con la cadena de conexión (de acceso total) del centro de notificaciones y el nombre del centro (disponible en el Portal de Azure):

    public class Notifications
    {
        public static Notifications Instance = new Notifications();
    
        public NotificationHubClient Hub { get; set; }
    
        private Notifications() {
            Hub = NotificationHubClient.CreateClientFromConnectionString("<your hub's DefaultFullSharedAccessSignature>",
                                                                            "<hub name>");
        }
    }
    

    Importante

    Escriba el nombre y elemento DefaultFullSharedAccessSignature del centro antes de continuar.

  7. A continuación, cree un controlador denominado RegisterController. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controladores, y seleccione Agregar y Controlador.

  8. Seleccione Controlador de API: en blanco y Agregar.

  9. En el cuadro Nombre del controlador, escriba RegisterController para denominar la nueva clase y seleccione Agregar.

    Ventana Agregar controlador.

  10. En RegisterController.cs, agregue las siguientes instrucciones using :

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. Agregue el siguiente código a la definición de clase RegisterController . En este código, agregamos una etiqueta de usuario para el usuario asociado a HttpContext. El usuario se autenticó y se asoció a HttpContext mediante un filtro de mensaje que agregó, AuthenticationTestHandler. También puede realizar comprobaciones opcionales para comprobar que el usuario puede registrarse para las etiquetas solicitadas.

    private NotificationHubClient hub;
    
    public RegisterController()
    {
        hub = Notifications.Instance.Hub;
    }
    
    public class DeviceRegistration
    {
        public string Platform { get; set; }
        public string Handle { get; set; }
        public string[] Tags { get; set; }
    }
    
    // POST api/register
    // This creates a registration id
    public async Task<string> Post(string handle = null)
    {
        string newRegistrationId = null;
    
        // make sure there are no existing registrations for this push handle (used for iOS and Android)
        if (handle != null)
        {
            var registrations = await hub.GetRegistrationsByChannelAsync(handle, 100);
    
            foreach (RegistrationDescription registration in registrations)
            {
                if (newRegistrationId == null)
                {
                    newRegistrationId = registration.RegistrationId;
                }
                else
                {
                    await hub.DeleteRegistrationAsync(registration);
                }
            }
        }
    
        if (newRegistrationId == null) 
            newRegistrationId = await hub.CreateRegistrationIdAsync();
    
        return newRegistrationId;
    }
    
    // PUT api/register/5
    // This creates or updates a registration (with provided channelURI) at the specified id
    public async Task<HttpResponseMessage> Put(string id, DeviceRegistration deviceUpdate)
    {
        RegistrationDescription registration = null;
        switch (deviceUpdate.Platform)
        {
            case "mpns":
                registration = new MpnsRegistrationDescription(deviceUpdate.Handle);
                break;
            case "wns":
                registration = new WindowsRegistrationDescription(deviceUpdate.Handle);
                break;
            case "apns":
                registration = new AppleRegistrationDescription(deviceUpdate.Handle);
                break;
            case "fcm":
                registration = new FcmRegistrationDescription(deviceUpdate.Handle);
                break;
            default:
                throw new HttpResponseException(HttpStatusCode.BadRequest);
        }
    
        registration.RegistrationId = id;
        var username = HttpContext.Current.User.Identity.Name;
    
        // add check if user is allowed to add these tags
        registration.Tags = new HashSet<string>(deviceUpdate.Tags);
        registration.Tags.Add("username:" + username);
    
        try
        {
            await hub.CreateOrUpdateRegistrationAsync(registration);
        }
        catch (MessagingException e)
        {
            ReturnGoneIfHubResponseIsGone(e);
        }
    
        return Request.CreateResponse(HttpStatusCode.OK);
    }
    
    // DELETE api/register/5
    public async Task<HttpResponseMessage> Delete(string id)
    {
        await hub.DeleteRegistrationAsync(id);
        return Request.CreateResponse(HttpStatusCode.OK);
    }
    
    private static void ReturnGoneIfHubResponseIsGone(MessagingException e)
    {
        var webex = e.InnerException as WebException;
        if (webex.Status == WebExceptionStatus.ProtocolError)
        {
            var response = (HttpWebResponse)webex.Response;
            if (response.StatusCode == HttpStatusCode.Gone)
                throw new HttpRequestException(HttpStatusCode.Gone.ToString());
        }
    }
    
  12. Guarde los cambios.

Enviar notificaciones desde el back-end de WebAPI

En esta sección se agrega un nuevo controlador que expone una manera de enviar notificaciones para los dispositivos cliente. La notificación se basa en la etiqueta de nombre de usuario que usa la biblioteca .NET de Microsoft Azure Notification Hubs en el back-end de ASP.NET WebAPI.

  1. Cree otro controlador denominado NotificationsController del mismo modo que creó RegisterController en la sección anterior.

  2. En NotificationsController.cs, agregue las siguientes instrucciones using :

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. Agregue el método siguiente a la clase NotificationsController:

    Este código envía un tipo de notificación basado en el parámetro pns del servicio Sistema de notificación de plataforma (PNS). El valor de to_tag se usa para definir la etiqueta username en el mensaje. Esta etiqueta debe coincidir con una etiqueta de nombre de usuario de un registro de un centro de notificaciones activo. El mensaje de notificación se extrae del cuerpo de la solicitud POST y se formatea para el PNS de destino.

    Dependiendo del PNS que usen los dispositivos compatibles para recibir notificaciones, se admiten distintos formatos. Por ejemplo, en dispositivos Windows, puede usar una notificación del sistema con WNS que no sea compatible directamente con otro PNS. En este caso, el back-end debe dar un formato a la notificación que sea compatible con el PNS de los dispositivos que prevea admitir. Posteriormente, use la API de envío adecuada en la clase NotificationHubClient.

    public async Task<HttpResponseMessage> Post(string pns, [FromBody]string message, string to_tag)
    {
        var user = HttpContext.Current.User.Identity.Name;
        string[] userTag = new string[2];
        userTag[0] = "username:" + to_tag;
        userTag[1] = "from:" + user;
    
        Microsoft.Azure.NotificationHubs.NotificationOutcome outcome = null;
        HttpStatusCode ret = HttpStatusCode.InternalServerError;
    
        switch (pns.ToLower())
        {
            case "wns":
                // Windows 8.1 / Windows Phone 8.1
                var toast = @"<toast><visual><binding template=""ToastText01""><text id=""1"">" + 
                            "From " + user + ": " + message + "</text></binding></visual></toast>";
                outcome = await Notifications.Instance.Hub.SendWindowsNativeNotificationAsync(toast, userTag);
                break;
            case "apns":
                // iOS
                var alert = "{\"aps\":{\"alert\":\"" + "From " + user + ": " + message + "\"}}";
                outcome = await Notifications.Instance.Hub.SendAppleNativeNotificationAsync(alert, userTag);
                break;
            case "fcm":
                // Android
                var notif = "{ \"data\" : {\"message\":\"" + "From " + user + ": " + message + "\"}}";
                outcome = await Notifications.Instance.Hub.SendFcmNativeNotificationAsync(notif, userTag);
                break;
        }
    
        if (outcome != null)
        {
            if (!((outcome.State == Microsoft.Azure.NotificationHubs.NotificationOutcomeState.Abandoned) ||
                (outcome.State == Microsoft.Azure.NotificationHubs.NotificationOutcomeState.Unknown)))
            {
                ret = HttpStatusCode.OK;
            }
        }
    
        return Request.CreateResponse(ret);
    }
    
  4. Para ejecutar la aplicación y garantizar que hasta ahora todo es correcto, seleccione la tecla F5. La aplicación abre un explorador web y se muestra en la página de inicio de ASP.NET.

Publicación del nuevo back-end de WebAPI

A continuación implementaremos la aplicación en un sitio web de Azure para que todos los dispositivos puedan acceder a ella.

  1. Haga clic con el botón derecho en el proyecto AppBackend y seleccione Publicar.

  2. Seleccione Microsoft Azure App Service como destino de publicación y, luego, \*\*Publicar. La ventana Crear servicio de aplicaciones se abre. Aquí puede crear todos los recursos de Azure necesarios para ejecutar la aplicación web ASP.NET en Azure.

    Icono de Microsoft Azure App Service

  3. En la ventana Crear servicio de aplicaciones, seleccione la cuenta de Azure. Seleccione Cambiar tipo>Aplicación web. Mantenga el Nombre de aplicación web predeterminado y seleccione los valores de Suscripción, Grupo de recursos y Plan de App Service.

  4. Seleccione Crear.

  5. Anote la propiedad Dirección URL del sitio de la sección Resumen. Esta dirección URL es el punto de conexión de back-end que se utilizará más adelante en el tutorial.

  6. Seleccione Publicar.

Una vez completado el asistente, este publica la aplicación web ASP.NET en Azure y la abre en el explorador predeterminado. La aplicación se puede ver en Azure App Services.

La dirección URL usa el nombre de la aplicación web que especificó anteriormente, con el formato http://<app_name>.azurewebsites.net.

Modificación de la aplicación iOS

  1. Abra la aplicación de vista de una página que creó en el tutorial Envío de notificaciones push a aplicaciones iOS mediante Azure Notification Hubs.

    Nota

    En esta sección se asume que configuró el proyecto con un nombre de organización vacío. Si no es así, debe anteponer el nombre de la organización a todos los nombres de clase.

  2. En el archivo Main.storyboard, agregue los componentes que se muestran en la captura de pantalla desde la biblioteca de objetos.

    Edición del guion gráfico en el generador de interfaz de Xcode

    • Nombre de usuario: campo de texto de la interfaz de usuario con el texto de marcador de posición, Escriba el nombre de usuario, inmediatamente debajo de la etiqueta de resultados de envío y limitado a los márgenes izquierdo y derecho y por debajo de la etiqueta de envío de resultados.

    • Contraseña: campo de texto de la interfaz de usuario con al texto de marcador de posición, Escriba la contraseña, inmediatamente debajo del campo de texto de no nombre de usuario y limitado a los márgenes izquierdo y derecho y por debajo del campo de texto del nombre de usuario. Active la opción Entrada de texto seguro en el Inspector de atributos, en Devolver clave.

    • Iniciar la sesión: botón de la interfaz de usuario inmediatamente debajo del campo de texto de contraseña y desactive la opción Habilitado del Inspector de atributos, debajo de Control-Content

    • WNS: etiqueta y modificador para habilitar el envío de la notificación del Servicio de notificaciones de Windows si se ha instalado en el centro. Consulte el tutorial introductorio de Windows.

    • GCM: etiqueta y modificador para habilitar el envío de la notificación a Google Cloud Messaging si se ha instalado en el centro. Consulte el tutorial introductorio de Android .

    • APNS: etiqueta y modificador para habilitar el envío de la notificación al Servicio de notificaciones de la plataforma Apple.

    • Nombre de usuario destinatario: campo de texto de la interfaz de usuario con texto de marcador de posición, Etiqueta de nombre de usuario destinatario, inmediatamente debajo de la etiqueta GCM y limitado a los márgenes izquierdo y derecho y por debajo de la etiqueta GCM.

      Se agregaron algunos componentes al tutorial Envío de notificaciones push a aplicaciones iOS mediante Azure Notification Hubs.

  3. Presione Ctrl y arrastre desde los componentes de la vista hasta ViewController.h y agregue estas nuevas salidas:

    @property (weak, nonatomic) IBOutlet UITextField *UsernameField;
    @property (weak, nonatomic) IBOutlet UITextField *PasswordField;
    @property (weak, nonatomic) IBOutlet UITextField *RecipientField;
    @property (weak, nonatomic) IBOutlet UITextField *NotificationField;
    
    // Used to enable the buttons on the UI
    @property (weak, nonatomic) IBOutlet UIButton *LogInButton;
    @property (weak, nonatomic) IBOutlet UIButton *SendNotificationButton;
    
    // Used to enabled sending notifications across platforms
    @property (weak, nonatomic) IBOutlet UISwitch *WNSSwitch;
    @property (weak, nonatomic) IBOutlet UISwitch *GCMSwitch;
    @property (weak, nonatomic) IBOutlet UISwitch *APNSSwitch;
    
    - (IBAction)LogInAction:(id)sender;
    
  4. En ViewController.h, agregue el siguiente elemento #define después de las instrucciones de importación. Sustituya el marcador de posición <Your backend endpoint> por la dirección URL de destino que usó para implementar el back-end de la aplicación en la sección anterior. Por ejemplo, http://your_backend.azurewebsites.net:

    #define BACKEND_ENDPOINT @"<Your backend endpoint>"
    
  5. En el proyecto, cree una nueva clase Cocoa Touch denominada RegisterClient que interactúe con el back-end de ASP.NET que ha creado. Cree la clase que hereda de NSObject. Después, agregue el siguiente código a RegisterClient.h:

    @interface RegisterClient : NSObject
    
    @property (strong, nonatomic) NSString* authenticationHeader;
    
    -(void) registerWithDeviceToken:(NSData*)token tags:(NSSet*)tags
        andCompletion:(void(^)(NSError*))completion;
    
    -(instancetype) initWithEndpoint:(NSString*)Endpoint;
    
    @end
    
  6. En RegisterClient.m, actualice la sección @interface:

    @interface RegisterClient ()
    
    @property (strong, nonatomic) NSURLSession* session;
    @property (strong, nonatomic) NSURLSession* endpoint;
    
    -(void) tryToRegisterWithDeviceToken:(NSData*)token tags:(NSSet*)tags retry:(BOOL)retry
                andCompletion:(void(^)(NSError*))completion;
    -(void) retrieveOrRequestRegistrationIdWithDeviceToken:(NSString*)token
                completion:(void(^)(NSString*, NSError*))completion;
    -(void) upsertRegistrationWithRegistrationId:(NSString*)registrationId deviceToken:(NSString*)token
                tags:(NSSet*)tags andCompletion:(void(^)(NSURLResponse*, NSError*))completion;
    
    @end
    
  7. Reemplace la sección @implementation en RegisterClient.m con el código siguiente:

    @implementation RegisterClient
    
    // Globals used by RegisterClient
    NSString *const RegistrationIdLocalStorageKey = @"RegistrationId";
    
    -(instancetype) initWithEndpoint:(NSString*)Endpoint
    {
        self = [super init];
        if (self) {
            NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
            _session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
            _endpoint = Endpoint;
        }
        return self;
    }
    
    -(void) registerWithDeviceToken:(NSData*)token tags:(NSSet*)tags
                andCompletion:(void(^)(NSError*))completion
    {
        [self tryToRegisterWithDeviceToken:token tags:tags retry:YES andCompletion:completion];
    }
    
    -(void) tryToRegisterWithDeviceToken:(NSData*)token tags:(NSSet*)tags retry:(BOOL)retry
                andCompletion:(void(^)(NSError*))completion
    {
        NSSet* tagsSet = tags?tags:[[NSSet alloc] init];
    
        NSString *deviceTokenString = [[token description]
            stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
        deviceTokenString = [[deviceTokenString stringByReplacingOccurrencesOfString:@" " withString:@""]
                                uppercaseString];
    
        [self retrieveOrRequestRegistrationIdWithDeviceToken: deviceTokenString
            completion:^(NSString* registrationId, NSError *error) {
            NSLog(@"regId: %@", registrationId);
            if (error) {
                completion(error);
                return;
            }
    
            [self upsertRegistrationWithRegistrationId:registrationId deviceToken:deviceTokenString
                tags:tagsSet andCompletion:^(NSURLResponse * response, NSError *error) {
                if (error) {
                    completion(error);
                    return;
                }
    
                NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
                if (httpResponse.statusCode == 200) {
                    completion(nil);
                } else if (httpResponse.statusCode == 410 && retry) {
                    [self tryToRegisterWithDeviceToken:token tags:tags retry:NO andCompletion:completion];
                } else {
                    NSLog(@"Registration error with response status: %ld", (long)httpResponse.statusCode);
    
                    completion([NSError errorWithDomain:@"Registration" code:httpResponse.statusCode
                                userInfo:nil]);
                }
    
            }];
        }];
    }
    
    -(void) upsertRegistrationWithRegistrationId:(NSString*)registrationId deviceToken:(NSData*)token
                tags:(NSSet*)tags andCompletion:(void(^)(NSURLResponse*, NSError*))completion
    {
        NSDictionary* deviceRegistration = @{@"Platform" : @"apns", @"Handle": token,
                                                @"Tags": [tags allObjects]};
        NSData* jsonData = [NSJSONSerialization dataWithJSONObject:deviceRegistration
                            options:NSJSONWritingPrettyPrinted error:nil];
    
        NSLog(@"JSON registration: %@", [[NSString alloc] initWithData:jsonData
                                            encoding:NSUTF8StringEncoding]);
    
        NSString* endpoint = [NSString stringWithFormat:@"%@/api/register/%@", _endpoint,
                                registrationId];
        NSURL* requestURL = [NSURL URLWithString:endpoint];
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestURL];
        [request setHTTPMethod:@"PUT"];
        [request setHTTPBody:jsonData];
        NSString* authorizationHeaderValue = [NSString stringWithFormat:@"Basic %@",
                                                self.authenticationHeader];
        [request setValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    
        NSURLSessionDataTask* dataTask = [self.session dataTaskWithRequest:request
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            if (!error)
            {
                completion(response, error);
            }
            else
            {
                NSLog(@"Error request: %@", error);
                completion(nil, error);
            }
        }];
        [dataTask resume];
    }
    
    -(void) retrieveOrRequestRegistrationIdWithDeviceToken:(NSString*)token
                completion:(void(^)(NSString*, NSError*))completion
    {
        NSString* registrationId = [[NSUserDefaults standardUserDefaults]
                                    objectForKey:RegistrationIdLocalStorageKey];
    
        if (registrationId)
        {
            completion(registrationId, nil);
            return;
        }
    
        // request new one & save
        NSURL* requestURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/api/register?handle=%@",
                                _endpoint, token]];
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestURL];
        [request setHTTPMethod:@"POST"];
        NSString* authorizationHeaderValue = [NSString stringWithFormat:@"Basic %@",
                                                self.authenticationHeader];
        [request setValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"];
    
        NSURLSessionDataTask* dataTask = [self.session dataTaskWithRequest:request
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
            if (!error && httpResponse.statusCode == 200)
            {
                NSString* registrationId = [[NSString alloc] initWithData:data
                    encoding:NSUTF8StringEncoding];
    
                // remove quotes
                registrationId = [registrationId substringWithRange:NSMakeRange(1,
                                    [registrationId length]-2)];
    
                [[NSUserDefaults standardUserDefaults] setObject:registrationId
                    forKey:RegistrationIdLocalStorageKey];
                [[NSUserDefaults standardUserDefaults] synchronize];
    
                completion(registrationId, nil);
            }
            else
            {
                NSLog(@"Error status: %ld, request: %@", (long)httpResponse.statusCode, error);
                if (error)
                    completion(nil, error);
                else {
                    completion(nil, [NSError errorWithDomain:@"Registration" code:httpResponse.statusCode
                                userInfo:nil]);
                }
            }
        }];
        [dataTask resume];
    }
    
    @end
    

    Este código implementa la lógica explicada en el artículo de referencia Registro desde el back-end de la aplicación usando NSURLSession para realizar llamadas REST al back-end de la aplicación y NSUserDefaults para almacenar localmente el identificador de registro devuelto por el Centro de notificaciones.

    Para que funcione correctamente, esta clase requiere que su propiedad authorizationHeaderesté establecida. Esta propiedad la establecer la clase ViewController después del inicio de sesión.

  8. En ViewController.h, agregue una instrucción #import para RegisterClient.h. A continuación, agregue una declaración para el token del dispositivo y haga referencia a una instancia RegisterClient en la sección @interface:

    #import "RegisterClient.h"
    
    @property (strong, nonatomic) NSData* deviceToken;
    @property (strong, nonatomic) RegisterClient* registerClient;
    
  9. En ViewController.m, agregue una declaración del método privado en la sección @interface :

    @interface ViewController () <UITextFieldDelegate, NSURLConnectionDataDelegate, NSXMLParserDelegate>
    
    // create the Authorization header to perform Basic authentication with your app back-end
    -(void) createAndSetAuthenticationHeaderWithUsername:(NSString*)username
                    AndPassword:(NSString*)password;
    
    @end
    

    Nota

    El siguiente fragmento de código no es un esquema de autenticación seguro; debe sustituir la implementación de createAndSetAuthenticationHeaderWithUsername:AndPassword: por un mecanismo de autenticación específico que genere un token de autenticación que consumirá la clase de cliente del registro, por ejemplo, OAuth, Active Directory, etc.

  10. A continuación, en la sección @implementation de ViewController.m, agregue el siguiente código que agrega la implementación para establecer el encabezado de autenticación y el token del dispositivo.

    -(void) setDeviceToken: (NSData*) deviceToken
    {
        _deviceToken = deviceToken;
        self.LogInButton.enabled = YES;
    }
    
    -(void) createAndSetAuthenticationHeaderWithUsername:(NSString*)username
                    AndPassword:(NSString*)password;
    {
        NSString* headerValue = [NSString stringWithFormat:@"%@:%@", username, password];
    
        NSData* encodedData = [[headerValue dataUsingEncoding:NSUTF8StringEncoding] base64EncodedDataWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
    
        self.registerClient.authenticationHeader = [[NSString alloc] initWithData:encodedData
                                                    encoding:NSUTF8StringEncoding];
    }
    
    -(BOOL)textFieldShouldReturn:(UITextField *)textField
    {
        [textField resignFirstResponder];
        return YES;
    }
    

    Observe cómo al establecer el token del dispositivo se habilita el botón Iniciar sesión. Esto se debe a que, como una parte de la acción de inicio de sesión, el controlador de vista registrará notificaciones de inserción con el back-end de la aplicación. Es preferible que la acción Iniciar sesión no sea accesible hasta que el token del dispositivo se haya configurado correctamente. Es posible que desacople el inicio de sesión del registro de inserción, siempre que lo primero ocurra antes que lo último.

  11. En ViewController.m, use los siguientes fragmentos de código para implementar el método de acción para el botón Iniciar sesión y un método para enviar el mensaje de notificación con el back-end ASP.NET.

    - (IBAction)LogInAction:(id)sender {
        // create authentication header and set it in register client
        NSString* username = self.UsernameField.text;
        NSString* password = self.PasswordField.text;
    
        [self createAndSetAuthenticationHeaderWithUsername:username AndPassword:password];
    
        __weak ViewController* selfie = self;
        [self.registerClient registerWithDeviceToken:self.deviceToken tags:nil
            andCompletion:^(NSError* error) {
            if (!error) {
                dispatch_async(dispatch_get_main_queue(),
                ^{
                    selfie.SendNotificationButton.enabled = YES;
                    [self MessageBox:@"Success" message:@"Registered successfully!"];
                });
            }
        }];
    }
    
    - (void)SendNotificationASPNETBackend:(NSString*)pns UsernameTag:(NSString*)usernameTag
                Message:(NSString*)message
    {
        NSURLSession* session = [NSURLSession
            sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:nil
            delegateQueue:nil];
    
        // Pass the pns and username tag as parameters with the REST URL to the ASP.NET backend
        NSURL* requestURL = [NSURL URLWithString:[NSString
            stringWithFormat:@"%@/api/notifications?pns=%@&to_tag=%@", BACKEND_ENDPOINT, pns,
            usernameTag]];
    
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestURL];
        [request setHTTPMethod:@"POST"];
    
        // Get the mock authenticationheader from the register client
        NSString* authorizationHeaderValue = [NSString stringWithFormat:@"Basic %@",
            self.registerClient.authenticationHeader];
        [request setValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"];
    
        //Add the notification message body
        [request setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding]];
    
        // Execute the send notification REST API on the ASP.NET Backend
        NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
            if (error || httpResponse.statusCode != 200)
            {
                NSString* status = [NSString stringWithFormat:@"Error Status for %@: %d\nError: %@\n",
                                    pns, httpResponse.statusCode, error];
                dispatch_async(dispatch_get_main_queue(),
                ^{
                    // Append text because all 3 PNS calls may also have information to view
                    [self.sendResults setText:[self.sendResults.text stringByAppendingString:status]];
                });
                NSLog(status);
            }
    
            if (data != NULL)
            {
                xmlParser = [[NSXMLParser alloc] initWithData:data];
                [xmlParser setDelegate:self];
                [xmlParser parse];
            }
        }];
        [dataTask resume];
    }
    
  12. Actualice la acción para que el botón Enviar notificación utilice el back-end ASP.NET y envíela a cualquier PNS habilitada por un modificador.

    - (IBAction)SendNotificationMessage:(id)sender
    {
        //[self SendNotificationRESTAPI];
        [self SendToEnabledPlatforms];
    }
    
    -(void)SendToEnabledPlatforms
    {
        NSString* json = [NSString stringWithFormat:@"\"%@\"",self.notificationMessage.text];
    
        [self.sendResults setText:@""];
    
        if ([self.WNSSwitch isOn])
            [self SendNotificationASPNETBackend:@"wns" UsernameTag:self.RecipientField.text Message:json];
    
        if ([self.GCMSwitch isOn])
            [self SendNotificationASPNETBackend:@"gcm" UsernameTag:self.RecipientField.text Message:json];
    
        if ([self.APNSSwitch isOn])
            [self SendNotificationASPNETBackend:@"apns" UsernameTag:self.RecipientField.text Message:json];
    }
    
  13. En la función ViewDidLoad, agregue el siguiente código para crear una instancia de RegisterClient y establezca el delegado de sus campos de texto.

    self.UsernameField.delegate = self;
    self.PasswordField.delegate = self;
    self.RecipientField.delegate = self;
    self.registerClient = [[RegisterClient alloc] initWithEndpoint:BACKEND_ENDPOINT];
    
  14. Ahora, en AppDelegate.m, quite todo el contenido del método application:didRegisterForPushNotificationWithDeviceToken: y reemplácelo por el siguiente (con el fin de asegurarse de que el controlador de vista contiene el token de dispositivo más reciente recuperado de los APN):

    // Add import to the top of the file
    #import "ViewController.h"
    
    - (void)application:(UIApplication *)application
                didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
        ViewController* rvc = (ViewController*) self.window.rootViewController;
        rvc.deviceToken = deviceToken;
    }
    
  15. Finalmente, en AppDelegate.m, asegúrese de que tiene el siguiente método:

    - (void)application:(UIApplication *)application didReceiveRemoteNotification: (NSDictionary *)userInfo {
        NSLog(@"%@", userInfo);
        [self MessageBox:@"Notification" message:[[userInfo objectForKey:@"aps"] valueForKey:@"alert"]];
    }
    

Prueba de la aplicación

  1. En XCode, ejecute la aplicación en un dispositivo iOS físico (las notificaciones de inserción no funcionan en el simulador).

  2. En la interfaz de usuario de la aplicación iOS, escriba el mismo valor para nombre de usuario y contraseña. A continuación, haga clic en Iniciar sesión.

    Prueba de la aplicación iOS

  3. Debería ver una ventana emergente que le informa del éxito de registro. Haga clic en OK.

    Notificación de prueba de iOS mostrada

  4. En el campo de texto para laetiqueta del nombre de usuario del destinatario , escriba la etiqueta del nombre de usuario usada con el registro desde otro dispositivo.

  5. Escriba un mensaje de notificación y haga clic en Enviar notificación. Solo los dispositivos que tienen un registro con la etiqueta de nombre de usuario del destinatario reciben el mensaje de notificación. Solo se envía a los usuarios.

    Notificación de prueba de iOS etiquetada

Pasos siguientes

En este tutorial, ha aprendido a enviar notificaciones push a usuarios concretos que tienen etiquetas asociadas a sus registros. Para aprender a enviar notificaciones push en función de la ubicación, pase al tutorial siguiente: