Uncategorized

【VS08 で実現する SharePoint UX (2)】 ASP.NET AJAX を使った Web Part

(2008/09/11: 赤字の箇所を訂正) 

環境 :
Microsoft Office SharePoint Server (MOSS) 2007 SP1
Visual Studio 2008
Visual Studio Extensions for Windows Share Service 3.0 (VSeWSS) Version 1.2
AJAX Contorol Toolkit 3.0.20229 (.NET Framework 3.5 対応版)

VS 08 で実現する SharePoint UX 

  1. はじめに (VSeWSS 1.2 リリースの真価)
  2. ASP.NET AJAX を使った Web Part
  3. Silverlight 2 を使った Web Part

こんにちは。

では、VSeWSS 1.2 を使った Visual Studio 2008 によるユーザエクスペリエンスの最初として、ASP.NET AJAX を使用した開発について実際のサンプルをみながらご説明していきます。

今回は、エバンジェリスト 小高 のマネをして、価格.com のサービスに接続して処理をおこなってみます。
テキストボックスにキーワードを入れると、AutoComplete を使用して「価格.com」に接続して商品情報の候補一覧を表示し、選択をおこなうと「価格.com」に接続して商品の写真を表示するというサンプルを SharePoint の Web パーツとして構築してみましょう。(本ブログに、今回構築したプロジェクトを添付しておきます。)

このシナリオでは、ASP.NET AJAX の UpdatePanel、AJAX Control Toolkit (コミュニティが提供する AJAX の部品)、Json の Web サービスとの連携など一連のテクノロジーを使用していますので、 テクノロジーのベースを理解するには良いサンプルではないかと思います。

実装するコンポーネントの流れとしては、ブラウザ上の AutoComplete のコンポーネント (JavaScript, AJAX Contorol Toolkit の1 部品) から Json を使用して同一マシンの Web サービスに接続し、この Web サービスの中で「価格.com 」のサービスを呼び出して商品候補の一覧を取得して AutoComplete に表示し、そしてこの AutoComplete Extender によりテキストボックスの内容が変更されたら、UpdatePanel の非同期ポストバックによりイメージ (img) の更新をおこなうという流れになります。

ASP.NET AJAX というと、耳慣れない方は、Microsoft のテクノロジーに隠ぺいされた “複雑な” 世界を想像される方も居られるかもしれませんが、その構造は非常にきれい、かつオープンにできています。そして、この “構造” さえ理解しておけば、SharePoint に限らず、さまざまな場面で設定や応用をおこなうことができます。
このあたりは、かなり昔になりますが、@IT「ASP.NET AJAXを使いこなす」 に “中の仕組み” を中心に記載しましたので、もし ASP.NET AJAX というものそのものに不慣れな方は、この記事を先に参照していただくと良いかもしれません。(なお、基本的構造は Visua Studio 2008 に同胞された ASP.NET AJAX 3.5 でも同じですが、同記事は Visual Studio 2005 時代のものですので注意してください。)
例えば、こうした “構造” を理解しておくと、下記にご紹介する方法以外に、JavaScript のライブラリだけを使用して同様の処理が構築できることにも気づくことでしょう。あるいは、AutoComplete Extender のような既成のコンポーネントではなく、自作のまったく独自なコンポーネントも同じように組み込めることがわかるはずです。

.NET Framework 3.5 では、この ASP.NET AJAX が ASP.NET の中のコンポーネントとして含まれています。開発環境では、Visual Studio 2008 のインストールによって .NET Framework 3.5 も同時にインストールされますが、SharePoint の本番環境でこの新しい ASP.NET AJAX 3.5 を使用するには、.NET Framework 3.5 をのランタイムをインストールしてください。(3.0 以降の追加のアセンブリ、クラス、などが追加されます)

では、早速、上記のアプリを構築してみます。

環境準備

まず準備として、SharePoint のサーバ上で、この ASP.NET AJAX や AJAX Contorol Toolkit (コミュニティが作成した部品群です。ここ からダウンロードできます) が使用できるように設定をおこなっておきます。

  1. SharePoint のサイトの web.config を開き、以下の通り変更しておきましょう。(これを設定しなくても動きますが、サーバ上でデバッグができなくて不便です)
    <compilation batch="false" debug="true">
  2. ASP.NET AJAX では、上記の記事にも記載しているようにサーバ側の dll を呼び出して、結局のところ JavaScript などを生成しています。
    ASP.NET AJAX で使用する System.Web.Extensions.dll などは .NET Framework 3.5 のインストール (つまり、Visual Studio 2008 のインストール) と共にインストールされていますが、AjaxContorlToolkit.dll については別途ダウンロードしたモジュールですのでインストールされていません。したがって、AJAX Contorol Toolkit の zip ファイルを展開し、この中の AjaxContorlToolkit.dll を SharePoint の Web サーバの bin フォルダ (通常は、<www root dir>\wss\VirtualDirectories\<port 番号>\bin です) に配置しておきます。
  3. サーバ上の処理でこれら dll を使用して処理がおこなえるように、web.config を変更します。変更方法については、以前このブログでもご紹介しましたが、 http://sharepoint.microsoft.com/blogs/mike/Lists/Posts/Post.aspx?ID=3 にまとめてくれています。
    ただし、以下に注意しましょう (追加で設定が必要なものもありますので、意味を理解しながら設定しましょう)。
  • AjaxControlToolkit のアセンブリなど、使用するその他のアセンブリがある場合は、この設定も同様に実施します。
  • アセンブリの正式名に注意してください。例えば、ASP.NET AJAX 1.0 を使用している場合と、ASP.NET AJAX 3.5 を使用している場合では以下の通りアセンブリの正式名が異なります。(Ajax Control Toolkit についても同様です)
  • IIS 6 では <httpHandler>, <httpModules> を設定、IIS 7 (統合モード) では <handlers>, <modules> を設定します (つまり、IIS 7 統合モードでは、一部、タグが変更されています)。
    なお、Visual Studio 2008 で Web アプリケーションを作成していただくとわかりますが、<validation validateIntegratedModeConfiguration=”false”/> としておくと、エラーが発生することなく両方 (IIS 6、IIS 7 統合モード) に対応して双方のタグを入れておくといったことも可能です。
    今回は、Ajax Control Toolkit を使用し、かつ私の環境では IIS6 を使用していますので、以下の追加をおこなうことになります。(IIS 7 の場合は、下のほうの設定内容が異なりますので注意してください)


    すみません、こちら、記述の誤りで、SharePoint は、IIS 7 にインストールをおこなう際でもクラシックモード (IIS6 の動作モード) で動作しますので、IIS 7.0 を使用している場合でも IIS 7 用に構成を変更する必要はありません (下記と同様の構成で構いません)

    <configuration>  <configSections>. . . . . . .<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">  <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"><section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" /><sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">  <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="Everywhere" />  <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />  <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" /></sectionGroup>  </sectionGroup></sectionGroup>. . . . . . .  </configSections>  . . . . . . .  <SharePoint> . . .<SafeControls>  . . . . . . .  <SafeControl Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI" TypeName="*" Safe="True" />  <SafeControl Assembly="AjaxControlToolkit, Version=3.0.20229.20843, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" Namespace="AjaxControlToolkit" TypeName="*" Safe="True" />  . . . . . . .</SafeControls> . . .  </SharePoint>  . . . . . . .  <system.web> . . .<httpHandlers>  . . . . . . .  <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />  <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />  <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />  . . . . . . .</httpHandlers>. . . . . . .<httpModules>  . . . . . . .  <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />  . . . . . . .</httpModules>. . . . . . .<compilation . . . >  <assemblies>. . . . . . .<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><add assembly="AjaxControlToolkit, Version=3.0.20229.20843, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />. . . . . . .  </assemblies> . . .</compilation><pages . . . >. . . . . . .  <!-- 今回、タグを使用することはないので、ここは要りませんが、、、(念のため)  -->  <controls><add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><add tagPrefix="cc2" namespace="AjaxControlToolkit" assembly="AjaxControlToolkit, Version=3.0.20229.20843, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />  </controls></pages> . . .  </system.web>  . . . . . . .  <system.web.extensions><scripting>  <webServices><!-- Uncomment this line to enable the authentication service. Include requireSSL="true" if appropriate. --><!--<authenticationService enabled="true" requireSSL = "true|false"/>  --><!-- Uncomment these lines to enable the profile service. To allow profile properties to be retrieved and modified in ASP.NET AJAX applications, you need to add each property name to the readAccessProperties and writeAccessProperties attributes. --><!--  <profileService enabled="true"  readAccessProperties="propertyname1,propertyname2"  writeAccessProperties="propertyname1,propertyname2" />  -->  </webServices>  <!--  <scriptResourceHandler enableCompression="true" enableCaching="true" />  --></scripting>  </system.web.extensions></configuration>

Web サービスの作成

それでは次に、AutoComplete Extender から呼び出されて 「価格.com」 からデータを取得する Json の Web サービスを作成します。

  1. まず、既存の SharePoint の Web Service を AJAX 対応にすることはできませんのでおぼえておいてください (つまり、既存の SharePoint Web Service に ScriptService 属性を設定することはできず、必ず新規に Web サービスを構築することになります)。Visual Studio 2008 を開き、[Web] – [ASP.NET Web サービス アプリケーション] を新規作成します。(.asmx の名前、サービス名、などはわかりやすい名前にしておきましょう。)
  2. Web サービスを以下の通り実装してみましょう。
    [WebService(Namespace = "http://tempuri.org/")][WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)][ToolboxItem(false)][System.Web.Script.Services.ScriptService]public class KakakuSearchService : System.Web.Services.WebService{[WebMethod]public string[] GetSelectableNames(string prefixText){ArrayList items = new ArrayList();Encoding e = Encoding.GetEncoding("shift_jis");string str = HttpUtility.UrlEncode(prefixText, e);XmlDocument document = new XmlDocument();XmlReader reader = XmlReader.Create("http://api.kakaku.com/Ver1/ItemSearch.asp?Keyword=" + str + "&CategoryGroup=ALL&ResultSet=medium&SortOrder=popularityrank&PageNum=1");document.Load(reader);XmlNodeList nodeList = document.SelectNodes(@"/ProductInfo/Item");foreach (XmlNode node in nodeList){ProductItem productItem = new ProductItem();foreach (XmlNode attrnode in node){switch (attrnode.Name){case "ProductID":productItem.ProductID = attrnode.InnerText;break;case "ProductName":productItem.ProductName = attrnode.InnerText;break;}}items.Add(productItem.ProductID + ": " + productItem.ProductName);}return (String[])items.ToArray(typeof(String));}}class ProductItem{public string ProductID, ProductName;}

     

  3. プロジェクトに署名を添付し、ビルドします
  4. 上記でビルドして作成されたコードビハインドの dll を GAC に登録します (これで、サーバ上のどこからでも厳密名でこの dll を呼び出すことができます)
  5. .asmx ファイルのマークアップを表示し、以下の通り変更します
    (public key token は、GAC の中の dll のプロパティ表示などをおこなって確認し、変更しておいてください)【変更前】

    <%@ WebService Language="C#" CodeBehind="KakakuSearch.asmx.cs" Class="KakakuItem.KakakuSearchService" %>

    【変更後】

    <%@ WebService Language="C#" Class="KakakuItem.KakakuSearchService, KakakuItem, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d14fbbbd34865fc7" %>

     

  6. この Web サービスを SharePoint に配置していきます。
    通常、SharePoint に Web サービスを配置する処理は面倒なのですが (※1 ご参考のため下記に記載しておきました)、今回のように Json / REST などによる通信の場合には SOAP の面倒な手続きは必要ありませんので、上記の .asmx を SharePoint の _vti_bin 仮想ディレクトリで参照できるように以下にコピーするのみです。%ProgramFiles%\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI

Web パーツの作成

  1. では、ASP.NET AJAX を使用した Web パーツを作成していきましょう。
    まず、SharePoint Designer などを使用し、この Web パーツを使用するマスターページ (.master) に以下のように ScriptManager を (form タグの中に) 設定しておきます。今回は、UpdatePanel も使用しますので、EnablePartialRendering を忘れずに true に設定しておきましょう。
    この設定によって、必要な JavaScript のライブラリが HTML 上に定義されるようになります。

    <asp:ScriptManager runat="server" ID="ScriptManager1" EnablePartialRendering="true" />

    製品開発などの場合は、カスタムマスターページと共にデプロイをおこない、このカスタムのマスターページ上にあらかじめ上記のタグを入れておくと良いでしょう。

  2. VSeWSS 1.2 で [SharePoint] – [Web パーツ] のプロジェクトを新規作成します。
  3. プロジェクトに System.Web.Extensions.dll と AjaxControlToolkit.dll への参照を追加します (AjaxControlToolkit.dll は GAC に入っていないので、ローカルのファイルから参照してください)
  4. では、Web パーツの中身を構築していきます。
    通常、ASP.NET AJAX では、デザイナーを使用してドラッグアンドドロップをおこなってサーバ側のコントロールを挿入し、プロパティ設定などをおこなうなどして、マークアップファイル(.aspx) を作成していきますます。しかし Web パーツを構築する場合は、このマークアップのページ内に挿入される部品になるため、こうしたテキストボックスの挿入や、ラベルの挿入などの処理をコードで記述する必要があります。このため、今回は、以下のようなコードを記述します (ASP.NET AJAX を使ったことがある方は、それほど難しいことをしていないのがおわかり頂けるでしょう、、、)

    public class WebPart1 : System.Web.UI.WebControls.WebParts.WebPart{Literal label1 = new Literal();TextBox textbox1 = new TextBox();Literal lCrLf = new Literal();UpdatePanel updPanel1 = new UpdatePanel();Image photoImage = new Image();AjaxControlToolkit.AutoCompleteExtender autoCompleteCtrl = new AjaxControlToolkit.AutoCompleteExtender();. . .protected override void CreateChildControls(){base.CreateChildControls();// SharePoint で UpdatePanel を使うときは、この関数 (下記) を "おまじないで" 呼び出しましょう !EnsurePanelFix();// ラベルを追加label1.Text = "商品 (キーワードを入力して選択します) : ";this.Controls.Add(label1);// テキストボックスを追加textbox1.ID = "SearchStringTextBox";textbox1.Width = 300;textbox1.AutoPostBack = true;textbox1.TextChanged += new EventHandler(autoCompleteTextbox_TextChanged);this.Controls.Add(textbox1);// 改行を追加lCrLf.Text = "<br />";this.Controls.Add(lCrLf);// UpdatePanel のトリガーを設定 (テキストボックスが更新されたら Update !)updPanel1.ChildrenAsTriggers = false;updPanel1.UpdateMode = UpdatePanelUpdateMode.Conditional;AsyncPostBackTrigger updTrigger1 = new AsyncPostBackTrigger();updTrigger1.ControlID = "SearchStringTextBox";updTrigger1.EventName = "TextChanged";updPanel1.Triggers.Add(updTrigger1);// UpdatePanel にイメージコントロールを追加updPanel1.ContentTemplateContainer.Controls.Add(photoImage);// UpdatePanel を Web パーツに追加this.Controls.Add(updPanel1);// さらに、上記のテキストボックスに AutoComplete Extender を設定autoCompleteCtrl.MinimumPrefixLength = 1;autoCompleteCtrl.ServicePath = "/_vti_bin/KakakuSearch.asmx";autoCompleteCtrl.ServiceMethod = "GetSelectableNames";autoCompleteCtrl.TargetControlID = "SearchStringTextBox";this.Controls.Add(autoCompleteCtrl);}// テキストボックスが変更されたときのイベント処理// (価格.com から写真の URL を取得して表示)void autoCompleteTextbox_TextChanged(object sender, EventArgs e){photoImage.Visible = false;if (textbox1.Text.Length > 0){string[] productInfo = textbox1.Text.Split(new Char[] { ':' });if (productInfo.Length > 0){Encoding enc = Encoding.GetEncoding("shift_jis");string str = HttpUtility.UrlEncode(productInfo[0], enc);XmlDocument document = new XmlDocument();XmlReader reader = XmlReader.Create("http://api.kakaku.com/Ver1/ItemInfo.ashx?ProductID=" + str + "&ResultSet=medium");document.Load(reader);XmlNodeList nodeList = document.SelectNodes(@"/ProductInfo/Item/ImageUrl");foreach (XmlNode node in nodeList){photoImage.ImageUrl = node.InnerText.Trim();photoImage.Visible = true;break;}}}}private void EnsurePanelFix(){if (this.Page.Form != null){String fixupScript = @" _spBodyOnLoadFunctionNames.push(""_initFormActionAjax""); function _initFormActionAjax() {   if (_spEscapedFormAction == document.forms[0].action)   { document.forms[0]._initialAction =  document.forms[0].action;   } } var RestoreToOriginalFormActionCore =RestoreToOriginalFormAction; RestoreToOriginalFormAction = function() {   if (_spOriginalFormAction != null)   { RestoreToOriginalFormActionCore(); document.forms[0]._initialAction =  document.forms[0].action;   } }";// ここのクラス名 (WebPart1) は、作成したクラスにあわせて変えておきましょう !ScriptManager.RegisterStartupScript(this,  typeof(WebPart1), "UpdatePanelFixup",  fixupScript, true);}}}

     

  5. あとはビルドと配置をおこなって終了です。この Web Part プロジェクトのプロパティ画面の [デバッグ] タブで、デバッグ先のサイトの URL を配置先 (デバッグ先) のURL に設定します。また、プロジェクトに含まれている .webpart や .xml を編集し、適当に属性 (Title, Description など) も指定しておきましょう。(むろん、そのままでも動きますが、、、)

    なお、配置した Web パーツは、setup.bat でアンインストールをおこなってもギャラリーにエントリは残ってしまいます(Web パーツそのものはアンインストールされますが)。その際には、SharePoint Designer で _catalogs/wp を展開して該当の Web パーツを削除しておいてください。(配置でエラー「特定の言語に固有の …」が表示される際には、再度、プロジェクトを開きなおしてから配置します)

    VSeWSS を使用していますから、F5 で簡単にデバッグもおこなえます!

以上で完了しました。

テキストボックスに「マイクロソフト」などのキーワードを入力すると、価格.com のサービスに接続して、「マイクロソフト」のキーワードで人気のある上位 5 位までの商品が AutoComplete で表示されます。そしてこの AutoComplete の項目を選択すると、選択した項目の写真が表示されるようになります。
そしてこれらはすべて、ページをポストバックすることなく、ページの一部を部分更新するなどで表示されます。

今回の例はまだ非実用的な事例かもしれませんが (キーワードを入れて AutoComplete で選択?ちょっと変な動きですね、、、)、足りない部品については @IT「ASP.NET AJAXを使いこなす」 でご紹介しているような方法で、独自の ASP.NET AJAX の部品と連携させて応用するとより実用的な形で構築することができます。

製品レベル開発のような高度なユーザインタフェースが求められるケースでは、SharePoint が持つ「コンテンツ管理」、「ワークフロー」、「電子フォーム」などのメリットをそのまま活用し、必要な場面ではこうした形で利用者が意識せずに社内のビジネスの流れと外部の up to date な情報などを連携させてユーザビリティの優れたビジネス部品を製品の一部として提供していくなど (例: 経費申請処理において価格情報をアウトソース先の外部の情報と連携させる、など)、さまざまな形で応用していくことができるでしょう。

 

【関連情報】
MSDN : Walkthrough: Creating a Basic ASP.NET AJAX-enabled Web Part
http://msdn.microsoft.com/en-us/library/bb861877.aspx

※1  【ご参考】 SOAP ベースの Web サービスを SharePoint に登録する場合

SOAP の Web サービスを SharePoint 上に配置する際は、ざっくり記述すると、以下の手順を取ります。

  1. .asmx ファイルをいったん _layouts フォルダなどへ配置する
  2. disco.exe コマンドにより、上記の .asmx を参照して .disco ファイル、.wsdl ファイルを生成する
  3. 作成された .disco, .wsdl の拡張子を .aspx に変更し、ファイル内にスタティックに記述されているアドレスなどの情報を 関数で取得するように変更する
  4. _vti_bin ディレクトリに .asmx と上記の .aspx (もともと .disco、.wsdl であったもの) を配置する
  5. spdisco.aspx ファイルにエントリを登録する

結構面倒ですね、、、
カスタムな Web サービスの配置方法の手順詳細については、http://msdn2.microsoft.com/en-us/library/ms464040.aspx に記載されていますのでご参照ください。

 

Categories: Uncategorized

Tagged as:

3 replies»

Leave a Reply