Azure 通知中心 - 使用 .NET 后端通知用户

概述

利用 Azure 中的推送通知支持,你可以访问易于使用且向外扩展的多平台推送基础结构,这大大简化了为移动平台的使用者应用程序和企业应用程序实现推送通知的过程。本教程说明如何使用 Azure 通知中心将推送通知发送到特定设备上的特定应用程序用户。ASP.NET WebAPI 后端用于对客户端进行身份验证。后端使用经过身份验证的客户端用户自动将标记添加通知注册。后端将使用此标记为特定的用户生成通知。有关使用应用后端注册通知的详细信息,请参阅指南主题从应用后端注册。本教程以你在通知中心入门教程中创建的通知中心和项目为基础。

此外,只有在学习本教程后,才可以学习安全推送教程。完成本教程中的步骤后,你可以继续学习安全推送教程,其中说明了如何修改本教程中的代码以安全地发送推送通知。

开始之前

我们非常重视你的反馈。如果你在完成本主题的过程中遇到任何难题,或者在改善内容方面有任何建议,请在页面底部提供反馈,我们将不胜感激。

可以在 GitHub 上的此处找到本教程的已完成代码。

先决条件

在开始本教程之前,必须已完成以下移动服务教程:

创建 WebAPI 项目

后续部分讨论如何创建新的 ASP.NET WebAPI 后端。 此过程有三个主要目的:

  • 对客户端进行身份验证:稍后会添加消息处理程序,以便对客户端请求进行身份验证,并将用户与请求相关联。

  • 使用 WebAPI 后端注册通知:将添加一个控制器来处理新的注册,使客户端设备能够接收通知。 经过身份验证的用户名将作为标记自动添加到注册。

  • 将通知发送到客户端:稍后还要添加一个控制器,以便用户触发安全推送到与标记关联的设备和客户端。

通过执行以下操作创建新的 ASP.NET WebAPI 后端:

Important

如果使用 Visual Studio 2015 或更低版本,则在开始学习本教程之前,请确保已安装用于 Visual Studio 的最新版 NuGet 包管理器。

若要进行检查,请启动 Visual Studio。 在“工具”菜单上,选择“扩展和更新”。 在你的 Visual Studio 版本中搜索“NuGet 包管理器”,确保你的版本为最新。 如果你的版本不是最新版本,请卸载它,然后重新安装 NuGet 包管理器。

Note

请确保已安装 Visual Studio Azure SDK 以便进行网站部署。

  1. 启动 Visual Studio 或 Visual Studio Express。

  2. 选择“服务器资源管理器”并登录到 Azure 帐户。 若要在帐户中创建网站资源,必须先登录。

  3. 在 Visual Studio 中,选择“文件” > “新建” > “项目”,依次展开“模板”、“Visual C#”,然后选择“Web”和“ASP.NET Web 应用程序”。

  4. 在“名称”框中,键入 AppBackend,然后选择“确定”。

    “新建项目”窗口

  5. 在“新建 ASP.NET 项目”窗口中,选择“Web API”复选框,然后选择“确定”。

    “新建 ASP.NET 项目”窗口

  6. 在“配置 Azure Web 应用”窗口中选择一个订阅,然后在“应用服务计划”列表中执行以下任一操作:

    • 选择已创建的应用服务计划。
    • 选择“创建新的应用服务计划”,然后新建一个应用服务计划。

    在本教程中,不需要使用数据库。 选择应用服务计划后,选择“确定”以创建项目。

    “配置 Azure Web 应用”窗口

在 WebAPI 后端对客户端进行身份验证

在本部分中,将为新的后端创建名为 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 类定义替换为以下代码:

    当以下三个条件都成立时,此处理程序将授权请求:

    • 请求包含 Authorization 标头。
    • 请求使用基本身份验证。
    • 用户名字符串和密码字符串是相同的字符串。

      否则,将会拒绝该请求。 这不是真正的身份验证和授权方法。 它只是本教程中一个非常简单的示例。

      如果请求消息已经过 AuthenticationTestHandler 的身份验证和授权,则基本身份验证用户将附加到 HttpContext 上的当前请求。 稍后,另一个控制器 (RegisterController) 会使用 HttpContext 中的用户信息,将标记添加到通知注册请求。

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

      Note

      安全说明:AuthenticationTestHandler 类不提供真正的身份验证。 它仅用于模拟基本身份验证并且是不安全的。 必须在生产应用程序和服务中实现安全的身份验证机制。

  5. 若要注册消息处理程序,请在 App_Start/WebApiConfig.cs 类中 Register 方法的末尾添加以下代码:

     config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. 保存所做更改。

