Tutorial: Enviar notificações push a utilizadores específicos através dos Hubs de Notificação do Azure

Este tutorial mostra-lhe como utilizar os Hubs de Notificação do Azure para enviar notificações push para um utilizador específico da aplicação num dispositivo específico. É utilizado um ASP.NET back-end webAPI para autenticar clientes e gerar notificações, conforme mostrado no tópico de orientação Registar a partir do back-end da aplicação.

Neste tutorial, siga os seguintes passos:

  • Criar o Projeto WebAPI
  • Autenticar clientes no back-end de WebAPI
  • Utilizar o back-end de WebAPI para registar notificações
  • Enviar notificações a partir do back-end de WebAPI
  • Publicar o back-end de WebAPI novo
  • Modificar a sua aplicação iOS
  • Testar a aplicação

Pré-requisitos

Este tutorial pressupõe que criou e configurou o seu hub de notificação, conforme descrito em Enviar notificações push para aplicações iOS com os Hubs de Notificação do Azure. Este tutorial também é o pré-requisito do tutorial Secure Push (iOS ). Se quiser utilizar as Aplicações Móveis como o seu serviço de back-end, consulte o artigo Introdução às Aplicações Móveis com Push.

Criar o Projeto WebAPI

As secções seguintes abordam a criação de um novo back-end de ASP.NET WebAPI. Este processo tem três objetivos principais:

  • Autenticar os clientes: é adicionado um processador de mensagens para autenticar os pedidos dos clientes e associar os utilizadores aos pedidos.
  • Registar-se para receber notificações ao utilizar o back-end de WebAPI: é adicionado um controlador para processar registos novos, para que um dispositivo de cliente receba notificações. O nome de utilizador autenticado é adicionado automaticamente ao registo como uma etiqueta.
  • Enviar notificações para os clientes: é adicionado um controlador para permitir que os utilizadores acionem um envio seguro para dispositivos e clientes associados à etiqueta.

Crie o novo back-end da API Web ASP.NET Core 6.0 efetuando as seguintes ações:

Para confirmar, inicie o Visual Studio. No menu Ferramentas, selecione Extensões e Atualizações. Procure o Gestor de Pacotes de NuGet na sua versão do Visual Studio e verifique se tem a versão mais recente. Se a sua versão não for a versão mais recente, desinstale-a e, em seguida, reinstale o Gestor de Pacotes de NuGet.

Captura de ecrã da caixa de diálogo Extensões e Atualizações com o pacote NuGet Manage for Visual Studios realçado.

Nota

Certifique-se de que instalou o SDK do Azure do Visual Studio para implementação de sites.

  1. Inicie o Visual Studio ou o Visual Studio Express.

  2. Selecione Explorador de Servidores e inicie sessão na sua conta do Azure. Para criar os recursos do site na sua conta, tem de ter sessão iniciada.

  3. No menu Ficheiro do Visual Studio, selecione Novo>Projeto.

  4. Introduza API Web na caixa de pesquisa.

  5. Selecione o modelo de projeto da API Web ASP.NET Core e selecione Seguinte.

  6. Na caixa de diálogo Configurar o novo projeto , atribua o nome AppBackend ao projeto e selecione Seguinte.

  7. Na caixa de diálogo Informações adicionais :

    • Confirme que o Framework é .NET 6.0 (suporte a longo prazo).
    • Confirme se a caixa de verificação Utilizar controladores (desmarcar para utilizar APIs mínimas) está selecionada.
    • Desmarque Ativar suporte openAPI.
    • Selecione Criar.

Remover os ficheiros de modelo WeatherForecast

  1. Remova os ficheiros de exemplo WeatherForecast.cs e Controllers/WeatherForecastController.cs do novo projeto AppBackend .
  2. Abra Properties\launchSettings.json.
  3. Altere as propriedades launchUrl de weatherforcast para appbackend.

Na janela Configurar Aplicação Web do Microsoft Azure, selecione uma subscrição e, em seguida, na lista Plano do Serviço de Aplicações, efetue uma das seguintes ações:

  • Selecione um Serviço de Aplicações do Azure plano que já criou.
  • Selecione Criar um novo plano do serviço de aplicações e, em seguida, crie um.

Não precisa de uma base de dados para este tutorial. Depois de selecionar o seu plano do serviço de aplicações, selecione OK para criar o projeto.

A janela Configurar Aplicação Web do Microsoft Azure

Se não vir esta página para configurar o plano do serviço de aplicações, continue com o tutorial. Pode configurá-la enquanto publica a aplicação mais tarde.

Autenticar clientes no back-end de WebAPI

Nesta secção, crie uma nova classe de processadores de mensagens com o nome AuthenticationTestHandler para o back-end novo. Esta classe é derivada de DelegatingHandler e adicionada como um processador de mensagens, para processar todos os pedidos enviados para o back-end.

  1. No Explorador de Soluções, clique com o botão direito do rato no projeto AppBackend, selecione Adicionar e, em seguida, selecione Classe.

  2. Dê o nome AuthenticationTestHandler.cs à classe e selecione Adicionar para gerar a classe. Esta classe autentica utilizadores com a Autenticação Básica, para simplicidade. A sua aplicação pode utilizar qualquer esquema de autenticação.

  3. No AuthenticationTestHandler.cs, adicione as instruções using seguintes:

    using System.Net.Http;
    using System.Threading;
    using System.Security.Principal;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    
  4. No AuthenticationTestHandler.cs, substitua a definição de classe AuthenticationTestHandler pelo seguinte código:

    O processador autoriza o pedido quando se verificarem as três condições seguintes:

    • O pedido inclui o cabeçalho Autorização.
    • O pedido utiliza a autenticação básica.
    • A cadeia de nome de utilizador e a cadeia de palavra-passe são a mesma cadeia.

    Caso contrário, o pedido é rejeitado. Esta autenticação não é uma verdadeira abordagem de autenticação e autorização. É apenas um exemplo simples para este tutorial.

    Se AuthenticationTestHandler autenticar e autorizar a mensagem do pedido, o utilizador de autenticação básica é anexado ao pedido atual em HttpContext. As informações do utilizador em HttpContext serão utilizadas por outro controlador (RegisterController) mais tarde, para adicionar uma etiqueta ao pedido de registo de notificação.

    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 segurança: a classe AuthenticationTestHandler não fornece uma autenticação verdadeira. É utilizada apenas para imitar a autenticação básica e não é segura. Tem de implementar um mecanismo de autenticação segura nos seus serviços e aplicações de produção.

  5. Para registar o processador de mensagens, adicione o seguinte código no final do Register método no ficheiro Program.cs :

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. Guarde as alterações.

Utilizar o back-end de WebAPI para registar notificações

