分享方式:


教學課程:使用 Azure 通知中樞將推播通知傳送給特定使用者

本教學課程將向您說明如何使用 Azure 通知中樞將推播通知傳送至特定裝置上的特定應用程式使用者。 ASP.NET WebAPI 後端可用來驗證用戶端並產生通知,如指引主題從您的應用程式後端註冊中所示。

在本教學課程中,您會執行下列步驟:

  • 建立 WebAPI 專案
  • 對 WebAPI 後端驗證用戶端
  • 使用 WebAPI 後端來註冊通知
  • 從 WebAPI 後端傳送通知
  • 發佈新的 WebAPI 後端
  • 修改您的 iOS 應用程式
  • 測試應用程式

必要條件

本教學課程假設您已建立並設定通知中樞,如使用 Azure 通知中樞將推播通知傳送至 iOS 應用程式中所述。 本教學課程還是 安全推播 (iOS) 教學課程的必要條件。 如果您想要使用 Mobile Apps 作為您的後端服務,請參閱 開始使用 Mobile Apps 推播

建立 WebAPI 專案

下列各節討論如何建立新的 ASP.NET WebAPI 後端。 此程序有三個主要用途:

  • 驗證用戶端:您可以新增訊息處理常式來驗證用戶端要求,並將使用者與要求建立關聯。
  • 使用 WebAPI 後端註冊通知:您可新增一個控制器來處理新的註冊,以便用戶端裝置接收通知。 經過驗證的使用者名稱會自動新增至註冊作為 標記
  • 傳送通知給用戶端:您可新增一個控制器,以便使用者對與標籤建立關聯的裝置和用戶端觸發安全的推送。

執行下列動作,建立新的 ASP.NET Core 6.0 Web API 後端:

若要檢查版本,請啟動 Visual Studio。 在 [工具] 功能表上,選取 [擴充功能和更新]。 搜尋您的 Visual Studio 版本中的 NuGet Package Manager,然後確定您已安裝最新版本。 如果您的版本不是最新版本,請將它解除安裝,然後重新安裝 NuGet 套件管理員。

Screenshot of the Extensions and Updates dialog box with the NuGet Package manage for Visual Studios package highlighted.

注意

確定您已安裝 Visual Studio Azure SDK 以供網站部署。

  1. 啟動 Visual Studio 或 Visual Studio Express。

  2. 選取 [伺服器總管] ,然後登入您的 Azure 帳戶。 若要在您的帳戶上建立網站資源,您必須登入。

  3. 在 Visual Studio 的 [檔案] 功能表上選取 [新增]> [專案]

  4. 在搜尋方塊中輸入 Web API

  5. 選取 [ASP.NET Core Web API] 專案範本並選取 [下一步]

  6. 在 [設定新專案] 對話方塊中,將專案命名為 AppBackend,然後選取 [下一步]

  7. 在 [其他資訊] 對話方塊中:

    • 確認 Framework.NET 6.0 (長期支援)
    • 確認已核取 [使用控制器 (取消核取以使用最小 API)] 核取方塊。
    • 取消核取 [啟用 OpenAPI 支援]
    • 選取 建立

移除 WeatherForecast 範本檔案

  1. 從新的 AppBackend 專案中移除 WeatherForecast.csControllers/WeatherForecastController.cs 範例檔案。
  2. 開啟 Properties\launchSettings.json
  3. launchUrl 屬性從 weatherforcast 變更為 appbackend

在 [設定 Microsoft Azure Web 應用程式] 視窗中,選取訂用帳戶,然後在 [App Service 方案] 清單中,執行下列其中一個動作:

  • 選取您已建立的 Azure App Service 方案。
  • 選取 [建立新的 App Service 方案],然後建立一個新方案。

在此教學課程中您不需要資料庫。 在您選取 App Service 方案之後,選取 [確定] 來建立專案。

The Configure Microsoft Azure Web App window

如果您沒有看到這個用於設定 App Service 方案的頁面,請繼續進行本教學課程。 您可以在稍後發佈應用程式時再進行設定。

