教學課程:使用 Azure 通知中樞和 Google 雲端通訊 (已淘汰) 將通知推送至特定 Android 應用程式使用者Tutorial: Push notification to specific Android application users by using Azure Notification Hubs and Google Cloud Messaging (deprecated)

警告

Google 已於 2018 年 4 月 10 日淘汰 Google 雲端通訊 (GCM)。As of April 10, 2018, Google has deprecated Google Cloud Messaging (GCM). GCM 伺服器和用戶端 API 會由其他項目取代,並且很快就會在 2019 年 5 月 29 日進行移除。The GCM server and client APIs are deprecated and will be removed as soon as May 29, 2019. 如需詳細資訊,請參閱 GCM 和 FCM 常見問題集For more information, see GCM and FCM Frequently Asked Questions.

本教學課程將示範如何使用 Azure 通知中心,來將推播通知傳送到特定裝置上的特定應用程式使用者。This tutorial shows you how to use Azure Notification Hubs to send push notifications to a specific app user on a specific device. ASP.NET WebAPI 後端可用來驗證用戶端並產生通知,如指引文章從您的應用程式後端註冊中所示。An ASP.NET WebAPI backend is used to authenticate clients and to generate notifications, as shown in the guidance article Registering from your app backend. 本教學課程以您在以下教學課程中建立的通知中樞為基礎:教學課程:使用 Azure 通知中樞和 Google 雲端通訊將通知推送至 Android 裝置This tutorial builds on the notification hub that you created in the Tutorial: Push notifications to Android devices by using Azure Notification Hubs and Google Cloud Messaging.

在本教學課程中,您會執行下列步驟:In this tutorial, you take the following steps:

  • 建立會驗證使用者的後端 Web API 專案。Create the backend Web API project that authenticates users.
  • 建立 Android 應用程式。Update the Android application.
  • 測試應用程式Test the app

必要條件Prerequisites

在開始進行本教學課程之前,請先完成教學課程:使用 Azure 通知中樞和 Google 雲端通訊將通知推送至 Android 裝置Complete the Tutorial: Push notifications to Android devices by using Azure Notification Hubs and Google Cloud Messaging before doing this tutorial.

建立 WebAPI 專案Create the WebAPI project

下列各節討論如何建立新的 ASP.NET WebAPI 後端。The following sections discuss the creation of a new ASP.NET WebAPI backend. 此程序有三個主要用途:This process has three main purposes:

  • 驗證用戶端:您可以新增訊息處理常式來驗證用戶端要求,並將使用者與要求建立關聯。Authenticate clients: You add a message handler to authenticate client requests and associate the user with the request.
  • 使用 WebAPI 後端來註冊通知:您會新增控制器來處理新的註冊,以便用戶端裝置接收通知。Register for notifications by using the WebAPI backend: You add a controller to handle new registrations for a client device to receive notifications. 經過驗證的使用者名稱會自動新增至註冊作為 標記The authenticated username is automatically added to the registration as a tag.
  • 傳送通知給用戶端:您可新增一個控制器,以便使用者對與標籤建立關聯的裝置和用戶端觸發安全的推送。Send notifications to clients: You add a controller to provide a way for users to trigger a secure push to devices and clients associated with the tag.

執行下列動作,建立新的 ASP.NET WebAPI 後端:Create the new ASP.NET WebAPI backend by doing the following actions:

重要

如果您使用 Visual Studio 2015 或更新版本,在開始本教學課程之前,請確定您已安裝適用於 Visual Studio 的最新版 NuGet 套件管理員。If you are using Visual Studio 2015 or earlier, before starting this tutorial, ensure that you have installed the latest version of NuGet Package Manager for Visual Studio.

若要檢查版本,請啟動 Visual Studio。To check, start Visual Studio. 在 [工具] 功能表上,選取 [擴充功能和更新] 。On the Tools menu, select Extensions and Updates. 搜尋您的 Visual Studio 版本中的 NuGet Package Manager,然後確定您已安裝最新版本。Search for NuGet Package Manager in your version of Visual Studio, and make sure you have the latest version. 如果您的版本不是最新版本,請將它解除安裝,然後重新安裝 NuGet 套件管理員。If your version is not the latest version, uninstall it, and then reinstall the NuGet Package Manager.

注意

