🐡

Azure DevOps Rest Apiで取得したデータをGraphApiを使ってSharePointドキュメントとして格納する

2024/12/05に公開

0. はじめに

Azure DevOpsで管理しているデータをSharePointに格納することで、チーム内の情報共有を効率化する方法をご紹介します。本記事では、Azure DevOps Rest APIとMicrosoft Graph APIを組み合わせて、WorkItemデータをSharePointにドキュメントとして保存する手順を解説します。

本記事の ソースコードは、Gitに登録しています。

https://github.com/yutaka-art/AzDoToSpoSample

1. Azure DevOps Rest APIとは

Azure DevOps REST APIは、Azure DevOpsの各機能に対してプログラム経由でアクセスできるインターフェースです。
プロジェクト情報、ビルド結果、WorkItem(タスクやバグ)など、さまざまなリソースを操作できます。

主な特徴

  • 多機能: プロジェクト管理、リポジトリ操作、パイプラインの制御が可能。
  • 認証方式: Personal Access Token(PAT)やOAuth 2.0をサポート。

ただし、Azure DevOps OAuth 2.0は2026年に非推奨となる予定です。詳細は公式ドキュメントをご覧ください。マネージドID経由で認証する方式が現時点ベストプラクティスです。

https://learn.microsoft.com/ja-jp/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops

2. Microsoft Graph APIとは

Microsoft Graph APIは、Microsoft 365全体にアクセスするための統一APIです。SharePointやTeams、Outlookなどのサービスに接続できます。

主な特徴

  • 統一されたエンドポイント: 各種Microsoft 365サービスに単一のAPIでアクセス可能。
  • Entra IDベースの認証: セキュアでスケーラブルな認証。

Graph APIを活用することで、SharePointへのファイル保存やリスト操作が可能です。

3. 認証方式の変化について

Azure DevOps OAuth 2.0の非推奨

Azure DevOps OAuth 2.0は2026年までに廃止予定です。今後はEntra IDベースの認証やPersonal Access Token(PAT)の利用が推奨されます。

SharePointでのAzure ACS廃止

SharePointのAzure Access Control Service(ACS)は廃止され、Entra IDベースの認証が標準となります。これにより、認証フローがよりセキュアに統一されます。

参考情報:

4. Azure DevOps Rest APIでWorkItemを取得する

必要な準備

  1. Personal Access Token(PAT)を発行:
    Azure DevOpsポータルからPATを取得します。
  2. APIエンドポイントの確認:
    WorkItemを取得するには以下のエンドポイントを利用します。
    GET https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/{id}?api-version=6.0
    

C#での実装例

以下は、特定のWorkItemを取得するコード例です。

using System.Net.Http;
using System.Net.Http.Headers;

var organization = "your-organization";
var project = "your-project";
var pat = "your-personal-access-token";
var workItemId = "your-azdo-target-workitem-id";

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($":{pat}")));

var url = $"https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/{workItemId}?api-version=6.0";
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();

var workItem = await response.Content.ReadAsStringAsync();
Console.WriteLine(workItem);

https://zenn.dev/yutakaosada/articles/1a61b2741e0644

5. Graph APIを利用したSharePointへの接続

Graph APIを利用するには、予めAzure側にサービスプリンシパルを作成し権限を付与します。
SharePoint上のSiteIdやDriveIdなどの情報を取得する必要があります。

HttpClientGraphServiceClient それぞれのパターンを紹介します。

5.1 サービスプリンシパルの構築

HttpClient or GraphServiceClient いずれもSharePointへの接続が必要となるので、サービスプリンシパルを作成します。

Azure Portalで以下を行います

  1. アプリ登録: Azure ADで新しいアプリケーションを登録。
  2. API権限の付与: Microsoft Graph APIの権限(Files.ReadWrite)を付与。
  3. クライアントIDとシークレットの取得: 認証に必要な情報を保存。

5.2 事前準備

Graph APIを利用するには、予めSiteID、DriveId、FolderIdが必要です。
それぞれGit Bashを利用したCurlコマンドのイメージを記載します。

アクセストークン取得
https://login.windows.net/%your-tenant-id%/oauth2/v2.0/token

curl -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=13663850-137f-4cda-9843-a1f5593e6c66" \
  -d "client_secret=OqC8Q~oULQq09D0lxa3z4QEB.na7IuAWBJHG2dsm" \
  -d "grant_type=client_credentials" \
  -d "scope=https://graph.microsoft.com/.default" \
  "https://login.windows.net/799893fa-a94f-43b0-80ea-596a79a3d01a/oauth2/v2.0/token"

curl -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=%your-client-id%" \
  -d "client_secret=%your-client-secret%" \
  -d "grant_type=client_credentials" \
  -d "scope=https://graph.microsoft.com/.default" \
  "https://login.windows.net/%your-tenant-id%/oauth2/v2.0/token"
SiteId取得
https://graph.microsoft.com/v1.0/sites/%yourtenant.sharepoint.com:/sites/yoursite%

curl -X GET \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/sites/{hostname}:/sites/{site-name}" | jq

curl -X GET \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Ild6N1Z0QjJibFFrWVgwX0lmcUNyYUdBaHZmdUlKVjNTVU90RzFBeEVtSlUiLCJhbGciOiJSUzI1NiIsIng1dCI6IjNQYUs0RWZ5Qk5RdTNDdGpZc2EzWW1oUTVFMCIsImtpZCI6IjNQYUs0RWZ5Qk5RdTNDdGpZc2EzWW1oUTVFMCJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83OTk4OTNmYS1hOTRmLTQzYjAtODBlYS01OTZhNzlhM2QwMWEvIiwiaWF0IjoxNzMxODU0ODcwLCJuYmYiOjE3MzE4NTQ4NzAsImV4cCI6MTczMTg1ODc3MCwiYWlvIjoiazJCZ1lNaEsvckwzemJVTExYdmFUZTA5TjhldkFBQT0iLCJhcHBfZGlzcGxheW5hbWUiOiJkZHNmd19lbnRyYV9zaGFyZXBvaW50XzAwMSIsImFwcGlkIjoiMTM2NjM4NTAtMTM3Zi00Y2RhLTk4NDMtYTFmNTU5M2U2YzY2IiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzk5ODkzZmEtYTk0Zi00M2IwLTgwZWEtNTk2YTc5YTNkMDFhLyIsImlkdHlwIjoiYXBwIiwib2lkIjoiMzU5YzFhZDctYTc0ZC00OGU0LTk5ZjYtMGVhYzhlYjI3NTFjIiwicmgiOiIxLkFXc0EtcE9ZZVUtcHNFT0E2bGxxZWFQUUdnTUFBQUFBQUFBQXdBQUFBQUFBQUFCckFBQnJBQS4iLCJyb2xlcyI6WyJTaXRlcy5SZWFkV3JpdGUuQWxsIiwiRmlsZXMuUmVhZFdyaXRlLkFsbCJdLCJzdWIiOiIzNTljMWFkNy1hNzRkLTQ4ZTQtOTlmNi0wZWFjOGViMjc1MWMiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiSlBSIiwidGlkIjoiNzk5ODkzZmEtYTk0Zi00M2IwLTgwZWEtNTk2YTc5YTNkMDFhIiwidXRpIjoiNWpPZGM4TjF5RXVZQnVZMVExQl9BQSIsInZlciI6IjEuMCIsIndpZHMiOlsiMDk5N2ExZDAtMGQxZC00YWNiLWI0MDgtZDVjYTczMTIxZTkwIl0sInhtc19pZHJlbCI6IjcgMjIiLCJ4bXNfdGNkdCI6MTcwODA0NDA2Mn0.L8zmLI5HHOE-nOe0NudqWCpMo6jhyIWp_bOGu262KLamz3cxFbnk6cdjSNHoQRbCwNLa1fF-IL-ByKwe9vkjPZtal9W075qpAg_m3hOfqoI4eT5EO-NH5VgYhMX_M-Fc7Mk3jTUkD-jb-gOZbm085Gp8C3G-75KJvNxTxA_rIXvwj5nwzZfjPDp6p5Y0viQea3EiGO4jQbmDY12axwSTaI8kbj4Ks9pDHPdwAgf_Zy9iy-X-srQ5hL-PfuK8OUmSeQOLblXYWcO6V27zzyqtn5dJViEJl-5eCHuMoE8raRx2iwrLjjFZlgjatA6ViD83cGKXjy7llUOFNd1dgHru0Q" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/sites/sompodds.sharepoint.com:/sites/sompodds-fw"
DriveId取得
https://graph.microsoft.com/v1.0/sites/%yourtenant%.sharepoint.com,%SiteId%/drives
curl -X GET \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/sites/$SITE_ID/drives" | jq

curl -X GET \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Ild6N1Z0QjJibFFrWVgwX0lmcUNyYUdBaHZmdUlKVjNTVU90RzFBeEVtSlUiLCJhbGciOiJSUzI1NiIsIng1dCI6IjNQYUs0RWZ5Qk5RdTNDdGpZc2EzWW1oUTVFMCIsImtpZCI6IjNQYUs0RWZ5Qk5RdTNDdGpZc2EzWW1oUTVFMCJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83OTk4OTNmYS1hOTRmLTQzYjAtODBlYS01OTZhNzlhM2QwMWEvIiwiaWF0IjoxNzMxODU0ODcwLCJuYmYiOjE3MzE4NTQ4NzAsImV4cCI6MTczMTg1ODc3MCwiYWlvIjoiazJCZ1lNaEsvckwzemJVTExYdmFUZTA5TjhldkFBQT0iLCJhcHBfZGlzcGxheW5hbWUiOiJkZHNmd19lbnRyYV9zaGFyZXBvaW50XzAwMSIsImFwcGlkIjoiMTM2NjM4NTAtMTM3Zi00Y2RhLTk4NDMtYTFmNTU5M2U2YzY2IiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzk5ODkzZmEtYTk0Zi00M2IwLTgwZWEtNTk2YTc5YTNkMDFhLyIsImlkdHlwIjoiYXBwIiwib2lkIjoiMzU5YzFhZDctYTc0ZC00OGU0LTk5ZjYtMGVhYzhlYjI3NTFjIiwicmgiOiIxLkFXc0EtcE9ZZVUtcHNFT0E2bGxxZWFQUUdnTUFBQUFBQUFBQXdBQUFBQUFBQUFCckFBQnJBQS4iLCJyb2xlcyI6WyJTaXRlcy5SZWFkV3JpdGUuQWxsIiwiRmlsZXMuUmVhZFdyaXRlLkFsbCJdLCJzdWIiOiIzNTljMWFkNy1hNzRkLTQ4ZTQtOTlmNi0wZWFjOGViMjc1MWMiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiSlBSIiwidGlkIjoiNzk5ODkzZmEtYTk0Zi00M2IwLTgwZWEtNTk2YTc5YTNkMDFhIiwidXRpIjoiNWpPZGM4TjF5RXVZQnVZMVExQl9BQSIsInZlciI6IjEuMCIsIndpZHMiOlsiMDk5N2ExZDAtMGQxZC00YWNiLWI0MDgtZDVjYTczMTIxZTkwIl0sInhtc19pZHJlbCI6IjcgMjIiLCJ4bXNfdGNkdCI6MTcwODA0NDA2Mn0.L8zmLI5HHOE-nOe0NudqWCpMo6jhyIWp_bOGu262KLamz3cxFbnk6cdjSNHoQRbCwNLa1fF-IL-ByKwe9vkjPZtal9W075qpAg_m3hOfqoI4eT5EO-NH5VgYhMX_M-Fc7Mk3jTUkD-jb-gOZbm085Gp8C3G-75KJvNxTxA_rIXvwj5nwzZfjPDp6p5Y0viQea3EiGO4jQbmDY12axwSTaI8kbj4Ks9pDHPdwAgf_Zy9iy-X-srQ5hL-PfuK8OUmSeQOLblXYWcO6V27zzyqtn5dJViEJl-5eCHuMoE8raRx2iwrLjjFZlgjatA6ViD83cGKXjy7llUOFNd1dgHru0Q" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/sites/sompodds.sharepoint.com,5345f009-0fe1-4b9b-b7b9-23434f09813a,e248498b-cecd-403b-a7bc-4c38d2a5711b/drives"
Upload
https://graph.microsoft.com/v1.0/sites/%yourtenant%.sharepoint.com,%SiteId%/drives
curl -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/octet-stream" \
  --data-binary "@C:\Work\file.txt" \
  "https://graph.microsoft.com/v1.0/sites/$SITE_ID/drives/$DRIVE_ID/root:/test/file.txt:/content"

curl -X PUT \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Ii1aTHpxSEtJYVozVmlBdUVENUtLdDNwVFdLRjRzbktCamZMSURTTk5uWVkiLCJhbGciOiJSUzI1NiIsIng1dCI6IjNQYUs0RWZ5Qk5RdTNDdGpZc2EzWW1oUTVFMCIsImtpZCI6IjNQYUs0RWZ5Qk5RdTNDdGpZc2EzWW1oUTVFMCJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83OTk4OTNmYS1hOTRmLTQzYjAtODBlYS01OTZhNzlhM2QwMWEvIiwiaWF0IjoxNzMxODg3ODEyLCJuYmYiOjE3MzE4ODc4MTIsImV4cCI6MTczMTg5MTcxMiwiYWlvIjoiazJCZ1lMZzNpOTIwOUdhSDdPRkhQYy83akppUEF3QT0iLCJhcHBfZGlzcGxheW5hbWUiOiJkZHNmd19lbnRyYV9zaGFyZXBvaW50XzAwMSIsImFwcGlkIjoiMTM2NjM4NTAtMTM3Zi00Y2RhLTk4NDMtYTFmNTU5M2U2YzY2IiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzk5ODkzZmEtYTk0Zi00M2IwLTgwZWEtNTk2YTc5YTNkMDFhLyIsImlkdHlwIjoiYXBwIiwib2lkIjoiMzU5YzFhZDctYTc0ZC00OGU0LTk5ZjYtMGVhYzhlYjI3NTFjIiwicmgiOiIxLkFXc0EtcE9ZZVUtcHNFT0E2bGxxZWFQUUdnTUFBQUFBQUFBQXdBQUFBQUFBQUFCckFBQnJBQS4iLCJyb2xlcyI6WyJTaXRlcy5SZWFkV3JpdGUuQWxsIiwiRmlsZXMuUmVhZFdyaXRlLkFsbCJdLCJzdWIiOiIzNTljMWFkNy1hNzRkLTQ4ZTQtOTlmNi0wZWFjOGViMjc1MWMiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiSlBSIiwidGlkIjoiNzk5ODkzZmEtYTk0Zi00M2IwLTgwZWEtNTk2YTc5YTNkMDFhIiwidXRpIjoicFJTVXVubkZjVWFELWlZcDhfd0ZBQSIsInZlciI6IjEuMCIsIndpZHMiOlsiMDk5N2ExZDAtMGQxZC00YWNiLWI0MDgtZDVjYTczMTIxZTkwIl0sInhtc19pZHJlbCI6IjcgMjYiLCJ4bXNfdGNkdCI6MTcwODA0NDA2Mn0.exVJ6heyewzix-ybWDTBbGqNY2cOX2aE0WfZ-wogd0iWVR2llyrdg74M2Y5zIcKBcKM1ijf6FoLALDl2Rgi6QZAZ8WuBj9YZ5kncKl2nV-JHzCwehTDZk6I_nbTg-zuJTfUrJtwHurnGaYv6aZzb9Clxu0j98e118EU76YMq4uR0t9_8hUsacTaSGp2CcieIGxp8RFpvkWJnZfMTK70ZkzLqdFaCPZMNioEjsxlOY0gSbZGun2ZUkE7p0Ea0uzOBxe8wOHN44Z7rIhr3jvHRUHYC7qgXRUOLbsazG8zAJSWG93aicM-F4px1N6cZosy1d9zhnypqJmRs5Iz1oPmstA" \
  -H "Content-Type: application/octet-stream" \
  --data-binary "@C:\Work\file.txt" \
  'https://graph.microsoft.com/v1.0/sites/sompodds.sharepoint.com,5345f009-0fe1-4b9b-b7b9-23434f09813a,e248498b-cecd-403b-a7bc-4c38d2a5711b/drives/b!CfBFU-EPm0u3uSNDTwmBOotJSOLNzjtAp7xMONKlcRu4Mcl5JpbJT6K0Ds3aA4bg/root:/test/file.txt:/content'

5.3 Graph API認証

5.3.1 HttpClientを利用するパターン

HttpClient を利用してGraph APIを呼び出す場合はアクセストークンを取得します。

var clientId = "your-client-id";
var clientSecret = "your-client-secret";
var tenantId = "your-tenant-id";
var scope = "https://graph.microsoft.com/.default";
var tokenEndpoint = $"https://login.windows.net/{tenantId}/oauth2/v2.0/token";

using (var client = new HttpClient())
{
    var content = new StringContent(
        $"client_id={clientId}&client_secret={clientSecret}&grant_type=client_credentials&scope={scope}",
        Encoding.UTF8,
        "application/x-www-form-urlencoded"
    );

    var response = await client.PostAsync(tokenEndpoint, content);

    if (response.IsSuccessStatusCode)
    {
        string responseBody = await response.Content.ReadAsStringAsync();
        var json = JObject.Parse(responseBody);
        return json["access_token"]?.ToString();
    }
    else
    {
        string errorResponse = await response.Content.ReadAsStringAsync();
        throw new Exception($"Failed to get token. Status Code: {response.StatusCode}, Details: {errorResponse}");
    }
}

5.3.2 GraphServiceClientを利用するパターン

GraphServiceClient を利用してGraph APIを呼び出す場合は、Azure.Identity パッケージを使って認証クレデンシャルを作成しClientへ付与します。

using Azure.Identity;
using Microsoft.Graph;

var clientId = "your-client-id";
var clientSecret = "your-client-secret";
var tenantId = "your-tenant-id";

var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential);

5.4 Graph APIでドキュメントフォルダへファイルとしてUpload

5.4.1 HttpClientを利用するパターン

HttpClient は、.NET 環境で HTTP リクエストを送信するための主要なライブラリですが、
このパターンでは、Graph API を使用してバイナリデータをドキュメントフォルダにアップロードします。
Graph API では、単一リクエストでアップロード可能なファイルサイズは最大 4 MB です。それを超える場合は「分割アップロード」を検討してください。

var siteId = "your-site-id";
var driveId = "your-drive-id";

var uploadUrl = $"https://graph.microsoft.com/v1.0/sites/{siteId}/drives/{driveId}/root:/test/{fileName}:/content";

byte[] byteArray = Encoding.UTF8.GetBytes(fileContent);

using (var client = new HttpClient())
{
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    var content = new ByteArrayContent(byteArray);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    var response = await client.PutAsync(uploadUrl, content);

    if (response.IsSuccessStatusCode)
    {
        Console.WriteLine("File uploaded successfully.");
    }
    else
    {
        string error = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Failed to upload file. Status Code: {response.StatusCode}");
        Console.WriteLine($"Error: {error}");
    }
}

5.4.2 GraphServiceClientを利用するパターン

GraphServiceClient は、Microsoft Graph SDK に含まれる高水準 API クライアントであり、Graph API を操作するための簡潔かつ直感的なインターフェースを提供します。
このパターンでは、GraphServiceClient を利用してファイルをドキュメントフォルダにアップロードします。
Stream を直接アップロードできるため、大規模なファイル処理や、API エンドポイントの構築の手間を軽減する場合に非常に有用な方法です。
GraphServiceClient を使用する場合でも、単一リクエストでアップロード可能なファイルサイズは最大 4 MB です。それ以上の場合は、分割アップロードを検討してください。

var driveId = "your-drive-id";

byte[] byteArray = Encoding.UTF8.GetBytes(fileContent);

using (Stream stream = new MemoryStream(byteArray))
{
      await graphClient.Drives[driveId].Root.ItemWithPath($"/test/{fileName}").Content.PutAsync(stream);
      Console.WriteLine("Stream created successfully.");
}

まとめ

このように、Azure DevOpsとSharePointを連携することで、チームのコラボレーションを効率化できます。本記事で解説した手法を基に、他のMicrosoft 365サービスとも連携してさらなる生産性向上を目指してください。

リファレンス

https://learn.microsoft.com/ja-jp/sharepoint/dev/sp-add-ins/retirement-announcement-for-azure-acs

https://learn.microsoft.com/ja-jp/azure/devops/integrate/get-started/authentication/entra-oauth?view=azure-devops

https://learn.microsoft.com/ja-jp/azure/devops/integrate/get-started/authentication/azure-devops-oauth?toc=%2Fazure%2Fdevops%2Fmarketplace-extensibility%2Ftoc.json&view=azure-devops

GitHubで編集を提案

Discussion