Nesta secção, adicione um controlador novo ao back-end de WebAPI para processar pedidos de registo de um utilizador e de um dispositivo para as notificações, através da biblioteca de cliente dos hubs de notificação. O controlador adiciona uma etiqueta de utilizador para o utilizador que AuthenticationTestHandler autenticou e anexou a HttpContext. A etiqueta tem o formato de cadeia "username:<actual username>".

  1. No Explorador de Soluções, clique com o botão direito no projeto AppBackend e, em seguida, selecione Gerir Pacotes de NuGet.

  2. No painel esquerdo, selecione Online e, na caixa Procurar, escreva Microsoft.Azure.NotificationHubs.

  3. Na lista de resultados, selecione Hubs de Notificação do Microsoft Azure e, em seguida, selecione Instalar. Conclua a instalação e feche a janela Gestor de Pacotes de NuGet.

    Esta ação adiciona uma referência ao SDK dos Hubs de Notificação do Azure mediante a utilização do Pacote NuGet Microsoft.Azure.Notification Hubs.

  4. Crie um ficheiro de classe novo que represente a ligação ao hub de notificação utilizado para enviar notificações. No Explorador de Soluções, clique com o botão direito do rato na pasta Modelos, selecione Adicionar e, em seguida, selecione Classe. Dê o nome Notifications.cs à classe nova e selecione Adicionar para gerá-la.

    A janela Adicionar Novo Item

  5. Em Notifications.cs, adicione a instrução using à parte superior do ficheiro:

    using Microsoft.Azure.NotificationHubs;
    
  6. Substitua a definição de classe Notifications pelo código seguinte e substitua os dois marcadores de posição pela cadeia de ligação (com acesso total) do seu hub de notificação e o nome do hub (disponível no portal do 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

    Introduza o nome e DefaultFullSharedAccessSignature do seu hub antes de continuar.

  7. Em seguida, crie um controlador novo com o nome RegisterController. No Explorador de Soluções, clique com o botão direito do rato na pasta Controladores, selecione Adicionar e, em seguida, selecione Controlador.

  8. Selecione Controlador de API – Vazio e, em seguida, selecione Adicionar.

  9. Na caixa Nome do controlador, escreva RegisterController para designar a classe nova e selecione Adicionar.

    A janela Adicionar Controlador.

  10. No RegisterController.cs, adicione as instruções using seguintes:

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. Adicione o código seguinte dentro da definição de classe RegisterController. Neste código, adicione uma etiqueta de utilizador para o utilizador que está anexado a HttpContext. O utilizador foi autenticado e anexado a HttpContext através do filtro de mensagem que adicionou, AuthenticationTestHandler. Também pode adicionar verificações opcionais para confirmar que o utilizador tem direitos para se registar nas etiquetas pedidas.

    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 as alterações.

Enviar notificações a partir do back-end de WebAPI

Nesta secção, irá adicionar um novo controlador que indica uma forma de os dispositivos de cliente enviarem uma notificação. A notificação baseia-se na etiqueta de nome de utilizador que utiliza a Biblioteca .NET dos Hubs de Notificação do Azure no back-end de ASP.NET WebAPI.

  1. Crie outro controlador novo com o nome NotificationsController da mesma forma que criou o RegisterController na secção anterior.

  2. No NotificationsController.cs, adicione as instruções using seguintes:

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. Adicione o método seguinte à classe NotificationsController:

    Este código envia um tipo de notificação com base no parâmetro pns do Serviço de Notificação de Plataforma (PNS). O valor de to_tag é utilizado para definir a etiqueta nome de utilizador na mensagem. Esta etiqueta tem de corresponder a uma etiqueta de nome de utilizador de um registo de hub de notificação ativo. A mensagem da notificação é retirada do corpo do pedido POST e formatada para o PNS de destino.

    Consoante o PNS que os seus dispositivos suportados utilizam para receber notificações, estas são suportadas com vários formatos. Por exemplo, em dispositivos Windows, poderá utilizar uma notificação de alerta com WNS que não seja diretamente suportada por outro PNS. Nesse caso, o seu back-end tem de formatar a notificação de modo a que seja suportada pelo PNS dos dispositivos que pretende incluir. Em seguida, utilize a API de envio adequada na classe 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 executar a aplicação e garantir a precisão do seu trabalho até ao momento, prima a tecla F5. A aplicação abre um browser e é apresentada na home page do ASP.NET.

Publicar o back-end de WebAPI novo

Em seguida, implemente a aplicação num site do Azure para que seja acessível a partir de todos os dispositivos.

  1. Clique com o botão direito do rato no projeto AppBackend e selecione Publicar.

  2. Selecione Serviço de Aplicações do Microsoft Azure como o destino da publicação e selecione \*\*Publicar. A janela Criar Serviço de Aplicações é aberta. Aí, pode criar todos os recursos do Azure necessários para executar a aplicação Web ASP.NET no Azure.

    O mosaico do Serviço de Aplicações do Microsoft Azure

  3. Na janela Criar Serviço de Aplicações, selecione a sua conta do Azure. Selecione Alterar Tipo de>Aplicação Web. Mantenha o Nome da Aplicação Web predefinido e, em seguida, selecione a Subscrição, o Grupo de Recursos e o Plano do Serviço de Aplicações.

  4. Selecione Criar.

  5. Anote a propriedade URL do Site, na secção Resumo. Este URL será o seu ponto final de back-end mais adiante no tutorial.

  6. Selecione Publicar.

Depois de concluir o assistente, este publica a aplicação Web ASP.NET no Azure e, em seguida, abre a aplicação no browser predefinido. A aplicação é visualizável nos Serviços Aplicacionais do Azure.

O URL utiliza o nome da aplicação Web que especificou anteriormente, com o formato http://< app_name.azurewebsites.net>.

Modificar a sua aplicação iOS

  1. Abra a aplicação Vista de Página Única que criou no tutorial Enviar notificações push para aplicações iOS com os Hubs de Notificação do Azure .

    Nota

    Esta secção pressupõe que o projeto está configurado com um nome de organização vazio. Caso contrário, prepare o nome da sua organização para todos os nomes de classes.

  2. Main.storyboard No ficheiro, adicione os componentes apresentados na captura de ecrã da biblioteca de objetos.

    Editar guião gráfico no construtor de interfaces Xcode

    • Nome de utilizador: um UITextField com texto de marcador de posição, Introduza Nome de Utilizador, imediatamente abaixo da etiqueta enviar resultados e restrito às margens esquerda e direita e abaixo da etiqueta enviar resultados.

    • Palavra-passe: um UITextField com texto de marcador de posição, Introduza Palavra-passe imediatamente abaixo do campo de texto do nome de utilizador e restrito às margens esquerda e direita e abaixo do campo de texto do nome de utilizador. Selecione a opção Entrada de Texto Seguro no Inspetor de Atributos, em Chave de Retorno.

    • Iniciar sessão: um UIButton etiquetado imediatamente abaixo do campo de texto da palavra-passe e desmarque a opção Ativado no Inspetor de Atributos, em Controlo-Conteúdo

    • WNS: identifique e mude para ativar o envio do Serviço de Notificação do Windows de notificação, caso tenha sido configurado no hub. Veja o tutorial do Windows Introdução.

    • GCM: etiquetar e mudar para ativar o envio da notificação para o Google Cloud Messaging, caso tenha sido configurada no hub. Veja Tutorial do Android Introdução.

    • APNS: etiquetar e mudar para ativar o envio da notificação para o Serviço de Notificação da Plataforma Apple.

    • Nome de Utilizador do Destinatário:Um UITextField com texto de marcador de posição, Etiqueta de nome de utilizador do destinatário, imediatamente abaixo da etiqueta GCM e restrito às margens esquerda e direita e abaixo da etiqueta GCM.

      Alguns componentes foram adicionados no tutorial Enviar notificações push para aplicações iOS com os Hubs de Notificação do Azure .

  3. Arraste ctrl dos componentes na vista para ViewController.h e adicione estas novas saídas:

    @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. Em ViewController.h, adicione o seguinte #define após as instruções de importação. Substitua o <Your backend endpoint> marcador de posição pelo URL de Destino que utilizou para implementar o back-end da aplicação na secção anterior. Por exemplo, http://your_backend.azurewebsites.net:

    #define BACKEND_ENDPOINT @"<Your backend endpoint>"
    
  5. No seu projeto, crie uma nova classe Cocoa Touch com o nome RegisterClient para interagir com a ASP.NET back-end que criou. Crie a classe que herda a partir de NSObject. Em seguida, adicione o seguinte código no 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. RegisterClient.mNa , atualize a @interface secção:

    @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. Substitua a @implementation secção no RegisterClient.m pelo seguinte código:

    @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 a lógica explicada no artigo de orientação Registar a partir do back-end da aplicação com NSURLSession para efetuar chamadas REST para o back-end da aplicação e NSUserDefaults para armazenar localmente o registrationId devolvido pelo hub de notificação.

    Esta classe requer que a respetiva propriedade authorizationHeader seja definida para funcionar corretamente. Esta propriedade é definida pela ViewController classe após o início de sessão.

  8. Em ViewController.h, adicione uma #import instrução para RegisterClient.h. Em seguida, adicione uma declaração para o token do dispositivo e uma referência a uma RegisterClient instância na @interface secção:

    #import "RegisterClient.h"
    
    @property (strong, nonatomic) NSData* deviceToken;
    @property (strong, nonatomic) RegisterClient* registerClient;
    
  9. Em ViewController.m, adicione uma declaração de método privado na @interface secção:

    @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

    O fragmento seguinte não é um esquema de autenticação seguro. Deve substituir a implementação do createAndSetAuthenticationHeaderWithUsername:AndPassword: pelo mecanismo de autenticação específico que gera um token de autenticação para ser consumido pela classe de cliente de registo, por exemplo, OAuth, Active Directory.

  10. Em seguida, na secção de , adicione o seguinte código, que adiciona a implementação para definir o token do dispositivo e o @implementation cabeçalho de ViewController.mautenticação.

    -(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;
    }
    

    Repare como a definição do token do dispositivo ativa o botão Iniciar sessão . Tal acontece porque, como parte da ação de início de sessão, o controlador de visualização regista as notificações push com o back-end da aplicação. Não pretende que a ação Iniciar sessão esteja acessível até que o token do dispositivo tenha sido configurado corretamente. Pode desassociar o início de sessão do registo push, desde que o primeiro aconteça antes deste último.

  11. Em ViewController.m, utilize os seguintes fragmentos para implementar o método de ação para o botão Iniciar sessão e um método para enviar a mensagem de notificação com o 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. Atualize a ação do botão Enviar Notificação para utilizar o ASP.NET back-end e enviar para qualquer PNS ativado por um comutador.

    - (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. ViewDidLoad Na função , adicione o seguinte para instanciar a RegisterClient instância e definir o delegado para os campos de texto.

    self.UsernameField.delegate = self;
    self.PasswordField.delegate = self;
    self.RecipientField.delegate = self;
    self.registerClient = [[RegisterClient alloc] initWithEndpoint:BACKEND_ENDPOINT];
    
  14. Agora, no AppDelegate.m, remova todo o conteúdo do método application:didRegisterForPushNotificationWithDeviceToken: e substitua-o pelo seguinte (para se certificar de que o controlador de vista contém o token de dispositivo mais recente obtido a partir de APNs):

    // 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. Por fim, no AppDelegate.m, certifique-se de que tem o seguinte método:

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

Testar a aplicação

  1. No XCode, execute a aplicação num dispositivo iOS físico (as notificações push não funcionam no simulador).

  2. Na IU da aplicação iOS, introduza o mesmo valor para nome de utilizador e palavra-passe. Em seguida, clique em Iniciar sessão.

    Aplicação de teste do iOS

  3. Deverá ver um pop-up a informá-lo sobre o êxito do registo. Clique em OK.

    Notificação de teste do iOS apresentada

  4. No campo de texto *Etiqueta de nome de utilizador do destinatário , introduza a etiqueta de nome de utilizador utilizada com o registo de outro dispositivo.

  5. Introduza uma mensagem de notificação e clique em Enviar Notificação. Apenas os dispositivos que têm um registo com a etiqueta de nome de utilizador do destinatário recebem a mensagem de notificação. Só é enviado para esses utilizadores.

    Notificação com etiquetas de teste do iOS

Passos seguintes

Neste tutorial, aprendeu a enviar notificações push para utilizadores específicos que têm etiquetas associadas aos respetivos registos. Para saber como enviar notificações push com base na localização, avance para o seguinte tutorial: