Руководство по отправке push-уведомлений определенным пользователям с помощью службы "Центры уведомлений Azure"

В этом учебнике показано, как использовать концентраторы уведомлений Azure для отправки push-уведомлений пользователю определенного приложения на конкретном устройстве. Серверная часть ASP.NET WebAPI используется для проверки подлинности клиентов и создания уведомлений, как показано в разделе руководства Управление регистрацией из серверной части.

При работе с этим руководством вы выполните следующие задачи:

  • Создание проекта веб-API
  • Аутентификация клиентов в серверной части веб-API.
  • Регистрация для получения уведомлений с помощью серверной части веб-API.
  • Отправка уведомлений из серверной части веб-API.
  • Публикация новой серверной части веб-API.
  • Изменение приложения iOS
  • Тестирование приложения

Предварительные требования

В этом руководстве предполагается, что вы создали и настроили центр уведомлений, как описано в статье Отправка push-уведомлений в приложения iOS с помощью службы "Центры уведомлений Azure". Это руководство также необходимо изучить перед переходом к руководству Безопасные push-уведомления для концентраторов уведомлений Azure. Если вы хотите использовать мобильные приложения в качестве внутренней службы, см. статью Добавление push-уведомлений в приложение iOS.

Создание проекта веб-API

В этой статье описывается создание серверной части веб-API ASP.NET. Этот процесс состоит из трех главных задач.

  • Аутентификация клиентов. Вы добавите обработчик сообщений для аутентификации клиентских запросов и связывания пользователя с запросом.
  • Регистрация для получения уведомлений с помощью серверной части веб-API. Вы добавите контроллер для обработки новых регистраций клиентских устройств для получения уведомлений. Имя пользователя, прошедшего аутентификацию, автоматически добавляется в регистрацию как тег.
  • Отправка уведомлений клиентам. Вы также добавите контроллер, который позволит активировать безопасную отправку push-уведомлений устройствам и клиентам, связанным с тегом.

Чтобы создать серверную часть веб-API ASP.NET Core 6.0, сделайте следующее:

Чтобы проверить, запустите Visual Studio. Откройте меню Средства и выберите пункт Расширения и обновления. Найдите диспетчер пакетов NuGet в своей версии Visual Studio и убедитесь, что у вас установлена последняя версия. Если вы используете не последнюю версию, удалите ее, а затем переустановите диспетчер пакетов NuGet.

Снимок экрана: диалоговое окно

Примечание

Убедитесь, что вы установили пакет SDK для Azure для Visual Studio, используемый для развертывания веб-сайта.

  1. Запустите Visual Studio или Visual Studio Express.

  2. Щелкните Обозреватель серверов и войдите в свою учетную запись Azure. Чтобы создать ресурсы веб-сайта в своей учетной записи, вы должны войти.

  3. В Visual Studio в меню Файл выберите пункты Создать>Проект.

  4. В поле поиска введите Веб-API.

  5. Выберите шаблон проекта Веб-API ASP.NET Core и нажмите кнопку Далее.

  6. В диалоговом окне Настроить новый проект присвойте проекту имя AppBackend и нажмите кнопку Далее.

  7. В диалоговом окне Дополнительные сведения выполните следующие действия.

    • Убедитесь, что для параметра Платформа выбрано значение .NET 6.0 (долгосрочная поддержка).
    • Убедитесь, что флажок Use controllers (uncheck to use minimal APIs) (Использовать контроллеры (снимите этот флажок для использования минимальных API)) установлен.
    • Снимите флажок Включить поддержку OpenAPI.
    • Нажмите кнопку создания.

Удаление файлов шаблонов WeatherForecast

  1. Удалите файлы примеров WeatherForecast.cs и Controllers/WeatherForecastController.cs из нового проекта AppBackend.
  2. Откройте файл Properties\launchSettings.json.
  3. Измените свойства launchUrl с weatherforcast на appbackend.

В окне Настройка веб-приложения Microsoft Azure выберите подписку, а затем в списке План службы приложений выполните одно из следующих действий:

  • Выберите план службы приложений Azure, который вы уже создали.
  • Выберите Создать план служб приложений, а затем создайте его.

База данных для этого руководства не требуется. Выбрав план служб приложений, нажмите кнопку ОК, чтобы создать проект.

Диалоговое окно

Если вы не видите эту страницу для настройки плана службы приложений, продолжайте выполнять руководство. Его можно настроить позднее при публикации приложения.

Аутентификация клиентов в серверной части веб-API.

В этом разделе вы создадите класс обработчика сообщений с именем AuthenticationTestHandler для новой серверной части. Этот класс является производным от DelegatingHandler. Он добавляется в качестве обработчика сообщений, чтобы обрабатывать все запросы, поступающие в серверную часть.

  1. В обозревателе решений щелкните правой кнопкой мыши проект AppBackend, выберите Добавить, а затем щелкните Класс.

  2. Присвойте новому классу имя AuthenticationTestHandler.cs и нажмите кнопку Добавить, чтобы создать класс. Этот класс используется для аутентификации пользователей с помощью обычной проверки подлинности для простоты. Ваше приложение может использовать любую схему аутентификации.

  3. В AuthenticationTestHandler.cs добавьте следующие операторы using :

    using System.Net.Http;
    using System.Threading;
    using System.Security.Principal;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    
  4. В классе AuthenticationTestHandler.cs замените определение AuthenticationTestHandler следующим кодом:

    Обработчик авторизует запрос, если выполнены все три следующих условия:

    • Запрос включен в заголовок авторизации.
    • Запрос использует обычную проверку подлинности.
    • Строка имени пользователя и строка пароля являются одной стройкой.

    В противном случае запрос отклоняется. Этот способ аутентификации нельзя назвать настоящим методом аутентификации и авторизации. Это простой пример для этого руководства.

    Если сообщение запроса аутентифицируется и авторизуется AuthenticationTestHandler, пользователь обычной проверки подлинности подключается к текущему запросу в HttpContext. Позднее информацию о пользователе в HttpContext будет использовать другой контроллер (RegisterController), чтобы добавить тег в запрос на регистрацию для получения уведомлений.

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

    Примечание

    Примечание о безопасности. Класс AuthenticationTestHandler не обеспечивает настоящую аутентификацию. Этот класс используется лишь для имитации базовой проверки подлинности и не является безопасным. В реальных приложениях и службах необходимо реализовать безопасный механизм проверки подлинности.

  5. Чтобы зарегистрировать обработчик событий, добавьте в конец метода Register в файле Program.cs следующий код:

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. Сохраните изменения.

Регистрация для получения уведомлений с помощью серверной части веб-API.

В этом разделе вы добавите новый контроллер в серверную часть веб-API, которая будет обрабатывать запросы на регистрацию пользователя и устройства для получения уведомлений с помощью клиентской библиотеки центров уведомлений. Контроллер будет добавлять тег пользователя для пользователя, который прошел аутентификацию и подключен к HttpContext с помощью AuthenticationTestHandler. Тег имеет формат строки: "username:<actual username>".

  1. В обозревателе решений щелкните правой кнопкой мыши проект AppBackend и выберите пункт Управление пакетами NuGet.

  2. На панели слева выберите В сети, затем в поле поиска введите Microsoft.Azure.NotificationHubs.

  3. В списке результатов выберите Центры уведомлений Microsoft Azure, а затем нажмите кнопку Установить. Завершите установку и закройте окно диспетчера пакетов NuGet.

    Это действие добавляет ссылку на пакет SDK для Центров уведомлений Azure с помощью пакета NuGet Microsoft.Azure.Notification Hubs.

  4. Создайте файл класса, представляющий подключение к концентратору уведомлений, который используется для отправки уведомлений. В обозревателе решений щелкните правой кнопкой мыши папку Модели, выберите Добавить, а затем щелкните Класс. Назовите новый класс Notifications.cs и нажмите кнопку Добавить, чтобы создать класс.

    Окно добавления нового элемента

  5. В Notifications.cs добавьте следующий оператор using в начало файла:

    using Microsoft.Azure.NotificationHubs;
    
  6. Замените определение класса Notifications следующим кодом и замените два заполнителя строкой подключения (с полным доступом) для своего концентратора уведомлений и именем концентратора (доступно на портале 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>");
        }
    }
    

    Важно!

    Прежде чем продолжить, введите имя и DefaultFullSharedAccessSignature вашего концентратора.

  7. Теперь создайте новый контроллер с именем RegisterController. В обозревателе решений щелкните правой кнопкой мыши папку Контроллеры, выберите Добавить, а затем щелкните Контроллер.

  8. Выберите Контроллер API — пустой, а затем нажмите кнопку Добавить.

  9. В поле Имя контроллера введите имя нового класса RegisterController, а затем нажмите кнопку Добавить.

    Окно Добавление контроллера.

  10. В RegiterController.cs добавьте следующие операторы using :

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. Добавьте в определение класса RegisterController следующий код: В этом коде вы добавите тег для пользователя, который подключен к HttpContext. Пользователь прошел аутентификацию и подключен к HttpContext с помощью добавленного фильтра сообщений AuthenticationTestHandler. Вы можете также добавить дополнительные проверки, чтобы убедиться, что у пользователя есть право регистрации запрошенных тегов.

    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. Сохраните изменения.

Отправка уведомлений из серверной части веб-API.

В этом разделе вы добавите новый контроллер, который позволяет клиентским устройствам отправлять уведомления. Уведомление основано на теге имени пользователя, который использует библиотеку .NET центров уведомлений Azure в серверной части веб-API ASP.NET.

  1. Создайте еще один контроллер с именем NotificationsController так же, как вы создали RegisterController в предыдущем разделе.

  2. В NotificationsController.cs добавьте следующие операторы using :

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. Добавьте следующий метод в класс NotificationsController:

    Этот код отправляет тип уведомлений, основанный на параметре pns системы отправки уведомлений платформы (PNS). Значение to_tag используется для задания тега имени пользователя в сообщении. Этот тег должен соответствовать тегу имени пользователя активной регистрации центра уведомлений. Сообщение уведомления извлекается из текста запроса POST и форматируется для целевого PNS.

    Поддержка форматов уведомлений зависит от того, какую систему PNS используют поддерживаемые устройства. Например, на устройствах Windows можно использовать всплывающие уведомления с помощью WNS, которые не поддерживает другая система PNS. В этом случае серверная часть решения конвертирует уведомление в формат, соответствующий PNS устройств, которые вы планируете поддерживать. Затем используйте соответствующий API отправки для класса 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. Нажмите клавишу F5, чтобы запустить приложение и убедиться, что все правильно работает. Приложение откроется в веб-браузере и отобразится на домашней странице ASP.NET.

Публикация новой серверной части веб-API.

Теперь разверните приложение на веб-сайте Azure, чтобы сделать его доступным для всех устройств.

  1. Щелкните правой кнопкой мыши проект AppBackend и нажмите кнопку Опубликовать.

  2. Выберите службу приложений Microsoft Azure, в которой вы хотите опубликовать приложение, и нажмите кнопку \*\*Опубликовать. Откроется окно создания службы приложений. В этом окне можно создать все необходимые ресурсы Azure для запуска веб-приложения ASP.NET в Azure.

    Плитка службы приложений Microsoft Azure

  3. В окне Создание службы приложений выберите свою учетную запись Azure. Выберите Изменить тип>Веб-приложение. Не изменяйте стандартное имя веб-приложения. Выберите подписку, группу ресурсов и план службы приложений.

  4. Нажмите кнопку создания.

  5. Запишите свойство URL-адрес сайта в разделе Сводка. Это URL-адрес конечной точки внутренней части.

  6. Нажмите Публиковать.

Когда мастер завершит работу, веб-приложение ASP.NET будет опубликовано в Azure и откроется в браузере по умолчанию. Приложение также будет отображаться в службах приложений Azure.

В URL-адресе используется имя веб-приложения, указанное ранее, в формате http://<имя_приложения>.azurewebsites.net.

