SharePoint における実用的なカスタム Web パーツ (WebPart) の開発
環境 :
Microsoft Office SharePoint Server (MOSS) 2007
こんにちは。
いろいろ検索すると先進的な開発者の方によりいくつか有用なサンプルなどがご紹介されていますが、これからはじめようという方にとって、SharePoint 上で動作する Web パーツの実用的なサンプルや、留意点などのまとまった情報がなかなか見つけられずに困られている方もおられるかもしれません。
そこで、今日は、SharePoint 上でのカスタム Web パーツ (WebParts) の開発ノウハウについて記載したいと思います。
SharePoint 上での Web パーツの開発ですが、簡単なものであればもうあえて書く必要はないでしょう。
過去にもご紹介しましたが、VSeWSS (Visual Studio extension for Windows SharePoint Services) を使うと Web パーツのフィーチャーやソリューションパッケージ (.dwp) の作成が自動化されることに加え、なんといってもデバッグが楽である (通常なら、また w3wp.exe へのアタッチが必要になります) という恩恵を受けることができますので、オンラインの Web キャストでもご紹介されているように、まさに、3 分レシピで作成できてしまいます。(製品などコアな機能を開発するプロDevの方は、このツールは是非おぼえておくべきでしょう。)
さて、これはこれで大変うれしいことではありますが、現実の Web パーツの開発においては、この他にも Web パーツの接続 (Connect) の機能に対処したり、製品開発などではデプロイも考慮してデータベースなどへの接続情報を分離したりと、いろいろ考えるべきことがたくさんあります。
そうしたポイントを思いつく限り記載してみたいと思います。
ビジネスデータカタログ (BDC) の考慮
まず、一般的に考えられる Web パーツとして、データベース上のデータの表示や、そうしたバックエンドデータとアイテムの接続などを作りたくなる場面は多いことでしょう。こうした場面については、既にご存知の通り、MOSS 2007 では、ビジネスデータカタログというものが存在しています。
デモでは毎回貧弱な UI のビジネスデータカタログしかお見せしていませんが、実際にはスタイルの設定もできるため UI もカスタマイズ可能で、データグリッドによるビューに相当するビジネスデータリスト Web パーツや、アイテムビューに相当するビジネスデータアイテム Web パーツなどの標準装備、さらには Web パーツ間の接続までコードを書かなくとも利用することが可能になります。
Webパーツの接続 (Connect)
せっかく Web パーツを作るのですから、接続の機能もふんだんに使いたくなることでしょう。
接続の概念を簡単に記載すると、Web パーツの接続には、Provider と Consumer という概念があります。Provider のクラスは、WebPart クラスの継承以外にインタフェースとして Microsoft.SharePoint.WebPartPages.ITransformableFilterValues を指定する必要があります。
接続の処理は、以外と簡単に記述できます。
Provider と Consumer の両者に、下記ソースサンプルの SetConnectionInterface に相当するメソッドを作成し、この中で Consumer 側は接続している Provider を記憶しておきます。また、どのパラメータと接続するか指定できるように、ParameterName も設定しておきます。
あとは、実際のレンダリング (OnPreRender) の中で読み込んだプロバイダのパラメータの内容を使って独自の処理を記述すればよいです。
接続している Web パーツの動的な更新 (Dynamic Update)
SharePoint 標準のテキストフィルタなどでは、表示の際に値を変更すると動的に接続先の Consumer の情報も変更します。この処理には、ポストバックが使用されています。(つまりポストバックによりもう一度再表示しなおしているのです。)ですから、下記サンプルソースのように AutoPostBack を true にしておくと、チェックボックスがチェックされるたびに接続先も更新されるといった処理が簡単に実現されます。
例えば、[適用] ボタンがあるときはこのボタンにより更新し、ないときには変更時に常に動的に更新する、といった場合でも、この AutoPostBack を制御することによって処理できます。(実際の SharePoint 標準のコントロールでも、こうした手法が使われています。)
もう少し手の込んだ連携 (How to act with JavaScript)
「こんなポストバックなんて更新方法はいやだ」 という場合には、Web パーツの中で JavaScript を使うという手法も考えられます。
例えば、ASP.NET の RegisterClientScriptBlock メソッドを使うと、ページ内のスクリプトブロックに関数を挿入して、ASP.NET サーバコントロールの Attributes.Add でクライアントの onchange や onclick に応じてスクリプトブロックに入れた JavaScript を動かすといったことが可能になります。
また、RegisterClientScriptBlock メソッドや function のチェック (if(<関数名> == function) ... などの実装) で、Web パーツ同士で連携したクライアント処理(JavaScriptの処理)なども埋め込むことが可能となるでしょう。
(実は、SharePoint 標準の Web パーツでは、こうした処理も多分に含まれています。ただし、これは大変煩雑な開発作業になってくるので注意が必要です。)
Webパーツの利用者 (User) にプロパティ (Property) を変更させるには
SharePoint の標準の Web パーツのように、Web パーツを使うユーザにプロパティの値を判断させたい場合があります。(Web パーツ編集モードの [編集] - [共有 Web パーツの変更] で表示される編集画面のカスタマイズです。)
こうした場合には、下記のサンプルコードに示したように、EditorPart から継承したカスタムの編集 (Edit) 用のコントロールを作成して、 Web パーツの CreateEditorParts メソッドでそのコレクションを返すようにします。(つまり、編集用のコントロールは複数設定できます。)
デプロイへの配慮 (Deploy)
またデプロイも考慮して接続情報を Web パーツと切り離して実装したい場合には、ユニバーサルデータ接続ファイル (UDC) を使うことができます。
さらに、サイトテンプレートからのサイト作成と同時に Web パーツの接続もいっしょにおこないたいときには、サイト定義のソリューションパッケージの Provisioning のコードを記述することで解決されるはずです。(デプロイのノウハウについては Web パーツに限りませんので、また機会があるときに、まとめてご紹介したいと思います。)
以下に Web パーツの「接続」のサンプルソースを掲載しておきます。
このサンプルでは、AutoPostBack を使い、Provider 側でチェックボックスをチェックすると、その選択された値に応じて Consumer 側の Web パーツの表示スタイルを動的に変更するというものです。また、EditorPart を使ってカスタムの編集画面を設定しています。
(以下のサンプルでは、VSeWSS (Visual Studio extension for Windows SharePoint Services) を使用しています。またこのサンプルでは、UDC は使用していません。)
===============================
Provider 側のソースサンプル
using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace DemoDisplayStyle
{
[Guid("e08a37ef-b244-4605-8d45-5745d50249a9")]
public class DemoDisplayStyle : System.Web.UI.WebControls.WebParts.WebPart, Microsoft.SharePoint.WebPartPages.ITransformableFilterValues
{
public DemoDisplayStyle()
{
this.ExportMode = WebPartExportMode.All;
}
CheckBoxList styleList;
public virtual bool AllowMultipleValues
{
get { return true; }
}
public virtual bool AllowAllValue
{
get { return true; }
}
public virtual bool AllowEmptyValue
{
get { return false; }
}
public virtual string ParameterName
{
get { return "DisplayStyle"; }
}
public virtual ReadOnlyCollection<string> ParameterValues
{
get
{
String[] choices = null;
for (int i = 0; i < styleList.Items.Count; i++)
{
if (styleList.Items[i].Selected)
{
if(choices == null) choices = new String[3];
choices[i] = styleList.Items[i].Text;
}
}
return choices == null ?
null :
new ReadOnlyCollection<string>(choices);
}
}
protected override void CreateChildControls()
{
ListItem styleItem;
styleList = new CheckBoxList();
styleList.AutoPostBack = true;
Controls.Add(styleList);
styleItem = new ListItem();
styleItem.Text = "ヘッダー";
styleList.Items.Add(styleItem);
styleItem = new ListItem();
styleItem.Text = "カラフル";
styleList.Items.Add(styleItem);
styleItem = new ListItem();
styleItem.Text = "イタリック";
styleList.Items.Add(styleItem);
base.CreateChildControls();
}
[System.Web.UI.WebControls.WebParts.ConnectionProvider("Region Filter", "ITransformableFilterValues", AllowsMultipleConnections = true)]
public Microsoft.SharePoint.WebPartPages.ITransformableFilterValues SetConnectionInterface()
{
return this;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
}
protected override void RenderContents(HtmlTextWriter output)
{
this.EnsureChildControls();
RenderChildren(output);
}
}
}
===============================
Consumer 側のソースサンプル
using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Collections.Generic;
namespace DemoItemsView
{
[Guid("503bb284-286e-4741-8318-bbe9ca1a9d24")]
public class DemoItemsView : System.Web.UI.WebControls.WebParts.WebPart
{
public DemoItemsView()
{
this.ExportMode = WebPartExportMode.All;
}
List<Microsoft.SharePoint.WebPartPages.IFilterValues> providers = new List<Microsoft.SharePoint.WebPartPages.IFilterValues>();
GridView gridCtrl = new GridView();
// Web パーツのメカニズムにより、EditorPart から参照しても
// コントロール (gridCtrl) は初期化されているため、ここに保存
private bool fontBold;
public bool FontBold
{
get { return fontBold; }
set
{
gridCtrl.Font.Bold = value;
fontBold = value;
}
}
DataSet productSet = new DataSet();
public override EditorPartCollection CreateEditorParts()
{
List<EditorPart> editorParts = new List<EditorPart>();
EditorPart editor = new MyCustomEditor();
editor.ID = ID + "_editor";
editor.Title = "List Selection";
editorParts.Add(editor);
EditorPartCollection epc = new EditorPartCollection(editorParts);
return epc;
}
protected override void CreateChildControls()
{
gridCtrl.ID = "list1";
Controls.Add(gridCtrl);
using (SqlConnection con = new SqlConnection(@"Server=.\OfficeServers;Database=AdventureWorks;Integrated Security=SSPI"))
{
con.Open();
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = new SqlCommand("select ProductId, Name, ProductNumber from Production.Product", con);
da.Fill(productSet);
con.Close();
}
base.CreateChildControls();
}
[System.Web.UI.WebControls.WebParts.ConnectionConsumer("Headlines Filter", "IFilterValues", AllowsMultipleConnections = true)]
public void SetConnectionInterface(Microsoft.SharePoint.WebPartPages.IFilterValues provider)
{
if (provider != null)
{
// データ接続プロバイダの記憶
this.providers.Add(provider);
// データ接続パラメータの記憶
List<Microsoft.SharePoint.WebPartPages.ConsumerParameter> paramList = new List<Microsoft.SharePoint.WebPartPages.ConsumerParameter>();
paramList.Add(new Microsoft.SharePoint.WebPartPages.ConsumerParameter("DisplayStyle",
Microsoft.SharePoint.WebPartPages.ConsumerParameterCapabilities.SupportsMultipleValues | Microsoft.SharePoint.WebPartPages.ConsumerParameterCapabilities.SupportsAllValue));
provider.SetConsumerParameters(new ReadOnlyCollection<Microsoft.SharePoint.WebPartPages.ConsumerParameter>(paramList));
}
}
protected override void OnPreRender(EventArgs e)
{
this.EnsureChildControls();
// いったん状態を初期化
gridCtrl.ShowHeader = false;
gridCtrl.ForeColor = Color.Black;
gridCtrl.BackColor = Color.White;
gridCtrl.Font.Italic = false;
// ProviderのWebパーツの状態をみて更新
foreach (Microsoft.SharePoint.WebPartPages.IFilterValues provider in this.providers)
{
ReadOnlyCollection<String> values = provider.ParameterValues;
if (values == null)
break;
foreach (string v in values)
{
if(v == "ヘッダー")
gridCtrl.ShowHeader = true;
if (v == "カラフル")
{
gridCtrl.ForeColor = Color.White;
gridCtrl.BackColor = Color.DarkGreen;
}
if (v == "イタリック")
gridCtrl.Font.Italic = true;
}
}
gridCtrl.DataSource = productSet;
gridCtrl.DataBind();
base.OnPreRender(e);
}
}
// カスタムのEditorを使って、Bold表示の有無を切り替えられるようにします
public class MyCustomEditor : EditorPart
{
CheckBox isBoldCtrl;
// Editor を表示/非表示にするときに呼ばれます (WebPart -> Editorへ反映)
public override void SyncChanges()
{
DemoItemsView part = (DemoItemsView)WebPartToEdit;
EnsureChildControls();
isBoldCtrl.Checked = part.FontBold;
}
// Editor で変更後に呼ばれます (Editor -> WebPart へ反映)
public override bool ApplyChanges()
{
DemoItemsView part = (DemoItemsView)WebPartToEdit;
part.FontBold = isBoldCtrl.Checked;
return true;
}
protected override void CreateChildControls()
{
isBoldCtrl = new CheckBox();
isBoldCtrl.Text = "太字にする";
Controls.Add(isBoldCtrl);
}
protected override void RenderContents(HtmlTextWriter writer)
{
isBoldCtrl.RenderControl(writer);
}
}
}