ASP.NET AJAX 1.0 の世界 【第5回 Web サービス連携とクライアントセントリックな実装】


※ この内容は、こちら の記事にまとめました (2007/06 更新)

赤字の箇所 → 2007/01/19 11:15 記述追加・削除 

【環境】
ASP.NET AJAX 1.0 RC
ASP.NET AJAX December CTP (with ASP.NET AJAX Control Toolkit)

こんにちは。
今日は、ブログとしては少々盛り沢山になってしまいましたが(配分が甘かったです。申し訳ありません)、Web サービスの活用と、クライアントセントリックな実装についてサンプルを使用しながら説明していきたいと思います。
一気に説明してしまうと混乱させてしまいますので、まずは Web サービスをサーバセントリックに活用し、つぎに同じ処理をクライアントセントリックな方法で構築しなおしていきましょう。

今まで SOAP を使った情報交換のための仕組みとして使用してきた XML Web サービスを ASP.NET AJAX では、そのままビジネスロジックを活用した形で、JavaScript (eval 関数など) と親和性の良い JSON と呼ばれるフォーマットの通信方法(App のプロトコルは、無論、HTTPが使用されます)に変更して利用することができるようになっています。以下に、そのサンプルを作成してみましょう。ここでは、文字列を渡すと候補一覧を返すサーバを作成し、これを ASP.NET AJAX を使ってオートコンプリートの Extender から利用するサンプルを作成します。

まずは、今まで通りの、サーバセントリックな実装方法で作成してみます。

  1. プロジェクトテンプレート [ASP.NET AJAX CTP-Enabled Web Site] から新規に Webサイト を作成してください。
  2. ソリューションエクスプローラでソリューション (Web サイト) を右クリックし、[新しい項目の追加 ...] を選択して、Web サービスを追加してください。(無論、別途の Web サイトとして作成しても構いませんが、今回は同一のソリューション内に実装します。)
    Web サービスの名前を「SimpleService」として作成してみましょう。
  3. Web サービスのクラス定義 (SimpleService.cs もしくは SimpleService.vb) に、以下の属性定義を追加してください。このように設定することで、JSON をベースとした通信をおこなうサーバとして構築されます。

    [System.Web.Script.Services.ScriptService()]
    public class SimpleService
    {
      ・・・・
  4. SimpleService に、以下のメソッド (WebMethod) を作成(追加)してください。
    本来ならデータベース検索などをおこなって候補を返すようにするのでしょうが、今回はサンプルということで下記の通りインチキな返し方をします。

      [WebMethod]
        public string[] GetWord(string prefixText) {
            ArrayList items = new ArrayList(3);
            items.Add(prefixText + "-test1");
            items.Add(prefixText + "-test2");
            items.Add(prefixText + "-test3");
            return (String[])items.ToArray(typeof(String));
        }

    (01/19補足追加: なお、変数名はこの通りの文字列「prefixText」を使用してください)
  5. つぎに、AJAX クライアントに相当するページを作成します。
    まず、Default.aspx に、TextBox (標準コントロール) と Div (HTMLコントロール) を追加します。
  6. 上記で貼り付けた TextBox の id を TextBox1 とします。
    また、Div のソースを修正し、id として「CompletionList」という名前を付けておきます。
  7. Default.aspx に AutoCompleteExtender を追加し、プロパティ画面で、以下のプロパティを設定してください。

    TargetControlIDプロパティ: TextBox1
    CompletionListElementIDプロパティ: CompletionList
    ServicePathプロパティ: SimpleService.asmx
    ServiceMethodプロパティ: GetWord

では、実行してみましょう。テキストボックスにテキストを入力すると、オートコンプリート形式で上記サーバが返している候補の一覧が表示されます。

少し内部的な話をしておきます。SimpleService.asmx/js をブラウザからアクセスしてコードを見るとわかりますが、サーバと連携するためのプロキシコードを内部でダウンロードしています。そして、これを使って、サーバ/クライアント(ブラウザ)間で通信処理をおこなっています。(←記述場所が適当でないので後段に移動しました 01/19)
通信の際に使われる交換の書式は、前述した JSON と呼ばれるもので、ASP.NETではセミナーの際などにも XML が重たい(何とかならないか)という開発者の方のご意見(フィードバック)をよく頂きましたが、この JSONと呼ばれる書式により、より軽量なデータ交換をおこなうようになっています。(が、通信量が劇的に減少するようなものではありませんので、やはり通常のWebサービスの時と同様、仕組みや性質を理解して設計してください。)

では、つぎに、上記のサンプルとまったく同じものを今度は クライアントセントリックに 実装してみましょう。まずはサンプルを示し、つぎに仕組みについて簡単に説明します。

2007/01/31:
以下に記載するコードは、Jan CTP 版のリリースにより同様のライブラリを使って動かせなくなっています。代わりに、DragOverlayExtender を使ったサンプルを こちら に記載しています。

  1. 上記で作成したWebサイトと同一のページ上に (注意:理由はあとで説明しますが、まずは、この記述に従って同一ページ上に作成してください。また AutoCompleteExtender も取らないでください。でないと動かないはずです)、追加で、Input (Text) (HTMLコントロール)、Div  (HTMLコントロール) を貼り付けてください。今回は、ここで新規に配置したテキストボックスと Div を使います。
    また、今度は、すべてクライアント上のスクリプトで処理しますので、サーバ上の論理コンポーネントである標準コントロールではなく、HTML のタグの実装である HTML コントロールを使うようにしてください。
  2. 上記で作成した Input (Text) の id を Text1 とします。また、Div の id として、今度は「completionList2」と設定してください。
  3. <BODY> タグに onload=“doLoad()” を追加してください。
  4. Default.aspx のソース上に、以下のスクリプトコードを追加してください。

    <script language="javascript" type="text/javascript">
    function doLoad() {
       var auto1 = new Sys.Preview.UI.AutoCompleteBehavior(document.getElementById('Text1'));
       auto1.set_completionList(document.getElementById('completionList2'));
       auto1.set_serviceURL('SimpleService.asmx');
       auto1.set_serviceMethod('GetWord');
       auto1.set_completionSetCount(10);
       auto1.set_completionInterval(1000);

       auto1.initialize();
    }
    </script>

では、実行してみてください。Text1 にテキストを入力すると、同様にオートコンプリート形式で一覧が表示されるといった最初の動きとまったく同じの動きが実装されているのがわかります。

実は、ここでは、サンプル構築を簡単にするため、サーバ側の ScriptManager コントロールが自動で取り込んだ JavaScript のライブラリの中で定義されているクラスをそのまま使用して、クライアントサイドのスクリプトから処理しました。このため、最初のサンプルと同一のページ上に作成してもらった訳ですが、http://ajax.asp.net/ では Microsoft AJAX Library というクライアントサイドのライブラリのみをダウンロードして単体で使うことができるようになっています。このライブラリを使えば、PHP など ASP.NET 以外でもサーバサイドの力を借りず、同ライブラリを使ってクライアントセントリックな方法のみで、高度な処理を実現することができるようになっています。上述のサンプルコードように、サーバサイドのコントロールと対のクライアントサイドのJavaScriptのオブジェクトを使ったモデル(構成)になっていて、これによりサーバサイドのコード(処理)の変換をシームレスにするばかりでなく、上述のようなクライアントサイドのクラスのみを使って高度な処理をまるでサーバ上で実装したときと同じように少ないコードで実装できるようになっているのです。また、 Sys.UI の便利なクラス群や、$get などの便利な関数も使えるといった嬉しい点もあります。

但し、上述のサンプルでは、一点、注意すべき点があります。
上記のサンプルでは、実は (ScriptManager が取り込んだ) "December CTP 用のライブラリ" を使用されています。現在のRC版のライブラリには、実は今回使用したクラスは含まれていません (RC版では、サーバコントロールとしても AutoCompleteExtender が存在していないことからおわかり頂けるかと思います)。このサンプルで使用しているクラスとまったく同じクラスをスクリプトライブラリ単体で、すなわちクライアントセントリックな方法のみで実装するには、現時点では、Microsoft AJAX Library ではなく、[ASP.NET AJAX December CTP のインストールディレクトリ] v1.0.61025ScriptLibrary 下のライブラリ(jsファイル)を使用してください。
尚、RC版で使用できるクラスについては、http://ajax.asp.net/ からライブラリリファレンスが参照できますので、これを参考にしてください。(js のソースコードの中は、通信バイト数をセービングするため、改行などがEraseされていて読みづらくなっていますので、こうしたドキュメントが役に立つと思います。)

また、ここではご紹介しませんでしたが、クライアントサイドで提供されるクラスを使って、クライアントサイドで取得した配列の内容とデータバインドをおこなうといった処理なども可能です。
さらには、上記と同じクライアントセントリックな記述を JavaScript を使わず、XML スクリプトと呼ばれる宣言型の記述で書くこともできます。(今回、サンプルは省略します。)

つぎに、上記の例は、AJAX Library (但し December CTP 版) が提供する AutoCompleteBehavior クラスを使って Web サービスとの通信はすべてこのクラスに任せていました。さいごに、クライアントサイドの JavaScript を使ってまったくカスタムな方法で Web サービスと連携するサンプルを以下に示します。

  1. 上述の Web サイト上の Default.aspx のソースを再度表示し、asp:ScriptManager タグの箇所を以下の通り変更してください。

    <asp:ScriptManager ID=“ScriptManager1” runat=“server”>
    <Services>
      <asp:ServiceReference Path=“SimpleService.asmx” />
    </Services>
    </asp:ScriptManager>
  2. Default.aspx に、さらに追加で、HTML コントロールの Input (Text), Input (Button) を貼り付けます。同様にクライアントサイドの処理ですから、標準コントロールではなく HTML コントロールを使用してください。(以下、ここで追加したInput(Text)、Input (Button) の ID をそれぞれ、「Text2」、「Button1」とします。)
  3. 上記で貼り付けた Input (Button) をダブルクリックしてクライアントサイド(JavaScript)のOnClickイベントを以下の通り実装します。

    function Button1_onclick() {
      reVal =  SimpleService.GetWord(document.getElementById('Text2').value, OnComplete, OnTimeOut, OnError);
      return true;
    }

    このように、Web サービスをJavaScript からカスタムに呼び出して処理を記述することができます。処理成功時は、結果が返ってくるのではなく、下記の第2引数に指定した関数が呼び出されますので、成功時の処理はこの関数に記述します。さらに、つぎの引数に、処理がタイムアウトした場合の処理関数、つぎの引数に、処理がエラーした場合の処理関数を指定します。(これらの関数の内部は、この後で実装します。)
  4. さらに、上記で使用しているOnComplete、OnTimeOut、OnError の関数をそれぞれ以下のように実装します。いずれも、クライアントサイドの JavaScript として実装します。

    function OnComplete(value) {
      for(i = 0; i < value.length; i++) {
        alert(value[i]);
      } 
    }

    function OnTimeOut(value) {
      alert('TIMEOUT');
    }

    function OnError(value) {
      alert('ERROR');
    }

では、実行してみましょう。テキストボックスに値を挿入してボタンを押すと、配列の内容が1つずつalert のテキストボックスに返ってきます。
上述のように、.NET 上のリストや配列は JavaScript の配列に自動で変換されます。無論、文字列などのプリミティブなデータ型も対応する近いデータ型に自動で変換されますし、開発者が定義したカスタムのクラスやそのリストなども扱うことができます。
また、上記の最初のステップ(asp:ScriptManager への属性の設定)をおこなうことで、SimpleService クラスが使えるようにプロキシコード(以下)が生成されています。SimpleService.asmx/js をブラウザからアクセスしてコードを見ると、このプロキシコードを確認することができます。ですので、PHP などから使用する場合など、このプロキシの箇所についてもサーバに依存せず、この箇所も含めてすべてクライアントセントリックに構築したい場合には、以下のようなプロキシコードをクライアントサイドのスクリプトに挿入します (こうした場合、上記のasp:ScriptManager への属性の設定は不要となります)。

var SimpleService=function() {
SimpleService.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
SimpleService.prototype={
GetWord:function(prefixText,succeededCallback, failedCallback, userContext) {
return this._invoke(SimpleService.get_path(), 'GetWord',false,{prefixText:prefixText},succeededCallback,failedCallback,userContext); },
HelloWord:function(succeededCallback, failedCallback, userContext) {
return this._invoke(SimpleService.get_path(), 'HelloWord',false,{},succeededCallback,failedCallback,userContext); }}
SimpleService.registerClass('SimpleService',Sys.Net.WebServiceProxy);
SimpleService._staticInstance = new SimpleService();
SimpleService.set_path = function(value) { SimpleService._staticInstance._path = value; }
SimpleService.get_path = function() { return SimpleService._staticInstance._path; }
SimpleService.set_timeout = function(value) { SimpleService._staticInstance._timeout = value; }
SimpleService.get_timeout = function() { return SimpleService._staticInstance._timeout; }
SimpleService.set_defaultUserContext = function(value) { SimpleService._staticInstance._userContext = value; }
SimpleService.get_defaultUserContext = function() { return SimpleService._staticInstance._userContext; }
SimpleService.set_defaultSucceededCallback = function(value) { SimpleService._staticInstance._succeeded = value; }
SimpleService.get_defaultSucceededCallback = function() { return SimpleService._staticInstance._succeeded; }
SimpleService.set_defaultFailedCallback = function(value) { SimpleService._staticInstance._failed = value; }
SimpleService.get_defaultFailedCallback = function() { return SimpleService._staticInstance._failed; }
SimpleService.set_path("/[サイトのPath]/SimpleService.asmx");
SimpleService.GetWord= function(prefixText,onSuccess,onFailed,userContext) {SimpleService._staticInstance.GetWord(prefixText,onSuccess,onFailed,userContext); }
SimpleService.HelloWord= function(onSuccess,onFailed,userContext) {SimpleService._staticInstance.HelloWord(onSuccess,onFailed,userContext); }

Comments (2)

  1. 松崎 剛 ブログ (Tsuyoshi Matsuzaki Blog) からです。 ASP.NET AJAX 1.0 の世界 【第5回 Web サービス連携とクライアントセントリックな実装】 松崎さんの

  2. develop .net says:

    うっかりしていると、エントリが途切れてしまいますね^_^; 先日、神奈川で Microsoft On を担当しました。実は、本来担当すべき者が会場指示の間違いで大阪に行ってしまい、急きょ呼び出されての担当でした。数分後の湘南新宿ラインに乗ろうと、オフィスから新宿駅まで20年ぶりくらいで全力疾走しましたが、わずかに及ばず、現地到着が大変遅くなってしまい、参加者の方々にご迷惑をおかけいたしましたことをお詫びいたします。

Skip to main content