Microsoft Graph で Microsoft 365 SharePoint サイトのドキュメントを取得する

Microsoft Graph API を使って、Microsoft 365 の SharePoint のドキュメントを取得するときに、 {site-id} がわかりにくかったのでメモを残します。

Microsoft Graph を使用して、OneDrive、OneDrive for Business、および SharePoint のドキュメント ライブラリに配置されるファイルに接続するアプリケーションを作成する場合、以下のドキュメントが参考になります。

https://docs.microsoft.com/ja-jp/graph/api/resources/onedrive?view=graph-rest-1.0

# Azure Active Directory にアプリケーション登録
前回記事「[Microsoft Graph で Microsoft 365 Enterprise 組織内のメールを取得する](https://qiita.com/kekekekenta/items/a009f3d8b925a3edebd7)」の「Azure Active Directory にアプリケーション登録」を参考に、アプリケーション登録を行ってください。今回は「Files.Read.All」と「Sites.Read.All」を使います。

![00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32580/b2710a89-d95d-44eb-087a-8aed56ed367b.png)

# SharePoint にサイトを作成してファイルをアップロード

SharePointにサイトを作成します。今回は「Communication Site」を選択しました。（既にSharePointサイトをお持ちの方は次の章にスキップしてください。）

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32580/d7a16a21-0170-2227-3ff9-aa8ad6e81e3d.png)

デザインは「Topic」、サイト名を「azure」で作成しました。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32580/d74cd9a8-9247-8d91-4844-f8fe4e2d10fe.png)

完成したサイトの「Documents」を選択します。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32580/13b5ab10-b16e-ac74-e208-d2b11240aee1.png)

Documentsにファイルをアップロードしておきます。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32580/2e3127b6-f7b3-0073-388c-b9c32d3cb3bf.png)

# バックグランドサービスとして動くアプリを作成
前回記事「[Microsoft Graph で Microsoft 365 Enterprise 組織内のメールを取得する](https://qiita.com/kekekekenta/items/a009f3d8b925a3edebd7)」の「バックグランドサービスとして動くアプリを作成」と同じようにプログラムを作成します。

注意点としては、/sites/{site-id} を使用して取得した別のサイトIDを使用して /sites/{site-id}/drive などにアクセスします。なお、初めに使用する /sites/{site-id} の {site-id} は、"{hostname}:/{server-relative-path}" のようなフォーマットになります。

```c#:GraphHelper.cs
using Microsoft.Graph;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace GraphFileSample
{
    public class GraphHelper
    {
        private static GraphServiceClient graphClient;
        public static void Initialize(IAuthenticationProvider authProvider)
        {
            graphClient = new GraphServiceClient(authProvider);
        }

        public static async Task<Site> GetSiteAsync(string siteId)
        {
            // siteId format is "{hostname}:/{server-relative-path}"
            try
            {
                var site = await graphClient.Sites[siteId].Request().GetAsync();
                return site;
            }
            catch (ServiceException ex)
            {
                Console.WriteLine($"Error getting events: {ex.Message}");
                return null;
            }
        }

        public static async Task<IEnumerable<DriveItem>> GetDriveRootChildrenAsync(string siteObjId)
        {
            // siteObjId format is "xxxxxxxx,xxxxxxxxx,xxxxxxxxx"
            try
            {
                List<DriveItem> dirobjects = new List<DriveItem>();
                var resultPage = await graphClient.Sites[siteObjId].Drive.Root.Children.Request().GetAsync();
                while (true)
                {
                    dirobjects.AddRange(resultPage);
                    if (resultPage.NextPageRequest == null)
                    {
                        break;
                    }
                    resultPage = resultPage.NextPageRequest.GetAsync().Result;
                }
                return dirobjects;
            }
            catch (ServiceException ex)
            {
                Console.WriteLine($"Error getting events: {ex.Message}");
                return null;
            }
        }
        public static async Task<Stream> GetDriveContentAsync(string siteObjId, string itemId)
        {
            // siteObjId format is "xxxxxxxx,xxxxxxxxx,xxxxxxxxx"
            try
            {
                var result = await graphClient.Sites[siteObjId].Drive.Items[itemId].Content.Request().GetAsync();
                return result;
            }
            catch (ServiceException ex)
            {
                Console.WriteLine($"Error getting events: {ex.Message}");
                return null;
            }
        }
        
    }
}
```

作成したGraphHelper.csを使用して、SharePointのサイトの、ドキュメントのルートに存在するファイルを全てダウンロードします。

```c#:Program.cs
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;

namespace GraphFileSample
{
    class Program
    {
        static Dictionary<string, string> LoadClientSecretAppSettings()
        {
            Dictionary<string, string> result = null;
            // Get config ftom AppSettings
            var appConfig = new ConfigurationBuilder()
                .AddUserSecrets<Program>()
                .Build();
            var appId = appConfig["appId"];
            var scopes = appConfig["scopes"];
            var tenantId = appConfig["tenantId"];
            var clientSecret = appConfig["clientSecret"];
            var siteId = appConfig["siteId"];
            if (string.IsNullOrEmpty(appId) == false &&
                string.IsNullOrEmpty(scopes) == false &&
                string.IsNullOrEmpty(tenantId) == false &&
                string.IsNullOrEmpty(clientSecret) == false &&
                string.IsNullOrEmpty(siteId) == false)
            {
                result = new Dictionary<string, string>()
                {
                    {"appId", appId},
                    {"scopes", scopes},
                    {"tenantId", tenantId},
                    {"clientSecret", clientSecret},
                    {"siteId", siteId}
                };
            }
            return result;
        }

        static void GetSiteDocuments(string siteId)
        {
            var site = GraphHelper.GetSiteAsync(siteId).Result;
            var items = GraphHelper.GetDriveRootChildrenAsync(site.Id).Result;
            foreach (var item in items)
            {
                Console.WriteLine($"Entity.Id: {item.Id}");
                Console.WriteLine($"BaseItem.File: {item.Name}");
                using (var itemContent =  GraphHelper.GetDriveContentAsync(site.Id, item.Id).Result)
                using (var fileStream = new FileStream(item.Name, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    itemContent.CopyTo(fileStream);
                }
            }
        }

        static void Main(string[] args)
        {
            IAuthenticationProvider authProvider = null;

            var appConfig = LoadClientSecretAppSettings();
            if (appConfig == null)
            {
                Console.WriteLine("Missing or invalid AppSettings");
                return;
            }
            var appId = appConfig["appId"];
            var scopesString = appConfig["scopes"];
            var scopes = scopesString.Split(';');
            var tenantId = appConfig["tenantId"];
            var clientSecret = appConfig["clientSecret"];
            var siteId = appConfig["siteId"];
            // Initialize the auth provider
            authProvider = new ClientSecretAuthProvider(appId, scopes, tenantId, clientSecret);
            // Initialize Graph client
            GraphHelper.Initialize(authProvider);
            // Get Files
            GetSiteDocuments(siteId);
        }
    }
}
```

# 実行する
上で書いたコードは、githubにおいてあるので、cloneします。

```sh
git clone https://github.com/KentaroAOKI/GraphFileSample.git
cd GraphFileSample
```

Azure Active Directory でアプリケーション登録をした際にメモした値を使って設定します。

```sh
dotnet user-secrets init
dotnet user-secrets set appId "{アプリケーション（クライアント）ID}"
dotnet user-secrets set scopes "https://graph.microsoft.com/.default"
dotnet user-secrets set tenantId "{ディレクトリ（テナント）ID}"
dotnet user-secrets set clientSecret "{クライアントシークレット}"
dotnet user-secrets set siteId "{テナント}.sharepoint.com:/sites/{サイト}"
```
今回作成した SharePoint サイトの siteId は、"xxxxx.sharepoint.com:/sites/azure" になります。

ビルドして実行すると、JAZUG(5).pptx がローカルにダウンロードされます。

```
dotnet build
dotnet run
```

# さいごに
Graph API のサイトIDで、かなり時間をかけてしてしまいましたが、知っていれば簡単に自分のアプリで使えそうです。
Microsoft 365との連携は色々面白いことができそうなので、引き続き他の機能も試していきたいです。