从应用后端注册

如前一部分所述,设备必须在通知中心创建一个或多个注册才能接收推送通知。 完成此注册的一种方法是让移动设备直接与通知中心联系,以指定其 PNS 句柄及其标记 (或更新) 。 此方法存在许多限制,在某些情况下,建议在设备刷新注册时联系自己的应用后端。 然后,后端将调用通知中心。

何时从应用后端注册

有两种方案建议通过应用后端路由设备注册。

必须保护标记

当设备直接注册到通知中心时,它可以指定所需的任何标记。 如果标记是公共利益组,任何设备都可以订阅 (,例如,有关体育团队的新闻源) ,则这不是问题。 但是,当某些标记仅适用于某些用户时,可能会出现问题。

若要仅将每个用户注册到允许的标记,必须通过自己的应用后端路由注册操作,该后端可以执行用户身份验证并授权注册所需的标记。

此方案的最常见示例是使用标记来表示用户 ID。 在这种情况下,你想要阻止设备注册到表示其他用户的标记,因为他们将收到其他用户的通知。

标记由应用后端修改

从设备注册很方便,使你能够快速设置推送通知和丰富的路由到兴趣组。 但是,如果希望由于其他设备上发生的事件而更改标记,则从设备注册效果不佳。

请考虑两种情况:如果 Alice 的手机上的标记由于 Alice 的手机上发生的事件而设置,则应用可以轻松地在通知中心更新标记。 另一方面,如果标记必须因其他设备上发生的事件而更改,例如,Alice 的笔记本电脑登录网站时 () ,则设备必须等待应用再次处于活动状态才能反映通知中心的更改。

上一方案的特定示例是包含 Web 体验和移动应用的音乐应用。 在这种情况下,特定用户可能会通过网站关注新乐队,并希望设备尽快开始接收有关新带的通知。 另一个示例是,当标记来自后端的其他部分 (CRM 时,例如) ,它可以将用户的状态从 Silver 更改为 Gold。 此更改可能会导致所有用户注册上设置新标记。

如何从后端注册

注册设备时,通知中心必须区分不同的设备。 这不能只是通过查看 PNS 句柄来完成,因为它们是暂时性的,而不是唯一的。 若要解决此问题,通知中心会生成一个长期注册 ID,每个设备必须在本地存储,以便在每次更新其 PNS 句柄、标记或模板时引用其自己的注册。

下图显示了本机通知的注册流:

  1. 在设备上,如果未在本地存储任何注册 ID,则

    1. 调用应用后端以获取注册 ID。

    2. 应用后端调用通知中心以创建新的注册 ID,然后将 ID 返回到设备。

    3. Microsoft Store设备本地存储中的注册 ID。

  2. 在设备上,从本地存储检索注册 ID:

    1. 调用应用后端,提供注册 ID、PNS 句柄和标记。

    2. 应用后端在通知中心创建或更新相应的注册。

    3. 如果应用后端返回状态代码 410,则必须创建新的注册 ID。 从本地存储中删除注册 ID,然后从步骤 1 重启。

Backend Registration

模板通知的流程类似。 唯一的区别如下:

  1. 如果设备使用多个模板,则必须为每个模板存储一个注册 ID。

  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 Microsoft Store应用实现注册方法:

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"];

        }
    }

将注册 ID 存储在后端数据库中

有时,应用程序希望在应用后端而不是设备本地存储中保留注册 ID。 当应用后端已经有一种方法来标识设备 (例如 installationId) ,以及一种将设备信息存储在后端存储 (的方法(例如,从存储 PNS 句柄的自定义推送解决方案迁移时)) 时,通常会发生这种情况。

如何从后端修改标记

如果要从后端修改标记,则必须让后端识别要修改的注册。 这通常是使用标记完成的。

例如,假设有一个音乐应用,用户从 Web 中添加一个新的收藏乐队,后端会在用户的移动注册中添加一个标记,因此。 在这种情况下,应用使用标记来标识用户,然后使用该标记检索要更新和更新的注册。

下面的代码示例检索注册,并向其添加新标记。

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

请注意,在此示例中,如果使用模板,则假定你在所有模板上添加标记。