教學課程:使用 Azure 通知中樞將推播通知傳送給特定使用者Tutorial: Send push notifications to specific users using Azure Notification Hubs

本教學課程將向您說明如何使用 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 topic Registering from your app backend.

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

  • 建立 WebAPI 專案Create the WebAPI project
  • 對 WebAPI 後端驗證用戶端Authenticate clients to the WebAPI backend
  • 使用 WebAPI 後端來註冊通知Register for notifications by using the WebAPI backend
  • 從 WebAPI 後端傳送通知Send notifications from the WebAPI backend
  • 發佈新的 WebAPI 後端Publish the new WebAPI backend
  • 修改您的 iOS 應用程式Modify your iOS app
  • 測試應用程式Test the application

必要條件Prerequisites

本教學課程假設您已建立並設定通知中樞,如 開始使用通知中樞 (iOS)中所述。This tutorial assumes that you have created and configured your notification hub as described in Getting Started with Notification Hubs (iOS). 本教學課程還是 安全推播 (iOS) 教學課程的必要條件。This tutorial is also the prerequisite to the Secure Push (iOS) tutorial. 如果您想要使用 Mobile Apps 作為您的後端服務,請參閱 開始使用 Mobile Apps 推播If you want to use Mobile Apps as your backend service, see the Mobile Apps Get Started with Push.

建立 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.

