Регистрация из серверной части приложения

Как упоминалось в предыдущих разделах, устройства должны создавать одну или несколько регистраций в центре уведомлений для получения push-уведомлений. Один из способов завершения этой регистрации заключается в том, чтобы мобильные устройства связались с центром уведомлений напрямую, чтобы указать (или обновить) дескриптор PNS и их теги. Этот подход имеет ряд ограничений, и есть некоторые сценарии, в которых рекомендуется связаться с собственной серверной частью приложения при обновлении регистрации устройства. Затем серверная часть вызывает центр уведомлений.

Когда следует зарегистрироваться из серверной части приложения

Существует два сценария, в которых рекомендуется маршрутизировать регистрацию устройств через серверную часть приложения.

Теги должны быть защищены

Когда устройство регистрируется непосредственно в концентраторе уведомлений, оно может указать любой нужный тег. Это не проблемы, если теги являются группами общественных интересов, на которые может подписаться любое устройство (например, новостные каналы в отношении спортивных команд). Однако это может быть проблемой, если некоторые теги доступны только для некоторых пользователей.

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

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

Теги изменяются серверной частью приложения

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

Рассмотрим два сценария: если теги на телефоне Алисы задаются в результате событий, происходящих на телефоне Алисы, приложение легко обновить теги в центре уведомлений. Если, с другой стороны, теги должны меняться в результате событий, происходящих на других устройствах (например, ноутбук Алисы при входе на веб-сайт), устройство должно будет ждать, пока приложение будет активно снова, чтобы отразить изменения в центре уведомлений.

Конкретный пример предыдущего сценария — это музыкальное приложение, которое включает веб-интерфейс и мобильное приложение. В этом случае конкретный пользователь может следить за новым диапазоном через веб-сайт и как можно скорее будет получать уведомления о новом диапазоне. Другой пример — когда теги поступают из других частей серверной части (например, CRM), что может изменить состояние пользователя с Silver на Gold. Это изменение может привести к настройке нового тега для всех регистраций пользователей.

Регистрация из серверной части

При регистрации устройства концентратор уведомлений должен различать разные устройства. Это невозможно сделать только путем просмотра дескрипторов PNS, так как они являются временными и не уникальными. Чтобы решить эту проблему, Центры уведомлений создают идентификаторы регистрации, которые должны храниться на каждом устройстве локально, чтобы иметь возможность ссылаться на собственную регистрацию при каждом обновлении дескриптора PNS, тегов или шаблона.

На следующем рисунке показан поток регистрации для собственных уведомлений:

  1. Если идентификатор регистрации на устройстве не хранится локально,

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

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

    3. Сохраните идентификатор регистрации в локальном хранилище устройства.

  2. На устройстве получите идентификатор регистрации из локального хранилища:

    1. Вызовите серверную часть приложения, указав идентификатор регистрации, дескриптор PNS и теги.

    2. Серверная часть приложения создает или обновляет соответствующую регистрацию в центре уведомлений.

    3. Если серверная часть приложения возвращает код состояния 410, необходимо создать новый идентификатор регистрации. Удалите идентификатор регистрации из локального хранилища и перезапустите его из шага 1.

Backend Registration

Поток уведомлений шаблона аналогиен. Ниже приведены единственные отличия.

  1. Если устройство использует несколько шаблонов, оно должно хранить один идентификатор регистрации для каждого шаблона.

  2. Шаблоны можно определить с помощью свойства TemplateName регистрации.

Следующий код является примером конечных точек внутренней части.

public class RegisterController : ApiController
    {

        private NotificationHubClient hub;

        public RegisterController()
        {
            hub = NotificationHubClient.CreateClientFromConnectionString("Endpoint=sb://buildhub-ns.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=DuWV4SQ08poV6HZly8O/KQNWv3YRTZlExJxu3pNCjGU=", "build2014_2");
        }
        
        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()
        {
            return await hub.CreateRegistrationIdAsync();
        }

        // PUT api/register/5
        // This creates or updates a registration (with provided PNS handle) at the specified id
        public async void Put(string id, DeviceRegistration deviceUpdate)
        {
            // IMPORTANT: add logic to make sure that caller is allowed to register for the provided tags
            
            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 "gcm":
                    registration = new GcmRegistrationDescription(deviceUpdate.Handle);
                    break;
                default:
                    throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            registration.RegistrationId = id;
            registration.Tags = new HashSet<string>(deviceUpdate.Tags);

            try
            {
                await hub.CreateOrUpdateRegistrationAsync(registration);
            } catch (MessagingException e) {
                ReturnGoneIfHubResponseIsGone(e);
            }
        }

        // DELETE api/register/5
        public async void Delete(string id)
        {
            await hub.DeleteRegistrationAsync(id);
        }


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

Обратите внимание, что в приведенном выше коде необходимо добавить логику, чтобы убедиться, что клиент, вызывающий эту конечную точку, авторизован для регистрации для запрошенных тегов. Кроме того, серверная часть может добавить сами теги (например, тег userid ).

В следующем примере кода показано, как реализовать метод регистрации для приложения магазина Windows с устройства с предыдущими конечными точками:

class RegisterClient
    {
        private string POST_URL = "{your back-end endpoints}";

        private class DeviceRegistration
        {
            public string Platform { get; set; }
            public string Handle { get; set; }
            public string[] Tags { get; set; }
        }

        public async Task RegisterAsync(string handle, IEnumerable<string> tags)
        {
            var regId = await RetrieveRegistrationIdOrRequestNewOneAsync();

            var deviceRegistration = new DeviceRegistration
            {
                Platform = "wns",
                Handle = handle,
                Tags = tags.ToArray<string>()
            };

            var statusCode = await UpdateRegistrationAsync(regId, deviceRegistration);

            if (statusCode == HttpStatusCode.Gone)
            {
                // regId is expired, deleting from local storage & recreating
                var settings = ApplicationData.Current.LocalSettings.Values;
                settings.Remove("__NHRegistrationId");
                regId = await RetrieveRegistrationIdOrRequestNewOneAsync();
                statusCode = await UpdateRegistrationAsync(regId, deviceRegistration);
            }

            if (statusCode != HttpStatusCode.Accepted)
            {
                // log or throw
            }
        }

        private async Task<HttpStatusCode> UpdateRegistrationAsync(string regId, DeviceRegistration deviceRegistration)
        {
            using (var httpClient = new HttpClient())
            {
                var putUri = POST_URL + "/" + regId;
                var response = await httpClient.PutAsJsonAsync<DeviceRegistration>(putUri, deviceRegistration);
                return response.StatusCode;
            }
        }

        private async Task<string> RetrieveRegistrationIdOrRequestNewOneAsync()
        {
            var settings = ApplicationData.Current.LocalSettings.Values;
            if (!settings.ContainsKey("__NHRegistrationId"))
            {
                using (var httpClient = new HttpClient())
                {
                    var response = await httpClient.PostAsync(POST_URL, new StringContent(""));
                    if (response.IsSuccessStatusCode)
                    {
                        string regId = await response.Content.ReadAsStringAsync();
                        regId = regId.Substring(1, regId.Length - 2);
                        settings.Add("__NHRegistrationId", regId);
                    }
                    else
                    {
                        throw new Exception();
                    }
                }
            }
            return (string)settings["__NHRegistrationId"];

        }
    }

Хранение идентификаторов регистрации во внутренней базе данных

Иногда приложения хотят хранить идентификаторы регистрации в серверной части приложения вместо локального хранилища устройства. Обычно это происходит, когда серверная часть приложения уже имеет способ идентификации устройств (например, installationId) и способ хранения сведений об устройстве в серверном хранилище (например, при миграции из настраиваемого решения push-уведомлений, в котором хранились дескрипторы PNS).

Изменение тегов из серверной части

Если вы хотите изменить теги из серверной части, необходимо иметь способ, чтобы серверная часть идентифицировать регистрации, которые необходимо изменить. Обычно это делается с помощью тега.

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

В следующем примере кода извлекаются регистрации и добавляется новый тег к ним.

var registrations = await hub.GetRegistrationsByTagAsync("{userId}", 10);
            foreach (var reg in registrations)
            {
                reg.Tags.Add("{newBand}");
                await hub.UpdateRegistrationAsync(reg);
            }

Обратите внимание, что в этом примере при использовании шаблонов предполагается, что вы добавляете тег во все шаблоны.