確定您已安裝 Visual Studio Azure SDK 以供網站部署。Make sure you have installed the Visual Studio Azure SDK for website deployment.

  1. 啟動 Visual Studio 或 Visual Studio Express。Start Visual Studio or Visual Studio Express.

  2. 選取 [伺服器總管] ,然後登入您的 Azure 帳戶。Select Server Explorer, and sign in to your Azure account. 若要在您的帳戶上建立網站資源,您必須登入。To create the web site resources on your account, you must be signed in.

  3. 在 Visual Studio 中,以滑鼠右鍵按一下 Visual Studio 解決方案,並指向 [新增] ,然後按一下 [新增專案] 。In Visual Studio, right-click Visual Studio solution, point to Add, and click New Project.

  4. 展開 [Visual C#] ,並選取 [Web] ,然後按一下 [ASP.NET Web 應用程式] 。Expand Visual C#, select Web, and click ASP.NET Web Application.

  5. 在 [名稱] 方塊中,輸入 AppBackend,然後選取 [確定] 。In the Name box, type AppBackend, and then select OK.

    [新增專案] 視窗

  6. 在 [新增 ASP.NET 專案] 視窗中,選取 [Web API] 核取方塊,然後選取 [確定] 。In the New ASP.NET Project window, select the Web API check box, and then select OK.

    [新增 ASP.NET 專案] 視窗

  7. 在 [設定 Microsoft Azure Web 應用程式] 視窗中,選取訂用帳戶,然後在 [App Service 方案] 清單中,執行下列其中一個動作:In the Configure Microsoft Azure Web App window, select a subscription and then, in the App Service plan list, do either of the following actions:

    • 選取您已建立的 App Service 方案。Select an app service plan that you've already created.
    • 選取 [建立新的 App Service 方案] ,然後建立一個新方案。Select Create a new app service plan, and then create one.

    在此教學課程中您不需要資料庫。You do not need a database for this tutorial. 在您選取 App Service 方案之後,選取 [確定] 來建立專案。After you have selected your app service plan, select OK to create the project.

    [定 Microsoft Azure Web 應用程式] 視窗

    如果您沒有看到這個用於設定 App Service 方案的頁面,請繼續進行本教學課程。If you don't see this page for configure app service plan, continue with the tutorial. 您可以在稍後發佈應用程式時再進行設定。You can configure it while publishing the app later.

對 WebAPI 後端驗證用戶端Authenticate clients to the WebAPI backend

在本節中,您會為新的後端建立名為 AuthenticationTestHandler 的新訊息處理常式類別。In this section, you create a new message-handler class named AuthenticationTestHandler for the new backend. 這個類別衍生自 DelegatingHandler 並新增為訊息處理常式,以便處理進入後端的所有要求。This class is derived from DelegatingHandler and added as a message handler so that it can process all requests that come into the backend.

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 [AppBackend] 專案,然後依序選取 [新增] 和 [類別] 。In Solution Explorer, right-click the AppBackend project, select Add, and then select Class.

  2. 將新類別命名為 AuthenticationTestHandler.cs,然後選取 [新增] 以產生類別。Name the new class AuthenticationTestHandler.cs, and then select Add to generate the class. 為了簡單起見,此類別使用「基本驗證」 來驗證使用者。This class authenticates users by using Basic Authentication for simplicity. 您的應用程式可以使用任何驗證結構描述。Your app can use any authentication scheme.

  3. 在 AuthenticationTestHandler.cs 中,加入下列 using 陳述式:In AuthenticationTestHandler.cs, add the following using statements:

    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 類別定義:In AuthenticationTestHandler.cs, replace the AuthenticationTestHandler class definition with the following code:

    下列三個條件都成立時,此處理常式會授權要求:The handler authorizes the request when the following three conditions are true:

    • 要求包含「授權」 標頭。The request includes an Authorization header.
    • 要求使用 基本 驗證。The request uses basic authentication.
    • 使用者名稱字串和密碼字串是相同的字串。The user name string and the password string are the same string.

    否則,會拒絕此要求。Otherwise, the request is rejected. 此驗證不是真正的驗證和授權方法。This authentication is not a true authentication and authorization approach. 這只是本教學課程的簡單範例。It is only a simple example for this tutorial.

    如果要求訊息已經由 AuthenticationTestHandler 驗證及授權,則基本驗證使用者會附加至 HttpContext 上的目前要求。If the request message is authenticated and authorized by AuthenticationTestHandler, the basic authentication user is attached to the current request on HttpContext. 稍後另一個控制器 (RegisterController) 會使用 HttpContext 中的使用者資訊,將 標記 新增至通知註冊要求。User information in HttpContext will be used by another controller (RegisterController) later to add a tag to the notification registration request.

    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 類別未提供真正的驗證。Security note: The AuthenticationTestHandler class does not provide true authentication. 它僅可用於模仿基本驗證而且並不安全。It is used only to mimic basic authentication and is not secure. 您必須在生產應用程式和服務中實作安全的驗證機制。You must implement a secure authentication mechanism in your production applications and services.

  5. 若要註冊訊息處理常式,請在 App_Start/WebApiConfig.cs 類別中 Register 方法的結尾新增下列程式碼:To register the message handler, add the following code at the end of the Register method in the App_Start/WebApiConfig.cs class:

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. 儲存您的變更。Save your changes.

使用 WebAPI 後端來註冊通知Register for notifications by using the WebAPI backend

在本節中,您會將新的控制器新增至 WebAPI 後端來處理要求,以使用通知中樞的用戶端程式庫為使用者和裝置註冊通知。In this section, you add a new controller to the WebAPI backend to handle requests to register a user and a device for notifications by using the client library for notification hubs. 控制器會對已由 AuthenticationTestHandler 驗證並附加至 HttpContext 的使用者,新增使用者標記。The controller adds a user tag for the user that was authenticated and attached to HttpContext by AuthenticationTestHandler. 此標籤具有以下字串格式:"username:<actual username>"The tag has the string format, "username:<actual username>".

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 [AppBackend] 專案,然後選取 [管理 NuGet 套件] 。In Solution Explorer, right-click the AppBackend project and then select Manage NuGet Packages.

  2. 在左窗格中,選取 [線上] ,然後在 [搜尋] 方塊中輸入 Microsoft.Azure.NotificationHubsIn the left pane, select Online and then, in the Search box, type Microsoft.Azure.NotificationHubs.

  3. 選取結果清單中的 [Microsoft Azure 通知中樞] ,然後選取 [安裝] 。In the results list, select Microsoft Azure Notification Hubs, and then select Install. 請完成安裝,然後關閉 [NuGet Package Manager] 視窗。Complete the installation, and then close the NuGet Package Manager window.

    此動作會使用 Microsoft.Azure.Notification Hubs NuGet 套件來新增對 Azure 通知中樞 SDK 的參考。This action adds a reference to the Azure Notification Hubs SDK by using the Microsoft.Azure.Notification Hubs NuGet package.

  4. 建立新的類別檔案,代表與用來傳送通知的通知中樞間的連線。Create a new class file that represents the connection with the notification hub that's used to send notifications. 在 [方案總管] 中,以滑鼠右鍵按一下 Models 資料夾,選取 [新增] ,然後選取 [類別] 。In Solution Explorer, right-click the Models folder, select Add, and then select Class. 將新類別命名為 Notifications.cs,然後選取 [新增] 以產生類別。Name the new class Notifications.cs, and then select Add to generate the class.

    [新增項目] 視窗

  5. 在 Notifications.cs 中,將下列 using 陳述式新增在檔案頂端:In Notifications.cs, add the following using statement at the top of the file:

    using Microsoft.Azure.NotificationHubs;
    
  6. 以下列程式碼取代 Notifications 類別定義,並以通知中樞的連接字串 (含完整存取權) 和中心名稱 (可在 Azure 入口網站取代) 取代兩個預留位置:Replace the Notifications class definition with the following code, and replace the two placeholders with the connection string (with full access) for your notification hub and the hub name (available at Azure portal):

    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,再繼續進行。Enter the name and the DefaultFullSharedAccessSignature of your hub before proceeding further.

  7. 接下來,建立名為 RegisterController 的新控制器。Next, create a new controller named RegisterController. 在 [方案總管] 中,以滑鼠右鍵按一下 Controllers 資料夾,選取 [新增] ,然後選取 [控制器] 。In Solution Explorer, right-click the Controllers folder, select Add, and then select Controller.

  8. 選取 [Web API 2 控制器 - 空的] ,然後選取 [新增] 。Select Web API 2 Controller - Empty, and then select Add.

    [新增 Scaffold] 視窗

  9. 在 [控制器名稱] 方塊中,輸入 RegisterController 為新的類別命名,然後選取 [新增] 。In the Controller name box, type RegisterController to name the new class, and then select Add.

    [新增控制器] 視窗

  10. 在 RegisterController.cs 中,加入下列 using 陳述式:In RegisterController.cs, add the following using statements:

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. RegisterController 類別定義中加入下列程式碼。Add the following code inside the RegisterController class definition. 在此程式碼中,您會為已附加至 HttpContext 的使用者新增使用者標籤。In this code, you add a user tag for the user that's attached to HttpContext. 您新增的訊息篩選 AuthenticationTestHandler 會驗證此使用者並附加至 HttpContext。The user was authenticated and attached to HttpContext by the message filter that you added, AuthenticationTestHandler. 您也可以新增選擇性檢查,以驗證使用者是否有權註冊所要求的標籤。You can also add optional checks to verify that the user has rights to register for the requested tags.

    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. 儲存您的變更。Save your changes.

從 WebAPI 後端傳送通知Send notifications from the WebAPI backend

在本節中,您會新增控制器,以便用戶端裝置傳送通知。In this section, you add a new controller that exposes a way for client devices to send a notification. 此通知是以使用者名稱標籤為基礎,其使用 ASP.NET WebAPI 後端中的 Azure 通知中樞 .NET 程式庫。The notification is based on the username tag that uses Azure Notification Hubs .NET Library in the ASP.NET WebAPI backend.

  1. 以您在上一節中建立 RegisterController 的相同方式,建立另一個名為 NotificationsController的新控制器。Create another new controller named NotificationsController the same way you created RegisterController in the previous section.

  2. 在 NotificationsController.cs 中,加入下列 using 陳述式:In NotificationsController.cs, add the following using statements:

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. NotificationsController 類別中新增下列方法:Add the following method to the NotificationsController class:

    此程式碼會傳送以平台通知服務 (PNS) pns 參數為基礎的通知類型。This code sends a notification type that's based on the Platform Notification Service (PNS) pns parameter. to_tag 的值用來設定訊息上的 username 標記。The value of to_tag is used to set the username tag on the message. 此標記必須符合作用中通知中樞註冊的使用者名稱標記。This tag must match a username tag of an active notification hub registration. 通知訊息是取自 POST 要求主體,並針對目標 PNS 格式化。The notification message is pulled from the body of the POST request and formatted for the target PNS.

    視您的支援裝置用來接收通知的 PNS 而言,可支援各種格式的通知。Depending on the PNS that your supported devices use to receive notifications, the notifications are supported by a variety of formats. 例如在 Windows 裝置上,您可以搭配 WNS 使用不受其他 PNS 直接支援的快顯通知For example, on Windows devices, you might use a toast notification with WNS that isn't directly supported by another PNS. 在這類情況下,您的後端必須針對您打算支援的裝置 PNS,將通知格式化為支援的通知。In such an instance, your backend needs to format the notification into a supported notification for the PNS of devices you plan to support. 然後在 NotificationHubClient 類別 上使用適當的傳送 API。Then use the appropriate send API on the NotificationHubClient class.

    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 鍵。To run the application and ensure the accuracy of your work so far, select the F5 key. 應用程式會開啟網頁瀏覽器並顯示於 ASP.NET 首頁上。The app opens a web browser, and it is displayed on the ASP.NET home page.

發佈新的 WebAPI 後端Publish the new WebAPI backend

接下來,您可將應用程式部署到 Azure 網站,讓它得以從所有裝置存取。Next, you deploy the app to an Azure website to make it accessible from all devices.

  1. 以滑鼠右鍵按一下 AppBackend 專案,然後選取 [發佈] 。Right-click the AppBackend project, and then select Publish.

  2. 選取 [Microsoft Azure App Service] 作為發佈目標,然後選取 [發佈]。Select Microsoft Azure App Service as your publish target, and then select **Publish. [建立 App Service] 視窗隨即開啟。The Create App Service window opens. 您可以在此建立在 Azure 中執行 ASP.NET Web 應用程式所需的所有 Azure 資源。Here you can create all the necessary Azure resources to run the ASP.NET web app in Azure.

    [Microsoft Azure App Service] 圖格

  3. 在 [建立 App Service] 視窗中,選取您的 Azure 帳戶。In the Create App Service window, select your Azure account. 選取 [變更類型] > [Web 應用程式] 。Select Change Type > Web App. 保留預設 [Web 應用程式名稱] ,然後選取 [訂用帳戶] 、[資源群組] 和 [App Service 方案] 。Keep the default Web App Name, and then select the Subscription, Resource Group, and App Service Plan.

  4. 選取 [建立] 。Select Create.

  5. 記下 [摘要] 區段中的 [網站 URL] 屬性。Make a note of the Site URL property in the Summary section. 此 URL 是您在本教學課程中稍後使用的「後端端點」 。This URL is your back-end endpoint later in the tutorial.

  6. 選取 [發佈] 。Select Publish.

精靈完成後,它會將 ASP.NET Web 應用程式發佈至 Azure,然後在預設瀏覽器中開啟應用程式。After you've completed the wizard, it publishes the ASP.NET web app to Azure and then opens the app in the default browser. 您的應用程式可在 Azure App Service 中檢視。Your application is viewable in Azure App Services.

URL 會使用您稍早指定的 Web 應用程式名稱,其格式為 http://<app_name>.azurewebsites.net。The URL uses the web app name that you specified earlier, with the format http://<app_name>.azurewebsites.net.

建立 Android 專案Create the Android Project

下一個步驟是更新在教學課程:使用 Azure 通知中樞和 Google 雲端通訊將通知推送至 Android 裝置The next step is to update the Android application created in the Tutorial: Push notifications to Android devices by using Azure Notification Hubs and Google Cloud Messaging.

  1. 開啟您的 res/layout/activity_main.xml 檔案,取代下列內容定義:Open your res/layout/activity_main.xml file, replace the following content definitions:

    這會加入新的 EditText 控制項,以便以使用者身分登入。It adds new EditText controls for logging in as a user. 此外,也會針對將成為您傳送的通知一部分的使用者名稱標記加入欄位:Also a field is added for a username tag that will be part of notifications you send:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
    <EditText
        android:id="@+id/usernameText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="@string/usernameHint"
        android:layout_above="@+id/passwordText"
        android:layout_alignParentEnd="true" />
    <EditText
        android:id="@+id/passwordText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="@string/passwordHint"
        android:inputType="textPassword"
        android:layout_above="@+id/buttonLogin"
        android:layout_alignParentEnd="true" />
    <Button
        android:id="@+id/buttonLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/loginButton"
        android:onClick="login"
        android:layout_above="@+id/toggleButtonGCM"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="24dp" />
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textOn="WNS on"
        android:textOff="WNS off"
        android:id="@+id/toggleButtonWNS"
        android:layout_toLeftOf="@id/toggleButtonGCM"
        android:layout_centerVertical="true" />
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textOn="GCM on"
        android:textOff="GCM off"
        android:id="@+id/toggleButtonGCM"
        android:checked="true"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textOn="APNS on"
        android:textOff="APNS off"
        android:id="@+id/toggleButtonAPNS"
        android:layout_toRightOf="@id/toggleButtonGCM"
        android:layout_centerVertical="true" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editTextNotificationMessageTag"
        android:layout_below="@id/toggleButtonGCM"
        android:layout_centerHorizontal="true"
        android:hint="@string/notification_message_tag_hint" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editTextNotificationMessage"
        android:layout_below="@+id/editTextNotificationMessageTag"
        android:layout_centerHorizontal="true"
        android:hint="@string/notification_message_hint" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/send_button"
        android:id="@+id/sendbutton"
        android:onClick="sendNotificationButtonOnClick"
        android:layout_below="@+id/editTextNotificationMessage"
        android:layout_centerHorizontal="true" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/text_hello"
    />  
    </RelativeLayout>
    
  2. 開啟 res/values/strings.xml 檔案,並以下列幾行程式碼取代 send_button 定義,這幾行程式碼可重新定義 send_button 的字串並新增其他控制項的字串:Open your res/values/strings.xml file and replace the send_button definition with the following lines that redefine the string for the send_button and add strings for the other controls:

    <string name="usernameHint">Username</string>
    <string name="passwordHint">Password</string>
    <string name="loginButton">1. Sign in</string>
    <string name="send_button">2. Send Notification</string>
    <string name="notification_message_hint">Notification message</string>
    <string name="notification_message_tag_hint">Recipient username</string>
    

    您的 main_activity.xml 圖形版面配置現在應如下圖所示:Your main_activity.xml graphical layout should now look like the following image:

  3. 在與 MainActivity 類別相同的套件中,建立名為 RegisterClient 的新類別。Create a new class named RegisterClient in the same package as your MainActivity class. 將下列程式碼使用於新的類別檔案。Use the code below for the new class file.

    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.util.Set;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.HttpStatus;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.client.methods.HttpUriRequest;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.util.EntityUtils;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.util.Log;
    
    public class RegisterClient {
        private static final String PREFS_NAME = "ANHSettings";
        private static final String REGID_SETTING_NAME = "ANHRegistrationId";
        private String Backend_Endpoint;
        SharedPreferences settings;
        protected HttpClient httpClient;
        private String authorizationHeader;
    
        public RegisterClient(Context context, String backendEndpoint) {
            super();
            this.settings = context.getSharedPreferences(PREFS_NAME, 0);
            httpClient =  new DefaultHttpClient();
            Backend_Endpoint = backendEndpoint + "/api/register";
        }
    
        public String getAuthorizationHeader() {
            return authorizationHeader;
        }
    
        public void setAuthorizationHeader(String authorizationHeader) {
            this.authorizationHeader = authorizationHeader;
        }
    
        public void register(String handle, Set<String> tags) throws ClientProtocolException, IOException, JSONException {
            String registrationId = retrieveRegistrationIdOrRequestNewOne(handle);
    
            JSONObject deviceInfo = new JSONObject();
            deviceInfo.put("Platform", "gcm");
            deviceInfo.put("Handle", handle);
            deviceInfo.put("Tags", new JSONArray(tags));
    
            int statusCode = upsertRegistration(registrationId, deviceInfo);
    
            if (statusCode == HttpStatus.SC_OK) {
                return;
            } else if (statusCode == HttpStatus.SC_GONE){
                settings.edit().remove(REGID_SETTING_NAME).commit();
                registrationId = retrieveRegistrationIdOrRequestNewOne(handle);
                statusCode = upsertRegistration(registrationId, deviceInfo);
                if (statusCode != HttpStatus.SC_OK) {
                    Log.e("RegisterClient", "Error upserting registration: " + statusCode);
                    throw new RuntimeException("Error upserting registration");
                }
            } else {
                Log.e("RegisterClient", "Error upserting registration: " + statusCode);
                throw new RuntimeException("Error upserting registration");
            }
        }
    
        private int upsertRegistration(String registrationId, JSONObject deviceInfo)
                throws UnsupportedEncodingException, IOException,
                ClientProtocolException {
            HttpPut request = new HttpPut(Backend_Endpoint+"/"+registrationId);
            request.setEntity(new StringEntity(deviceInfo.toString()));
            request.addHeader("Authorization", "Basic "+authorizationHeader);
            request.addHeader("Content-Type", "application/json");
            HttpResponse response = httpClient.execute(request);
            int statusCode = response.getStatusLine().getStatusCode();
            return statusCode;
        }
    
        private String retrieveRegistrationIdOrRequestNewOne(String handle) throws ClientProtocolException, IOException {
            if (settings.contains(REGID_SETTING_NAME))
                return settings.getString(REGID_SETTING_NAME, null);
    
            HttpUriRequest request = new HttpPost(Backend_Endpoint+"?handle="+handle);
            request.addHeader("Authorization", "Basic "+authorizationHeader);
            HttpResponse response = httpClient.execute(request);
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                Log.e("RegisterClient", "Error creating registrationId: " + response.getStatusLine().getStatusCode());
                throw new RuntimeException("Error creating Notification Hubs registrationId");
            }
            String registrationId = EntityUtils.toString(response.getEntity());
            registrationId = registrationId.substring(1, registrationId.length()-1);
    
            settings.edit().putString(REGID_SETTING_NAME, registrationId).commit();
    
            return registrationId;
        }
    }
    

    為註冊推播通知,此元件會實作連絡應用程式後端所需的 REST 呼叫。This component implements the REST calls required to contact the app backend, in order to register for push notifications. 它也會在本機儲存通知中心所建立的 registrationIds ,如 從您的應用程式後端註冊中的詳細說明。It also locally stores the registrationIds created by the Notification Hub as detailed in Registering from your app backend. 當您按一下 [登入] 按鈕時,系統便會使用儲存在本機儲存體中的授權權杖。It uses an authorization token stored in local storage when you click the Sign in button.

  4. 在您的類別中,針對 NotificationHub 移除或註解排除您的私用欄位,並對 RegisterClient 類別新增一個欄位,以及對 ASP.NET 後端端點新增一個字串。In your class, remove or comment out your private field for NotificationHub, and add a field for the RegisterClient class and a string for your ASP.NET backend's endpoint. 請務必將 <Enter Your Backend Endpoint> 取代為您先前取得的實際後端端點。Be sure to replace <Enter Your Backend Endpoint> with your actual backend endpoint obtained previously. 例如: http://mybackend.azurewebsites.netFor example, http://mybackend.azurewebsites.net.

    //private NotificationHub hub;
    private RegisterClient registerClient;
    private static final String BACKEND_ENDPOINT = "<Enter Your Backend Endpoint>";
    
  5. MainActivity 類別的 onCreate 方法中,移除或註解排除 hub 欄位的初始化和 registerWithNotificationHubs 方法的呼叫。In your MainActivity class, in the onCreate method, remove, or comment out the initialization of the hub field and the call to the registerWithNotificationHubs method. 然後加入程式碼以初始化 RegisterClient 類別的執行個體。Then add code to initialize an instance of the RegisterClient class. 此方法應包含下列程式碼行:The method should contain the following lines:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        mainActivity = this;
        NotificationsManager.handleNotifications(this, NotificationSettings.SenderId, MyHandler.class);
        gcm = GoogleCloudMessaging.getInstance(this);
    
        //hub = new NotificationHub(HubName, HubListenConnectionString, this);
        //registerWithNotificationHubs();
    
        registerClient = new RegisterClient(this, BACKEND_ENDPOINT);
    
        setContentView(R.layout.activity_main);
    }
    
  6. MainActivity 類別中,刪除或註解排除整個 registerWithNotificationHubs 方法。In your MainActivity class, delete or comment out the entire registerWithNotificationHubs method. 本教學課程不會使用此方法。It will not be used in this tutorial.

  7. 將下列 import 陳述式新增至 MainActivity.java 檔案。Add the following import statements to your MainActivity.java file.

    import android.util.Base64;
    import android.view.View;
    import android.widget.EditText;
    
    import android.widget.Button;
    import android.widget.ToggleButton;
    import java.io.UnsupportedEncodingException;
    import android.content.Context;
    import java.util.HashSet;
    import android.widget.Toast;
    import org.apache.http.client.ClientProtocolException;
    import java.io.IOException;
    import org.apache.http.HttpStatus;
    
    import android.os.AsyncTask;
    import org.apache.http.HttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.DefaultHttpClient;
    
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    
  8. 將 onStart 方法的程式碼取代為下列程式碼:Replace code on the onStart method with the following code:

    super.onStart();
    Button sendPush = (Button) findViewById(R.id.sendbutton);
    sendPush.setEnabled(false);
    
  9. 然後,新增下列方法來處理 [登入] 按鈕 click 事件及傳送推播通知。Then, add the following methods to handle the Sign in button click event and sending push notifications.

    public void login(View view) throws UnsupportedEncodingException {
        this.registerClient.setAuthorizationHeader(getAuthorizationHeader());
    
        final Context context = this;
        new AsyncTask<Object, Object, Object>() {
            @Override
            protected Object doInBackground(Object... params) {
                try {
                    String regid = gcm.register(NotificationSettings.SenderId);
                    registerClient.register(regid, new HashSet<String>());
                } catch (Exception e) {
                    DialogNotify("MainActivity - Failed to register", e.getMessage());
                    return e;
                }
                return null;
            }
    
            protected void onPostExecute(Object result) {
                Button sendPush = (Button) findViewById(R.id.sendbutton);
                sendPush.setEnabled(true);
                Toast.makeText(context, "Logged in and registered.",
                        Toast.LENGTH_LONG).show();
            }
        }.execute(null, null, null);
    }
    
    private String getAuthorizationHeader() throws UnsupportedEncodingException {
        EditText username = (EditText) findViewById(R.id.usernameText);
        EditText password = (EditText) findViewById(R.id.passwordText);
        String basicAuthHeader = username.getText().toString()+":"+password.getText().toString();
        basicAuthHeader = Base64.encodeToString(basicAuthHeader.getBytes("UTF-8"), Base64.NO_WRAP);
        return basicAuthHeader;
    }
    
    /**
        * This method calls the ASP.NET WebAPI backend to send the notification message
        * to the platform notification service based on the pns parameter.
        *
        * @param pns     The platform notification service to send the notification message to. Must
        *                be one of the following ("wns", "gcm", "apns").
        * @param userTag The tag for the user who will receive the notification message. This string
        *                must not contain spaces or special characters.
        * @param message The notification message string. This string must include the double quotes
        *                to be used as JSON content.
        */
    public void sendPush(final String pns, final String userTag, final String message)
            throws ClientProtocolException, IOException {
        new AsyncTask<Object, Object, Object>() {
            @Override
            protected Object doInBackground(Object... params) {
                try {
    
                    String uri = BACKEND_ENDPOINT + "/api/notifications";
                    uri += "?pns=" + pns;
                    uri += "&to_tag=" + userTag;
    
                    HttpPost request = new HttpPost(uri);
                    request.addHeader("Authorization", "Basic "+ getAuthorizationHeader());
                    request.setEntity(new StringEntity(message));
                    request.addHeader("Content-Type", "application/json");
    
                    HttpResponse response = new DefaultHttpClient().execute(request);
    
                    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                        DialogNotify("MainActivity - Error sending " + pns + " notification",
                            response.getStatusLine().toString());
                        throw new RuntimeException("Error sending notification");
                    }
                } catch (Exception e) {
                    DialogNotify("MainActivity - Failed to send " + pns + " notification ", e.getMessage());
                    return e;
                }
    
                return null;
            }
        }.execute(null, null, null);
    }
    

    [登入] 按鈕的 login 處理常式會使用輸入使用者名稱和密碼 (這代表驗證結構描述使用的任何權杖) 產生基本驗證權杖,然後使用 RegisterClient 呼叫後端進行註冊。The login handler for the Sign in button generates a basic authentication token using on the input username and password (it represents any token your authentication scheme uses), then it uses RegisterClient to call the backend for registration.

    sendPush 方法會呼叫後端,以根據使用者標記觸發使用者的安全通知。The sendPush method calls the backend to trigger a secure notification to the user based on the user tag. sendPush 鎖定目標的平台通知服務取決於傳入的 pns 字串。The platform notification service that sendPush targets depends on the pns string passed in.

  10. 將下列 DialogNotify 方法新增至 MainActivity 類別。Add the following DialogNotify method to the MainActivity class.

    protected void DialogNotify(String title, String message)
    {
        AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create();
        alertDialog.setTitle(title);
        alertDialog.setMessage(message);
        alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
        alertDialog.show();
    }
    
  11. MainActivity 類別中,更新 sendNotificationButtonOnClick 方法以透過使用者選取的平台通知服務呼叫 sendPush 方法,如下所示。In your MainActivity class, update the sendNotificationButtonOnClick method to call the sendPush method with the user's selected platform notification services as follows.

    /**
    * Send Notification button click handler. This method sends the push notification
    * message to each platform selected.
    *
    * @param v The view
    */
    public void sendNotificationButtonOnClick(View v)
            throws ClientProtocolException, IOException {
    
        String nhMessageTag = ((EditText) findViewById(R.id.editTextNotificationMessageTag))
                .getText().toString();
        String nhMessage = ((EditText) findViewById(R.id.editTextNotificationMessage))
                .getText().toString();
    
        // JSON String
        nhMessage = "\"" + nhMessage + "\"";
    
        if (((ToggleButton)findViewById(R.id.toggleButtonWNS)).isChecked())
        {
            sendPush("wns", nhMessageTag, nhMessage);
        }
        if (((ToggleButton)findViewById(R.id.toggleButtonGCM)).isChecked())
        {
            sendPush("gcm", nhMessageTag, nhMessage);
        }
        if (((ToggleButton)findViewById(R.id.toggleButtonAPNS)).isChecked())
        {
            sendPush("apns", nhMessageTag, nhMessage);
        }
    }
    
  12. build.gradle 檔案中,將以下這一行新增至 buildTypes 區段後面的 android 區段。In the build.gradle file, add the following line to the android section after the buildTypes section.

    useLibrary 'org.apache.http.legacy'
    
  13. 建置專案。Build the project.