使用 WebAPI 后端注册通知

在本部分中,要将新的控制器添加到 WebAPI 后端来处理请求,以使用通知中心的客户端库为用户和设备注册通知。 控制器将为已由 AuthenticationTestHandler 验证并附加到 HttpContext 的用户添加用户标记。 该标记采用以下字符串格式:"username:<actual username>"

  1. 在“解决方案资源管理器”中,右键单击“AppBackend”项目,并选择“管理 NuGet 包”。

  2. 在左窗格中,选择“联机”,然后在“搜索”框中,键入 Microsoft.Azure.NotificationHubs

  3. 在结果列表中选择“Azure 通知中心”,然后选择“安装”。 完成安装后,关闭“NuGet 程序包管理器”窗口。

    此操作会使用 Microsoft.Azure.Notification Hubs NuGet 包添加对 Azure 通知中心 SDK 的引用。

  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>");
         }
     }
    
  7. 接下来将创建一个名为 RegisterController 的新控制器。 在“解决方案资源管理器”中,右键单击“控制器”文件夹,选择“添加”,并选择“控制器”。

  8. 选择“Web API 2 控制器 - 空”,并选择“添加”。

    “添加基架”窗口

  9. 在“控制器名称”框中,键入 RegisterController 以命名新类,并选择“添加”。

    “添加控制器”窗口

  10. 在 RegisterController.cs 中,添加以下 using 语句:

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. RegisterController 类定义中添加以下代码: 请注意,在此代码中,我们将为已附加到 HttpContext 的用户添加用户标记。 添加的消息筛选器 AuthenticationTestHandler 将对该用户进行身份验证并将其附加到 HttpContext。 还可以通过添加可选复选框来验证用户是否有权注册以获取请求标记。

    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 "gcm":
                registration = new GcmRegistrationDescription(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. 保存所做更改。

从 WebAPI 后端发送通知

在本部分中,将会添加一个新的控制器,使客户端设备可以发送通知。 通知基于在 ASP.NET WebAPI 后端中使用 Azure 通知中心服务管理库的用户名标记。

  1. 以在前面的部分中创建 RegisterController 的相同方式创建另一个名为 NotificationsController 的新控制器。

  2. 在 NotificationsController.cs 中,添加以下 using 语句:

     using AppBackend.Models;
     using System.Threading.Tasks;
     using System.Web;
    
  3. NotificationsController 类中添加以下方法:

    此代码会发送基于平台通知服务 (PNS) pns 参数的通知类型。 to_tag 的值用于设置消息中的 username 标记。 此标记必须与活动的通知中心注册的用户名标记相匹配。 将从 POST 请求正文提取通知消息,并根据目标 PNS 将其格式化。

    通知受多种格式支持,具体取决于受支持设备用来接收通知的 PNS。 例如,在 Windows 设备上,可能会将 toast 通知与其他 PNS 不直接支持的 WNS 配合使用。 在这种情况下,后端需要将通知格式化为打算使用的设备 PNS 所支持的通知。 然后针对 NotificationHubClient 类使用相应的发送 API。

     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 "gcm":
                 // Android
                 var notif = "{ \"data\" : {\"message\":\"" + "From " + user + ": " + message + "\"}}";
                 outcome = await Notifications.Instance.Hub.SendGcmNativeNotificationAsync(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 键。 应用将打开 Web 浏览器,并且将显示在 ASP.NET 主页上。

发布新的 WebAPI 后端

接下来会将此应用部署到 Azure 网站,以便可以从任意设备访问它。

  1. 右键单击 AppBackend 项目,并选择“发布”。

  2. 选择“Azure 应用服务”作为发布目标,然后选择“发布”。
    “创建应用服务”窗口将打开。 可以在这里创建在 Azure 中运行 ASP.NET Web 应用所需的全部 Azure 资源。

    “Azure 应用服务”磁贴

  3. 在“创建应用服务”窗口中,选择 Azure 帐户。 选择“更改类型” > “Web 应用”。 保留默认的“Web 应用名称”,然后依次选择“订阅”、“资源组”和“应用服务计划”。

  4. 选择“创建” 。

  5. 记下“摘要”部分的“站点 URL”属性。 此 URL 是本教程中稍后提到的后端终结点

  6. 选择“发布”。

完成向导后,它会将 ASP.NET Web 应用发布到 Azure,然后在默认浏览器中打开该应用。 可以在 Azure 应用服务中查看应用程序。

URL 使用前面指定的 Web 应用名称,其格式为 http://<app_name>.chinacloudsites.cn。

更新客户端项目的代码

在本部分中,你将更新你在通知中心入门教程中完成的项目中的代码。这些代码应该已与应用商店关联,并已针对通知中心进行配置。在本部分中,你将添加代码以调用新的 WebAPI 后端,并使用该后端来注册和发送通知。

  1. 在 Visual Studio 中,打开为通知中心入门教程创建的解决方案。

  2. 在“解决方案资源管理器”中,右键单击“(Windows 8.1)”项目,然后单击“管理 NuGet 包”。

  3. 在左侧单击“联机”。

  4. 在“搜索”框中键入 Http 客户端

  5. 在结果列表中,单击“Microsoft HTTP 客户端库”,然后单击“安装”。完成安装。

  6. 返回到 NuGet“搜索”框,键入 Json.net。安装 Json.NET 包,然后关闭“NuGet 包管理器”窗口。

  7. 针对“(Windows Phone 8.1)”项目重复上述步骤,以安装 Windows Phone 项目的 JSON.NET NuGet 包。

  8. 在解决方案资源管理器中的“(Windows 8.1)”项目内,双击“MainPage.xaml”在 Visual Studio 编辑器中打开它。

  9. MainPage.xaml XML 代码中,将 <Grid> 节替换为以下代码:此代码将添加用户用来进行身份验证的用户名和密码文本框。它还会添加通知消息的文本框,以及应接收通知的用户名标记:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
        <TextBlock Grid.Row="0" Text="Notify Users" HorizontalAlignment="Center" FontSize="48"/>
    
        <StackPanel Grid.Row="1" VerticalAlignment="Center">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Row="0" Grid.ColumnSpan="3" Text="Username" FontSize="24" Margin="20,0,20,0"/>
                <TextBox Name="UsernameTextBox" Grid.Row="1" Grid.ColumnSpan="3" Margin="20,0,20,0"/>
                <TextBlock Grid.Row="2" Grid.ColumnSpan="3" Text="Password" FontSize="24" Margin="20,0,20,0" />
                <PasswordBox Name="PasswordTextBox" Grid.Row="3" Grid.ColumnSpan="3" Margin="20,0,20,0"/>
    
                <Button Grid.Row="4" Grid.ColumnSpan="3" HorizontalAlignment="Center" VerticalAlignment="Center"
                            Content="1. Login and register" Click="LoginAndRegisterClick" Margin="0,0,0,20"/>
    
                <ToggleButton Name="toggleWNS" Grid.Row="5" Grid.Column="0" HorizontalAlignment="Right" Content="WNS" IsChecked="True" />
                <ToggleButton Name="toggleGCM" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Center" Content="GCM" />
                <ToggleButton Name="toggleAPNS" Grid.Row="5" Grid.Column="2" HorizontalAlignment="Left" Content="APNS" />
    
                <TextBlock Grid.Row="6" Grid.ColumnSpan="3" Text="Username Tag To Send To" FontSize="24" Margin="20,0,20,0"/>
                <TextBox Name="ToUserTagTextBox" Grid.Row="7" Grid.ColumnSpan="3" Margin="20,0,20,0" TextWrapping="Wrap" />
                <TextBlock Grid.Row="8" Grid.ColumnSpan="3" Text="Enter Notification Message" FontSize="24" Margin="20,0,20,0"/>
                <TextBox Name="NotificationMessageTextBox" Grid.Row="9" Grid.ColumnSpan="3" Margin="20,0,20,0" TextWrapping="Wrap" />
                <Button Grid.Row="10" Grid.ColumnSpan="3" HorizontalAlignment="Center" Content="2. Send push" Click="PushClick" Name="SendPushButton" />
            </Grid>
        </StackPanel>
    </Grid>
    
  10. 在“解决方案资源管理器”的“(Windows Phone 8.1)”项目中,打开 MainPage.xaml,并将 Windows Phone 8.1 <Grid> 节替换为上述相同的代码。界面看起来应如下所示。

  11. 在“解决方案资源管理器”中,打开“(Windows 8.1)”和“(Windows 8.1)”项目的 MainPage.xaml.cs 文件。在这两个文件顶部添加以下 using 语句:

    using System.Net.Http;
    using Windows.Storage;
    using System.Net.Http.Headers;
    using Windows.Networking.PushNotifications;
    using Windows.UI.Popups;
    using System.Threading.Tasks;
    
  12. 在“(Windows 8.1)”和“(Windows 8.1)”项目的 MainPage.xaml.cs 中,将以下成员添加 MainPage 类。确保使用前面获取的实际后端终结点来替换 <Enter Your Backend Endpoint>。例如,http://mybackend.chinacloudsites.cn

    private static string BACKEND_ENDPOINT = "<Enter Your Backend Endpoint>";
    
  13. 将以下代码添加到“(Windows 8.1)”和“(Windows Phone 8.1)”项目的 MainPage.xaml.cs 中的 MainPage 类。

    PushClick 方法是“发送推送”按钮的单击处理程序。它调用后端以触发向用户名标记与 to_tag 参数匹配的所有设备发送通知。通知消息作为请求正文中的 JSON 内容发送。

    LoginAndRegisterClick 方法是“登录和注册”按钮的单击处理程序。它在本地存储中存储基本身份验证令牌(请注意,这代表身份验证方案使用的任何令牌),然后使用 RegisterClient 来通过后端注册通知。

    private async void PushClick(object sender, RoutedEventArgs e)
    {
        if (toggleWNS.IsChecked.Value)
        {
            await sendPush("wns", ToUserTagTextBox.Text, this.NotificationMessageTextBox.Text);
        }
        if (toggleGCM.IsChecked.Value)
        {
            await sendPush("gcm", ToUserTagTextBox.Text, this.NotificationMessageTextBox.Text);
        }
        if (toggleAPNS.IsChecked.Value)
        {
            await sendPush("apns", ToUserTagTextBox.Text, this.NotificationMessageTextBox.Text);
    
        }
    }
    
    private async Task sendPush(string pns, string userTag, string message)
    {
        var POST_URL = BACKEND_ENDPOINT + "/api/notifications?pns=" +
            pns + "&to_tag=" + userTag;
    
        using (var httpClient = new HttpClient())
        {
            var settings = ApplicationData.Current.LocalSettings.Values;
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", (string)settings["AuthenticationToken"]);
    
            try
            {
                await httpClient.PostAsync(POST_URL, new StringContent(""" + message + """,
                    System.Text.Encoding.UTF8, "application/json"));
            }
            catch (Exception ex)
            {
                MessageDialog alert = new MessageDialog(ex.Message, "Failed to send " + pns + " message");
                alert.ShowAsync();
            }
        }
    }
    
    private async void LoginAndRegisterClick(object sender, RoutedEventArgs e)
    {
        SetAuthenticationTokenInLocalStorage();
    
        var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
    
        // The "username:<user name>" tag gets automatically added by the message handler in the backend.
        // The tag passed here can be whatever other tags you may want to use.
        try
        {
            // The device handle used will be different depending on the device and PNS. 
            // Windows devices use the channel uri as the PNS handle.
            await new RegisterClient(BACKEND_ENDPOINT).RegisterAsync(channel.Uri, new string[] { "myTag" });
    
            var dialog = new MessageDialog("Registered as: " + UsernameTextBox.Text);
            dialog.Commands.Add(new UICommand("OK"));
            await dialog.ShowAsync();
            SendPushButton.IsEnabled = true;
        }
        catch (Exception ex)
        {
            MessageDialog alert = new MessageDialog(ex.Message, "Failed to register with RegisterClient");
            alert.ShowAsync();
        }
    }
    
    private void SetAuthenticationTokenInLocalStorage()
    {
        string username = UsernameTextBox.Text;
        string password = PasswordTextBox.Password;
    
        var token = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password));
        ApplicationData.Current.LocalSettings.Values["AuthenticationToken"] = token;
    }
    
  14. 在“解决方案资源管理器”中的“共享”项目下,打开 App.xaml.cs 文件。在 OnLaunched() 事件处理程序中,查找对 InitNotificationsAsync() 的调用。注释掉或删除对 InitNotificationsAsync() 的调用。上面添加的按钮处理程序将初始化通知注册。

    protected override void OnLaunched(LaunchActivatedEventArgs e)
    {
        //InitNotificationsAsync();
    
  15. 在“解决方案资源管理器”中,右键单击“共享”项目,然后依次单击“添加”和“类”。将类命名为 RegisterClient.cs,然后单击“确定”以生成该类。

    此类将包装所需的 REST 调用,以便能够联系应用程序后端来注册推送通知。它还会在本地存储通知中心创建的 registrationIds (从应用后端注册中提供了详细信息)。请注意,该组件使用当你单击“登录并注册”按钮时存储在本地存储中的授权令牌。

  16. 在 RegisterClient.cs 文件的顶部添加以下 using 语句:

    using Windows.Storage;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using Newtonsoft.Json;
    using System.Threading.Tasks;
    using System.Linq;
    
  17. RegisterClient 类定义中添加以下代码:

    private string POST_URL;
    
    private class DeviceRegistration
    {
        public string Platform { get; set; }
        public string Handle { get; set; }
        public string[] Tags { get; set; }
    }
    
    public RegisterClient(string backendEndpoint)
    {
        POST_URL = backendEndpoint + "/api/register";
    }
    
    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
            throw new System.Net.WebException(statusCode.ToString());
        }
    }
    
    private async Task<HttpStatusCode> UpdateRegistrationAsync(string regId, DeviceRegistration deviceRegistration)
    {
        using (var httpClient = new HttpClient())
        {
            var settings = ApplicationData.Current.LocalSettings.Values;
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", (string) settings["AuthenticationToken"]);
    
            var putUri = POST_URL + "/" + regId;
    
            string json = JsonConvert.SerializeObject(deviceRegistration);
                            var response = await httpClient.PutAsync(putUri, new StringContent(json, Encoding.UTF8, "application/json"));
            return response.StatusCode;
        }
    }
    
    private async Task<string> RetrieveRegistrationIdOrRequestNewOneAsync()
    {
        var settings = ApplicationData.Current.LocalSettings.Values;
        if (!settings.ContainsKey("__NHRegistrationId"))
        {
            using (var httpClient = new HttpClient())
            {
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", (string)settings["AuthenticationToken"]);
    
                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 System.Net.WebException(response.StatusCode.ToString());
                }
            }
        }
        return (string)settings["__NHRegistrationId"];
    
    }
    
  18. 保存所有更改。

测试应用程序

  1. 在 Windows 8.1 和 Windows Phone 8.1 上启动应用程序。对于 Windows Phone 8.1,可以在模拟器或实际设备中运行实例。

  2. 在应用的 Windows 8.1 实例中,输入“用户名”和“密码”,如以下屏幕中所示。这应该与在 Windows Phone 上输入的用户名和密码不同。

  3. 单击“登录和注册”,然后验证是否会确认显示你已登录的对话框。这也会启用“发送推送”按钮。

  4. 在 Windows Phone 8.1 实例上,于“用户名”和“密码”字段中输入用户名字符串,然后单击“登录和注册”。

  5. 然后,在“接收方用户名标记”字段中,输入在 Windows 8.1 上注册的用户名。输入通知消息,然后单击“发送推送”。

  6. 只有已使用匹配用户名标记进行注册的设备才会收到通知消息。

后续步骤