チュートリアル:Azure Notification Hubs を使用して特定のユーザーにプッシュ通知を送信する

このチュートリアルでは、Azure Notification Hubs を使用して特定のデバイスで特定のアプリケーション ユーザーにプッシュ通知を送信する方法について説明します。 アプリ バックエンドからの登録管理に関するガイダンス トピックに示すように、ASP.NET WebAPI バックエンドを使用してクライアントを認証し、通知を生成します。

このチュートリアルでは、次の手順を実行します。

  • Web API プロジェクトを作成する
  • WebAPI バックエンドに対してクライアントを認証する
  • WebAPI バックエンドを使用して通知に登録する
  • WebAPI バックエンドから通知を送信する
  • 新しい WebAPI バックエンドを発行する
  • iOS アプリを変更する
  • アプリケーションをテストする

前提条件

このチュートリアルでは、「Azure Notification Hubs を使用して iOS アプリにプッシュ通知を送信する」の説明のとおり通知ハブを作成し、構成したと想定しています。 また、「 安全なプッシュ (iOS) 」チュートリアルの前提条件でもあります。 バックエンド サービスとして Mobile Apps を使用する場合は、「 iOS アプリへのプッシュ通知の追加」を参照してください。

Web API プロジェクトを作成する

次のセクションでは、新しい ASP.NET WebAPI バックエンドの作成について説明します。 このプロセスには、主に次の 3 つの目的があります。

  • クライアントの認証: クライアント要求を認証し、ユーザーを要求と関連付けるメッセージ ハンドラーを追加します。
  • WebAPI バックエンドを使用した通知の登録: クライアント デバイスで通知を受信するための新しい登録を処理するコントローラーを追加します。 認証されたユーザー名はタグとして自動的に登録に追加されます。
  • クライアントへの通知の送信: ユーザーがタグに関連するデバイスやクライアントにセキュリティで保護されたプッシュ通知をトリガーできるコントローラーを追加します。

次の操作を実行して、新しい ASP.NET Core 6.0 Web API バックエンドを作成します。

確認するには、Visual Studio を起動します。 [ツール] メニューの [拡張機能と更新プログラム] を選びます。 お使いの Visual Studio に対応した NuGet パッケージ マネージャーを探し、バージョンが最新であることを確認します。 最新バージョンでない場合は、アンインストールして、NuGet パッケージ マネージャーを再インストールしてください。

Visual Studio 用 NuGet パッケージ マネージャー パッケージが強調表示された [拡張機能と更新プログラム] ダイアログ ボックスのスクリーンショット。

注意

Web サイトのデプロイ用に Visual Studio Azure SDK がインストールされていることを確認してください。

  1. Visual Studio または Visual Studio Express を起動します。

  2. [サーバー エクスプローラー] を選択し、Azure アカウントにサインインします。 アカウントの Web サイト リソースを作成するには、サインインする必要があります。

  3. Visual Studio の [ファイル] メニューで、[新規作成]>[プロジェクト] を選択します。

  4. 検索ボックスに「Web API」と入力します。

  5. [ASP.NET Core Web API] プロジェクト テンプレートを選択して、[次へ] を選択します。

  6. [新しいプロジェクトの構成] ダイアログで、プロジェクトに AppBackend という名前を付けて、[次へ] を選択します。

  7. [追加情報] ダイアログで、次を行います。

    • [フレームワーク][.NET 6.0 (長期的なサポート)] になっていることを確認します。
    • [コントローラーを使用する (最小限の API を使用する場合はオフにします)] チェック ボックスがオンになっていることを確認します。
    • [OpenAPI サポートを有効にする] チェック ボックスをオフにします。
    • [作成] を選択します

WeatherForecast テンプレート ファイルを削除する

  1. 新しい AppBackend プロジェクトからサンプル ファイルの WeatherForecast.csControllers/WeatherForecastController.cs を削除します。
  2. Properties\launchSettings.json を開きます。
  3. launchUrl プロパティを weatherforcast から appbackend に変更します。

[Microsoft Azure Web App の構成] ウィンドウで、サブスクリプションを選択し、 [App Service プラン] の一覧で次のいずれかの操作を実行します。

  • 作成済みの Azure App Service プランを選択します。
  • [新しい App Service プランの作成] を選択し、App Service プランを作成します。

このチュートリアルではデータベースは必要ありません。 App Service プランを選択したら、 [OK] を選択して、プロジェクトを作成します。

[Microsoft Azure Web App の構成] ウィンドウ

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 クラス定義を次のコードに置き換えます。

    このハンドラーは、次の 3 つの条件を満たす場合に要求を承認します。

    • 要求に Authorization ヘッダーが含まれている。
    • 要求に 基本 認証が使用されている。
    • ユーザー名の文字列とパスワードの文字列が同じである。

    それ以外の場合、要求は拒否されます。 この認証は、認証と認可の正規のアプローチではありません。 このチュートリアルのための単なる例にすぎません。

    要求メッセージが認証され、AuthenticationTestHandler によって承認される場合、基本認証ユーザーは HttpContext の現在の要求に添付されます。 HttpContext のユーザー情報は、後で別のコントローラー (RegisterController) で使用され、通知登録の要求にタグを追加します。

    public class AuthenticationTestHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var authorizationHeader = request.Headers.GetValues("Authorization").First();
    
            if (authorizationHeader != null && authorizationHeader
                .StartsWith("Basic ", StringComparison.InvariantCultureIgnoreCase))
            {
                string authorizationUserAndPwdBase64 =
                    authorizationHeader.Substring("Basic ".Length);
                string authorizationUserAndPwd = Encoding.Default
                    .GetString(Convert.FromBase64String(authorizationUserAndPwdBase64));
                string user = authorizationUserAndPwd.Split(':')[0];
                string password = authorizationUserAndPwd.Split(':')[1];
    
                if (VerifyUserAndPwd(user, password))
                {
                    // Attach the new principal object to the current HttpContext object
                    HttpContext.Current.User =
                        new GenericPrincipal(new GenericIdentity(user), new string[0]);
                    System.Threading.Thread.CurrentPrincipal =
                        System.Web.HttpContext.Current.User;
                }
                else return Unauthorized();
            }
            else return Unauthorized();
    
            return base.SendAsync(request, cancellationToken);
        }
    
        private bool VerifyUserAndPwd(string user, string password)
        {
            // This is not a real authentication scheme.
            return user == password;
        }
    
        private Task<HttpResponseMessage> Unauthorized()
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);
            return tsc.Task;
        }
    }
    

    Note

    セキュリティに関する注意: AuthenticationTestHandler クラスは、本当の認証を提供するわけではありません。 基本認証を模倣するためだけに使用されるため、安全ではありません。 実稼働のアプリケーションとサービスでは、セキュリティで保護された認証メカニズムを実装する必要があります。

  5. メッセージ ハンドラーを登録するには、Program.cs ファイルの Register メソッドの末尾に次のコードを追加します。

    config.MessageHandlers.Add(new AuthenticationTestHandler());
    
  6. 変更を保存します。

WebAPI バックエンドを使用して通知に登録する

