Azure Notification Hubs の登録情報を一括でエクスポートおよびインポートする

シナリオによっては、通知ハブで多数の登録情報を作成または変更する必要があります。 このようなシナリオとして、バッチ計算の後でタグを更新する場合や、Azure Notification Hubs を使用できるように既存のプッシュ実装を移行する場合などがあります。

この記事では、通知ハブで多数の操作を実行したり、すべての登録情報を一括でエクスポートしたりする方法について説明します。

メモ: 一括インポート/エクスポートは、'Standard' 価格レベルでのみ使用できます

大まかな流れ

バッチ サポートは、数百万件の登録情報が関係する長時間実行ジョブをサポートできるように設計されています。 バッチ サポートでは、この規模に対応するため、Azure Storage を使用してジョブの詳細と出力を格納します。 一括更新操作の場合、ユーザーは、登録更新操作のリストを内容とするファイルを BLOB コンテナーに作成する必要があります。 ジョブの開始時に、ユーザーは入力 BLOB の URL と、出力ディレクトリ (これも BLOB コンテナー内に含まれる) の URL を提供します。 ジョブの開始後、ユーザーはジョブの開始時に提供した URL の場所にクエリを実行して、ステータスを確認できます。 各ジョブでは、特定の種類の操作 (作成、更新、削除) のみを実行できます。 エクスポート操作も同じように実行されます。

[インポート]

設定

ここでは、次のエンティティがあることを前提としています。

入力ファイルを作成して BLOB に格納する

入力ファイルには、XML 形式でシリアル化された登録情報のリストが 1 行につき 1 件ずつ含まれています。 次のコード例は、Azure SDK を使用して登録情報をシリアル化し、BLOB コンテナーにアップロードする方法を示しています。

private static async Task SerializeToBlobAsync(BlobContainerClient container, RegistrationDescription[] descriptions)
{
     StringBuilder builder = new StringBuilder();
     foreach (var registrationDescription in descriptions)
     {
          builder.AppendLine(registrationDescription.Serialize());
     }

     var inputBlob = container.GetBlobClient(INPUT_FILE_NAME);
     using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(builder.ToString())))
     {
         await inputBlob.UploadAsync(stream);
     }
}

重要

上記のコードでは、登録情報がメモリ内でシリアル化され、ストリーム全体が BLOB にアップロードされます。 数 MB を超えるファイルをアップロードした場合は、Azure BLOB ガイダンスでこれらの手順の実行方法 (ブロック BLOB など) を参照してください。

URL トークンを作成する

入力ファイルをアップロードしたら、入力ファイルと出力ディレクトリの両方の URL を生成して、通知ハブに提供できるようにします。 入力と出力で 2 つの異なる BLOB コンテナーを使用できます。

static Uri GetOutputDirectoryUrl(BlobContainerClient container)
{
      Console.WriteLine(container.CanGenerateSasUri);
      BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.All, DateTime.UtcNow.AddDays(1));
      return container.GenerateSasUri(builder);
}

static Uri GetInputFileUrl(BlobContainerClient container, string filePath)
{
      Console.WriteLine(container.CanGenerateSasUri);
      BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.Read, DateTime.UtcNow.AddDays(1));
      return container.GenerateSasUri(builder);
}

ジョブを送信する

入力と出力の 2 つの URL を生成したら、バッチ ジョブを開始できます。

NotificationHubClient client = NotificationHubClient.CreateClientFromConnectionString(CONNECTION_STRING, HUB_NAME);
var job = await client.SubmitNotificationHubJobAsync(
     new NotificationHubJob {
             JobType = NotificationHubJobType.ImportCreateRegistrations,
             OutputContainerUri = outputContainerSasUri,
             ImportFileUri = inputFileSasUri
         }
     );

long i = 10;
while (i > 0 && job.Status != NotificationHubJobStatus.Completed)
{
    job = await client.GetNotificationHubJobAsync(job.JobId);
    await Task.Delay(1000);
    i--;
}

この例では、入力 URL と出力 URL に加えて、NotificationHubJob オブジェクトを作成します。これには、次のいずれかのタイプを指定できる JobType オブジェクトが含まれます。

  • ImportCreateRegistrations
  • ImportUpdateRegistrations
  • ImportDeleteRegistrations

この呼び出しが完了すると、通知ハブによってジョブが続行され、そのステータスを GetNotificationHubJobAsync の呼び出しによって確認できます。

ジョブが完了したら、出力ディレクトリ内の次のファイルを調べて結果を確認できます。

  • /<hub>/<jobid>/Failed.txt
  • /<hub>/<jobid>/Output.txt

これらのファイルには、バッチで成功および失敗した操作のリストが含まれています。 ファイル形式は .cvs で、各行には元の入力ファイルの行番号と、操作の出力 (通常は作成または更新された登録の説明) が含まれています。

完全なサンプル コード

次のサンプル コードでは、登録情報を通知ハブにインポートします。

using Microsoft.Azure.NotificationHubs;
using Azure.Storage.Blobs;
using Azure.Storage.Sas;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        private static string CONNECTION_STRING = "namespace"; 
        private static string HUB_NAME = "demohub";
        private static string INPUT_FILE_NAME = "CreateFile.txt";
        private static string STORAGE_ACCOUNT_CONNECTIONSTRING = "connectionstring";
        private static string CONTAINER_NAME = "containername";

        static async Task Main(string[] args)
        {
            var descriptions = new[]
            {
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMkUxREQFBlVTTkMwMQ"),
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMjUxREQFBlVTTkMwMQ"),
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMhUxREQFBlVTTkMwMQ"),
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMdUxREQFBlVTTkMwMQ"),
            };

            // Get a reference to a container named "sample-container" and then create it
            BlobContainerClient container = new BlobContainerClient(STORAGE_ACCOUNT_CONNECTIONSTRING, CONTAINER_NAME);

            await container.CreateIfNotExistsAsync();

            await SerializeToBlobAsync(container, descriptions);

            // TODO then create Sas
            var outputContainerSasUri = GetOutputDirectoryUrl(container);
            
            BlobContainerClient inputcontainer = new BlobContainerClient(STORAGE_ACCOUNT_CONNECTIONSTRING, STORAGE_ACCOUNT_CONNECTIONSTRING + "/" +         INPUT_FILE_NAME);

            var inputFileSasUri = GetInputFileUrl(inputcontainer, INPUT_FILE_NAME);


            // Import this file
            NotificationHubClient client = NotificationHubClient.CreateClientFromConnectionString(CONNECTION_STRING, HUB_NAME);
            var job = await client.SubmitNotificationHubJobAsync(
                new NotificationHubJob {
                    JobType = NotificationHubJobType.ImportCreateRegistrations,
                    OutputContainerUri = outputContainerSasUri,
                    ImportFileUri = inputFileSasUri
                }
            );

            long i = 10;
            while (i > 0 && job.Status != NotificationHubJobStatus.Completed)
            {
                job = await client.GetNotificationHubJobAsync(job.JobId);
                await Task.Delay(1000);
                i--;
            }
        }

        private static async Task SerializeToBlobAsync(BlobContainerClient container, RegistrationDescription[] descriptions)
        {
            StringBuilder builder = new StringBuilder();
            foreach (var registrationDescription in descriptions)
            {
                builder.AppendLine(registrationDescription.Serialize());
            }

            var inputBlob = container.GetBlobClient(INPUT_FILE_NAME);
            using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(builder.ToString())))
            {
                await inputBlob.UploadAsync(stream);
            }
        }

        static Uri GetOutputDirectoryUrl(BlobContainerClient container)
        {
            Console.WriteLine(container.CanGenerateSasUri);
            BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.All, DateTime.UtcNow.AddDays(1));
            return container.GenerateSasUri(builder);
        }

        static Uri GetInputFileUrl(BlobContainerClient container, string filePath)
        {
            Console.WriteLine(container.CanGenerateSasUri);
            BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.Read, DateTime.UtcNow.AddDays(1));
            return container.GenerateSasUri(builder);

        }
    }
}

[エクスポート]

登録情報のエクスポートは、インポートと似ていますが、次の点が異なります。

  • 出力 URL のみが必要です。
  • ExportRegistrations タイプの NotificationHubJob を作成します。

サンプル コード スニペット

Java で登録をエクスポートするためのサンプル コード スニペットを次に示します。

// Submit an export job
NotificationHubJob job = new NotificationHubJob();
job.setJobType(NotificationHubJobType.ExportRegistrations);
job.setOutputContainerUri("container uri with SAS signature");
job = hub.submitNotificationHubJob(job);

// Wait until the job is done
while(true){
    Thread.sleep(1000);
    job = hub.getNotificationHubJob(job.getJobId());
    if(job.getJobStatus() == NotificationHubJobStatus.Completed)
        break;
}

次のステップ

登録について詳しくは、次の記事を参照してください。