Azure Functions と SharePoint webhook の使用
Azure Functions を使用すると、SharePoint Webhook をホストできます。ブラウザーで Webhook C# や Javascript コードを追加すると、Azure で関数のホストとスケーリングが行われます。 このガイドでは、Webhook 用に Azure Functions をセットアップして使用する方法を説明します。
Azure 関数アプリを作成する
最初に Azure 関数アプリを作成する必要があります。これは、Azure Functions のホスティングに特化した特殊な Azure Web アプリです。
https://portal.azure.com に移動して、関数アプリを検索します。 検索結果で関数アプリを選択します。
[追加] オプションをクリックします。
関数アプリの作成に必要な情報を入力して、[レビュー + 作成] をクリックします。
[作成] をクリックします
展開が完了したら、[リソースに移動] をクリックします。
Azure 関数を作成する
関数をホストするためのアプリが完成したら、新しい関数リンクをクリックして、最初の Azure 関数を作成できます。
ここでは、テンプレートから関数を開始できます。SharePoint Webhooks では、HTTP でトリガーされる関数が必要です。このサンプルでは C# コードを作成するため、HttpTrigger-CSharp 関数テンプレートを使用します。
ポータル内開発環境オプションを選択し、[続行] をクリックします。
Webhook + API トリガーの種類を選択し、[作成] をクリックします。
結果は、C# で記述された "既定" の Azure 関数になります。
今回は、この 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 を出力バインディングとして構成し、メッセージが入ってきた場合にキューに追加できるようにすることだけです。
[統合] を選択し、[新規出力] を選択して出力バインディングを追加します。
バインディングの種類として [Azure Queue Storage] を選択して、[選択] をクリックします。
[保存] をクリックします。
Azure 関数をテストする (検証トークン テスト)
最初の Azure 関数をテストする準備ができました。
ナビゲーション パネルで関数名 HttpTrigger1 をクリックして、コード画面に戻ります。 [テスト] タブをクリックして、右側のテスト パネルを開きます。
値としてランダムな文字列を含む URL パラメーター validationtoken を追加します。
このセットアップを使用して、新しく追加した Webhook の検証の際に、Webhook サービスを呼び出す SharePoint の動作を模倣します。
[実行] をクリックしてテストします...
正常に完了すると、ログ セクションには、サービスが呼び出され、渡された値と HTTP 200 応答が返されたことが表示されます。
Azure 関数をテストする (SharePoint リスト イベント テスト)
2 回目のテストを行います。 このテストでは、作成した関数が SharePoint リスト イベントから呼び出されたかのようにテストされます。
- テスト パネルで、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 URL の把握
どの Webhook URL を使用するかを SharePoint に認識させる必要があります。 最初に Azure 関数の URL をコピーします。
[関数の URL の取得] をクリックします。
[コピー] をクリックして、Azure 関数アプリの URL をクリップボードにコピーします。
今回の場合、以下の Webhook URL を使用します。
https://fa-acto-spwebhook-dev-westus.azurewebsites.net/api/HttpTrigger1?code=LTFbMHrbVVQkhbL2xUplGRmY5XnAI9q/E4s5jvfeIh00KsF9Y7QsJw==