おでんはじめました。

required ちくわぶ and 巾着,optional はんぺん.

.NET MAUI で Azure Notification Hubs でのプッシュ通知を実装する(iOS編)

表題の通り .NET MAUI の iOS で Azure Notification Hubs(以下 Notification Hubs) でのプッシュ通知を実装してみます。

残念ながら Xamarin.Forms の iOS で使用していたクライアント用の Xamarin.Azure.NotificationHubs.iOS パッケージは MAUI 用にアップデートされていないので、.NET MAUI の issue にもある shiny パッケージを使用します。このパッケージはプレビュー版なのでご注意ください(2023年1月15日現在)。

Maui+iOS での Notification Hubs でのプッシュ通知は現時点でこのパッケージ一択のようです。

www.nuget.org

github.com

github.com

プッシュ通知に関する設定に関しては Xamarin.Forms と基本的に同じなので、このドキュメントに倣って進めていきます。クライアントの実装部分が shiny パッケージを使うこと以外は基本的に Xamarin.Forms と同じになります。

learn.microsoft.com

1. 証明書の署名要求ファイルを生成する

Apple Push Notification Service (APNs) では、証明書を使用してプッシュ通知を認証します。 次の手順に従って、通知を送受信するために必要なプッシュ証明書を作成します。

まずはじめに、証明書署名要求 (CSR) ファイルを生成します。 これは、Apple が署名済みのプッシュ証明書を生成するために使用します。

キーチェーンアクセス を起動します。メニューから、「キーチェーンアクセス>証明書アシスタント>認証局に証明書を要求」を選択します。

「ユーザのメールアドレス」は適当で構いません。名前も適当に入力して「ディスクに保存」を選択して、「続ける」を選択して保存します。

2. アプリケーションをプッシュ通知に登録する

次に、アプリを Apple Developer にて登録をおこないプッシュ通知を有効にします。その後、先ほどエクスポートした CSR をアップロードしてプッシュ証明書を作成します。

Apple Developer ポータルのアカウントから ID(英語) に移動します (最近デザインが変わった感じ)。

Identifiers の横にある+をクリックします。

「App IDs」を選択して「Continue」をクリックします。

「Register a new identifier」で「App」を選択して「Continue」をクリックします。

Bundle IDアプリディストリビューションガイドに従い、 <組織 ID>.<製品名> の形式のバンドル ID を入力します。この Bundle ID は MAUI プロジェクトで使用します。

App ID Prefix は複数ある場合は(組織か個人かなど)適切な方を選択します。 Description は任意で構いません。その下にある Push Notifications を選択して「Continue」をクリックします。

登録の確認で「Register」を忘れずにクリックします。

登録が完了すると、新しいアプリ ID が Certificates, Identifiers & Profiles ページに項目として表示されます。

3. Notification Hubs の証明書を作成する

Notification Hubs を APNs と連携させるには、証明書が必要です。これには、次の 2 とおりの方法があります。

  1. Notification Hubs に直接アップロードできる .p12 を作成する方法
  2. トークンベースの認証に使用できる .p8 を作成する方法

ここでは新しい方法である後者の方で進めていきます。

Apple Developer の Certificates, Identifiers & Profiles から Keys の横の+マークをクリックします。

Apple Push Notification services(APNs) にチェックを入れて、任意の名前で Key Name を入力します。

Register で登録します。

Download をクリックして .p8 ファイル(①)をダウンロードします。また、Key ID(②)をコピーしておきます。 あわせて、チーム ID(③、右上にある 10 桁の英数字)、バンドル ID(④)もあわせてコピーしておきます。

この後出てくる Notification Hubs と紐付ける際にこの4つの情報が必要になります。

4. アプリケーションのプロビジョニング プロファイルを作成する

Apple Developr の「Profiles」の「+」をクリックします。「iOS App Development」を選択して「Continue」をクリックします。

作成した App ID(Bundle ID) を選択して「Continue」をクリックします。

対象のユーザーを選択して「Continue」をクリックします。

使用するデバイスを選択して「Continue」をクリックします。

名前を決めて証明書を生成します。

生成した証明書をダウンロードします。

ダウンロードしたファイルをビルドする対象の Mac でダブルクリックすると Xcode 起動してインストールされます。

5. iOS プッシュ通知向けに Notification Hubs を構成する

Notification Hub の Settings > Apple(APNS)を選択します。

トークンベースの認証なので Token を選択します。以下の情報をコピペします。 - Key ID: 上記でコピーした② - Bundle ID: 上記の④ - Team ID: 上記の③ - Token: ダウンロードした .p8 ファイル(①)をエディタで開いてコピペ

Application Mode は開発用なので Sandbox を選択します。

6. MAUI アプリケーションの実装をおこなう

ここからやっと本題に入ることができます。

ここでは MAUI アプリケーションを作成して、shiny パッケージを使用して Notification Hubs のメッセージを受信するところまでの実装をおこないます。

MAUI アプリケーションを作成後、プロジェクトのプロパティ→MAUI 共有→アプリケーション ID に Bundle ID をペーストとします。

Entitlements.plist というファイルを作成して以下の内容をペースとして、Platforms > iOS にコピーします。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>aps-environment</key>
    <string>development</string>
</dict>
</plist>

.csproj を開いて以下を追加します。「パッケージの追加」からおこなう場合は、「プレリリースをチェックします」にチェックを入れてください。バージョンは 2023年1月15日現在のものです。

<ItemGroup>
  <PackageReference Include="Shiny.Extensions.Configuration" Version="3.0.0-alpha-0495" />
  <PackageReference Include="Shiny.Hosting.Maui" Version="3.0.0-alpha-0495" />
  <PackageReference Include="Shiny.Push.AzureNotificationHubs" Version="3.0.0-alpha-0495" />
  <PackageReference Include="Shiny.Push" Version="3.0.0-alpha-0495" />
</ItemGroup>

プロジェクトに appsettings.apple.jsonappsettings.json を追加します。プロパティのビルドアクションをそれぞれ MauiAsset にします。

appsettings.apple.json は下記をコピペします(または空でも良いです)。アプリケーションでこの情報は実際には使いませんがビルドにはこのファイルが必要になります。

{
    "Firebase": {
        "ProjectId": "",
        "AppId": "",
        "SenderId": "",
        "ApiKey": ""
    }
}

appsettings.json は以下の形式で各値を Azure Portal からコピペします。ListenerConnectionStringAccess Policies → DefaultListenSharedAccessSignature から、HubNameOverViewName からそれぞれコピペします。

{
  "AzureNotificationHubs": {
    "ListenerConnectionString": "接続文字列(DefaultListenSharedAccessSignature)",
    "HubName": "ハブ名"
  }
}

MauiProgram.cs を以下のように修正します。
①はshiny 全体の機能を追加します。この中にプッシュ通知の機能も含まれています。
②は json を読み込むようにする機能を追加しています。
③で appsettings.json の値を取得して、④で取得した値を Notification Hubs へ設定しています。

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .UseShiny()  // 追加①
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            })
            .Configuration.AddJsonPlatformBundle(false);    // 追加②

  // 追加③
        var cfg = builder.Configuration.GetSection("AzureNotificationHubs");

  // 追加④
        builder.Services.AddPush<MyPushDelegate>();
        var service = builder.Services.AddPushAzureNotificationHubs<MyPushDelegate>(
            cfg["ListenerConnectionString"],
            cfg["HubName"]
        );
        
        return builder.Build();
    }
}

AddPushAzureNotificationHubs メソッドで渡している <MyPushDelegate クラスは(shiny のサンプルでは SQlite への書き込みを実装していますが)最低限の実装の場合は下記のような感じになります。ここにメッセージの受信をした場合などプッシュ通知に関するイベントが流れてくることになります。

public class MyPushDelegate : IPushDelegate
{
    readonly IPushManager pushManager;

    public MyPushDelegate(IPushManager pushManager)
    {
        this.pushManager = pushManager;
    }

    public Task OnEntry(PushNotification push)
        => this.Insert("PUSH ENTRY");

    public Task OnReceived(PushNotification push)
        => this.Insert("PUSH RECEIVED");

    public Task OnTokenRefreshed(string token)
        => this.Insert("PUSH TOKEN REFRESH");

    Task Insert(string info)
    {
        Console.WriteLine(info);
        return Task.CompletedTask;
    } 
}

Platforms > iOS > AppDelegate.cs を下記のように修正します。デバイスが登録された場合は RegisteredForRemoteNotifications メソッドが、メッセージを受信した場合は DidReceiveRemoteNotification メソッドが呼び出されます。

[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
    protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
// 以下を追加
    [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
    public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
        => global::Shiny.Hosting.Host.Current.Lifecycle().OnRegisteredForRemoteNotifications(deviceToken);

    [Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
    public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
        => global::Shiny.Hosting.Host.Current.Lifecycle().OnFailedToRegisterForRemoteNotifications(error);

    [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
    public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
        Action<UIBackgroundFetchResult> completionHandler)
    {
        global::Shiny.Hosting.Host.Current.Lifecycle().OnDidReceiveRemoveNotification(userInfo, completionHandler);
    }
}

ViewModel のクラスを追加して、DI で登録された IPushManager クラスを使えるようにします。IPushManager クラスにある RequestAccess メソッドを使ってデバイスを Notification Hubs へ登録することになります。ここでは最低限の BaseViewModel クラスを作成してそれを継承しています。

public class MainPageViewModel : BaseViewModel
{
    private readonly IPushManager _pushManager;

    public MainPageViewModel(IPushManager pushManager)
    {
        _pushManager = pushManager;
    }

    public async Task<PushAccessState> RequestAccess()
    {
        // iOS デバイスを Notification Hub に登録する
        var result = await _pushManager.RequestAccess();

        var accessState = result.Status;

        // 発行されたデバイストークンが返ってくる(iOSのデバイスIDではないので注意)
        var regToken = _pushManager.RegistrationToken;

        return result;
    }
}
public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

MainPage クラスを以下のように修正します。OnAppearing イベントでデバイスをプッシュ通知へ登録(RequestAccess)してますが、これでアプリケーション起動時に(最初に MainPage が表示される時に)実行されることになります。

public partial class MainPage : ContentPage
{
    int count = 0;
    MainPageViewModel viewModel => BindingContext as MainPageViewModel;

    //    }
    public MainPage(MainPageViewModel vm)
    {
        InitializeComponent();
        BindingContext = vm;
    }

    protected override async void OnAppearing()
    {
        base.OnAppearing();
        await viewModel.RequestAccess();
    }
}

これで実装は完了したので、実機デバイス(シミュレータではプッシュ通知のテストはできません)でデバッグをします。

7. プッシュ通知のメッセージを送信する

Azure Portal の Notification Hub の Test Send からメッセージを送ることができます。PlatformApple を選択して送信しますが、このときペイロードcontent-available: 1 が必要なので注意してください。これが無いとアプリケーションがアクティブなときにメッセージを受信することができません。

{"aps":{
  "alert":"Notification Hub test notification",
  "content-available": 1
}}