注册管理

本文介绍如何向 Azure 通知中心注册设备,以便接收推送通知。 本文简要介绍了注册,然后介绍了注册设备的两种主要模式:直接从设备注册到通知中心,并通过应用程序后端注册。

什么是设备注册?

使用通知中心时,可以通过注册安装来完成设备注册。

注册

注册将设备的平台通知服务 (PNS) 句柄与标记(有时还包括模板)相关联。 PNS 句柄可以是 ChannelURI 或设备令牌注册 ID。 标记用于将通知路由到正确的设备句柄集。 有关详细信息,请参阅路由和标记表达式。 模板用于实现按注册转换。 有关详细信息,请参阅 模板

注释

Azure 通知中心支持每个设备最多 60 个标记。

安装

安装是增强型的注册,包含一组与推送相关的属性。 这是使用服务器端 .NET SDK(用于后端作的通知中心 SDK)注册设备的最新和最佳方法。 还可以使用 通知中心 REST API 方法在客户端设备本身上注册安装。 如果使用后端服务,则应能够将 通知中心 SDK 用于后端操作

以下是使用装置/设施的主要优势:

  • 创建或更新安装是完全幂等的。 可以重试它,而不必担心重复注册。
  • 安装模型支持特殊的标记格式($InstallationId:{INSTALLATION_ID}),该格式允许将通知直接发送到特定设备。 例如,如果应用程序的代码为这台特定设备设置了安装 ID joe93developer,开发人员可以通过向 $InstallationId:{joe93developer} 标签发送通知来定位该设备。 这样,便可以将特定设备作为目标,而无需执行任何其他编码。
  • 使用安装还可以执行部分注册更新。 可以使用 JSON-Patch standard 以 PATCH 方法来请求安装部分更新。 如果要更新注册上的标记,此方法非常有用。 不需要删除整个注册,然后重新发送前面的所有标记。

安装可以包含以下属性。 有关安装属性的完整列表,请参阅使用 REST API 创建或覆盖安装安装属性

// Example installation format to show some supported properties
{
    installationId: "",
    expirationTime: "",
    tags: [],
    platform: "",
    pushChannel: "",
    ………
    templates: {
        "templateName1" : {
            body: "",
            tags: [] },
        "templateName2" : {
            body: "",
            // Headers are for Windows Store only
            headers: {
                "X-WNS-Type": "wns/tile" }
            tags: [] }
    },
    secondaryTiles: {
        "tileId1": {
            pushChannel: "",
            tags: [],
            templates: {
                "otherTemplate": {
                    bodyTemplate: "",
                    headers: {
                        ... }
                    tags: [] }
            }
        }
    }
}

注释

默认情况下,注册和安装不会过期。

注册与安装必须包含每个设备/通道的有效 PNS 句柄。 由于 PNS 句柄只能在设备上的客户端应用中获取,因此一种模式是直接在该设备上向客户端应用注册。 另一方面,与标记相关的安全注意事项和业务逻辑可能需要在应用后端管理设备注册。

当推送到被 PNS 标记为过期的标识符时,Azure 通知中心会根据从 PNS 服务器接收到的响应自动清理关联的安装/注册记录。 若要从辅助通知中心清理过期的记录,请添加处理来自每个发送的反馈的自定义逻辑。 然后,使辅助通知中心中的安装/注册过期。

注释

安装 API 不支持百度服务(尽管注册 API 确实支持)。

模板

如果要 使用模板,设备安装还保留 JSON 格式与该设备关联的所有模板(请参阅上一部分的示例)。 模板名称可以帮助识别同一设备的不同模板。

每个模板名称映射到模板正文和一组可选的标记。 每个平台可以具有其他模板属性。 对于 APN,可以将过期属性设置为常量或模板表达式。 有关完整的安装属性列表,请参阅使用 REST 创建或覆盖安装主题。

Windows 应用商店应用的辅助磁贴

对于 Windows 应用商店客户端应用程序,向辅助磁贴发送通知与将它们发送到主要磁贴相同。 在安装时也支持这一功能。 辅助磁贴具有不同的 ChannelUri,客户端应用上的 SDK 会以透明方式处理此 ChannelUri。

SecondaryTiles 字典使用与在 Windows 应用商店应用中创建 SecondaryTiles 对象时所用相同的 TileId。 与主 ChannelUri 一样,辅助磁贴的 ChannelUri 可随时更改。 为了使通知中心中的安装保持更新,设备必须使用辅助磁贴的当前 ChannelUri 刷新这些安装。

从设备管理注册

从客户端应用管理设备注册时,后端仅负责发送通知。 客户端应用使 PNS 句柄保持最新状态,并且会注册标记。 下图演示了此模式。

从设备注册

设备先从 PNS 中检索 PNS 句柄,然后直接注册到通知中心。 注册成功后,应用后端可以发送针对该注册的通知。 有关如何发送通知的详细信息,请参阅 路由和标记表达式

在此情况下,只可使用“侦听”权限从设备访问通知中心。 有关详细信息,请参阅安全性

从设备注册是最简单的方法,但它有一些缺点:

  • 客户端应用只能在应用处于活动状态时更新其标记。 例如,如果用户有两个设备注册与运动团队相关的标记,当第一个设备注册附加标记(例如 Seahawks),则第二台设备不会收到有关 Seahawk 的通知,直到第二台设备上的应用再次执行。 更普遍的是,当标记受到多个设备的影响时,从后端管理标记是一个理想的选项。
  • 由于应用可能会受到黑客攻击,因此保护注册到特定标记需要额外小心,如 “安全性”一文中所述。

使用安装从设备向通知中心注册的示例代码

目前,仅支持使用 通知中心 REST API

也可以使用 JSON-Patch standard 以 PATCH 方法更新安装。

class DeviceInstallation
{
    public string installationId { get; set; }
    public string platform { get; set; }
    public string pushChannel { get; set; }
    public string[] tags { get; set; }

    private async Task<HttpStatusCode> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation,
        string hubName, string listenConnectionString)
    {
        if (deviceInstallation.installationId == null)
            return HttpStatusCode.BadRequest;

        // Parse connection string
        ConnectionStringUtility connectionSaSUtil = new ConnectionStringUtility(listenConnectionString);
        string hubResource = "installations/" + deviceInstallation.installationId + "?";
        string apiVersion = "api-version=2015-04";

        // Determine the targetUri that we will sign
        string uri = connectionSaSUtil.Endpoint + hubName + "/" + hubResource + apiVersion;

        //=== Generate SaS Security Token for Authorization header ===
        string SasToken = connectionSaSUtil.getSaSToken(uri, 60);

        using (var httpClient = new HttpClient())
        {
            string json = JsonConvert.SerializeObject(deviceInstallation);

            httpClient.DefaultRequestHeaders.Add("Authorization", SasToken);

            var response = await httpClient.PutAsync(uri, new StringContent(json, System.Text.Encoding.UTF8, "application/json"));
            return response.StatusCode;
        }
    }

    var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

    string installationId = null;
    var settings = ApplicationData.Current.LocalSettings.Values;

    // If we didn't store an installation ID in application data, create and store as application data.
    if (!settings.ContainsKey("__NHInstallationId"))
    {
        installationId = Guid.NewGuid().ToString();
        settings.Add("__NHInstallationId", installationId);
    }

    installationId = (string)settings["__NHInstallationId"];

    var deviceInstallation = new DeviceInstallation
    {
        installationId = installationId,
        platform = "wns",
        pushChannel = channel.Uri,
        //tags = tags.ToArray<string>()
    };

    var statusCode = await CreateOrUpdateInstallationAsync(deviceInstallation, 
                    "<HUBNAME>", "<SHARED LISTEN CONNECTION STRING>");

    if (statusCode != HttpStatusCode.Accepted)
    {
        var dialog = new MessageDialog(statusCode.ToString(), "Registration failed. Installation Id : " + installationId);
        dialog.Commands.Add(new UICommand("OK"));
        await dialog.ShowAsync();
    }
    else
    {
        var dialog = new MessageDialog("Registration successful using installation Id : " + installationId);
        dialog.Commands.Add(new UICommand("OK"));
        await dialog.ShowAsync();
    }
}

使用注册从设备向通知中心注册的示例代码

这些方法为调用它们的设备创建或更新注册。 这意味着,若要更新句柄或标记,必须覆盖整个注册。 请记住,注册是过渡性的,因此您应始终拥有一个可靠的存储系统,保存特定设备所需的当前标签。

// Initialize the notification hub
NotificationHubClient hub = NotificationHubClient.CreateClientFromConnectionString(listenConnString, hubName);