このセクションでは、Notification Hubs のクライアント ライブラリを使用して、通知用にユーザーとデバイスを登録する要求を処理する新しいコントローラーを WebAPI バックエンドに追加します。 コントローラーでは、AuthenticationTestHandler で認証され、HttpContext に添付されたユーザーのユーザー タグを追加します。 タグは、"username:<actual username>" という形式の文字列になります。

  1. ソリューション エクスプローラーで AppBackend プロジェクトを右クリックし、 [NuGet パッケージの管理] を選択します。

  2. 左側のウィンドウにある [オンライン] を選択し、 [検索] ボックスに「Microsoft.Azure.NotificationHubs」と入力します。

  3. 結果の一覧で、 [Microsoft Azure Notification Hubs] を選択してから、 [インストール] を選択します。 インストールが完了したら、NuGet パッケージ マネージャーのウィンドウを閉じます。

    この操作によって、Microsoft.Azure.Notification Hubs NuGet パッケージを使用して Azure Notification Hubs SDK に参照が追加されます。

  4. 通知の送信に使用する通知ハブとの接続を表す新しいクラス ファイルを作成します。 ソリューション エクスプローラーで、Models フォルダーを右クリックし、 [追加][クラス] の順に選択します。 新しいクラスに「Notifications.cs」という名前を付け、 [追加] を選択して、クラスを生成します。

    [新しい項目の追加] ウィンドウ

  5. Notifications.cs で、ファイルの先頭に次の using ステートメントを追加します。

    using Microsoft.Azure.NotificationHubs;
    
  6. Notifications クラス定義を以下のコードに置き換えます。2 つのプレースホルダーは、通知ハブに対する (フル アクセス権を持つ) 接続文字列と、ハブ名 (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 を入力します。

  7. 次に、RegisterController という名前の新しいコントローラーを作成します。 ソリューション エクスプローラーで、Controllers フォルダーを右クリックし、 [追加][コントローラー] の順に選択します。

  8. [API コントローラー - 空][追加] の順に選択します。

  9. [コントローラー名] ボックスに「RegisterController」と入力して、新しいクラスに名前を付け、 [追加] を選択します。

    [コントローラーの追加] ウィンドウ。

  10. RegisterController.cs に次の using ステートメントを追加します。

    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Azure.NotificationHubs.Messaging;
    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  11. RegisterController クラス定義内で、次のコードを追加します。 このコードでは、HttpContext に添付されるユーザーのユーザー タグを追加します。 ユーザーは認証され、追加したメッセージ フィルター AuthenticationTestHandler で HttpContext に添付されます。 また、要求されたタグを登録する権限をユーザーが持っていることを確認するコードをオプションで追加することもできます。

    private NotificationHubClient hub;
    
    public RegisterController()
    {
        hub = Notifications.Instance.Hub;
    }
    
    public class DeviceRegistration
    {
        public string Platform { get; set; }
        public string Handle { get; set; }
        public string[] Tags { get; set; }
    }
    
    // POST api/register
    // This creates a registration id
    public async Task<string> Post(string handle = null)
    {
        string newRegistrationId = null;
    
        // make sure there are no existing registrations for this push handle (used for iOS and Android)
        if (handle != null)
        {
            var registrations = await hub.GetRegistrationsByChannelAsync(handle, 100);
    
            foreach (RegistrationDescription registration in registrations)
            {
                if (newRegistrationId == null)
                {
                    newRegistrationId = registration.RegistrationId;
                }
                else
                {
                    await hub.DeleteRegistrationAsync(registration);
                }
            }
        }
    
        if (newRegistrationId == null) 
            newRegistrationId = await hub.CreateRegistrationIdAsync();
    
        return newRegistrationId;
    }
    
    // PUT api/register/5
    // This creates or updates a registration (with provided channelURI) at the specified id
    public async Task<HttpResponseMessage> Put(string id, DeviceRegistration deviceUpdate)
    {
        RegistrationDescription registration = null;
        switch (deviceUpdate.Platform)
        {
            case "mpns":
                registration = new MpnsRegistrationDescription(deviceUpdate.Handle);
                break;
            case "wns":
                registration = new WindowsRegistrationDescription(deviceUpdate.Handle);
                break;
            case "apns":
                registration = new AppleRegistrationDescription(deviceUpdate.Handle);
                break;
            case "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 Notification Hubs .NET ライブラリを使用する username タグに基づいています。

  1. 前のセクションで RegisterController を作成したときと同じ方法で、NotificationsController という別の新しいコントローラーを作成します。

  2. NotificationsController.cs に次の using ステートメントを追加します。

    using AppBackend.Models;
    using System.Threading.Tasks;
    using System.Web;
    
  3. NotificationsController クラスに次のメソッドを追加します。

    このコードでは、プラットフォーム通知サービス (PNS) の pns パラメーターに基づく通知の種類を送信します。 to_tag の値はメッセージの ユーザー名 タグを設定するために使用します。 このタグは、アクティブな通知ハブ登録のユーザー名のタグと一致する必要があります。 通知メッセージは、POST 要求の本文からプルされ、ターゲット PNS に合わせた形式に設定されます。

    サポートされているデバイスで通知の受信に使用される PNS に応じて、各種形式で通知がサポートされています。 たとえば Windows デバイスの場合、別の PNS で直接はサポートされていない WNS によるトースト通知を使用することもできます。 このような場合、バックエンドが、通知を、サポートを計画しているデバイスの PNS でサポートされている形式に設定する必要があります。 その後、NotificationHubClient クラスで適切な送信 API を使用します。

    public async Task<HttpResponseMessage> Post(string pns, [FromBody]string message, string to_tag)
    {
        var user = HttpContext.Current.User.Identity.Name;
        string[] userTag = new string[2];
        userTag[0] = "username:" + to_tag;
        userTag[1] = "from:" + user;
    
        Microsoft.Azure.NotificationHubs.NotificationOutcome outcome = null;
        HttpStatusCode ret = HttpStatusCode.InternalServerError;
    
        switch (pns.ToLower())
        {
            case "wns":
                // Windows 8.1 / Windows Phone 8.1
                var toast = @"<toast><visual><binding template=""ToastText01""><text id=""1"">" + 
                            "From " + user + ": " + message + "</text></binding></visual></toast>";
                outcome = await Notifications.Instance.Hub.SendWindowsNativeNotificationAsync(toast, userTag);
                break;
            case "apns":
                // iOS
                var alert = "{\"aps\":{\"alert\":\"" + "From " + user + ": " + message + "\"}}";
                outcome = await Notifications.Instance.Hub.SendAppleNativeNotificationAsync(alert, userTag);
                break;
            case "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 キーを選択します。 アプリにより、Web ブラウザーが開き、ASP.NET ホーム ページに表示されます。

新しい WebAPI バックエンドを発行する

次に、このアプリを Azure の Web サイトにデプロイして、すべてのデバイスからアクセスできるようにします。

  1. AppBackend プロジェクトを右クリックして [発行] を選択します。

  2. 発行先として [Microsoft Azure App Service] を選択し、[発行] を選択します。 [App Service の作成] ウィンドウが開きます。 このウィンドウでは、Azure で ASP.NET Web アプリを実行するために必要なすべての Azure リソースを作成できます。

    [Microsoft Azure App Service] タイル

  3. [App Service の作成] ウィンドウで、Azure アカウントを選択します。 [変更の種類]>[Web アプリ] の順に選択します。 既定の Web アプリ名をそのまま保持し、 [サブスクリプション][リソース グループ][App Service プラン] の順に選択します。

  4. [作成] を選択します

  5. [概要] セクションの [サイト URL] プロパティをメモします。 この URL は、このチュートリアルの後半で使用する "バックエンドエンドポイント" です。

  6. [発行] を選択します。

ウィザードの完了後に、Azure に ASP.NET Web アプリを発行してから、既定のブラウザーでアプリを開きます。 アプリケーションが Azure App Services に表示されます。

URL では、前に指定した Web アプリ名が http://<アプリ名>.azurewebsites.net という形式で使用されます。

iOS アプリを変更する

  1. Azure Notification Hubs を使用して iOS アプリにプッシュ通知を送信する」で作成した、単一ページの表示アプリを開きます。

    注意

    このセクションでは、プロジェクトは空の組織名で構成されていると想定しています。 そうでない場合は、すべてのクラス名の先頭に組織名を追加します。

  2. Main.storyboard ファイルで、オブジェクト ライブラリから、スクリーンショットに表示されているコンポーネントを追加します。

    Xcode Interface Builder でストーリーボードを編集する

    • [ユーザー名] : "Enter Username" というプレースホルダー テキストが入っている UI テキスト フィールドです。結果の送信ラベルのすぐ下にあり、左右の余白と、結果の送信ラベルの下の余白による制約が適用されます。

    • パスワード:"Enter Password" というプレースホルダー テキストが入っている UI テキスト フィールドです。ユーザー名のテキスト フィールドのすぐ下にあり、左右の余白と、ユーザー名のテキスト フィールドの下の余白による制約が適用されます。 Attributes Inspector で [Return Key] の下にある [Secure Text Entry] オプションをオンにします。

    • Log in: パスワードのテキスト フィールドのすぐ下にあるラベル付きの UI ボタンです。Attributes Inspector の [Control-Content] の下にある [Enabled] オプションをオフにします

    • WNS: Windows Notification Service に通知を送信するためのスイッチとそのラベルです。ハブ上でセットアップを済ませておく必要があります。 詳しくは、Windows で通知ハブを使用する方法に関するチュートリアルをご覧ください。

    • GCM: Google Cloud Messaging に通知を送信するためのスイッチとそのラベルです。ハブ上でセットアップを済ませておく必要があります。 詳しくは、Android で通知ハブを使用する方法に関するチュートリアルをご覧ください。

    • APNS: Apple Platform Notification Service に通知を送信するためのスイッチとそのラベルです。

    • Recipient Username: "Recipient username tag" というプレースホルダー テキストが入っている UI テキスト フィールドです。GCM ラベルのすぐ下にあり、左右の余白と、GCM ラベルの下にあることの制約が適用されます。

      Azure Notification Hubs を使用して 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. プロジェクトで、作成済みの ASP.NET バックエンドとのインターフェイスになる、RegisterClient という名前の新しい Cocoa Touch クラスを作成します。 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
    

    このコードは、アプリケーション バックエンドへの REST 呼び出しを実行するための NSURLSession、および通信ハブによって返される registrationId をローカルに格納するための NSUserDefaults の使用についてのガイダンス記事「アプリ バックエンドからの登録」で説明したロジックを実装します。

    このクラスが適切に機能するためには、プロパティ 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;
    }
    

    デバイス トークンの設定によって、どのように [Log in] ボタンが有効になるかに注目してください。 これは、ログイン アクションの一部として、View Controller がアプリケーション バックエンドでプッシュ通知を登録するためです。 デバイス トークンが適切に設定されるまで、ログイン アクションにアクセスできないようにします。 プッシュ登録の前にログインが発生する場合には、プッシュ登録からログインを切り離す必要があります。

  11. ViewController.m で以下のスニペットを使用して、 [Log in] ボタンのアクション メソッドを実装し、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. [Send Notification] ボタンのアクションを更新し、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 で、ユーザー名とパスワードの両方に同じ値を入力します。 次に、 [Log in] をクリックします。

    iOS テスト アプリケーション

  3. 登録の成功を通知するポップアップが表示されます。 [OK] をクリックします。

    iOS テスト通知の表示

  4. *Recipient username tag というテキストが表示されているフィールドに、別のデバイスから登録するときに使用したユーザー名のタグを入力します。

  5. 通知メッセージを入力して Send Notificationをクリックします。 入力したタグが登録されているデバイスのみ、通知メッセージを受信します。 通知は、該当するユーザーにのみ送信されます。

    iOS テストのタグ付けされた通知

次のステップ

このチュートリアルでは、タグが登録に関連付けられている特定のユーザーにプッシュ通知を送信する方法を学習しました。 場所に基づいたプッシュ通知を送信する方法を学習するには、次のチュートリアルに進んでください。