Изменение приложения iOS

  1. Откройте одностраничное приложение просмотра, созданное в руководстве Отправка push-уведомлений в приложения iOS с помощью службы "Центры уведомлений Azure".

    Примечание

    В этом разделе подразумевается, что вы настроили свой проект, указав пустое имя организации. В противном случае нужно будет добавлять имя своей организации перед всеми именами классов.

  2. В файл Main.storyboard добавьте компоненты из библиотеки объектов, показанные на снимке экрана.

    Редактирование раскадровки в конструкторе интерфейсов Xcode

    • Имя пользователя: текстовое поле UITextField с замещающим текстом Enter Username, расположенное под меткой отправки результатов, справа, слева и сверху ограниченное полями.

    • Пароль: текстовое поле UITextField с замещающим текстом Enter Password, расположенное под текстовым полем имени пользователя, справа, слева и сверху ограниченное полями. Отметьте параметр Защищенный ввод текста в инспекторе атрибутов в разделе Символ вывода.

    • Вход: кнопка UIButton с меткой, которая расположена под текстовым полем пароля. Снимите флажок Включить в инспекторе атрибутов в разделе Управление содержимым.

    • WNS: метка и переключатель для включения отправки уведомлений службы уведомлений Windows, если она установлена в центре. См. руководство Начало работы с центрами уведомлений для приложений универсальной платформы Windows.

    • GCM: метка и переключатель для включения отправки уведомлений в службу Google Cloud Messaging, если она установлена в центре. См. руководство Отправка push-уведомлений в приложения Android с помощью центров уведомлений Azure.

    • APNS: метка и переключатель для включения отправки уведомлений в службу уведомлений платформы Apple Platform Notification Service.

    • Имя получателя: текстовое поле UITextField с замещающим текстом Введите имя получателя, расположенное непосредственно под меткой GCM, справа, слева и сверху ограниченное полями.

      Некоторые компоненты были добавлены в руководстве Отправка push-уведомлений в приложения iOS с помощью службы "Центры уведомлений Azure".

  3. Перетащите CTRL из компонентов в представлении в ViewController.h и добавьте эти новые выходы:

    @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. В файл ViewController.h добавьте следующее #define после импорта операторов. Замените заполнитель <Your backend endpoint> URL-адресом назначения, который использовался для развертывания серверной части приложения в предыдущем разделе. Например, http://your_backend.azurewebsites.net:

    #define BACKEND_ENDPOINT @"<Your backend endpoint>"
    
  5. В проекте создайте класс Cocoa Touch с именем RegisterClient для взаимодействия с серверной частью ASP.NET. Создайте класс, наследующий NSObject. Затем добавьте следующий код в 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.m измените раздел @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. Замените раздел @implementation в RegisterClient.m следующим кодом.

    @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
    

    В этом коде реализуется логика, описываемая в статье Регистрация из серверной части приложения, при этом используется NSURLSession для выполнения вызовов REST к серверной части приложения и NSUserDefaults — для локального сохранения идентификатора registrationId, возвращаемого центром уведомлений.

    Для нормальной работы этого класса должно быть правильно задано свойство authorizationHeader. Это свойство установлено классом ViewController после выполнения входа.

  8. В ViewController.h добавьте инструкцию #import для RegisterClient.h. Затем добавьте объявление для токена устройства и ссылку на экземпляр RegisterClient в разделе @interface.

    #import "RegisterClient.h"
    
    @property (strong, nonatomic) NSData* deviceToken;
    @property (strong, nonatomic) RegisterClient* registerClient;
    
  9. Добавьте в ViewController.m объявление частного метода в разделе @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
    

    Примечание

    Этот фрагмент не является безопасной схемой проверки подлинности. Следует заменить реализацию createAndSetAuthenticationHeaderWithUsername:AndPassword: своим собственным механизмом проверки подлинности, формирующим маркер проверки подлинности, который используется классом клиента регистра, например OAuth, Active Directory.

  10. Затем в разделе @implementation в ViewController.m добавьте приведенный ниже код, который задает маркер устройства и заголовок аутентификации.

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

    Обратите внимание, как при задании маркера устройства активируется кнопка Вход. Это происходит по той причине, что контроллер представления, являясь частью действия входа, регистрирует push-уведомления в серверной части приложения. Таким образом, действие Вход должно быть недоступно, пока маркер устройства не будет настроен должным образом. Можно разделить вход и push-регистрацию, если первая операция выполняется раньше второй.

  11. Чтобы в ViewController.m реализовать метод действия для кнопки Вход и метод отправки сообщений уведомления с помощью серверной части 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. Обновите действие для кнопки Отправить уведомление для использования серверной части ASP.NET и отправки любой PNS, разрешенной коммутатором.

    - (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 добавьте следующий код, чтобы создать экземпляр RegisterClient и задать делегат для текстовых полей.

    self.UsernameField.delegate = self;
    self.PasswordField.delegate = self;
    self.RecipientField.delegate = self;
    self.registerClient = [[RegisterClient alloc] initWithEndpoint:BACKEND_ENDPOINT];
    
  14. Теперь удалите в AppDelegate.m все содержимое метода application:didRegisterForPushNotificationWithDeviceToken: и замените его следующим содержимым (чтобы убедиться, что контроллер представления содержит последний маркер устройства, полученный из 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. Наконец, убедитесь, что в AppDelegate.m есть следующий метод.

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

Тестирование приложения

  1. В XCode запустите приложение на физическом устройстве под управлением iOS (push-уведомления не будут работать в симуляторе).

  2. В пользовательском интерфейсе приложения iOS введите одинаковое значение для имени пользователя и пароля. Затем нажмите кнопку Вход.

    Тестирование приложения iOS

  3. Должно отобразиться всплывающее окно с сообщением об успешной регистрации. Нажмите кнопку ОК.

    Отображение уведомления о тестировании iOS

  4. В текстовом поле \*Тег имени получателя введите тег имени получателя, используемый при регистрации с другого устройства.

  5. Введите сообщение уведомления и нажмите кнопку Отправить уведомление. Сообщения уведомления могут получать только устройства с зарегистрированным тегом имени получателя. Сообщения отправляются только таким пользователям.

    Отправка уведомлений тестирования iOS с тегами

Дальнейшие действия

В этом руководстве вы узнали, как отправлять push-уведомления определенным пользователям, с регистрацией которых связаны теги. Чтобы узнать, как отправлять push-уведомления на основе расположения, перейдите к следующему руководству: