June 2016

Volume 31 Number 6

Azure App Service - Azure App Service を使用した Web ページから PDF への変換

Benjamin Perkins

Web ページから PDF への変換は目新しいものではありません。しかし、今回目標とするのはやや複雑です。つまり、Web サイトにリンクを置き、サイトの利用者がリンク先のページを PDF にリアルタイムで変換できるようにします。こうした目標を実現している Web サイトはたくさんあります。これを可能にするオープン ソース バイナリも存在します。しかし、さまざまな手法をつなぎ合わせてみましたが、希望どおりの出力を得ることができませんでした。

Web ページを PDF に変換するお気に入りのコンバーターは、今のところコマンド ラインを使用する wkhtmltopdf というオープン ソース プログラム (wkhtmltopdf.org、英語) です (図 1 参照)。

コンソールからwkhtmltopdf Converterを実行します
図 1 コンソールから実行する wkhtmltopdf コンバーター

とは言え、コマンド ラインから実行するプログラムを、Web ページのボタンを使ってリアルタイムに変換するプログラムにするのは大変です。

ここ数か月にわたってこのソリューションのさまざまな部分に取り組みましたが、wkhtmltopdf プロセスの実行がどうしても高い壁になり、目標を達成できませんでした。答えを見つけられなかったのは、PDF を作成するために、Microsoft Azure App Service Web アプリからこのプロセスを呼び出す方法でした。App Service Web アプリはサンドボックス内で実行されるため、こうしたプロセスの呼び出しを実行できないことは最初からわかっていました。つまり、クライアント コンピューターから送信された要求によってサーバー上のプロセスを呼び出して実行することはできません。長い間 IIS サポート チームに携わってきたため、スタンドアロン バージョンの IIS でさえ、これを実現するにはセキュリティ アナリストが不眠不休で構成しなければならないような大変な作業になることはわかっていました。そこで、考えたのが Web ジョブです。

Web ジョブはこうした状況に最適です。Web ジョブは、実行可能ファイルを絶えず実行するか、外部ソースからトリガーされたときに実行することができます。たとえば、Azure SDK から手動でトリガーすることも、Azure Scheduler、Cron、Azure WebJob API (bit.ly/1SD9gVJ、英語) を使用してトリガーすることもできます。そして、ひらめいたのです。Web ジョブ API を使用すれば App Service Web アプリから wkhtmltopdf プログラム呼び出せるかもしれません。このソリューションの他のコンポーネントは解決済みでした。このパズルの最後のピースがようやく手に入ったのです (図 2 参照)。

完全なソリューション
図 2 完成形のソリューション

今回のコード例には ASP.NET Web サイトを含めています。この Web サイトのインデックス ページでは、ユーザーが URL を入力して、その Web ページを送信することで PDF に変換し、その PDF をクライアント デバイスにダウンロードできます。ほとんど手間をかけずに、この URL を現在のページに動的に設定し、ボタンを使ってそのページを変換およびダウンロード用の Web ジョブ API に送信できます。ここからは、このソリューションの作成に使用するテクノロジを説明し、これらのテクノロジをビルドして利用する方法を紹介します。

HTML を PDF に変換するコンバーターの概要

HTML を PDF にリアルタイム変換する App Service Web アプリ ソリューションを作成するには、数多くのテクノロジを使用します。図 3 の表で、使用するテクノロジを簡単に説明します。詳しくはその後の各セクションで取り上げます。

図 3 このソリューションで使用するテクノロジ

テクノロジ 簡単な説明
Azure App Service Web アプリ (S2 プラン) SignalR コードをホストするフロント エンド
App Service の認証と承認 クライアント ID の確認
Azure Storage PDF ドキュメントの保存
Azure Web ジョブ HTML から PDF への変換と、Azure Storage への PDF のアップロード
Azure Web ジョブ API Web ジョブをトリガーするためのインターフェイス
ASP.NET SignalR サーバーからクライアントに返す応答の管理

各セクションでは、テクノロジの機能面と技術面についての説明のほか、コーディング要件や構成要件も詳しく示しています。今回のソリューションの各部分を作成したときの順番に説明していますが、順番を変えても同じことを実現できます。技術的な目標は、URL を App Service Web アプリに渡して PDF を返すことです。では、始めましょう。

Azure App Service Web アプリ

Azure App Service では、Web、Mobile、Logic (プレビュー)、API など、さまざまな種類のアプリを扱うことができます。バック エンドではすべての App Service が同じように機能しますが、各フロント エンドには構成可能な追加機能が用意されています。バック エンドと言っても App Service はさまざまなサービス プラン (Free、Shared、Basic、Standard、Premium) とインスタンス サイズ (F1 ~ P4) で実行されます。詳細については、https://azure.microsoft.com/ja-jp/pricing/details/app-service/ を参照してください。採用するプランに応じて、デプロイメント スロット、ディスク要量制限、自動スケーリング、最大インスタンス数などの特徴が異なります。また、インスタンス サイズは、専用 CPU 数と、App Service プラン (ASP) あたりのメモリを示します。これは、仮想マシン (VM) と同じものです。フロント エンドでは、指定した App Service の特性に応じて具体的に設計された機能が特定の種類の App Service に提供され、ほとんど時間をかけずにアプリをデプロイ、構成、および実行できるようになります。

HTML を PDF に変換する今回のコンバーターでは、S2 の Azure App Service Web アプリを使用します。S2 を採用したのは、他の種類の App Service で提供される機能を必要としないためです。

まず、Azure ポータル内で Web アプリを作成します。[新規]、[Web + モバイル]、[Web アプリ] の順に選択し、アプリ名、サブスクリプション、リソース グループ、および App Service プランを指定して、[作成] ボタンをクリックします。アプリを作成したら、この場所を使用して、ダウンロード可能な付属の Visual Studio 2015 ソリューション convertHTMLtoPDF に含まれるソース コードをデプロイします。デプロイの詳細については最後に説明します。特定の Web アプリと Web ジョブでこのコードが機能させるには変更をいくつか加える必要があります。

Web Apps、Mobile Apps、API Apps には、Azure Active Directory や、Facebook、Microsoft Live、Twitter などの ID プロバイダーの認証と承認を設定するために使用できるフェデレーション ID ベースの機能が含まれています。次は、これについて説明します。

App Service の認証と承認

今回の Web アプリには App Service の認証/承認機能を構成することにしました。それは、表示名またはクライアントの ID を求める SignalR のしくみにこの機能がうまく適合するためです。SignalR はクライアントごとに ConnectionId を作成しますが、メッセージの送信や投稿にはユーザーの実名を使用する方がわかりやすく、パーソナライズしやすくなります。そのためには、認証機能のコールバックから実名を取得し、SignalR コードを使用してその実名を表示します。今回は Microsoft アカウント ID プロバイダー (IDP) を実装したので、認証を受けたユーザーの名前が X-MS-CLIENT-PRINCIPAL-NAME 要求ヘッダーに含められて返されます。この識別名には System.Security.Principle.IPrinciple.Identity.Name プロパティからアクセスすることもできます。

認証/承認機能を機能させるためにアプリのバック エンド コードに変更を加える必要はありません。https://azure.microsoft.com/ja-jp/documentation/articles/app-service-mobile-how-to-configure-microsoft-authentication/ に記載されている手順に従うだけです。この機能の実装に必要なのは、特定の App Service の [設定] ブレードからアクセスできる [App Service 認証] をオンにして、1 つ以上の認証プロバイダーを構成することだけです (図 4 参照)。

アプリケーションサービスの認証/承認機能
図 4 App Service の認証/承認機能

この機能では、[要求が認証されない場合に実行するアクション] に対する選択肢が多数用意されています。たとえば、今回の HTML を PDF に変換する Web アプリにアクセスするには、Microsoft アカウントを所持していることと、ID プロバイダーによる認証が必要です。この IDP 認証が行われる前に Web アプリのコードが実行されることはありません。今回は、ドロップダウン リストで [Microsoft アカウントでのログイン] を選択したので事前認証が必要になります。アクションを適用すると、App Service のすべてのリソースでこのような認証が要求されます。また、認証機能を構成することで、Azure でホストされる App Service のログイン ページや他のエンドポイントにユーザーがアクセスできるようになります。そのためには、ドロップダウン リストから [要求の許可 (操作不要)] を選択します。ただし、この場合でも保護されたページへのアクセスを制限するかどうかはアプリケーション コードしだいです。さらに細かいアプローチは通常、ページ内でコードを実行する前に Context.User.Identity.IsAuthenticated ブール値を確認することで実現されます。

HTML を PDF に変換するリアルタイム ソリューションで、コードを必要としない最後のコンポーネントは、Azure Storage アカウントとコンテナーの作成と構成です。

Azure Storage

Azure Storage コンテナーはダウンロード用に PDF ファイルを保存する場所です。Azure Storage コンテナーを公開すると、https://{ストレージ アカウント}.blob.core.windows.net/{コンテナー名}/{ファイル名 .pdf} のような URL を使用してファイル名を参照すれば、そのコンテナーでホストされているファイルにだれでもアクセスできます。コンテナーのファイルを挿入、更新、削除をコードから実行する場合はアクセス キーが必要になります。こうした操作を Azure 管理ポータルから行ったり、Visual Studio 内から行うことを制限できます。それには、ロールベースのアクセス制御 (RBAC) を使用するか、単純に Azure サブスクリプションへのユーザー アクセスを禁止します。

ストレージ アカウントを作成するには、[新規]、[データ + ストレージ]、[ストレージ アカウント] の順に選択します。[名前] 属性がコンテナーの作成先のストレージ アカウントになります。これは、URL https//{ストレージ アカウント}.blob.core.windows.net の最初の部分です。[デプロイ モデル] 属性には、[リソース マネージャー] または [クラシック] を選択できます。従来の仮想ネットワーク (VNET) に既存のアプリケーションを配置している場合を除き、すべての新しい配置 (デプロイ) 操作に [リソース マネージャー] を使用することをお勧めします。Azure Resource Manager (ARM) は、テンプレートとスクリプトを使用する宣言型の性質がより高いアプローチです。これに対して、クラシック モデルのインターフェイスは、通常、コードとライブラリを使用して実行する Azure Service Manager (ASM) を指すのが一般的です。

[パフォーマンス] で [Standard] または [Premium] 選ぶときは、コストとスループットを考えます。Standard はコスト効率が非常に高く、アクセス機会が少なく、大量のデータを保存するアプリケーションに最適です。Premium ストレージは、ソリッドステート ドライブ (SSD) によって支えられ、I/O を集中的に使用することを要件とする仮想マシンに最適なパフォーマンスを提供します。

[レプリケーション] 属性には [ローカル]、[ゾーン]、[グローバル]、[Read-Access Global] (読み取りアクセス グローバル) など多数のオプションがあり、それぞれ優れたレベルの冗長性とアクセス機能が提供されます。今回の HTML を PDF に変換するソリューションでは既定の設定を使用し、[サブスクリプション]、[リソース グループ]、[場所] には以前に作成した Web アプリと同じ設定を選択しています。

ストレージ アカウントを正常に作成したら、最後に、そのストレージ アカウントの [全般] ブレードから [BLOB] サービスを選択し、コンテナーを追加します。

[新しいコンテナー] ブレードの [アクセスの種類] には、[プライベート] (すべての操作にアクセス キーが必要)、[BLOB] (パブリック読み取りアクセスを許可)、または [コンテナー] (パブリック読み取りとリストへ アクセスを許可) のいずれかを設定できます。

これで準備完了です。このソリューションに必要な Azure の構成はすべて終わりました。ここからは、C# コードに取り組み、HTML から PDF への変換をリアルタイムに実現する方法を紹介します。

Azure Web ジョブ

Azure Web ジョブ機能は、スクリプトや実行可能ファイルの連続実行、トリガーによる実行、スケジュールに従った実行をサポートします (bit.ly/1Og9P95、英語)。Windows サービスと混同しないでください。Web ジョブは、特定のタイミングまたは特定の事象の発生時に実行されるタスクやバッチ ジョブと考えます。今回の場合、API を使用する Web ジョブをトリガーする HTML から PDF へのリアルタイム変換ツールを使用します。また、Web ジョブは Visual Studio から手動で開始したり、Azure Scheduler のジョブ コレクション機能を使用して開始することができます。

Azure App Service プラットフォームは、Web ジョブが保存されているパスに従って、その Web ジョブをトリガーするか、連続実行するかを判断します。Web ジョブをトリガーする場合は、\home\site\wwwroot\app_data\jobs\triggered\{ジョブ名} に配置します。連続実行する場合は、triggered ディレクトリ パスを continuous に置き換えます。Web ジョブをデプロイするには、bit.ly/1Uczf8L (英語) で説明されているように、Visual Studio の Web サイト プロジェクトに app_data\jobs\triggered\{ジョブ名} ディレクトリを追加し、このディレクトリにスクリプトまたは実行可能ファイルを追加して、それを Azure App Service プラットフォームに発行します。

今回作成する Web ジョブでは 2 つのタスクを実行します。指定された Web アドレスのページを PDF ファイルに変換するタスクと、その PDF ファイルを Azure Storage コンテナーにアップロードするタスクの 2 つです。Web ジョブ API を使用して wkhtmltopdf.exe を直接呼び出してもかまいませんが、もう 1 つ API 呼び出しを行い、ファイルをストレージにアップロードする必要があります。それにはファイルを管理してクライアントに結果を返す複雑な作業が多数必要になります。そのため、convertToPdf というコンソール アプリケーションを作成しました (これはソースで確認できます)。このアプリケーションは上記の 2 つのタスクを順番に実行し、PDF ファイルの場所を要求元のクライアントに返します。

wkhtmltopdf.exe を開始し、このアプリケーションに 2 つの必須パラメーター、Web アドレスと PDF ファイル名を渡すために、System.Diagnostics.ProcessStartInfo を使用します (図 5 参照)。

図 5 wkhtmltopdf.exe の開始

static void Main(string[] args)
{
  var URL = args[0];
  var filename = args[1];
  try
  { 
    using (var p = new System.Diagnostics.Process())
    {
      var startInfo = new System.Diagnostics.ProcessStartInfo
      {
        FileName = "wkhtmltopdf.exe",
        Arguments = URL + " " + filename,
        UseShellExecute = false
      };
      p.StartInfo = startInfo;
      p.Start();
      p.WaitForExit();
      p.Close();
    }
  }
  catch (Exception ex) { WriteLine($"Something Happened: {ex.Message}"); }
}

コードでは、ProcessStartInfo クラスのインスタンスを作成し、このクラスの FileName、Arguments などのプロパティを設定しています。次に、そのクラスのメソッドを使って、FileName プロパティで識別されるプロセスを開始し、完了を待機して、プロセスを終了します。既定では、Azure App Service 環境に Web ジョブをアップロードすると、プラットフォームによってその Web ジョブが一時ローカル ディレクトリ D:\local\temp\jobs\triggered\{ジョブ名}\*****\ にコピーされます。***** は動的に生成されるディレクトリ名です。このディレクトリは、Azure Storage コンテナーにアップロードする前に PDF を物理的に格納しておく場所にもなります。ファイルはローカルに限定されるため、永続化することも、Azure App Service Web アプリの他のインスタンスからアクセスされることもありません。複数のインスタンスで実行すると、ファイルがローカル ディレクトリに見つからないこともありますが、Azure Storage コンテナーにはグローバルにアクセスできます。

PDF ファイルを作成したら、Azure Storage コンテナーへアップロードする必要があります。その方法を詳しく説明したすばらしいチュートリアルについては、https://azure.microsoft.com/ja-jp/documentation/articles/storage-dotnet-how-to-use-blobs/ を参照してください。手短に言えば、コンテナーのコンテンツを作成、読み取り、更新、削除する機能は、Microsoft Azure Configuration Manager Library for .NET ライブラリと、Microsoft Azure Storage Client library for .NET ライブラリという 2 つの NuGet パッケージによって制御されます。どちらのパッケージも convertToPdf Web ジョブ コンソール アプリケーションから参照します。これらをインストールするには、コンソール アプリケーション プロジェクトを右クリックし、[NuGet パッケージの管理] をクリックします。次に、目的のライブラリを探してインストールします。

