Esercitazione: Inviare notifiche push a utenti specifici usando Hub di notifica di Azure

In questa esercitazione viene illustrato come usare Hub di notifica di Azure per inviare notifiche push a un utente specifico dell'app su un dispositivo specifico. Per autenticare i client e generare le notifiche viene usato un back-end di API Web ASP.NET, come illustrato nell'argomento Registering from your app backend (Registrazione dal back-end dell'app).

In questa esercitazione vengono completati i passaggi seguenti:

  • Creare il progetto WebAPI
  • Autenticare client con il back-end WebAPI
  • Eseguire la registrazione per le notifiche tramite il back-end WebAPI
  • Inviare notifiche dal back-end WebAPI
  • Pubblicare il nuovo back-end WebAPI
  • Modificare l'app per iOS
  • Test dell'applicazione

Prerequisiti

Questa esercitazione presuppone che sia stato creato e configurato l'hub di notifica come descritto in Inviare notifiche push alle app iOS usando Hub di notifica di Azure. È inoltre propedeutica all'esercitazione Push sicuro (iOS) . Se si desidera usare le app per dispositivi mobili come servizio back-end, vedere Introduzione alle app per dispositivi mobili con notifiche push.

Creare il progetto WebAPI

Le sezioni seguenti illustrano la creazione di un nuovo back-end WebAPI ASP.NET. Questo processo ha tre obiettivi principali:

  • Autenticare i client: si aggiunge un gestore di messaggi per autenticare le richieste client e associare l'utente alla richiesta.
  • Eseguire la registrazione per le notifiche usando il back-end WebAPI: si aggiunge un controller per gestire le nuove registrazioni e consentire a un dispositivo client di ricevere le notifiche. Il nome utente autenticato verrà aggiunto automaticamente alla registrazione come tag.
  • Inviare notifiche ai client: si aggiunge un controller per consentire agli utenti di attivare un push sicuro per i dispositivi e i client associati al tag.

Creare il nuovo back-end api Web ASP.NET Core 6.0 eseguendo le azioni seguenti:

A questo scopo, avviare Visual Studio. Scegliere Estensioni e aggiornamenti dal menu Strumenti. Cercare Gestione pacchetti NuGet nella versione di Visual Studio e verificare che sia installata la versione più recente. Se la versione installata non è la più recente, disinstallarla e reinstallare Gestione pacchetti NuGet.

Screenshot della finestra di dialogo Estensioni e aggiornamenti con il pacchetto Gestione pacchetti NuGet per Visual Studio evidenziato.

Nota

Assicurarsi che sia installato Visual Studio Azure SDK per la distribuzione del sito Web.

  1. Avviare Visual Studio o Visual Studio Express.

  2. Selezionare Esplora server e accedere all'account Azure. Per creare le risorse del sito Web nell'account è necessario eseguire l'accesso.

  3. Nel menu File di Visual Studio selezionare Nuovo>progetto.

  4. Immettere l'API Web nella casella di ricerca.

  5. Selezionare il modello di progetto API Web ASP.NET Core e selezionare Avanti.

  6. Nella finestra di dialogo Configura il nuovo progetto assegnare al progetto il nome AppBackend e selezionare Avanti.

  7. Nella finestra di dialogo Informazioni aggiuntive :

    • Verificare che Framework sia .NET 6.0 (supporto a lungo termine) .
    • Verificare che sia selezionata la casella di controllo Usa controller (deselezionare per l'uso delle API minime).
    • Deselezionare Abilita supporto OpenAPI.
    • Selezionare Crea.

Rimuovere i file del modello WeatherForecast

  1. Rimuovere i file di esempio WeatherForecast.cs e Controllers/WeatherForecastController.cs dal nuovo progetto AppBackend .
  2. Aprire Properties\launchSettings.json.
  3. Modificare le proprietà launchUrl da weatherforcast a appbackend.

Selezionare una sottoscrizione nella finestra Configura app Web di Microsoft Azure e nell'elenco Piano di Servizio app eseguire una delle azioni seguenti:

  • Selezionare un piano Servizio app di Azure già creato.
  • Selezionare Crea un nuovo piano di servizio app e quindi creare un piano.

Per questa esercitazione non è necessario disporre di un database. Dopo aver selezionato il piano di servizio app, fare clic su OK per creare il progetto.

Finestra Configura app Web di Microsoft Azure

Se questa pagina per la configurazione del piano di servizio app non viene visualizzata, continuare con l'esercitazione. È possibile completare la configurazione in seguito durante la pubblicazione dell'app.

Autenticare client con il back-end WebAPI

In questa sezione si crea una nuova classe del gestore di messaggi denominata AuthenticationTestHandler per il nuovo back-end. Questa classe deriva da DelegatingHandler e viene aggiunta come gestore di messaggi per poter elaborare tutte le richieste in arrivo nel back-end.

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto AppBackend, scegliere Aggiungi e quindi selezionare Classe.

  2. Assegnare alla nuova classe il nome AuthenticationTestHandler.cs e fare clic su Aggiungi per generarla. Questa classe autentica gli utenti tramite l'autenticazione di base. L'app può usare qualsiasi schema di autenticazione.

  3. In AuthenticationTestHandler.cs aggiungere le istruzioni using seguenti:

    using System.Net.Http;
    using System.Threading;
    using System.Security.Principal;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    
  4. In AuthenticationTestHandler.cs sostituire la definizione di classe AuthenticationTestHandler con il codice seguente:

    Questo gestore autorizza la richiesta quando le tre condizioni seguenti sono vere:

    • La richiesta include un'intestazione di autorizzazione.
    • La richiesta usa l'autenticazione di base .
    • La stringa del nome utente corrisponde alla stringa della password.

    In caso contrario, la richiesta viene rifiutata. Non si tratta di un vero approccio di autenticazione e autorizzazione. È solo un esempio semplice per questa esercitazione.

    Se il messaggio di richiesta viene autenticato e autorizzato da AuthenticationTestHandler, l'utente dell'autenticazione di base viene associato alla richiesta corrente in HttpContext. Le informazioni utente in HttpContext verranno usate da un altro controller (RegisterController) in un secondo momento per aggiungere un tag alla richiesta di registrazione per le notifiche.

    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 sulla sicurezza: la classe AuthenticationTestHandler non fornisce un'effettiva autenticazione. Viene usata solo per imitare l'autenticazione di base e non è sicura. È necessario implementare un meccanismo di autenticazione sicuro nelle applicazioni e nei servizi di produzione.

  5. Per registrare il gestore messaggi, aggiungere il codice seguente alla fine del Register metodo nel file Program.cs :

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. Salvare le modifiche.

Eseguire la registrazione per le notifiche tramite il back-end WebAPI

In questa sezione si aggiunge un nuovo controller al back-end WebAPI per gestire le richieste di registrazione di un utente e un dispositivo per le notifiche tramite la libreria client per gli hub di notifica. Il controller aggiunge un tag user per l'utente che è stato autenticato e collegato a HttpContext da AuthenticationTestHandler. Il tag ha il formato stringa, "username:<actual username>".

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto AppBackend e quindi scegliere Gestisci pacchetti NuGet.

  2. Nel riquadro sinistro selezionare Online e nella casella Cerca digitare Microsoft.Azure.NotificationHubs.

  3. Nell'elenco dei risultati fare clic su Hub di notifica di Microsoft Azure e quindi selezionare Installa. Completare l'installazione e quindi chiudere la finestra di Gestione pacchetti NuGet.

    Questa azione aggiunge un riferimento ad Azure Notification Hubs SDK usando il pacchetto NuGet Microsoft.Azure.NotificationHubs.

  4. Creare un nuovo file di classe che rappresenta la connessione con l'hub di notifica usato per inviare le notifiche. In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Modelli, scegliere Aggiungi e quindi fare clic su Classe. Assegnare alla nuova classe il nome Notifications.cs e quindi selezionare Aggiungi per generarla.

    Finestra Aggiungi nuovo elemento

  5. In Notifications.cs aggiungere l'istruzione using seguente all'inizio del file:

    using Microsoft.Azure.NotificationHubs;
    
  6. Sostituire la definizione di classe Notifications con il codice seguente e sostituire i due segnaposto con la stringa di connessione (con accesso completo) per l'hub di notifica e con il nome dell'hub, disponibile nel portale di 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

    Immettere il nome e il valore DefaultFullSharedAccessSignature dell'hub prima di continuare.

  7. Creare quindi un nuovo controller denominato RegisterController. In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Controller, scegliere Aggiungi e quindi fare clic su Controller.

  8. Selezionare Controller API - Vuoto e quindi selezionare Aggiungi.

  9. Nella casella Nome controller digitare RegisterController per assegnare un nome alla nuova classe, quindi selezionare Aggiungi.

    Finestra Aggiungi controller.

  10. In RegisterController.cs aggiungere le istruzioni using seguenti:

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. Aggiungere il codice seguente all'interno della definizione di classe RegisterController . In questo codice viene aggiunto un tag user per l'utente associato a HttpContext. L'utente è stato autenticato e associato a HttpContext dal filtro messaggi aggiunto, AuthenticationTestHandler. È anche possibile aggiungere controlli facoltativi per verificare che l'utente disponga dei diritti per la registrazione per i tag richiesti.

    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. Salvare le modifiche.

Inviare notifiche dal back-end WebAPI

In questa sezione si aggiunge un nuovo controller che consente ai dispositivi client di inviare una notifica. La notifica si basa sul tag username che usa la libreria .NET di Hub di notifica di Azure nel back-end WebAPI ASP.NET.

  1. Creare un altro nuovo controller denominato NotificationsController seguendo la stessa procedura usata per creare RegisterController nella sezione precedente.

  2. In NotificationsController.cs aggiungere le istruzioni using seguenti:

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. Aggiungere il metodo seguente alla classe NotificationsController:

    Questo codice invia un tipo di notifica basato sul parametro pns del servizio di notifica della piattaforma (PNS). Il valore to_tag viene usato per impostare il tag username nel messaggio. Questo tag deve corrispondere a un tag username di una registrazione dell'hub di notifica attiva. Il messaggio di notifica viene estratto dal corpo della richiesta POST ed è formattato per il PNS di destinazione.

    A seconda del servizio di notifica della piattaforma (PNS) usato dai dispositivi supportati per ricevere le notifiche, sono supportate notifiche in una serie di formati. Nei dispositivi Windows è ad esempio possibile usare una notifica di tipo avviso popup con WNS che non è supportata direttamente da un altro PNS. In tal caso, il back-end deve formattare la notifica come notifica supportata per il PNS dei dispositivi che si intende supportare. Usare quindi l'API di invio appropriata per la 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. Premere F5 per eseguire l'applicazione e verificare l'accuratezza del lavoro svolto sinora. L'app apre un Web browser e viene visualizzata nella home page di ASP.NET.

Pubblicare il nuovo back-end WebAPI

L'app verrà ora distribuita in un sito Web Azure per renderla accessibile da tutti i dispositivi.

  1. Fare clic con il pulsante destro del mouse sul progetto AppBackend e scegliere Pubblica.

  2. Selezionare Servizio app di Microsoft Azure come destinazione di pubblicazione e quindi selezionare **Pubblica. Verrà visualizzata la finestra Crea servizio app. Qui è possibile creare tutte le risorse di Azure necessarie per eseguire l'app Web ASP.NET in Azure.

    Riquadro Servizio app di Microsoft Azure

  3. Nella finestra Crea servizio app selezionare l'account Azure. Selezionare Modifica tipo>App Web. Mantenere il valore di Nome app Web predefinito e selezionare la sottoscrizione, il gruppo di risorse e il piano di servizio app.

  4. Selezionare Crea.

  5. Prendere nota della proprietà URL sito nella scheda Riepilogo. Questo URL sarà l'endpoint back-end più avanti nell'esercitazione.

  6. Selezionare Pubblica.

Al termine della procedura guidata, l'app Web ASP.NET viene pubblicata in Azure e aperta nel browser predefinito. L'applicazione sarà visibile in Servizi app di Azure.

L'URL usa il nome dell'app Web specificato in precedenza, con il formato http:// app_name.azurewebsites.net<>.

Modificare l'app per iOS

  1. Aprire l'app Visualizzazione pagina singola creata nell'esercitazione Invia notifiche push alle app iOS usando Hub di notifica di Azure .

    Nota

    In questa sezione si presuppone che il progetto sia configurato con un nome di organizzazione vuoto. In caso contrario, prependò il nome dell'organizzazione a tutti i nomi di classe.

  2. Nel file Main.storyboard aggiungere i componenti illustrati nello screenshot dalla libreria di oggetti.

    Modifica storyboard in Xcode Interface Builder

    • Nome utente: oggetto UITextField con testo segnaposto, Enter Username, immediatamente sotto l'etichetta per l'invio dei risultati e limitato dai margini sinistro e destro.

    • Password: oggetto UITextField con testo segnaposto, Enter Password, immediatamente sotto il campo di testo del nome utente e limitato dai margini sinistro e destro. Selezionare l'opzione Secure Text Entry in Attribute Inspector sotto Return Key.

    • Accesso: oggetto UIButton con etichetta immediatamente sotto il campo di testo della password; deselezionare l'opzione Abilitato in Attributes Inspector sotto Control-Content.

    • WNS: etichetta e opzione per consentire l'invio della notifica al servizio di notifica Windows se è stato configurato nell'hub. Vedere l'esercitazione Introduzione a Windows.

    • GCM: etichetta e opzione per consentire l'invio della notifica a Google Cloud Messaging se è stato configurato nell'hub. Vedere l'esercitazione Introduzione ad Android .

    • APNS: etichetta e opzione per consentire l'invio della notifica al servizio di notifica della piattaforma di Apple.

    • Nome utente del destinatario: oggetto UITextField con testo segnaposto, Recipient username tag, immediatamente sotto l'etichetta GCM e limitato dai margini sinistro e destro.

      Alcuni componenti sono stati aggiunti nell'esercitazione Invia notifiche push alle app iOS usando Hub di notifica di Azure .

  3. Ctrl trascinamento dai componenti nella visualizzazione per ViewController.h aggiungere questi nuovi punti di vendita:

    @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. In ViewController.h aggiungere l'elemento seguente #define sotto le istruzioni di importazione. Sostituire il segnaposto <Your backend endpoint> con l'URL di destinazione usato per distribuire il back-end dell'app nella sezione precedente. Ad esempio, http://your_backend.azurewebsites.net:

    #define BACKEND_ENDPOINT @"<Your backend endpoint>"
    
  5. Nel progetto creare una nuova classe Cocoa Touch denominata RegisterClient da usare come interfaccia con il back-end ASP.NET creato. Creare la classe che eredita da NSObject. Aggiungere quindi il codice seguente in 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. In RegisterClient.m aggiornare la sezione @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. Sostituire la sezione @implementation in RegisterClient.m con il codice seguente:

    @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
    

    Questo codice implementa la logica illustrata nell'articolo di istruzioni Registrazione dal back-end dell'app usando NSURLSession per eseguire chiamate REST al back-end dell'app e NSUserDefaults per archiviare in locale l'ID registrazione restituito dall'hub di notifica.

    Per il corretto funzionamento di questa classe è necessario che sia impostata la relativa proprietà authorizationHeader. Questa proprietà viene impostata dalla classe ViewController dopo l'accesso.

  8. In ViewController.h aggiungere un'istruzione #import per RegisterClient.h. Aggiungere quindi una dichiarazione per il token del dispositivo e fare riferimento a un'istanza di RegisterClient nella sezione @interface:

    #import "RegisterClient.h"
    
    @property (strong, nonatomic) NSData* deviceToken;
    @property (strong, nonatomic) RegisterClient* registerClient;
    
  9. In ViewController.m aggiungere una dichiarazione di metodo privato nella sezione @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

    Il frammento di codice seguente non è uno schema di autenticazione sicuro, è necessario sostituire l'implementazione del con il meccanismo di autenticazione specifico che genera un token di createAndSetAuthenticationHeaderWithUsername:AndPassword: autenticazione da utilizzare dalla classe client di registrazione, ad esempio OAuth, Active Directory.

  10. Aggiungere quindi nella sezione @implementation di ViewController.m il codice seguente, che aggiunge l'implementazione per l'impostazione del token del dispositivo e dell'intestazione di autenticazione.

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

    Si noti come impostare il token del dispositivo abilita il pulsante Accedi . Questo dipende dal fatto che, nell'ambito dell'azione di accesso, il controller di visualizzazione esegue la registrazione per le notifiche push con il back-end dell'app. Non si vuole che l'azione Log in sia accessibile fino a quando il token del dispositivo non è stato configurato correttamente. È possibile separare l'accesso dalla registrazione push, purché la prima azione avvenga prima della seconda.

  11. In ViewController.m usare i frammenti di codice seguenti per implementare il metodo di azione per il pulsante Log in e un metodo per inviare il messaggio di notifica usando il 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. Aggiornare l'azione per il pulsante Send Notification per usare il back-end ASP.NET e inviare a qualsiasi PNS abilitato da un'opzione.

    - (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. Nella funzione ViewDidLoad aggiungere quanto segue per creare l'istanza di RegisterClient e impostare il delegato per i campi di testo.

    self.UsernameField.delegate = self;
    self.PasswordField.delegate = self;
    self.RecipientField.delegate = self;
    self.registerClient = [[RegisterClient alloc] initWithEndpoint:BACKEND_ENDPOINT];
    
  14. In AppDelegate.m rimuovere tutto il contenuto del metodo application:didRegisterForPushNotificationWithDeviceToken: e sostituirlo con il seguente (per assicurarsi che il controller di visualizzazione contenga il token del dispositivo più recente recuperato da Apple Push Notification Service):

    // 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. Infine in AppDelegate.m verificare che sia presente il metodo seguente:

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

Test dell'applicazione

  1. In XCode eseguire l'app su un dispositivo iOS fisico (le notifiche push non funzionano nel simulatore).

  2. Nell'interfaccia utente dell'app per iOS immettere lo stesso valore per il nome utente e per la password. Fare quindi clic su Accedi.

    Test dell'applicazione iOS

  3. Verrà visualizzata una finestra popup che informa che la registrazione è stata completata. Fare clic su OK.

    Notifica del test iOS visualizzata

  4. Nel campo di testo *Recipient username tag (Tag nome utente destinatario) immettere il tag del nome utente usato con la registrazione da un altro dispositivo.

  5. Immettere un messaggio di notifica e fare clic su Send Notification. Solo i dispositivi che hanno una registrazione con il tag del nome utente del destinatario riceveranno il messaggio di notifica. Viene inviato solo a tali utenti.

    Notifica con tag del test iOS

Passaggi successivi

In questa esercitazione è stato descritto come inviare notifiche push a utenti specifici che hanno tag associati alle loro registrazioni. Per informazioni sulle procedure per effettuare il push di notifiche in base alla posizione, passare all'esercitazione seguente: