Microsoft Graph の変更通知
Webhook を使った一般的な仕組みで、One Drive にファイルを保存した時や、新しいユーザーがグループに追加されたなどのイベントを、任意の API に通知できます。
現在は以下のリソースで通知がサポートされています。
- Messages (メール)
- イベント (予定)
- 連絡先
- ユーザー
- グループ
- グループ会話
- SharePoint サイトに関連付けられたドライブを含む、OneDrive で共有されるコンテンツ
- ユーザーの個人用 OneDrive フォルダー
以下 3 つの処理で変更通知を利用します。
1. Webhook でイベントが発生した際にくる通知を処理
2. サブスクリプションの登録
3. 有効期限が切れる前にサブスクリプションの更新
Webhook の作成
まずは通知を受け取るための Webhook を用意します。今回は Functions App を使っていきます。ここでは 60 分だけ Functions App を試せるサイトを使います。
1. https://azure.microsoft.com/ja-jp/try/app-service/ にアクセスし、Functions App を選択。「Functions を試す」をクリック。
2. 「Webhook ⁺ API」、「C#」を選択して「Create this function」をクリック。
3. 任意のプロバイダーでサインイン。ここでは GitHub を利用。
4. Functions が準備されたら以下のコードで中身を差し替え。Subscription が作成された時点で validationToken が渡されるためそれを返信。また Content の中身をログに出力。詳細は サブスクリプションを作成する を参照。
using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
log.Info(await req.Content.ReadAsStringAsync());
// parse query parameter
string token = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "validationToken", true) == 0)
.Value;
if(token != null){
log.Info(token);
}
return token != null
? req.CreateResponse(HttpStatusCode.OK, token)
: req.CreateResponse(HttpStatusCode.OK);
}
4. Function が作成されるので「Get function URL」をクリックして Webhook アドレスを取得。
サブスクリプションの登録
今回は Graph エクスプローラーを使って登録します。
- Webhook は上記で作成したものを利用
- 予定が作成または削除された場合に通知
Graph エクスプローラー にアクセスしてサインイン。以下リクエストを実行。
※ expirationDateTime は現在から 3 日以内に設定
POST: https://graph.microsoft.com/v1.0/me/subscriptions
{
"changeType": "created,deleted",
"notificationUrl": "https://Functions065bac0f.azurewebsites.net/api/HttpTriggerCSharp1?code=7e97ec30126a1b0c3002c9ff06edaa5a12ef6835",
"resource": "me/events",
"expirationDateTime": "2018-05-16T23:23:59Z",
"clientState": "mywebhooktest"
}
結果としてサブスクリプション ID が返ります。
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#subscriptions/$entity",
"id": "a5d287ef-0d54-4116-86e8-a45bc1b551a8",
"resource": "me/events",
"changeType": "created,deleted",
"clientState": "mywebhooktest",
"notificationUrl": "https://Functions065bac0f.azurewebsites.net/api/HttpTriggerCSharp1?code=7e97ec30126a1b0c3002c9ff06edaa5a12ef6835",
"expirationDateTime": "2018-05-16T23:23:59Z"
}
SDK を使った場合
await graphClient.Subscriptions.Request().AddAsync(new Subscription()
{
ChangeType = "created, deleted",
NotificationUrl = "https://Functions065bac0f.azurewebsites.net/api/HttpTriggerCSharp1?code=7e97ec30126a1b0c3002c9ff06edaa5a12ef6835",
Resource = "me/events",
ExpirationDateTime = new DateTimeOffset(2018, 5, 16, 23, 23, 59, new TimeSpan(9)),
ClientState = "mywebhooktest"
});
動作のテスト
ここで一旦動作をテストしてみます。
1. 予定を Outlook で作成。
2. Functions App のログから Webhook に通知された内容を確認。
- changeType が created のため作成
- resource に実際のアイテムのアドレス
- 詳細は resourceData にあるが、詳細は含まれない。
{
"value": [
{
"subscriptionId": "a5d287ef-0d54-4116-86e8-a45bc1b551a8",
"subscriptionExpirationDateTime": "2018-05-16T23:23:59+00:00",
"changeType": "created",
"resource": "Users/6746d79d-8c77-4eec-a61f-cce6beb162f7/Events/AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAAD3lKsAAA=",
"resourceData": {
"@odata.type": "#Microsoft.Graph.Event",
"@odata.id": "Users/6746d79d-8c77-4eec-a61f-cce6beb162f7/Events/AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAAD3lKsAAA=",
"@odata.etag": "W/\"DwAAABYAAABvNJgFk4PxRY7qUEy18VJ6AAAD3WON\"",
"id": "AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAAD3lKsAAA="
},
"clientState": "mywebhooktest"
}
]
}
3. Graph エクスプローラーでリソースにアクセスし、予定が取れることを確認。
4. 同様に削除もテスト。changeType が deleted で削除イベントであることが分かる。また削除されたリソースの情報も特定可能。
{
"value": [
{
"subscriptionId": "a5d287ef-0d54-4116-86e8-a45bc1b551a8",
"subscriptionExpirationDateTime": "2018-05-16T23:23:59+00:00",
"changeType": "deleted",
"resource": "Users/6746d79d-8c77-4eec-a61f-cce6beb162f7/Events/AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAAD3lKsAAA=",
"resourceData": {
"@odata.type": "#Microsoft.Graph.Event",
"@odata.id": "Users/6746d79d-8c77-4eec-a61f-cce6beb162f7/Events/AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAAD3lKsAAA=",
"@odata.etag": "W/\"CQAAAA==\"",
"id": "AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAAD3lKsAAA="
},
"clientState": "mywebhooktest"
}
]
}
サブスクリプションの更新
Graph エクスプローラーを使って更新してみます。
サブスクリプションの ID を指定した URL に対して、Patch として送信。ここでも有効な expirationDateTime は 3 日以内です。
PATCH: https://graph.microsoft.com/v1.0/me/subscriptions/{id}
{
"expirationDateTime":"2018-05-17T00:00:00Z"
}
結果として更新された expirationDateTime が返ります。
SDK を使った場合
await graphClient.Subscriptions["a5d287ef-0d54-4116-86e8-a45bc1b551a8"].Request().UpdateAsync(new Subscription()
{
ExpirationDateTime = new DateTimeOffset(2018, 5, 17, 0, 0, 0, new TimeSpan(9))
});
サブスクリプションの削除
サブスクリプションの ID を指定した URL に対して、Delete を送信すれば削除完了。
DELETE: https://graph.microsoft.com/v1.0/me/subscriptions/{id}
SDK を使った場合
await graphClient.Subscriptions["a5d287ef-0d54-4116-86e8-a45bc1b551a8"].Request().DeleteAsync();
サブスクリプションのリスト
現在のサブスクリプション一覧はベータ機能として提供されています。
GET: https://graph.microsoft.com/beta/subscriptions
SDK を使った場合
var subscriptions = await graphClient.Subscriptions.Request().GetAsync();
Tips
Webhook を使う場合の Tips をいくつか紹介します。
clientState
Webhook が Microsoft Graph から実行されたかを検証するためには、指定した clientState の値を検証します。
サブスクリプションの更新
かならず有効期限内に実施する。期限が切れた場合更新ができないため、新規作成となる。
詳細の取得
変更通知には、変更されたアイテムの詳細は入っていないため、別途必要に応じて詳細を所得するが、通知に対しては極力早く 200 OK を返したほうがいいため、一旦キューに通知情報を格納し、キューをトリガーとして別のプログラムで詳細の取得や処理を行う。
デルタクエリ
変更通知が来た場合、個別のアイテムを取得するためにリソースをダイレクトに呼び出してもいいが、シナリオによってはデルタクエリを利用して、差分を一括取得することも可能。
まとめ
ポーリングで差分を取るのは効率が悪く、リアルタイムの更新も出来ませんが、Webhook ならより効率のいいプログラムが書けます。通知には実際のデータは含まれませんので、差分クエリも同時に活用してください。
参照
Microsoft Graph の Webhooks での作業
Microsoft Graph の Webhooks での作業(英語) ※最新情報はこちらを確認してください※
リソースの種類別のサブスクリプションの最大の長さ
サブスクリプション