對 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 類別定義:

    下列三個條件都成立時,此處理常式會授權要求:

    • 要求包含「授權」標頭。
    • 要求使用 基本 驗證。
    • 使用者名稱字串和密碼字串是相同的字串。

    否則,會拒絕此要求。 此驗證不是真正的驗證和授權方法。 這只是本教學課程的簡單範例。

    如果要求訊息已經由 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;
        }
    }
    

    注意

    安全性注意事項:AuthenticationTestHandler 類別未提供真正的驗證。 它僅可用於模仿基本驗證而且並不安全。 您必須在生產應用程式和服務中實作安全的驗證機制。

  5. 若要註冊訊息處理常式,請在 Program.cs 檔案中 Register 方法的結尾新增下列程式碼:

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

使用 WebAPI 後端來註冊通知

在本節中,您會將新的控制器新增至 WebAPI 後端來處理要求,以使用通知中樞的用戶端程式庫為使用者和裝置註冊通知。 控制器會對已由 AuthenticationTestHandler 驗證並附加至 HttpContext 的使用者,新增使用者標記。 此標籤具有以下字串格式:"username:<actual username>"

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 [AppBackend] 專案,然後選取 [管理 NuGet 套件]

  2. 在左窗格中,選取 [線上],然後在 [搜尋] 方塊中輸入 Microsoft.Azure.NotificationHubs

  3. 選取結果清單中的 [Microsoft Azure 通知中樞],然後選取 [安裝]。 請完成安裝,然後關閉 [NuGet Package Manager] 視窗。

    此動作會使用 Microsoft.Azure.Notification Hubs NuGet 套件來新增對 Azure 通知中樞 SDK 的參考。

  4. 建立新的類別檔案,代表與用來傳送通知的通知中樞間的連線。 在 [方案總管] 中,以滑鼠右鍵按一下 Models 資料夾,選取 [新增],然後選取 [類別]。 將新類別命名為 Notifications.cs,然後選取 [新增] 以產生類別。

    The Add New Item window

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

    重要

    請輸入中樞的名稱DefaultFullSharedAccessSignature,再繼續進行。

  7. 接下來,建立名為 RegisterController 的新控制器。 在 [方案總管] 中,以滑鼠右鍵按一下 Controllers 資料夾,選取 [新增],然後選取 [控制器]

  8. 選取 [API 控制器 - 空的],然後選取 [新增]

  9. 在 [控制器名稱] 方塊中,輸入 RegisterController 為新的類別命名,然後選取 [新增]

    The Add Controller window.

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

從 WebAPI 後端傳送通知

在本節中,您會新增控制器,以便用戶端裝置傳送通知。 此通知是以使用者名稱標籤為基礎,其使用 ASP.NET WebAPI 後端中的 Azure 通知中樞 .NET 程式庫。

  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 裝置上,您可以搭配 WNS 使用不受其他 PNS 直接支援的快顯通知。 在這類情況下,您的後端必須針對您打算支援的裝置 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 "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 鍵。 應用程式會開啟網頁瀏覽器並顯示於 ASP.NET 首頁上。

發佈新的 WebAPI 後端

接下來,您可將應用程式部署到 Azure 網站,讓它得以從所有裝置存取。

  1. 以滑鼠右鍵按一下 AppBackend 專案,然後選取 [發佈]

  2. 選取 [Microsoft Azure App Service] 作為發佈目標,然後選取 [發佈]。 [建立 App Service] 視窗隨即開啟。 您可以在此建立在 Azure 中執行 ASP.NET Web 應用程式所需的所有 Azure 資源。

    The Microsoft Azure App Service tile

  3. 在 [建立 App Service] 視窗中,選取您的 Azure 帳戶。 選取 [變更類型]>[Web 應用程式]。 保留預設 [Web 應用程式名稱],然後選取 [訂用帳戶]、[資源群組] 和 [App Service 方案]

  4. 選取 建立

  5. 記下 [摘要] 區段中的 [網站 URL] 屬性。 此 URL 是您在本教學課程中稍後使用的「後端端點」

  6. 選取發行

精靈完成後,它會將 ASP.NET Web 應用程式發佈至 Azure,然後在預設瀏覽器中開啟應用程式。 您的應用程式可在 Azure App Service 中檢視。

URL 會使用您稍早指定的 Web 應用程式名稱,其格式為 http://<app_name>.azurewebsites.net。