修改您的 iOS 應用程式Modify your iOS app

  1. 開啟您在 開始使用通知中心 (iOS) 教學課程中建立的 [單頁] 檢視應用程式。Open the Single Page view app you created in the Getting Started with Notification Hubs (iOS) tutorial.

    注意

    本節假設您已使用空白組織名稱來設定您的專案。This section assumes that your project is configured with an empty organization name. 否則,您需要在所有類別名稱的前方加上您的組織名稱。If not, you need to prepend your organization name to all class names.

  2. Main.storyboard 檔案中,從物件程式庫新增螢幕擷取畫面中顯示的元件。In the Main.storyboard file, add the components shown in the screenshot from the object library.

    在 Xcode interface builder 中編輯分鏡腳本

    • 使用者名稱:含有預留位置文字 ( 輸入使用者名稱) 的 UITextField,位於傳送結果標籤正下方且受到左右邊界限制並位於傳送結果標籤正下方。Username: A UITextField with placeholder text, Enter Username, immediately beneath the send results label and constrained to the left and right margins and beneath the send results label.

    • 密碼:含有預留位置文字 ( 輸入密碼) 的 UITextField,位於使用者名稱文字欄位正下方且受到左右邊界限制並位於使用者文字欄位正下方。Password: A UITextField with placeholder text, Enter Password, immediately beneath the username text field and constrained to the left and right margins and beneath the username text field. 勾選 [ 傳回金鑰 ] 底下屬性偵測器中的 [ 安全文字輸入] 選項。Check the Secure Text Entry option in the Attribute Inspector, under Return Key.

    • 登入:密碼文字欄位正下方標記的 UIButton,並取消勾選 [控制項內容] 底下屬性偵測器中的 [啟用] 選項。Log in: A UIButton labeled immediately beneath the password text field and uncheck the Enabled option in the Attributes Inspector, under Control-Content

    • WNS:當中樞中已設定 Windows 通知服務時,用來啟用傳送通知至該服務的標籤與開關。WNS: Label and switch to enable sending the notification Windows Notification Service if it has been set up on the hub. 請參閱 Windows 入門教學課程。See the Windows Getting Started tutorial.

    • GCM:當中樞中已設定 Google Cloud Messaging 時,用來啟用傳送通知至該服務的標籤與開關。GCM: Label and switch to enable sending the notification to Google Cloud Messaging if it has been set up on the hub. 請參閱 Android 入門 教學課程。See Android Getting Started tutorial.

    • APNS:啟用傳送通知給 Apple 平台通知服務之功能的標籤與開關。APNS: Label and switch to enable sending the notification to the Apple Platform Notification Service.

    • 收件者使用者名稱: 含有預留位置文字 (收件者使用者名稱標記) 的 UITextField,位於 GCM 標籤正下方,且受到左右邊界與 GCM 正下方的限制。Recipient Username:A UITextField with placeholder text, Recipient username tag, immediately beneath the GCM label and constrained to the left and right margins and beneath the GCM label.

      開始使用通知中心 (iOS) 教學課程中已經新增一些元件。Some components were added in the Getting Started with Notification Hubs (iOS) tutorial.

  3. Ctrl 可拖曳檢視中的元件到 ViewController.h,並新增這些新的輸出。Ctrl drag from the components in the view to ViewController.h and add these new outlets.

    @property (weak, nonatomic) IBOutlet UITextField *UsernameField;
    @property (weak, nonatomic) IBOutlet UITextField *PasswordField;
    @property (weak, nonatomic) IBOutlet UITextField *RecipientField;
    @property (weak, nonatomic) IBOutlet UITextField *NotificationField;
    
    // Used to enable the buttons on the UI
    @property (weak, nonatomic) IBOutlet UIButton *LogInButton;
    @property (weak, nonatomic) IBOutlet UIButton *SendNotificationButton;
    
    // Used to enabled sending notifications across platforms
    @property (weak, nonatomic) IBOutlet UISwitch *WNSSwitch;
    @property (weak, nonatomic) IBOutlet UISwitch *GCMSwitch;
    @property (weak, nonatomic) IBOutlet UISwitch *APNSSwitch;
    
    - (IBAction)LogInAction:(id)sender;
    
  4. ViewController.h 中,在匯入陳述式後面新增以下的 #defineIn ViewController.h, add the following #define after your import statements. <Enter Your Backend Endpoint> 預留位置替換成上一節中用來部署應用程式後端的目的地 URL。Substitute the <Enter Your Backend Endpoint> placeholder with the Destination URL you used to deploy your app backend in the previous section. 例如: http://your_backend.azurewebsites.netFor example, http://your_backend.azurewebsites.net.

    #define BACKEND_ENDPOINT @"<Enter Your Backend Endpoint>"
    
  5. 在您的專案中,建立一個名為 RegisterClient 的新 Cocoa Touch 類別,作為與您所建立 ASP.NET 後端互動的介面。In your project, create a new Cocoa Touch class named RegisterClient to interface with the ASP.NET back-end you created. 建立繼承自 NSObject的類別。Create the class inheriting from NSObject. 然後,將下列程式碼新增至 RegisterClient.hThen add the following code in the RegisterClient.h.

    @interface RegisterClient : NSObject
    
    @property (strong, nonatomic) NSString* authenticationHeader;
    
    -(void) registerWithDeviceToken:(NSData*)token tags:(NSSet*)tags
        andCompletion:(void(^)(NSError*))completion;
    
    -(instancetype) initWithEndpoint:(NSString*)Endpoint;
    
    @end
    
  6. RegisterClient.m 中,更新 @interface 區段:In the RegisterClient.m, update the @interface section:

    @interface RegisterClient ()
    
    @property (strong, nonatomic) NSURLSession* session;
    @property (strong, nonatomic) NSURLSession* endpoint;
    
    -(void) tryToRegisterWithDeviceToken:(NSData*)token tags:(NSSet*)tags retry:(BOOL)retry
                andCompletion:(void(^)(NSError*))completion;
    -(void) retrieveOrRequestRegistrationIdWithDeviceToken:(NSString*)token
                completion:(void(^)(NSString*, NSError*))completion;
    -(void) upsertRegistrationWithRegistrationId:(NSString*)registrationId deviceToken:(NSString*)token
                tags:(NSSet*)tags andCompletion:(void(^)(NSURLResponse*, NSError*))completion;
    
    @end
    
  7. 使用以下程式碼取代 RegisterClient.m 中的 @implementation 區段:Replace the @implementation section in the RegisterClient.m with the following code:

    @implementation RegisterClient
    
    // Globals used by RegisterClient
    NSString *const RegistrationIdLocalStorageKey = @"RegistrationId";
    
    -(instancetype) initWithEndpoint:(NSString*)Endpoint
    {
        self = [super init];
        if (self) {
            NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
            _session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
            _endpoint = Endpoint;
        }
        return self;
    }
    
    -(void) registerWithDeviceToken:(NSData*)token tags:(NSSet*)tags
                andCompletion:(void(^)(NSError*))completion
    {
        [self tryToRegisterWithDeviceToken:token tags:tags retry:YES andCompletion:completion];
    }
    
    -(void) tryToRegisterWithDeviceToken:(NSData*)token tags:(NSSet*)tags retry:(BOOL)retry
                andCompletion:(void(^)(NSError*))completion
    {
        NSSet* tagsSet = tags?tags:[[NSSet alloc] init];
    
        NSString *deviceTokenString = [[token description]
            stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
        deviceTokenString = [[deviceTokenString stringByReplacingOccurrencesOfString:@" " withString:@""]
                                uppercaseString];
    
        [self retrieveOrRequestRegistrationIdWithDeviceToken: deviceTokenString
            completion:^(NSString* registrationId, NSError *error) {
            NSLog(@"regId: %@", registrationId);
            if (error) {
                completion(error);
                return;
            }
    
            [self upsertRegistrationWithRegistrationId:registrationId deviceToken:deviceTokenString
                tags:tagsSet andCompletion:^(NSURLResponse * response, NSError *error) {
                if (error) {
                    completion(error);
                    return;
                }
    
                NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
                if (httpResponse.statusCode == 200) {
                    completion(nil);
                } else if (httpResponse.statusCode == 410 && retry) {
                    [self tryToRegisterWithDeviceToken:token tags:tags retry:NO andCompletion:completion];
                } else {
                    NSLog(@"Registration error with response status: %ld", (long)httpResponse.statusCode);
    
                    completion([NSError errorWithDomain:@"Registration" code:httpResponse.statusCode
                                userInfo:nil]);
                }
    
            }];
        }];
    }
    
    -(void) upsertRegistrationWithRegistrationId:(NSString*)registrationId deviceToken:(NSData*)token
                tags:(NSSet*)tags andCompletion:(void(^)(NSURLResponse*, NSError*))completion
    {
        NSDictionary* deviceRegistration = @{@"Platform" : @"apns", @"Handle": token,
                                                @"Tags": [tags allObjects]};
        NSData* jsonData = [NSJSONSerialization dataWithJSONObject:deviceRegistration
                            options:NSJSONWritingPrettyPrinted error:nil];
    
        NSLog(@"JSON registration: %@", [[NSString alloc] initWithData:jsonData
                                            encoding:NSUTF8StringEncoding]);
    
        NSString* endpoint = [NSString stringWithFormat:@"%@/api/register/%@", _endpoint,
                                registrationId];
        NSURL* requestURL = [NSURL URLWithString:endpoint];
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestURL];
        [request setHTTPMethod:@"PUT"];
        [request setHTTPBody:jsonData];
        NSString* authorizationHeaderValue = [NSString stringWithFormat:@"Basic %@",
                                                self.authenticationHeader];
        [request setValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    
        NSURLSessionDataTask* dataTask = [self.session dataTaskWithRequest:request
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            if (!error)
            {
                completion(response, error);
            }
            else
            {
                NSLog(@"Error request: %@", error);
                completion(nil, error);
            }
        }];
        [dataTask resume];
    }
    
    -(void) retrieveOrRequestRegistrationIdWithDeviceToken:(NSString*)token
                completion:(void(^)(NSString*, NSError*))completion
    {
        NSString* registrationId = [[NSUserDefaults standardUserDefaults]
                                    objectForKey:RegistrationIdLocalStorageKey];
    
        if (registrationId)
        {
            completion(registrationId, nil);
            return;
        }
    
        // request new one & save
        NSURL* requestURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/api/register?handle=%@",
                                _endpoint, token]];
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestURL];
        [request setHTTPMethod:@"POST"];
        NSString* authorizationHeaderValue = [NSString stringWithFormat:@"Basic %@",
                                                self.authenticationHeader];
        [request setValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"];
    
        NSURLSessionDataTask* dataTask = [self.session dataTaskWithRequest:request
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
            if (!error && httpResponse.statusCode == 200)
            {
                NSString* registrationId = [[NSString alloc] initWithData:data
                    encoding:NSUTF8StringEncoding];
    
                // remove quotes
                registrationId = [registrationId substringWithRange:NSMakeRange(1,
                                    [registrationId length]-2)];
    
                [[NSUserDefaults standardUserDefaults] setObject:registrationId
                    forKey:RegistrationIdLocalStorageKey];
                [[NSUserDefaults standardUserDefaults] synchronize];
    
                completion(registrationId, nil);
            }
            else
            {
                NSLog(@"Error status: %ld, request: %@", (long)httpResponse.statusCode, error);
                if (error)
                    completion(nil, error);
                else {
                    completion(nil, [NSError errorWithDomain:@"Registration" code:httpResponse.statusCode
                                userInfo:nil]);
                }
            }
        }];
        [dataTask resume];
    }
    
    @end
    

    此程式碼會實作在指引文章從您的應用程式後端註冊中所說明的邏輯,方法是使用 NSURLSession 來對您的應用程式後端執行 REST 呼叫,然後使用 NSUserDefaults 來將自通知中樞傳回的 registrationId 儲存於本機。This code implements the logic explained in the guidance article Registering from your app backend using NSURLSession to perform REST calls to your app backend, and NSUserDefaults to locally store the registrationId returned by the notification hub.

    此類別需要設定其 authorizationHeader 屬性,才能正常運作。This class requires its property authorizationHeader to be set in order to work properly. 此屬性是在登入後由 ViewController 類別所設定。This property is set by the ViewController class after the login.

  8. ViewController.h 中,為 RegisterClient.h 新增 #import 陳述式。In ViewController.h, add a #import statement for RegisterClient.h. 然後為裝置權杖新增宣告,並參照 @interface 區段中的 RegisterClient 執行個體:Then add a declaration for the device token and reference to a RegisterClient instance in the @interface section:

    #import "RegisterClient.h"
    
    @property (strong, nonatomic) NSData* deviceToken;
    @property (strong, nonatomic) RegisterClient* registerClient;
    
  9. 在 ViewController.m 中,於 @interface 區段中新增私用方法宣告:In ViewController.m, add a private method declaration in the @interface section:

    @interface ViewController () <UITextFieldDelegate, NSURLConnectionDataDelegate, NSXMLParserDelegate>
    
    // create the Authorization header to perform Basic authentication with your app back-end
    -(void) createAndSetAuthenticationHeaderWithUsername:(NSString*)username
                    AndPassword:(NSString*)password;
    
    @end
    

    注意

    下列片段不是安全驗證結構描述,您應將 createAndSetAuthenticationHeaderWithUsername:AndPassword: 的實作替代成特定的驗證機制,使該機制產生註冊用戶端類別所利用的驗證權杖,例如 OAuth、Active Directory。The following snippet is not a secure authentication scheme, you should substitute the implementation of the createAndSetAuthenticationHeaderWithUsername:AndPassword: with your specific authentication mechanism that generates an authentication token to be consumed by the register client class, e.g. OAuth, Active Directory.

  10. 然後在 ViewController.m@implementation 區段中新增以下程式碼,這會新增實作以設定裝置權杖與驗證標頭。Then in the @implementation section of ViewController.m, add the following code, which adds the implementation for setting the device token and authentication header.

    -(void) setDeviceToken: (NSData*) deviceToken
    {
        _deviceToken = deviceToken;
        self.LogInButton.enabled = YES;
    }
    
    -(void) createAndSetAuthenticationHeaderWithUsername:(NSString*)username
                    AndPassword:(NSString*)password;
    {
        NSString* headerValue = [NSString stringWithFormat:@"%@:%@", username, password];
    
        NSData* encodedData = [[headerValue dataUsingEncoding:NSUTF8StringEncoding] base64EncodedDataWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
    
        self.registerClient.authenticationHeader = [[NSString alloc] initWithData:encodedData
                                                    encoding:NSUTF8StringEncoding];
    }
    
    -(BOOL)textFieldShouldReturn:(UITextField *)textField
    {
        [textField resignFirstResponder];
        return YES;
    }
    

    請留意到設定裝置權杖會啟用登入按鈕。Notice how setting the device token enables the log in button. 這是因為作為登入動作的一部分,檢視控制器會向應用程式後端註冊推播通知。It's because as a part of the login action, the view controller registers for push notifications with the app backend. 因此,在裝置權杖已正確設定之前,不要讓登入動作可被存取。Hence, do not want Log In action to be accessible until the device token has been properly set up. 只要登入是在推播註冊之前發生,您就可以將前者與後者分開。You can decouple the login from the push registration as long as the former happens before the latter.

  11. 在 ViewController.m 中,使用以下程式碼片段為您的 [ 登入 ] 按鈕實作動作方法,以及實作一個方法來使用 ASP.NET 後端傳送通知訊息。In ViewController.m, use the following snippets to implement the action method for your Log In button and a method to send the notification message using the ASP.NET backend.

    - (IBAction)LogInAction:(id)sender {
        // create authentication header and set it in register client
        NSString* username = self.UsernameField.text;
        NSString* password = self.PasswordField.text;
    
        [self createAndSetAuthenticationHeaderWithUsername:username AndPassword:password];
    
        __weak ViewController* selfie = self;
        [self.registerClient registerWithDeviceToken:self.deviceToken tags:nil
            andCompletion:^(NSError* error) {
            if (!error) {
                dispatch_async(dispatch_get_main_queue(),
                ^{
                    selfie.SendNotificationButton.enabled = YES;
                    [self MessageBox:@"Success" message:@"Registered successfully!"];
                });
            }
        }];
    }
    
    - (void)SendNotificationASPNETBackend:(NSString*)pns UsernameTag:(NSString*)usernameTag
                Message:(NSString*)message
    {
        NSURLSession* session = [NSURLSession
            sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:nil
            delegateQueue:nil];
    
        // Pass the pns and username tag as parameters with the REST URL to the ASP.NET backend
        NSURL* requestURL = [NSURL URLWithString:[NSString
            stringWithFormat:@"%@/api/notifications?pns=%@&to_tag=%@", BACKEND_ENDPOINT, pns,
            usernameTag]];
    
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestURL];
        [request setHTTPMethod:@"POST"];
    
        // Get the mock authenticationheader from the register client
        NSString* authorizationHeaderValue = [NSString stringWithFormat:@"Basic %@",
            self.registerClient.authenticationHeader];
        [request setValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"];
    
        //Add the notification message body
        [request setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding]];
    
        // Execute the send notification REST API on the ASP.NET Backend
        NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
            if (error || httpResponse.statusCode != 200)
            {
                NSString* status = [NSString stringWithFormat:@"Error Status for %@: %d\nError: %@\n",
                                    pns, httpResponse.statusCode, error];
                dispatch_async(dispatch_get_main_queue(),
                ^{
                    // Append text because all 3 PNS calls may also have information to view
                    [self.sendResults setText:[self.sendResults.text stringByAppendingString:status]];
                });
                NSLog(status);
            }
    
            if (data != NULL)
            {
                xmlParser = [[NSXMLParser alloc] initWithData:data];
                [xmlParser setDelegate:self];
                [xmlParser parse];
            }
        }];
        [dataTask resume];
    }
    
  12. 更新 [ 傳送通知 ] 按鈕的動作來使用 ASP.NET 後端,並傳送至由任何開關啟用的任何 PNS。Update the action for the Send Notification button to use the ASP.NET backend and send to any PNS enabled by a switch.

    - (IBAction)SendNotificationMessage:(id)sender
    {
        //[self SendNotificationRESTAPI];
        [self SendToEnabledPlatforms];
    }
    
    -(void)SendToEnabledPlatforms
    {
        NSString* json = [NSString stringWithFormat:@"\"%@\"",self.notificationMessage.text];
    
        [self.sendResults setText:@""];
    
        if ([self.WNSSwitch isOn])
            [self SendNotificationASPNETBackend:@"wns" UsernameTag:self.RecipientField.text Message:json];
    
        if ([self.GCMSwitch isOn])
            [self SendNotificationASPNETBackend:@"gcm" UsernameTag:self.RecipientField.text Message:json];
    
        if ([self.APNSSwitch isOn])
            [self SendNotificationASPNETBackend:@"apns" UsernameTag:self.RecipientField.text Message:json];
    }
    
  13. ViewDidLoad 函數中,新增下列內容以具現化 RegisterClient 執行個體,並設定文字欄位的委派。In the ViewDidLoad function, add the following to instantiate the RegisterClient instance and set the delegate for your text fields.

    self.UsernameField.delegate = self;
    self.PasswordField.delegate = self;
    self.RecipientField.delegate = self;
    self.registerClient = [[RegisterClient alloc] initWithEndpoint:BACKEND_ENDPOINT];
    
  14. 現在於 AppDelegate.m 中,移除 application:didRegisterForPushNotificationWithDeviceToken: 方法中的所有內容,並使用下列內容取代它 (確定檢視控制器包含從 APNs 擷取的最新裝置權杖):Now in AppDelegate.m, remove all the content of the method application:didRegisterForPushNotificationWithDeviceToken: and replace it with the following (to make sure that the view controller contains the latest device token retrieved from APNs):

    // Add import to the top of the file
    #import "ViewController.h"
    
    - (void)application:(UIApplication *)application
                didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
        ViewController* rvc = (ViewController*) self.window.rootViewController;
        rvc.deviceToken = deviceToken;
    }
    
  15. 最後,在 AppDelegate.m 中,確定您有下列方法:Finally in AppDelegate.m, make sure you have the following method:

    - (void)application:(UIApplication *)application didReceiveRemoteNotification: (NSDictionary *)userInfo {
        NSLog(@"%@", userInfo);
        [self MessageBox:@"Notification" message:[[userInfo objectForKey:@"aps"] valueForKey:@"alert"]];
    }
    

測試應用程式Test the application

  1. 在 XCode 中,在實體 iOS 裝置上執行應用程式 (推播通知無法在模擬器中運作)。In XCode, run the app on a physical iOS device (push notifications do not work in the simulator).

  2. 在 iOS 應用程式 UI 中,為使用者名稱和密碼輸入相同的值。In the iOS app UI, enter same value for both username and password. 然後按一下 [ 登入]。Then click Log In.

    iOS 測試應用程式

  3. 您應該會看到註冊成功的快顯通知。You should see a pop-up informing you of registration success. 按一下 [確定]。Click OK.

    顯示的 iOS 測試通知

  4. 在 *收件者使用者名稱標記 文字欄位中,輸入從其他裝置註冊時搭配使用的使用者名稱標記。In the *Recipient username tag text field, enter the user name tag used with the registration from another device.

  5. 輸入通知訊息並按一下 [ 傳送通知]。Enter a notification message and click Send Notification. 只有已經使用收件者使用者名稱標記註冊的裝置才會收到通知訊息。Only the devices that have a registration with the recipient user name tag receive the notification message. 通知訊息只會傳送給那些使用者。It is only sent to those users.

    iOS 測試已標記的通知

後續步驟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: