クライアント オブジェクト モデルを使用したプログラミング 第 2 回 : フォームアプリケーションの作成

こんにちは。SharePoint サポートの多田です。

第 2 回では、.NET マネージ API を使用して、フォームアプリケーションを作成する際の手順および tips をご紹介します。

- 環境

・SharePoint Server 2010

・Visual Studio 2010

- 参照する DLL ファイル

Visual Studio にてアプリケーションの作成を始める前に、クライアント オブジェクト モデルを使用するために参照すべき、DLL ファイルを紹介します。

以下のファイルは SharePoint サーバーの以下のディレクトリに保存されています。

- フォームアプリケーション

%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ISAPI\Microsoft.SharePoint.Client.dll

%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ISAPI\Microsoft.SharePoint.Client.Runtime.dll

また、クライアント オブジェクト モデルを含むアプリケーションをクライアント側で動作させるためにも上記の DLL ファイルが必要になります。上記の DLL ファイルは以下の URL よりダウンロード可能です。また、以下からダウンロードできる DLL ファイルは再配布可能となっております。

タイトル : SharePoint Foundation 2010 Client Object Model Redistributable

アドレス : https://www.microsoft.com/download/en/details.aspx?id=21786

- 準備

1. まずは Visual Studio にて WPF アプリケーションやコンソール アプリケーションなどのクライアント アプリケーション プロジェクトを作成します。

2. 次に [参照] を追加します。追加するのは Microsoft.SharePoint.Client.dll と Microsoft.SharePoint.Client.Runtime.dll になります。

- Hello World

まず最初に Hello World ということでサイトのタイトルと概要の、設定と取得を行うサンプルコードをご案内します。

- サイトのタイトルと概要の設定

private void SetWebName()

{

    ClientContext context = new ClientContext("https://sharepoint/sites/site01");

    Web web = context.Web;

    web.Title = "Hello World";

    web.Description = "Welcome to Hello World site";

    web.Update();

    context.ExecuteQuery();

}

- サイトのタイトルと概要の取得

private void GetWebName()

{

    ClientContext context = new ClientContext("https://sharepoint/sites/site01");

    Web web = context.Web;

    context.Load(web);

    context.ExecuteQuery();

    listBox1.Items.Add(web.Title);

    listBox1.Items.Add(web.Description);

}

SharePoint へのデータの設定 & 取得はバッチ単位で行われます。上記のサイトのタイトルと概要の取得の例では、web というバッチ単位を context.ExecuteQuery() メソッドが実行されたタイミングでサーバー側に送信し、結果を取得します。

参考までに context.ExecuteQuery() メソッドが実行された際に送受信されたパケット内容を以下に記載いたします。

- 送信

POST https://sharepoint/sites/site01/_vti_bin/client.svc/ProcessQuery HTTP/1.1

X-RequestDigest: 0x058A3260E7EBC17DEC6E3A6EB0AC86B7C33CD2BEB3A067F3642E97B17D71040190251CEBAB5CD44A9883D4F07D17EE77540202F76D21851E298BBC72F2FBAAF0,05 Apr 2012 00:52:22 -0000

Content-Type: text/xml

X-RequestForceAuthentication: true

Host: sharepoint

Content-Length: 553

Expect: 100-continue

<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName=".NET Library" xmlns="https://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="2" ObjectPathId="1" /><ObjectPath Id="4" ObjectPathId="3" /><Query Id="5" ObjectPathId="3"><Query SelectAllProperties="true"><Properties /></Query></Query></Actions><ObjectPaths><StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" /><Property Id="3" ParentId="1" Name="Web" /></ObjectPaths></Request>

- 受信

HTTP/1.1 200 OK

Cache-Control: private

Content-Type: application/json

Server: Microsoft-IIS/7.5

SPRequestGuid: 75df9b49-d899-45a6-9ea9-cb77bcae578c

Set-Cookie: WSS_KeepSessionAuthenticated={a57a50a1-35eb-4a4f-a4f6-85a350b09804}; path=/

X-SharePointHealthScore: 0

X-Content-Type-Options: nosniff

X-AspNet-Version: 2.0.50727

X-Powered-By: ASP.NET

MicrosoftSharePointTeamServices: 14.0.0.6117

X-MS-InvokeApp: 1; RequireReadOnly

Date: Thu, 05 Apr 2012 00:52:22 GMT

Content-Length: 807

[

{

"SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.6108.5000","ErrorInfo":null

},2,{

"IsNull":false

},4,{

"IsNull":false

},5,{

"_ObjectType_":"SP.Web","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:983a0d2c-0ef3-4907-85f8-a3e96f4cd5f9","Description":"Welcome to Hello World site","Created":"\/Date(1332488344000)\/","LastItemModifiedDate":"\/Date(1333348288000)\/","RecycleBinEnabled":true,"Title":"Hello World","ServerRelativeUrl":"\u002f","Id":"\/Guid(983a0d2c-0ef3-4907-85f8-a3e96f4cd5f9)\/","SyndicationEnabled":true,"AllowRssFeeds":true,"QuickLaunchEnabled":true,"TreeViewEnabled":false,"Language":1041,"UIVersion":4,"UIVersionConfigurationEnabled":false,"AllowRevertFromTemplateForCurrentUser":true,"AllowMasterPageEditingForCurrentUser":true,"ShowUrlStructureForCurrentUser":true

}

]

※ 取得した内容は JSON 形式で受信していることが確認できます。クライアント オブジェクト モデルは JSON 形式のレスポンスを解析してプログラム内で使用できる形として公開します。

- プログラミングでの注意点

次はサイト内のユーザーの一覧を取得してみようと思います。

    private void GetAllUsers()

    {

        ClientContext context = new ClientContext(textBox1.Text);

        GroupCollection siteGroups = context.Web.SiteGroups;

        foreach (Group group in siteGroups)

        {

            context.Load(group.Users);

            context.ExecuteQuery();

            foreach (User member in group.Users)

            {

                listBox1.Items.Add(member.Title);

            }

        }

    }

このプログラムは実行するとエラーになります。どこでエラーになるでしょうか?

foreach (Group group in siteGroups) の行で siteGroups が取得できず、CollectionNotIntializedException になります。

クライアント オブジェクト モデルにおいて、プログラム内で参照するオブジェクトはその都度バッチ単位で取得する必要があり、取得していない場合はそのオブジェクトは空の状態になり参照エラーがおこります。そのため、今回は siteGroups を取得しに行く処理を入れる必要があります。以下に正しいプログラムを記載します。

    private void GetAllUsers()

    {

        ClientContext context = new ClientContext(textBox1.Text);

        GroupCollection siteGroups = context.Web.SiteGroups;

        context.Load(siteGroups);

        context.ExecuteQuery();

        foreach (Group group in siteGroups)

        {

            context.Load(group.Users);

            context.ExecuteQuery();

            foreach (User member in group.Users)

            {

                listBox1.Items.Add(member.Title);

            }

        }

    }

- プログラムを書く上でのテクニック

クライアント オブジェクト モデルでは ClientContext.ExecuteQuery() メソッドを実行したタイミングで ClientContext.Load() メソッドに指定したオブジェクトをサーバー側に取得しに行きます。

そのため、ネットワーク間のオーバーヘッドをなくすために、クライアント側では出来る限り ClientContext.ExecuteQuery() メソッドを実行する回数を減らしたほうがよいことになります。

以下に Hidden プロパティが false であるリストの Title と DefaultDisplayFormUrl を取得するプログラムを記載します。

    private void GetListTitle()

    {

        ClientContext context = new ClientContext(textBox1.Text);

        context.Load(context.Web.Lists);

        context.ExecuteQuery();

        foreach (Microsoft.SharePoint.Client.List list in context.Web.Lists)

        {

            context.Load(list);

            context.ExecuteQuery();

            if(list.Hidden == false)

                listBox1.Items.Add(list.Title + " : " + list.DefaultDisplayFormUrl);

        }

    }

上記のプログラムでは ClientContext.ExecuteQuery() メソッドを 2 回実施しております。これを一回の実施ですむようにプログラムを工夫してみます。

    private void GetListTitle()

    {

        ClientContext context = new ClientContext(textBox1.Text);

        context.Load(context.Web, w => w.Lists.Include(l => l.Hidden, l => l.Title, l => l.DefaultDisplayFormUrl).Where(l => l.Hidden == false));

        context.ExecuteQuery();

        foreach(Microsoft.SharePoint.Client.List list in context.Web.Lists)

        {

            listBox1.Items.Add(list.Title + " : " + list.DefaultDisplayFormUrl);

        }

    }

このプログラムでは、ClientContext.Load() メソッドにて引数にラムダ形式を使い、必要なプロパティ値のみを取得するようにしています。ほかにも ClientContext.LoadQuery() メソッドを使い、引数に LINQ 文を指定することもできます。

このようにして、できるだけクライアント側で取得するオブジェクトを絞り込むことでプログラムの動作を速くすることができます。

ちなみに弊社検証環境で上記のメソッドの実行速度を計測したところ、以下の結果になりました。

1 つ目のメソッド : 1800 ミリ秒

2 つ目のメソッド : 150 ミリ秒

※ この計測は Stopwatch クラスを使用して計測しました。また一回目の実行は JIT コンパイルにより通常より時間がかかるため、2 回目以降のだいたいの平均値を使用しています。

- 参考資料

以下に関連する参考資料をご案内します。

タイトル : マネージ クライアント オブジェクト モデル

アドレス : https://msdn.microsoft.com/ja-jp/library/ee537247.aspx

タイトル : クライアント オブジェクト モデルのガイドライン

アドレス : https://msdn.microsoft.com/ja-jp/library/ee535717.aspx

タイトル : 値プロパティを使用する前に Load と ExecuteQuery を呼び出す

アドレス : https://msdn.microsoft.com/ja-jp/library/ee535262.aspx

タイトル : ClientRuntimeContext.Load(T) Method

アドレス : https://msdn.microsoft.com/en-us/library/ee536388.aspx

タイトル : ClientRuntimeContext.LoadQuery(T) Method (IQueryable(T))

アドレス : https://msdn.microsoft.com/en-us/library/ee544076.aspx