今回は、Microsoft Azure Configuration Manager ライブラリに含まれる CloudConfigurationManager.GetSetting を使用して、Azure Storage コンテナーと接続するためのストレージ接続文字列値を取得しています。値は AccountName と AccountKey です。AccountName は Azure Storage アカウント名 (今回の場合は converthtmltopdf) で、コンテナー名ではありません。AccountKey は、[ストレージ アカウント] ブレードから [設定]、[アクセス キー] をクリックすることで取得できます。図 6 は、前の手順で作成した Azure Storage コンテナーに PDF ファイルをアップロードする方法を示しています。

図 6 Azure Storage コンテナーへの PDF のアップロード

static void Main(string[] args)
{
  try
  {
    CloudStorageAccount storageAccount =
      CloudStorageAccount.Parse(
      CloudConfigurationManager.GetSetting("StorageConnectionString"));
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    CloudBlobContainer container =
      blobClient.GetContainerReference("pdf");
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(filename);
    using (var fileStream = System.IO.File.OpenRead(filename))
    {
      blockBlob.UploadFromStream(fileStream);
    }
  }
  catch (StorageException ex) { WriteLine($"StorageException: {ex.Message}"); }
  catch (Exception ex) { WriteLine($"Exception: {ex.Message}"); }
}

この構成情報は、Microsoft Azure Storage Client ライブラリに含まれる CloudStorageAccount クラスへの入力として使用します。App.config ファイルから StorageConnectionString を取得するための CloudConfigurationManager に代わる手段として、System.Configuration.ConfigurationManager.AppSettings["StorageConnectionString"] も使用できます。

CloudStorageAccount クラスのインスタンスを使用して、CloudBlobClient を作成します。次に、この CloudBlobClient を使用して、GetContainerReference メソッドで Azure Storage コンテナーへの参照を取得します。その後、CloudBlobContainer クラスの GetBlockBlobReference メソッドを使用して、アップロードするファイル名を含める CloudBlockBlob を作成します。前述のとおり、両方の実行可能ファイルは D:\local\temp\jobs\triggered\convertToPdf\***\ にあります。この同じ場所を使って、PDF ファイルの保存と参照を行います。つまり、ファイルを作成するのは実行可能ファイルと同じ一時ディレクトリなので、filename のパスは必要ありません。最後に、System.IO.File.OpenRead メソッドを使用して System.IO.FileStream のインスタンスを渡し、CloudBlockBlock クラスの UploadFromStream メソッドを使用してそれをコンテナーにアップロードします。

コードが完成してコンパイルが終わったら、wkhtmltopdf.exe と convertToPdf.exe の両ファイルを、Azure App Service Web アプリに発行する Visual Studio ソリューションの \app_data\jobs\triggered\convertToPdf ディレクトリに追加します。また、FTP ツールを使用して Web ジョブ ファイルのみを発行し、コードを Web サイトに直接転送することもできます。

PDF を作成して保存する convertToPdf Web ジョブが完成したので、HttpClient を使用して Web ジョブを C# コードから呼び出す方法を見ていきます。それが終われば、残りは SignalR ベースの Azure App Service Web アプリ フロント エンドを作成し、利用者が URL を Web ジョブに送信することで PDF のダウンロード用 URL を取得できるようにするだけです。

Azure Web ジョブ API

以前 Azure Web ジョブ API に関する記事 (bit.ly/1SD9gVJ、英語) で、Web ジョブをトリガーする API を呼び出す方法を取り上げました。要約すれば、Web ジョブ API とは、URL で渡された引数を使用してスクリプトまたは実行可能ファイルを実行する Web インターフェイスです。

Web ジョブ API をトリガーする SignalR ハブを作成する前に、Web ジョブ API を呼び出す簡単なコンソール アプリケーション コンシューマーを作成します (図 7 参照)。これはダウンロード可能な本稿付属のソリューションに「convertToPDF-consumer」という名前で含めてあります。このコンソール アプリケーションは、コーディング、トラブルシューティング、テストが簡単になるように、シナリオから SignalR 機能を取り除いています。

図 7 簡単なコンソール アプリケーション コンシューマー

static async Task<string> ConvertToPDFWebJobAPIAsync(string Url)
{
  try
  {
    using (var client = new HttpClient())
    {
      client.BaseAddress = new Uri(
        "https://converthtmltopdf.scm.azurewebsites.net/");
      client.DefaultRequestHeaders.Accept.Clear();
      var userName = "your userName";
      var password = "your userPWD ";
      var encoding = new ASCIIEncoding();
      var authHeader =
        new AuthenticationHeaderValue("Basic",
          Convert.ToBase64String(
          encoding.GetBytes(string.Format($"{userName}:{password}"))));
      client.DefaultRequestHeaders.Authorization = authHeader;
      var content = new System.Net.Http.StringContent("");
      string filename = Guid.NewGuid().ToString("N").Substring(0, 8) + ".pdf";
      HttpResponseMessage response =
        await client.PostAsync(
        $"api/triggeredwebjobs/convertToPDF/run?arguments={Url} 
          {filename}", content);
      if (!response.IsSuccessStatusCode)
      {
        return $"Conversion for {Url} {filename} failed: " +
          DateTime.Now.ToString();
      }
      return $"{response.StatusCode.ToString()}:
        your PDF can be downloaded from here:";
    }
  }
  catch (Exception ex)  {  return ex.Message;  } }

System.Net.Http.HttpClient クラスの HttpClient メソッドを使用して要求を行います。次に、Azure App Service Web アプリのソース管理 (SCM) ベースの URL をこの要求の BaseAddress プロパティとして使用します。各 Azure App Service Web アプリには SCM URL (別称 Kudu コンソール) が含まれます。この SCM URL は https://{アプリ名}.scm.azurewebsites.net を使用してアクセスでき、Web ジョブ API を呼び出すために使用します。この URL の末尾に /basicAuth を追加すると、呼び出し元のクライアントではチャンレンジ/レスポンスの基本ハンドシェイクを使用した認証が可能になります。userName と password は発行プロファイルの資格情報です。この資格情報は、Azure 管理ポータルから目的の Azure App Service Web アプリに移動し、[発行プロファイルの取得] をクリックすることでダウンロードできます。ダウンロードした *.PublishSettings ファイル内に、コードで使用する userName と userPWD が見つかります。わかりやすくするために、userName と password をアプリケーションにハードコーディングしていますが、実際の場合はこれらを安全な場所に保管して、コードから取得します。そうすれば、Azure 管理ポータルで [発行プロファイルの取得] 選択して、userName と password を必要に応じて変更できるようになります。変更を加えるたびに更新したコードをデプロイしなければならない状況は望ましくありません。

基本認証では、ASCII エンコードされる Base64 文字列の userName と password を、Basic userName:password という形式で Basic ヘッダーに関連付ける必要があります。System.Convert クラスの ToBase64String メソッドと共に、System.TextASCIIEncoding クラスの ASCIIEncoding メソッドを使用してヘッダー値を作成したら、System.Net.Http.Headers.AuthenticationHeaderValue クラスの新しいインスタンスに Basic ヘッダー名と共にそのヘッダー値を追加します。using ステートメントで作成した System.Net.Http.HttpClient の新しいインスタンスを使用して、AuthenticationHeaderValue を System.Net.Http.Headers.HttpRequestHeaders クラスの DefaultRequestHeaders.Authorization プロパティに追加します。

filename については、String クラスの Substring メソッドを使用して 8 文字の GUID を使用しています。ただし、GUID からダッシュは取り除きます。GUID は、System.Guid クラスの NewGuid メソッドを使用して、Guid クラスの ToString メソッドに N パラメーターを渡すことで作成します。最後に、System.Net.Http.HttpClient クラスの PostAsync メソッドを使用して Web ジョブ API に非同期でポストし、URL と filename を Web ジョブの引数として渡して、完了を待機します。プロセスが正常に完了すると、filename が連結された Azure Storage コンテナーの URL がコンソールに表示されます。正常に完了しなかった場合は、PDF を作成できなかったという通知が送信されます。

Web ジョブの状態を確認するには、Azure 管理ポータルにアクセスし、Web ジョブを実行している Azure App Service Web アプリに移動して、[設定]、[Web ジョブ] の順に選択します。[Web ジョブ] ブレードには [名前]、[タイプ]、[状態] に加えて、Web ジョブ実行ログへの非常に便利なリンクが含まれます。このリンクをクリックすると、Web ジョブ固有の Kudu コンソールにアクセスでき、最近のジョブの実行、その状態、および Web ジョブの実際のログ出力へのリンクが表示されます (図 8 参照)。たとえば、Web ジョブがコンソール アプリケーションの場合、System.Console.WriteLine メソッドを使用して実行状態をコンソールの出力ウィンドウに書き込むと、この情報は Web ジョブ ログにも書き込まれるため、Azure 管理ポータルのリンクを使って確認できます。

AzureのWebJobの出力ログ
図 8 Azure Web ジョブ出力ログ

この部分が想定どおりに動作したら、後は単純にコピーして SignalR ソリューションに貼り付けるだけです。

ASP.NET SignalR

ASP.NET SignalR は ASP.NET 開発者向けのオープン ソース ライブラリで、ブラウザベース、モバイル、または .NET クライアントのアプリケーションに対して通知を簡単にリアルタイム送信できるようにします。このサーバーからクライアントに対するリモート プロシージャ コール (RPC) では、サーバー側の .NET コードからクライアント上の JavaScript 機能を呼び出す API を使用します。このテクノロジの登場以前に同様のソリューションを実現するには、ASP.NET UpdatePanel コントロールを使用するのが一般的でした。この UpdatePanel コントロールは、サーバーに要求してデータの状態に変更が加えられているかどうかをチェックすることによって、自らを頻繁に更新します。これはかなりプル型寄りのアプローチで、利用可能になったデータがサーバーからすぐにリアルタイムでプッシュされるのではなく、クライアント側でサーバーに対する要求をトリガーするものです。

クライアント側の JavaScript コードはハブ プロキシのインスタンスを作成し、サーバー側でトリガーできるメソッドを公開します。また、クリック イベントが発生したときにサーバー側のメソッドを特定して (Send を) 呼び出します。

var pdf = $.connection.pDFHub;
pdf.client.broadcastMessage = function (userId, message) {};
pdf.client.individualMessage = function (userId, message) {};
$('#sendmessage').click(function () {
  pdf.server.send($('#displayname').val(), $('#message').val());
});

クライアント側の JavaScript のハブ プロキシの名前はサーバー側で実行するために作成するハブの名前です。今回の例のハブは PDFHub という名前で、Microsoft.AspNet.SignalR.Hub クラスから継承しています。クライアントが公開する 2 つのメソッドは broadcastMessage と individualMessage です。いずれも、サーバー側の Send メソッドの userId と message のパターンに一致するパラメーターを使用する関数を含みます。Send メソッドは、Web アプリ上で利用者が送信ボタンをクリックすると、サーバー上で呼び出されます。ConvertToPDFWebJobAsync メソッドは、前のセクションで作成したコンソール アプリケーションを切り取って貼り付けたものです。このアプリケーションは Azure WebJob API を呼び出して、指定された Web ページを PDF ファイルに変換し、Azure Storage コンテナーに読み込みます。最後に、サーバー側の Send メソッドが Microsoft.AspNet.SignalR.Hub.Clients プロパティのインスタンスを使用します。このプロパティは、IHubCallerConnectionContext インターフェイスを実装します。この Microsoft.AspNet.SignalR.Hub.Clients プロパティは、2 つのクライアント側のメソッドに関連付けられ、サーバーから適切なクライアントに送信される情報を提供します (図 9 参照)。

図 9 PDFHub クラス

public class PDFHub : Hub
{
  public void Send(string userId, string message)
  {
    string name = Context.User.Identity.Name;
    string convertMessage = "no message yet";           
    Task.Run(async () =>
    {
      convertMessage = await ConvertToPDFWebJobAPIAsync(message);
    }).Wait();
    Clients.All.broadcastMessage(userId, "just converted: " + message +
      " to a pdf");
    Clients.Client(Context.ConnectionId).individualMessage(
      name, convertMessage);
  }
}

Azure Web ジョブ API を使用するために、簡単な ASP.NET Web フォーム アプリケーションや ASP.NET MVC Web アプリケーションではなく、SignalR を選んだ理由に触れておきます。確かに、API を使用する方法は数多く存在します。たとえば、このソリューションのダウンロード可能な付属コードには Azure Web ジョブ API を使用するコンソール アプリケーションを含めています。しかし、SignalR を使用するのにはそれなりの理由があります。

その疑問の答えは、図 9 にあります。ここでは接続済みのクライアント向けのメッセージがサーバーにあると、クライアント側のメソッドを 2 つ呼び出しています。まず、broadcastMessage メソッドにより、ある利用者が特定の URL を PDF に変換したことを示す通知が接続済みのすべてのクライアントに送られます。ただし、Azure Storage コンテナーとダウンロード用 PDF ファイルへのリンクは提供しません。2 番目のクライアント側のメソッドは individualMessage です。このメソッドにより、HTML から PDF への変換の状態と、PDF ファイル名が連結された Azure Storage コンテナーへのリンクが送られます。SignalR を使用する理由は、Azure App Service Web アプリ上で起こっていることを接続済みのクライアントすべてに情報として提供することにより、API を使用するクライアント側での対話操作にソーシャル感覚を提供することです。

前述のように System.Security.Principle.IPrinciple.Identity.Name のプロパティを使用することにより、たとえば、固有であっても汎用的な ConnectionId ではなく、利用者の名前をクライアントに表示できるため、Web アプリがわかりやすくなります。利用者の名前を設定するために使用する Context.User.Identity.Name プロパティは Microsoft アカウントによって検証されます。これにより、クライアントにソーシャル感覚が生まれます。

これで、残りは Visual Studio または FTP アプリケーションを使用してコード (クライアント コード、サーバー コード、および Azure Web ジョブ) を Azure App Service Web プラットフォームにデプロイし、テストするだけです。Azure App Service Web アプリにデプロイする方法の詳しい手順については、https://azure.microsoft.com/ja-jp/documentation/articles/web-sites-deploy/ を参照してください。

Software as a Service

本稿を執筆しながら、Software as a Service (SaaS) について考えるようになりました。つまり、HTML を PDF にリアルタイム変換するこのコンバーターは SaaS ソリューションなのか、それとも単にクラウドで実行されていて API にアクセスできるアプリなのかを考えました。Azure Web ジョブ API を公開することは、それ自体の名前からして、API であって SaaS ではないと判断しました。今回のソリューションでは、この Web ジョブ API は URL 経由で公開され、基本認証によって保護されます。この API は、他のコンシューマーではそれを基盤として構築したり、機能を追加したりする目的で利用できます。これは、API の定義に該当します。ただし、API のコンシューマーが出現するとすぐに、使用される API にまつわる機能がさらに追加され、その上この API は複数のオンライン ユーザーが利用できます。そのため、この Web ジョブ API は SaaS の定義にも当てはまります。したがって、Azure Web ジョブ API だけなら単なる API ですが、Azure App Service Web アプリ プラットフォームで実行される今回の ASP.NET SignalR クライアントは SaaS ソリューションです。確かに、これは OneDrive、Office 365、CRM Dynamics Online、Hotmail ではありません。ですが、Web サイトを PDF に非常に短時間で変換する必要がある場合は、どうすればよいかおわかりいただけたのではないでしょうか。

まとめ

今回は Azure の 3 つの機能を取り上げました。Azure App Service Web アプリ、Azure Service の認証と承認、および Azure Storage アカウントとコンテナーの 3 つです。これらの機能が Azure Web ジョブをサポートし、Azure Web ジョブ API を公開して、ブラウザベースの ASP.NET SignalR コンシューマーをホストする基盤になります。今回は、これらの機能を 1 つずつ紹介し、その機能を構成するのに必要な手順を説明しました。また、Azure Web ジョブのコード、Azure Web ジョブ API を呼び出すコード、および ASP.NET SignalR クライアントについても取り上げました。


Benjamin Perkins は、マイクロソフトのエスカレーション エンジニアで、C#、IIS、NHibernate、および Microsoft Azure に関する書籍を 4 冊執筆しています。そして、最近出版された『Beginning C# 6 Programming with Visual Studio 2015』(John Wiley & Sons、2015 年) の共著者でもあります。連絡先は benperk@microsoft.com (英語のみ) です。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Richard Marr に心より感謝いたします。
Richard Marr は、マクロソフトのシニア エスカレーション エンジニアです。マイクロソフトのサポート組織に 16 年間従事しており、IIS、ASP.Net をサポートしてきました。現在は Azure App Service のサポートに携わっています。