修改您的 iOS 應用程式

  1. 開啟您在使用 Azure 通知中樞將推播通知傳送至 iOS 應用程式教學課程推中建立的單一頁面檢視應用程式。

    注意

    本節假設您已使用空白組織名稱來設定您的專案。 否則,在所有類別名稱的前面加上您的組織名稱。

  2. Main.storyboard 檔案中,從物件程式庫新增螢幕擷取畫面中顯示的元件。

    Edit storyboard in Xcode interface builder

    • 使用者名稱:含有預留位置文字 ( 輸入使用者名稱) 的 UITextField,位於傳送結果標籤正下方且受到左右邊界限制並位於傳送結果標籤正下方。

    • 密碼:含有預留位置文字 ( 輸入密碼) 的 UITextField,位於使用者名稱文字欄位正下方且受到左右邊界限制並位於使用者文字欄位正下方。 勾選 [ 傳回金鑰 ] 底下屬性偵測器中的 [ 安全文字輸入] 選項。

    • 登入:密碼文字欄位正下方標記的 UIButton,並取消勾選 [控制項內容] 底下屬性偵測器中的 [啟用] 選項。

    • WNS:當中樞中已設定 Windows 通知服務時,用來啟用傳送通知至該服務的標籤與開關。 請參閱 Windows 入門教學課程。

    • GCM:當中樞中已設定 Google Cloud Messaging 時,用來啟用傳送通知至該服務的標籤與開關。 請參閱 Android 入門 教學課程。

    • APNS:啟用傳送通知給 Apple 平台通知服務之功能的標籤與開關。

    • 收件者使用者名稱:含有預留位置文字 (收件者使用者名稱標記) 的 UITextField,位於 GCM 標籤正下方,且受到左右邊界與 GCM 正下方的限制。

      有些元件已在使用 Azure 通知中樞將推播通知傳送至 iOS 應用程式教學課程中新增。

  3. Ctrl 可拖曳檢視中的元件到 ViewController.h,並新增這些新的輸出:

    @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 中,在匯入陳述式後面新增以下的 #define。 將 <Your backend endpoint> 預留位置替換成上一節中用來部署應用程式後端的目的地 URL。 例如,http://your_backend.azurewebsites.net

    #define BACKEND_ENDPOINT @"<Your backend endpoint>"
    
  5. 在您的專案中,建立一個名為 RegisterClient 的新 Cocoa Touch 類別,作為與您所建立 ASP.NET 後端互動的介面。 建立繼承自 NSObject的類別。 然後,將下列程式碼新增至 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 區段:

    @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 區段:

    @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 儲存於本機。

    此類別需要設定其 authorizationHeader 屬性,才能正常運作。 此屬性是在登入後由 ViewController 類別所設定。

  8. ViewController.h 中,為 RegisterClient.h 新增 #import 陳述式。 然後為裝置權杖新增宣告,並參照 @interface 區段中的 RegisterClient 執行個體:

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

    @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。

  10. 然後在 ViewController.m@implementation 區段中新增以下程式碼,這會新增實作以設定裝置權杖與驗證標頭。

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

    請留意到設定裝置權杖會啟用 [登入] 按鈕。 這是因為作為登入動作的一部分,檢視控制器會向應用程式後端註冊推播通知。 在正確設定裝置權杖之前,不要讓 [登入] 動作可供存取。 只要登入是在推播註冊之前發生,您就可以將前者與後者分開。

  11. 在 ViewController.m 中,使用以下程式碼片段為您的 [登入] 按鈕實作動作方法,以及實作一個方法以使用 ASP.NET 後端傳送通知訊息。

    - (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。

    - (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 執行個體,並設定文字欄位的委派。

    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 擷取的最新裝置權杖):

    // 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 中,確定您有下列方法:

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

測試應用程式

  1. 在 XCode 中,在實體 iOS 裝置上執行應用程式 (推播通知無法在模擬器中運作)。

  2. 在 iOS 應用程式 UI 中,為使用者名稱和密碼輸入相同的值。 然後按一下 [登入]

    iOS test application

  3. 您應該會看到註冊成功的快顯通知。 按一下 [確定]

    iOS test notification displayed

  4. 在 *收件者使用者名稱標記 文字欄位中,輸入從其他裝置註冊時搭配使用的使用者名稱標記。

  5. 輸入通知訊息並按一下 [ 傳送通知]。 只有已經使用收件者使用者名稱標記註冊的裝置才會收到通知訊息。 通知訊息只會傳送給那些使用者。

    iOS test tagged notification

下一步

在本教學課程中,您已學會如何針對具有與其註冊相關聯標記的使用者,將通知推送至這些特定使用者。 若要了解如何推送以位置為基礎的通知,請繼續進行下列教學課程: