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で記載しているサンプルコード ...

続きを読む

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