Azure Functions と SharePoint webhook の使用

Azure Functions を使用すると、SharePoint Webhook をホストできます。ブラウザーで Webhook C# や Javascript コードを追加すると、Azure で関数のホストとスケーリングが行われます。 このガイドでは、Webhook 用に Azure Functions をセットアップして使用する方法を説明します。

Azure 関数アプリを作成する

最初に Azure 関数アプリを作成する必要があります。これは、Azure Functions のホスティングに特化した特殊な Azure Web アプリです。

  1. https://portal.azure.com に移動して、関数アプリを検索します。 検索結果で関数アプリを選択します。

    Azure 関数アプリの作成

  2. [追加] オプションをクリックします。

    Azure 関数アプリを追加する

  3. 関数アプリの作成に必要な情報を入力して、[レビュー + 作成] をクリックします。

    Azure 関数アプリの詳細を記入する

  4. [作成] をクリックします

    Azure 関数アプリ確認ページ

  5. 展開が完了したら、[リソースに移動] をクリックします。

    Azure 関数アプリ完了ページ

Azure 関数を作成する

関数をホストするためのアプリが完成したら、新しい関数リンクをクリックして、最初の Azure 関数を作成できます。

Azure 関数アプリ ランディング ページ

ここでは、テンプレートから関数を開始できます。SharePoint Webhooks では、HTTP でトリガーされる関数が必要です。このサンプルでは C# コードを作成するため、HttpTrigger-CSharp 関数テンプレートを使用します。

  1. ポータル内開発環境オプションを選択し、[続行] をクリックします。

    開発環境ページを選択する

  2. Webhook + API トリガーの種類を選択し、[作成] をクリックします。

    トリガーの種類を選択ページ

    結果は、C# で記述された "既定" の Azure 関数になります。

    既定の C# コードを表示する開発環境ページ

今回は、この Azure 関数を SharePoint Webhook サービスとして機能させるため、C# で以下の内容を実装する必要があります。

  • 呼び出しの URL パラメーターとして指定されている場合、validationtoken を返します。 これが必要な理由は「新しいサブスクリプションを作成する」で説明されています。SharePoint では 5 秒以内に返信が行われることを想定しています。
  • JSON Webhook 通知を処理します。 以下のサンプルで、JSON をストレージ キューに格納することを選択することにより、Azure Web ジョブはその JSON を取得して、非同期的に処理できます。
  • 必要に応じて、通知を Webhook サービスで直接処理することもできますが、Webhook サービスのすべての呼び出しは 5 秒以内に完了する必要があるため、非同期モデルの使用をお勧めします。

これは、既定のコードを以下のコードに置き換えることによって実行できます。

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req,
  ICollector<string> outputQueueItem, ILogger log)
{
  log.LogInformation($"Webhook was triggered!");

  // Grab the validationToken URL parameter
  string validationToken = req.Query["validationtoken"];

  // If a validation token is present, we need to respond within 5 seconds by
  // returning the given validation token. This only happens when a new
  // webhook is being added
  if (validationToken != null)
  {
    log.LogInformation($"Validation token {validationToken} received");
    return (ActionResult)new OkObjectResult(validationToken);
  }

  log.LogInformation($"SharePoint triggered our webhook...great :-)");
  var content = await new StreamReader(req.Body).ReadToEndAsync();
  log.LogInformation($"Received following payload: {content}");

  var notifications = JsonConvert.DeserializeObject<ResponseModel<NotificationModel>>(content).Value;
  log.LogInformation($"Found {notifications.Count} notifications");

  if (notifications.Count > 0)
  {
    log.LogInformation($"Processing notifications...");
    foreach(var notification in notifications)
    {
      // add message to the queue
      string message = JsonConvert.SerializeObject(notification);
      log.LogInformation($"Before adding a message to the queue. Message content: {message}");
      outputQueueItem.Add(message);
      log.LogInformation($"Message added :-)");
    }
  }

  // if we get here we assume the request was well received
  return (ActionResult)new OkObjectResult($"Added to queue");
}

// supporting classes
public class ResponseModel<T>
{
  [JsonProperty(PropertyName = "value")]
  public List<T> Value { get; set; }
}

public class NotificationModel
{
  [JsonProperty(PropertyName = "subscriptionId")]
  public string SubscriptionId { get; set; }

  [JsonProperty(PropertyName = "clientState")]
  public string ClientState { get; set; }

  [JsonProperty(PropertyName = "expirationDateTime")]
  public DateTime ExpirationDateTime { get; set; }

  [JsonProperty(PropertyName = "resource")]
  public string Resource { get; set; }

  [JsonProperty(PropertyName = "tenantId")]
  public string TenantId { get; set; }

  [JsonProperty(PropertyName = "siteUrl")]
  public string SiteUrl { get; set; }

  [JsonProperty(PropertyName = "webId")]
  public string WebId { get; set; }
}

public class SubscriptionModel
{
  [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
  public string Id { get; set; }

  [JsonProperty(PropertyName = "clientState", NullValueHandling = NullValueHandling.Ignore)]
  public string ClientState { get; set; }

  [JsonProperty(PropertyName = "expirationDateTime")]
  public DateTime ExpirationDateTime { get; set; }

  [JsonProperty(PropertyName = "notificationUrl")]
  public string NotificationUrl {get;set;}

  [JsonProperty(PropertyName = "resource", NullValueHandling = NullValueHandling.Ignore)]
  public string Resource { get; set; }
}

Azure 関数を構成する

開始に使用する適切なテンプレートを選択したので、構成はほぼ完了しています。 さらに必要なことは、Azure Queue Storage を出力バインディングとして構成し、メッセージが入ってきた場合にキューに追加できるようにすることだけです。

  1. [統合] を選択し、[新規出力] を選択して出力バインディングを追加します。

    Azure 関数の統合設定

  2. バインディングの種類として [Azure Queue Storage] を選択して、[選択] をクリックします。

    Azure 関数のバインディング選択

  3. [保存] をクリックします。

    Azure 関数 Azure Queue Storage 設定

Azure 関数をテストする (検証トークン テスト)

最初の Azure 関数をテストする準備ができました。

  1. ナビゲーション パネルで関数名 HttpTrigger1 をクリックして、コード画面に戻ります。 [テスト] タブをクリックして、右側のテスト パネルを開きます。

    Azure 関数テスト パネルに移動する

  2. 値としてランダムな文字列を含む URL パラメーター validationtoken を追加します。

このセットアップを使用して、新しく追加した Webhook の検証の際に、Webhook サービスを呼び出す SharePoint の動作を模倣します。

[実行] をクリックしてテストします...

Azure 関数検証トークン テストの設定

正常に完了すると、ログ セクションには、サービスが呼び出され、渡された値と HTTP 200 応答が返されたことが表示されます。

検証トークン テストの結果

Azure 関数をテストする (SharePoint リスト イベント テスト)

2 回目のテストを行います。 このテストでは、作成した関数が SharePoint リスト イベントから呼び出されたかのようにテストされます。

  1. テスト パネルで、validationtoken の URL パラメーターを消去し、要求本文を次の JSON オブジェクトに置き換えます。 次に、[実行] をクリックします。
{
  "value": [{
    "subscriptionId":"1111111111-3ef7-4917-ada1-xxxxxxxxxxxxx",
    "clientState":null,
    "expirationDateTime":"2020-06-14T16:22:51.2160000Z","resource":"xxxxxx-c0ba-4063-a078-xxxxxxxxx","tenantId":"4e2a1952-1ed1-4da3-85a6-xxxxxxxxxx",
    "siteUrl":"/sites/webhooktest",
    "webId":"xxxxx-3a7c-417b-964e-39f421c55d59"
  }]
}

正常に完了すると、メッセージがキューに追加されたことを示すログ メッセージを含むすべてのログ メッセージが表示されるはずです。

webhook 通知のテスト結果

実装で使用する Webhook URL の把握

どの Webhook URL を使用するかを SharePoint に認識させる必要があります。 最初に Azure 関数の URL をコピーします。

  1. [関数の URL の取得] をクリックします。

    ログ メッセージ コンソール

  2. [コピー] をクリックして、Azure 関数アプリの URL をクリップボードにコピーします。

    関数の URL のリンクを取得する

今回の場合、以下の Webhook URL を使用します。

https://fa-acto-spwebhook-dev-westus.azurewebsites.net/api/HttpTrigger1?code=LTFbMHrbVVQkhbL2xUplGRmY5XnAI9q/E4s5jvfeIh00KsF9Y7QsJw==

関連項目