// The Device ID from the PNS
var pushChannel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

// If you are registering from the client itself, then store this registration ID in device
// storage. Then when the app starts, you can check if a registration ID already exists or not before
// creating.
var settings = ApplicationData.Current.LocalSettings.Values;

// If we didn't store a registration ID in application data, store in application data.
if (!settings.ContainsKey("__NHRegistrationId"))
{
    // Make sure there are no existing registrations for this push handle (used for iOS and Android)    
    string newRegistrationId = null;
    var registrations = await hub.GetRegistrationsByChannelAsync(pushChannel.Uri, 100);
    foreach (RegistrationDescription registration in registrations)
    {
        if (newRegistrationId == null)
        {
            newRegistrationId = registration.RegistrationId;
        }
        else
        {
            await hub.DeleteRegistrationAsync(registration);
        }
    }

    newRegistrationId = await hub.CreateRegistrationIdAsync();

    settings.Add("__NHRegistrationId", newRegistrationId);
}

string regId = (string)settings["__NHRegistrationId"];

RegistrationDescription registration = new WindowsRegistrationDescription(pushChannel.Uri);
registration.RegistrationId = regId;
registration.Tags = new HashSet<string>(YourTags);

try
{
    await hub.CreateOrUpdateRegistrationAsync(registration);
}
catch (Microsoft.WindowsAzure.Messaging.RegistrationGoneException e)
{
    settings.Remove("__NHRegistrationId");
}

从后端进行注册管理

从后端管理注册需要编写其他代码。 设备上的应用必须在每次启动时将更新的 PNS 句柄(包括标记和模板)提供给后端,后端必须在通知中心中更新这个句柄。 下图说明了此设计。

注册管理

从后端管理注册的优点包括即使设备上的相应应用处于非活动状态,也能够将标记修改为注册,并在将标记添加到其注册之前对客户端应用进行身份验证。

用于在后端通过安装向通知中心注册的示例代码

客户端设备仍会像以前一样获取其 PNS 句柄和相关安装属性,并在后端调用可执行注册和授权标记等的自定义 API。后端可以利用 通知中心 SDK 执行后端作

也可以使用 JSON-Patch standard 以 PATCH 方法更新安装。

// Initialize the Notification Hub
NotificationHubClient hub = NotificationHubClient.CreateClientFromConnectionString(listenConnString, hubName);

// Custom API on the backend
public async Task<HttpResponseMessage> Put(DeviceInstallation deviceUpdate)
{

    Installation installation = new Installation();
    installation.InstallationId = deviceUpdate.InstallationId;
    installation.PushChannel = deviceUpdate.Handle;
    installation.Tags = deviceUpdate.Tags;

    switch (deviceUpdate.Platform)
    {
        case "wns":
            installation.Platform = NotificationPlatform.Wns;
            break;
        case "apns":
            installation.Platform = NotificationPlatform.Apns;
            break;
        case "fcm":
            installation.Platform = NotificationPlatform.Fcm;
            break;
        default:
            throw new HttpResponseException(HttpStatusCode.BadRequest);
    }

    // In the backend we can control if a user is allowed to add tags
    //installation.Tags = new List<string>(deviceUpdate.Tags);
    //installation.Tags.Add("username:" + username);

    await hub.CreateOrUpdateInstallationAsync(installation);

    return Request.CreateResponse(HttpStatusCode.OK);
}

使用注册 ID 从后端向通知中心注册的示例代码

从应用后端可以对注册执行基本 CRUDS 操作。 例如:

var hub = NotificationHubClient.CreateClientFromConnectionString("{connectionString}", "hubName");

// create a registration description object of the correct type, e.g.
var reg = new WindowsRegistrationDescription(channelUri, tags);

// Create
await hub.CreateRegistrationAsync(reg);

// Get by ID
var r = await hub.GetRegistrationAsync<RegistrationDescription>("id");

// update
r.Tags.Add("myTag");

// update on hub
await hub.UpdateRegistrationAsync(r);

// delete
await hub.DeleteRegistrationAsync(r);

后端必须处理注册更新之间的并发。 通知中心为注册管理提供了乐观并发控制。 在 HTTP 层面,这是通过使用 ETag 来实现注册管理操作的。 Azure SDK 以透明方式使用此功能,如果因并发原因拒绝更新,则会引发异常。 应用后端负责处理这些异常,并在必要时重试更新。