M365関連 プログラム 技術

SharePointを操作する(GraphAPI版)

以前、トークン等を使用することなくSharePointの情報を取得する方法を記載しましたが、今回はGraphAPI経由でSharePointの操作をしてみます

関連記事
SharePointRestAPIを使ってみる - ナストンのまとめ
SharePointRestAPIを使ってみる - ナストンのまとめ

SharePointに対して、これまでCSOMやPowerShellのPnPコマンド等を使っていましたがRestAPIは ...

続きを読む

Azureアプリの登録および権限設定

これまでGraphAPIの記事内でも何度も出てきたアプリの登録をしていきます

アプリの登録

Auzre上の検索窓で「アプリの登録」と検索する
「新規登録」をクリックします

ここからは各々の用途に合わせて設定してください
アプリケーション名には「SharePointRestAPI用」としてます。サポートされているアカウントの種類を「この組織ディレクトリのみに含まれているアカウント(シングルテナント)」とし、リダイレクトURIは今回使用しないので設定しません。登録ボタンをクリックします

トークンを取得できるようにする

対象のアプリで「認証」>「パブリック クライアント フローを許可する」を「はい」に切り替えて、「保存」をクリック

APIアクセス許可の設定

対象のアプリで「API アクセス許可」>「アクセス許可の追加」>「MicroSoft Graph」>「委任されたアクセス許可」の順番にクリックし、検索窓に「Sites.Read」と入力します。「Sites.ReadWrite.All」を選択し、「アクセス許可の追加」ボタンをクリック

設定したAPIアクセス許可に管理者の同意を与える

管理者の同意が「いいえ」になっているにも関わらず、管理者の同意がないとこれ以降のコードが動作しません。要因はよくわかりません

「~に管理者の同意を与えます」をクリック
画像のように「~に付与されました」と表示されればOK

SharePointに対してGraphAPIを実行する

これから先のコードを実行するには「Azure.Identity」と「Microsoft.Graph」をNugetパッケージからインストールしておく必要があります

GraphAPIを実行するためのトークン取得

今回はユーザー認証を使用して、GraphAPI用アクセストークンを取得します。ユーザー認証なのであらかじめ対象サイトに認証で使用するユーザー(二段階認証が設定されていないもの)をサイト管理者等にSharePoint管理センターから設定しておく必要があります

private readonly string tenantId = "Azure上で取得するTenantID(ディレクトリID)";
private readonly string clientId = "Azure上で取得するClientID(アプリケーションID)";

private readonly string userName = "認証時に使用するユーザーアドレス";
private readonly string userPass = "認証時に使用するユーザーパスワード";
// Azureアプリ上のAPIアクセス許可
private  string[] scopes { get; } = { "Sites.ReadWrite.All" };

GraphServiceClient graphClient = null;

private void GetAccessToken()
{
    var options = new UsernamePasswordCredentialOptions
    {
        AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
    };

    var userNamePasswordCredential = new UsernamePasswordCredential(userName, userPass, tenantId, clientId, options);

    graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);
}

サイトの全リストを取得

対象サイトの全リストを取得します。SiteIdのフォーマットは少し変則的になっており、そのIdでリスト情報を取得できないので一旦、サイト情報を取得してそこに含まれるIdを利用してリスト情報を取得する流れとなります

public async Task GetAllLists(string _siteId)
{
    try
    {
        GetAccessToken();
        // 対象のサイト情報を一旦取得し、その中に含まれるSiteIdを使用しないといけないため
        // _siteId のフォーマット「サイトドメイン:/sites/サイトURL」 ex: testdomain.sharepoint.jp:/sites/testSite
        var site = await graphClient.Sites[_siteId].GetAsync();
        var lists = await graphClient.Sites[site.Id].Lists.GetAsync();
        foreach (var list in lists.Value)
        {
            // リスト名、リスト作成日時、リスト作成者名
            Console.WriteLine($"ListTitle : {list.DisplayName}, CreatedDateTime : {list.CreatedDateTime}, CreatedByUser : {list.CreatedBy.User.DisplayName}");
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

対象リストの全アイテムを取得

対象リストの全アイテムを指定した列情報のみ取得します。ちなみに、更新者や更新日時等はデフォルトで持っており、それ以外の列(Titleやユーザーが作成した列等)はこちらで取得するようにしないといけないという罠があります

public async Task GetListAllItems(string _siteId, string _listDisplayName)
{
    try
    {
        GetAccessToken();
        // 対象のサイト情報を一旦取得し、その中に含まれるSiteIdを使用しないといけないため
        var site = await graphClient.Sites[_siteId].GetAsync();
        var listItems = await graphClient.Sites[site.Id].Lists[_listDisplayName].Items.GetAsync((requestConfiguration) =>
        {
            // 指定したフィールドの値を取得
            requestConfiguration.QueryParameters.Expand = new string[] { "fields($select=Title,ShortName)" };
        });

        foreach (var item in listItems.Value)
        {
            // アイテム名、アイテム最終更新日、アイテム最終更新者
            Console.WriteLine($"アイテム名 : {item.Fields.AdditionalData["Title"]}, LastModifiedDateTime : {item.LastModifiedDateTime}, LastModifiedByUser : {item.LastModifiedBy.User.DisplayName}");
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

指定した列のみ取得の罠

公式のMSドキュメントなどに記載されているC#サンプルコードの方法で列指定取得しようとするとエラーが発生して、このちょっとした違いにそこそこ時間を取られました(公式ドキュメントはちゃんとしてほしい。。。)

var listItems = await graphClient.Sites[site.Id].Lists[_listDisplayName].Items.GetAsync((requestConfiguration) =>
{
    // 指定したフィールドの値を取得
    // NG(エラーになる) : selectの前に$(ドルマーク)がない
    requestConfiguration.QueryParameters.Expand = new string[] { "fields(select=Title,ShortName)" };
    // OK : selectの前に$(ドルマーク)がある
    requestConfiguration.QueryParameters.Expand = new string[] { "fields($select=Title,ShortName)" };
});

対象のリストアイテムを取得する

条件にあったリストアイテムのみ取得します。こちらも罠があり、少し面倒でした

public async Task<ListItemCollectionResponse> GetListItem(string _siteId, string _listDisplayName, string _itemTitle)
{
    ListItemCollectionResponse listItems = null;
    try
    {
        GetAccessToken();
        // 対象のサイト情報を一旦取得し、その中に含まれるSiteIdを使用しないといけないため
        var site = await graphClient.Sites[_siteId].GetAsync();
        listItems = await graphClient.Sites[site.Id].Lists[_listDisplayName].Items.GetAsync((requestConfiguration) =>
        {
            // リストアイテムの全列情報を取得する
            requestConfiguration.QueryParameters.Expand = new string[] { "fields" };
            // 指定したフィールドの値が一致するものだけを取得
            // フィルター対象の列がインデックスを作成していないとエラーとなる
            requestConfiguration.QueryParameters.Filter = $"fields/Title eq '{_itemTitle}'";
        });

        if(listItems.Value.Count == 0)
        {
            listItems = null;
        }
    }
    catch( Exception ex)
    {
        Console.WriteLine(ex.ToString());
        listItems = null;
    }

    return listItems;
}

フィルター条件を指定する際の罠

今回使用しているフィルター条件に列を指定する際は、その列でインデックスを作成しておく必要があります。インデックスを作成していない列でフィルターするとエラーが発生します

リストアイテムの作成する

対象リストにアイテムを作成します。ここからは素直に実装ができます

public async Task CreateListItem(string _siteId, string _listDisplayName, Dictionary<string, object> _itemData)
{
    try
    {
        GetAccessToken();
        // 対象のサイト情報を一旦取得し、その中に含まれるSiteIdを使用しないといけないため
        var site = await graphClient.Sites[_siteId].GetAsync();
        var requestBody = new ListItem
        {
            Fields = new FieldValueSet
            {
                AdditionalData = _itemData,
            },
        };

        var result = await graphClient.Sites[site.Id].Lists[_listDisplayName].Items.PostAsync(requestBody);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

// 登録データ
var createData = new Dictionary<string, object>
{
    {
        "Title" , "Italy"
    },
    {
        "ShortName" , "IT"
    },
};

CreateListItem(siteId, listTitle, data).Wait();

リストアイテムの更新する

対象のリストアイテムを更新します

public async Task UpdateListItem(string _siteId, string _listDisplayName, string _itemId, Dictionary<string, object> _itemData)
{
    try
    {
        GetAccessToken();
        // 対象のサイト情報を一旦取得し、その中に含まれるSiteIdを使用しないといけないため
        var site = await graphClient.Sites[_siteId].GetAsync();
        var requestBody = new FieldValueSet
        {
            AdditionalData = _itemData,
        };
        var result = await graphClient.Sites[site.Id].Lists[_listDisplayName].Items[_itemId].Fields.PatchAsync(requestBody);
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

// 更新データ
 var updateData = new Dictionary<string, object>
 {
     {
         "Title" , "ItalyUpdate!!"
     },
 };
UpdateListItem(siteId, listTitle, item.Id, updateData).Wait();

リストアイテムを削除する

対象のリストアイテムを削除します

public async Task DeleteListItem(string _siteId, string _listDisplayName, string _itemId)
{
    try
    {
        GetAccessToken();
        // 対象のサイト情報を一旦取得し、その中に含まれるSiteIdを使用しないといけないため
        var site = await graphClient.Sites[_siteId].GetAsync();
        await graphClient.Sites[site.Id].Lists[_listDisplayName].Items[_itemId].DeleteAsync();
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

最期に

今回記事内に記載しているコードをGitHubにアップしていますので参考にしてみてください

サンプル
BlogSampleCodeProjects/SharePointGraphAPI at main · nasuton/BlogSampleCodeProjects · GitHub
BlogSampleCodeProjects/SharePointGraphAPI at main · nasuton/BlogSampleCodeProjects · GitHub

Project for sample code used in the blog.(Blogで記載しているサンプルコード ...

続きを読む

会社紹介

私が所属しているアドバンスド・ソリューション株式会社(以下、ADS)は一緒に働く仲間を募集しています

会社概要
「技術」×「知恵」=顧客課題の解決・新しい価値の創造

この方程式の実現はADSが大切にしている考えで、技術を磨き続けるgeekさと、顧客を思うloveがあってこそ実現できる世界観だと思っています
この『love & geek』の精神さえあれば、得意不得意はno problem!
技術はピカイチだけど顧客折衝はちょっと苦手。OKです。技術はまだ未熟だけど顧客と知恵を出し合って要件定義するのは大好き。OKです
凸凹な社員の集まり、色んなカラーや柄の個性が集まっているからこそ、常に新しいソリューションが生まれています

ミッション
私たちは、テクノロジーを活用し、業務や事業の生産性向上と企業進化を支援します

ホームページ
アドバンスド・ソリューション株式会社
アドバンスド・ソリューション株式会社

アドバンスド・ソリューションは主にMicrosoft製品を使用して、企業の生産性向上に取り組んでいます。要件定義から導入 ...

サイトへ移動

お問い合わせ
お問い合わせ  | アドバンスド・ソリューション株式会社
お問い合わせ | アドバンスド・ソリューション株式会社

お問い合わせはこちら

-M365関連, プログラム, 技術
-, ,