Microsoft Graph で SharePoint Onlineのリストアイテムを取得する（ユーザー列・選択肢列・参照列）

SharePoint Online周りの開発ではC#＋CSOMを使うことが多かったのですが、Microsoft Graphを指定されることが増えてきたのでC#＋Microsoft Graphでリストアイテムの取得をしてみました。
ユーザー列、選択肢列、参照列の取得に少し手間取ったので記載しておきます。

以下 SharePoint OnlineはSPO、Microsoft Graphは Graphと表記します。

# 前提条件
アプリ：C#コンソールアプリ
ここでは例外処理などは除外しています。
システムへ実装の際はセキュリティの考慮等もお願いいたします。

# 下準備
### 1. Microsoft Entra IDアプリ
 1.1  Azure Portalから Microsoft Entra IDアプリを作成する
 1.2  APIのアクセス許可設定で、Microsoft.GraphのSites.Read.Allを付与
 　  （ここでは「アプリケーションの許可」に付与）
 1.3  シークレットを作成し、値を保持しておく

### 2. C#プロジェクト
2.1 C#コンソールアプリを作成
2.2 下記モジュールを追加する
+ Microsoft.Graph
+ Azure.Identity

# 開発手順
### 1. Graphクライアント作成
認証を有効にしてGraphクライアントを作成します。
```C#:Graphクライアント作成
// Entra IDへの認証を有効にする
//（下準備1で作ったアプリのテナントID、クライアントID、シークレットを指定)
var clientSecretCredential = new ClientSecretCredential(TenantId, clientId, clientSecret);
// Graphクライアントを作成する
var graphServiceClient = new GraphServiceClient(clientSecretCredential);
```
### 2. SPOのアイテムを検索して取得
QueryParametersで必要な情報を設定しながらアイテムを取得します。
[サイトID](###-追記２)、[リストID](###-追記３)はあらかじめ取得しておきます。

```C#:指定したリストのアイテムを取得

var items = await graphServiceClient
    .Sites[siteId]     // サイトIDを設定
    .Lists[listId]     // リストIDを設定
    .Items
    .GetAsync(requestConfiguration =>
{
    // このHeaderでインデックス無し列をFilterで使用できるが、大きなリストで失敗する可能性がある。
    requestConfiguration.Headers.Add("Prefer", "HonorNonIndexedQueriesWarningMayFailRandomly");
    requestConfiguration.QueryParameters.Select = new string[] { "id", "fields" };
    requestConfiguration.QueryParameters.Expand = 
        new string[] { "fields($select=Id, Title, User1, User1LookupId, Tools, Country)" };
    requestConfiguration.QueryParameters.Filter = $"fields/Title eq 'テスト'";
    requestConfiguration.QueryParameters.Orderby = new string[] { "fields/DisplayOrder" };
});
```
　 取得しようとしているリストの列は下記の通りです。
  | 列名 | 情報の種類 |
  |:-----------|:------------|
  | Title      | 1行テキスト        |
  | User1      | ユーザーまたはグループ  |
  | Tools      |  選択肢(複数選択可)  |
  | Country    |  選択肢(複数選択可・情報元が他のリスト)  |
  | DisplayOrder         | 数値          |

### 3. 取得したアイテムの確認

2.でアイテムを取得した後、列の値は下記で取得できます。
```C#:アイテムの列の値を取得
var userdata = items.Value[i].Fields?.AdditionalData["User1"]
```

### 4. ユーザー列の値の取得
3のコードで取得すると、ユーザー列はユーザー名(おそらくDisplayName)のみ取得しています。
対象ユーザーのメールアドレスなど、名前以外の情報を取得したい場合は下記の通りです。

####  4.1 アイテム取得時に 列名＋LookupId のフィールドを取得
  ここでは、2 で User1LookupId を取得しています。
####  4.2 ユーザー列＋LookupId の値からユーザー情報リストを参照
ユーザー列＋LookupId の値を下記コードで見ると、"3" "12"などの数字が入っています。
この数字は、SPOのサイトコレクションのユーザー情報一覧を持つ [「ユーザー情報リスト」](###‐追記１) のアイテムIDです。
ユーザー情報リストの内容を取得するコードは下記の通りです。（例：メールアドレス取得）

```C#:ユーザー情報リストからユーザー情報を取得 (変数idに取得したアイテムIDを設定する)
var userItems = await graphServiceClient
    .Sites[siteId]
    .Lists[userInformationListId]
    .Items[id]
    .GetAsync((requestConfiguration) =>
    {
        requestConfiguration.QueryParameters.Expand = new string[] { "fields" };
    });

mailAddress = userItems?.Fields?.AdditionalData["EMail"].ToString() ?? string.Empty;
```
####  4.3 ユーザー情報リストから取得した内容
下記のような情報が取れるので、必要なものを取得して使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94732/287ab3a4-2fa1-8468-c2b3-c21f83805c7a.png)

### 5. 選択肢列・参照列の値の取得
3のコードで取得した選択肢列の値は文字列の配列となっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94732/f1c3f56f-7e09-3e3f-7e30-d00dadeb9732.png)
また、参照列の場合は、LookupId/LookupValueを持つオブジェクトの配列となっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94732/a508bc7d-a500-aeb7-b4f8-5816ac268888.png)

この値からの取得方法は色々あるかと思いますが、以下は一例となります。
```C#:選択肢列から選択された値をListとして保持する
var value = systemInfoItems.Value[i].Fields?.AdditionalData["Tools"]    // 列の値を取得
var valueList = new List<string>();    // 選択肢列の値をListとして保持

var jsonArray = JsonDocument.Parse(value).RootElement;  // 取得した値のルートドキュメントを取得
if (jsonArray.ValueKind == JsonValueKind.Array)
{
    foreach (var element in jsonArray.EnumerateArray())
    {
        if (element.ValueKind == JsonValueKind.String)       // 選択肢列
        {
            //選択肢の場合
            var choiceValue = element.GetString();
            if (string.IsNullOrEmpty(choiceValue)) continue;
            returnList.Add(choiceValue);
        }
        else if (element.ValueKind == JsonValueKind.Object)   // 参照列
        {
            //選択肢で情報元が別リストの場合
            var lookupId = element.GetProperty("LookupId").GetInt32();     // 参照したリストのアイテムIDを取得
            if (string.IsNullOrEmpty(lookupId.ToString())) continue;
            returnList.Add(lookupId.ToString());
        }
    }
}
```
***
### 追記１
#### ユーザー情報リスト
http://{サイトURL}/_catalogs/users/ で参照できます。
https://learn.microsoft.com/ja-jp/sharepoint/dev/scenario-guidance/user-information
***
### 追記２
#### サイトIDの取得方法
こちらを参考にさせていただきました。ありがとうございます。
https://qiita.com/teracy164/items/b9c19d1928ba5ba092dc
***
### 追記３
#### リストIDの取得方法
[Microsoft Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) で該当のテナントにログオンしたうえで下記実行して取得可能です。
```
https://graph.microsoft.com/v1.0/sites/{サイトID}/lists?$filter=displayName eq '{リスト名}'
```
ユーザー情報リストのIDを取得したい場合は、環境にもよりますが
リスト名を 'User Information List' または  'ユーザー情報リスト'にしていただくと取得可能かと思います。
***
### 追記４
#### サイトID、リストIDの取得方法（2025/3 Update)
Microsoft Graph Explorerを使用せずにサイトID,リストIDを取得する方法を別の記事としてまとめました。

https://qiita.com/miesque/items/b04cf6a5a198705915f5
***



# 参考資料
ClientSecretCredential クラス
https://learn.microsoft.com/ja-jp/dotnet/api/azure.identity.clientsecretcredential?view=azure-dotnet

Microsoft Graph クライアントを作成する
https://learn.microsoft.com/ja-jp/graph/sdks/create-client?tabs=csharp

クエリ パラメーターを使用して応答をカスタマイズする
https://learn.microsoft.com/ja-jp/graph/query-parameters?tabs=csharp

List リソース ‐ Microsoft Graph REST API v1.0
https://learn.microsoft.com/ja-jp/graph/api/resources/list?view=graph-rest-1.0

***
以上です。
記載ミスなどありましたらご指摘いただけますと幸いです。