測試應用程式Test the app

  1. 在使用 Android Studio 的裝置或模擬器上執行應用程式。Run the application on a device or an emulator using Android Studio.

  2. 在 Android 應用程式中,輸入使用者名稱和密碼。In the Android app, enter a username and password. 兩者必須是相同的字串值,而且不得包含空格或特殊字元。They must both be the same string value and they must not contain spaces or special characters.

  3. 在 Android 應用程式中,按一下 [登入] 。In the Android app, click Sign in. 等待快顯訊息出現,指出 [ 已登入並註冊]。Wait for a toast message that states Logged in and registered. 這會啟用 [傳送通知] 按鈕。It enables the Send Notification button.

  4. 按一下切換按鈕,啟用您已執行應用程式並註冊使用者的所有平台。Click the toggle buttons to enable all platforms where you ran the app and registered a user.

  5. 輸入會收到通知訊息的使用者名稱。Enter the user's name that receives the notification message. 該使用者必須在目標裝置上註冊通知。That user must be registered for notifications on the target devices.

  6. 輸入做為推播通知訊息的訊息,以便使用者接收。Enter a message for the user to receive as a push notification message.

  7. 按一下 [ 傳送通知]。Click Send Notification. 每個具有相符使用者名稱標記之註冊的裝置都會收到推播通知。Each device that has a registration with the matching username tag receives the push notification.

後續步驟Next steps

在本教學課程中,您已學會如何針對具有與其註冊相關聯標記的使用者,將通知推送至這些特定使用者。In this tutorial, you learned how to push notifications to specific users that have tags associated with their registrations. 若要了解如何推送以位置為基礎的通知,請繼續進行下列教學課程:To learn how to push location-based notifications, advance to the following tutorial: