印刷用ページ       送信     
クリックして評価とフィードバックをお寄せください
 Cutting Edge: ASP.NET AJAX とクライアント側...
Related Articles

この記事では、CLR を使用したアセンブリのバインドと読み込みに関するベスト プラクティスをいくつか紹介します。

Aarthi Ramamurthy および Mark Miller

MSDN Magazine May 2009

...

Read more!

この記事では、同じアプリケーションの 2 つのバージョン (オンプレミス データ サービスを利用するアプリケーションと Azure Table データ サービスを利用するアプリケーション) を取り上げて、クラウドのデータにアクセスする方法について説明します。

Elisa Flasko

MSDN Magazine May 2009

...

Read more!

今月は、AJAX アプリケーションでフォームを使用するケースについて説明し、自動保存、JIT 検証、送信回数の削減などの機能を実装するさまざまな方法を紹介します。

Dino Esposito

MSDN Magazine June 2009

...

Read more!

開発者は、ワークステーションおよび関連するクラスのバージョン管理にいつも頭を悩ませています。ワークフローのバージョン管理の主要な問題点と、ワークフロー定義、アクティビティ、およびワークフロー サービスへの変更に関する推奨事項について、Matt Milner が説明します。

Matthew Milner

MSDN Magazine May 2009

...

Read more!

今月は、IronPython を使用して .NET ベースのライブラリを簡単にテストできることを実演します。

James McCaffrey

MSDN Magazine June 2009

...

Read more!

Also by this Author

今月の記事では、jQuery でモーダルおよびモードレス ダイアログ ボックスを作成し、作成したダイアログ ボックスから Web サーバーにデータをポストする方法を説明します。

Dino Esposito

MSDN Magazine May 2009

...

Read more!

今月の Dino は、Silverlight アプリケーションのダウンロード サイズが大きい場合の問題に取り組み、どのような場合にストリームを使用し、ダウンロードを分割するかを説明し、ネットワーク上のパフォーマンスを向上させるその他の方法を示します。

Dino Esposito

MSDN Magazine January 2009

...

Read more!

ブラウザ間でイベントの互換性を保持することは簡単な作業ではありません。API を処理する jQuery イベントはブラウザ間のイベント処理の違いに対処するものです。このイベントを使用し、より予測可能な JavaScript を記述できます。

Dino Esposito

MSDN Magazine April 2009

...

Read more!

今月は、入れ子になった ListView コントロールを使用して階層的なデータのビューを作成し、カスタム ListView クラスを派生させて ListView のイベント モデルを拡張します。

Dino Esposito

MSDN Magazine April 2008

...

Read more!

今回は、BST パターンの拡張された実装について説明し、それを HTM ソリューションと比較します。

Dino Esposito

MSDN Magazine July 2008

...

Read more!

Popular Articles

この記事では、Windows Presentation Foundation でのプログラムおよび宣言によるデータ バインドと表示の手法を説明します。

Josh Smith

MSDN Magazine July 2008

...

Read more!

The MVP pattern helps you separate your logic and keep your UI layer free of clutter. This month learn how.

Jean-Paul Boodhoo

MSDN Magazine August 2006

...

Read more!

Microsoft patterns & practices の Composite Application Guidance for WPF で複合アプリケーションを作成する利点を紹介します。

Glenn Block

MSDN Magazine September 2008

...

Read more!

Jason Clark

MSDN Magazine July 2003

...

Read more!

When incorporating the ASP.NET DataGrid control into your Web apps, common operations such as paging, sorting, editing, and deleting data require more effort than you might like to expend. But all that is about to change. The GridView control--the successor to the DataGrid-- extends the DataGrid's functionality it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, as long as its bound data source object supports these capabilities. In addition, ...

Read more!

Cutting Edge
ASP.NET AJAX とクライアント側テンプレート
Dino Esposito

コードのダウンロード : CuttingEdge2008_06.exe (239 KB)
オンラインでのコードの参照

ASP.NET AJAX アプリケーションの開発者に対して部分的なレンダリングが特に魅力的である理由の 1 つは、その本質的な単純さです。既存のページに与える影響が小さく、開発者のスキルに対する要求もそれほど高くありません。しかし、部分的なレンダリングは、ポストバック要求の実行でブラウザをバイパスするための単なるトリックに過ぎません。部分的なレンダリングを使用するページは、引き続き古いポストバック手法に依存しているため、真の AJAX ページとは言えません。では、真の AJAX アプリケーションとは、何を意味するのでしょうか。
本質的に、真の AJAX アプリケーションは、XMLHttpRequest オブジェクトを使用してブラウザをバイパスし、Web サーバーおよびホストされた HTTP エンドポイントとの間に直接通信を確立します。アプリケーションは、必要なデータを非同期で取得でき、ユーザー インターフェイスの各ブロックを独立して更新できます。この主要な動作に基づいて、いくつかの AJAX フレームワークがデザインされています。これらのフレームワークは、それぞれ異なる量の糖衣構文を使用し、機能セットの大きさもさまざまで、提供するユーザー インターフェイス ウィジェットのファミリも豊富なものから簡素なものまであります。ASP.NET AJAX は、そのようなフレームワークの 1 つです。
真の AJAX アプリケーションのデザインや構築は簡単な作業ではなく、適切なプログラミング ツールがなければさらに難しくなります。他の AJAX フレームワークと比較して、ASP.NET AJAX は、サーバー側コードにアクセスするためのサポートが優れています。ASP.NET AJAX を使用した場合、スクリプト対応の Web または Windows® Communication Foundation (WCF) サービスへの接続は、開発者の観点からは非常に簡単です。URL を参照するだけで、フレームワークによって自動的に JavaScript プロキシが生成されるためです。これはコストがかからず、かつ効果的です。XMLHttpRequest プラミング、シリアル化および逆シリアル化、データ形式、HTTP パッケージ化などの詳細は、すべてプロキシ クラスによって隠蔽されます。単にメソッドを非同期で呼び出し、固有のプロパティのセットと共に返されるプレーンなデータ転送オブジェクト (DTO) を受け取るだけです。
ただし、ダウンロードされたデータをブラウザで処理する段階になると、ASP.NET AJAX にはそれと同じように効率的なメカニズムがありません。これは間違いなくいずれ改良されますが、現時点で ASP.NET AJAX は、サーバー側サービス モデルに見合うほどリッチなクライアント側 UI モデルを提供できません。
それでは現在、ASP.NET AJAX の純粋な Web アプリケーションの UI モデルを改良するにはどうすればよいでしょうか。先月号では、シングル ページ インターフェイス (SPI) の概念を紹介し、SPI ページの構築に利用できる単純なパターンをいくつか示しました。今月は、クライアント側テンプレートとデータ バインドについて説明します。

AJAX の一般的な機能
AJAX アプローチの能力を示す一般的な例の 1 つとして、リアルタイムで株価を取得するアプリケーションがあります。現在、この機能を提供する非 AJAX の Web サイトでは、メタリフレッシュ タグを使用するか、または Flash、Silverlight™、ActiveX® コントロールなどのプラグイン ベースのアプローチを使用しています。一般に、表示される株価の一覧はサーバー側のグリッド コントロールにバインドされ、このグリッドは、次のポストバックまたはページ要求時にページ内の他の部分と一緒に更新されます。このようなページは、少しの部分的なレンダリングによってすばやく拡張できます。その方法を見てみましょう。図 1 に示すマークアップでは、Timer コントロールによって定期的に更新される UpdatePanel コントロール内にグリッド コントロールをラップしています。
<asp:UpdatePanel runat="server" ID="UpdatePanel1" UpdateMode="Conditional">
  <ContentTemplate>
    <div style="margin:10px;">
      <asp:Label runat="server" ID="Label2" />
      <asp:GridView ID="GridView1" runat="server" SkinID="ClassicGrid"
        AutoGenerateColumns="false" 
        OnRowDataBound="GridView1_RowDataBound">
        <Columns>
          <asp:BoundField DataField="Symbol" HeaderText="SYMBOL" />
          <asp:BoundField DataField="Quote" HeaderText="LAST" >
            <ItemStyle HorizontalAlign="Right" />
          </asp:BoundField>
          <asp:BoundField DataField="Change" HeaderText="CHANGE" >
            <ItemStyle HorizontalAlign="Right" />
          </asp:BoundField>
        </Columns>
      </asp:GridView>
      <small><asp:Label runat="server" ID="Label1" /></small>
    </div> 
  </ContentTemplate>
  <Triggers>
    <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" /> 
  </Triggers> 
</asp:UpdatePanel>

<asp:Timer runat="server" id="Timer1" Enabled="true" 
  Interval="600000" OnTick="Timer1_Tick" />
このグリッド コントロールは、Symbol、Quote、Change という少なくとも 3 つのプロパティを持つオブジェクトのコレクションにバインドされます。グリッドの内容は、10 分ごとに更新されます。ASP.NET AJAX の Timer コントロールは、クライアント タイマをアクティブにし、更新間隔に達するとポストバックを実行します。これは UpdatePanel にラップされているため、株価を表示するグリッドはページとは別に更新されます。サーバーでは、タイマの周期ごとに次のコードが実行されます。
protected void Timer1_Tick(object sender, EventArgs e)
{
    Label2.Text = "<b>Last update: </b>" + DateTime.Now.ToLocalTime();
    GridView1.DataSource = picker.Update();
    GridView1.DataBind();
}
picker オブジェクトは、カスタム ヘルパ クラス StockPicker のインスタンスです。このクラスは、株価の取得を行うサービス クラスのインスタンスを含んでいます。StockPicker クラスのコンストラクタを次に示します。
public StockPicker(string stocks)
{
   string provider = ConfigurationManager.AppSettings["StockProvider"];
   _service = GetFactory(serviceProvider, stocks);

    if (_service == null)
      return;

    _desc = _service.GetServiceDescription();
}
picker は構成ファイルからサービス プロバイダ クラスの名前を読み取り、それをインスタンス化します。picker は、IQuoteService インターフェイスによって表されるコントラクトを通じて、サービス クラスとやり取りします。
interface IQuoteService
{
    StockInfoCollection GetQuotes();
    string GetServiceDescription();
}
このコラムに付属するサンプル コードでは、株価の代わりにランダムな数字を返すだけの仮の株価サービスを使用しています。ただし、このページ コードは、実際の株価サービスを使用するように簡単に構成できます。必要なのは、IQuoteService インターフェイスを実装するクラスを作成して、実際の株価データ サービスに接続することだけです。サンプル コードでは、バインド可能なデータを StockInfo オブジェクトのコレクションとして表しています。
public class StockInfo
{
    public string Symbol { get; set; }
    public string Quote { get; set; } 
    public string Change { get; set; }
    public string Day { get; set; }
    public string Time { get; set; } 
}
図 2 に、この API の動作に基づくサンプル ASP.NET AJAX ページを示します。
図 2 部分的なレンダリングを使用したリアルタイム株価ページ (クリックすると拡大画像が表示されます)

部分的なレンダリングの制限
機能的に見れば、図 2 に示したページは、期待したとおりに動作します。そのパフォーマンスと全体的な動作に満足できれば、それで問題はありません。ただし、そのようなページには、皆さんの安眠を妨げるほどではないにしても、注意を要する欠点がいくつかあります。
まず、このページは更新されるたびに大量のデータをやり取ります。このデータ量は、10 分ごとに更新されるこのページのような比較的更新頻度の緩いページでは、それほど問題とならないかもしれません。Fiddler または Web Development Helper を使用して監視すると、ペイロードのサイズがダウンロード時には 3 KB、アップロード時はそれより多少小さいことがわかります。この数値に問題がなければ、部分的なレンダリングをぜひ導入してください。
しかし、ページの内部動作をよく見ると、この数値が決して安心できるものではないことがわかります。ページのビュー ステートは、更新のたびに送受信されます。応答には、データとレイアウト情報の両方が含まれます。さらに、データは周辺のマークアップと結合され、それらを分離することはできません。最後に、ページ上に同様に機能する更新可能なパネルが他にもある場合、別の部分的なレンダリングの操作が開始されるとタイマが一時的に停止する可能性があります。または、タイマがトリガされたときに他の保留中の操作が停止する場合もあります。
結局、部分的なレンダリングはすぐに習得して適用できる反面、非同期の同時呼び出しをサポートする必要がある場合には、適切的なメカニズムとは言えません。次に、図 2 のページを純粋な AJAX アプローチを用いて書き直すには何が必要かを見ていきます。

リアルタイム株価ページを再検討する
インサイト : ASP.NET AJAX

テンプレート駆動のレンダリングは、長い間にわたり、ASP.NET サーバー コントロールの主要な能力の 1 つとなっています。<% %> および <%= %> ブロックを使用してマークアップの周囲でループする古い技法に比べて、テンプレートにはいくつかの利点があります。たとえば、双方向のバインドや、モデルの全般的なクリーンさが挙げられます。クライアントでは、フレームワークがないと単純な <% %> ブロックさえも使用できないため、良質なテンプレート エンジンは性能向上に欠かせません。また、リスト全体を再レンダリングする代わりにリスト内で変更された項目だけを再レンダリングするなど、クライアント側では簡単に実現できて、サーバー側ではそれよりずっと困難であるような最適化の多くが可能になります。その一方で、クライアントでのレンダリングのパフォーマンスは不十分であるため、テンプレート エンジンはパフォーマンスについて特に注意深く最適化される必要があります。
Dino 氏はこの記事で、セットアップが簡単な、現時点で適切に動作するクライアント側テンプレート レンダリングを実現するための、単純なアプローチを紹介しています。もちろん、ASP.NET チームでもテンプレートの威力は認識しています。実際、ASP.NET AJAX の最初のプレビューのリリース時には、テンプレート駆動レンダリングを実現する機能の 1 つとして、XML-Script と呼ばれる非常に精巧な宣言構文を導入しました。残念ながら、これはあまりに複雑で冗長であると判断され、また、パフォーマンスも私たちやユーザーが満足するレベルではありませんでした。さらに、ツールのサポートも欠けていました。
テンプレートには、単に HTML 文字列内のプレースホルダにデータを挿入する以外に、ずっと多くの要件があることに注意してください。まず、テンプレート エンジンは、単純なフィールド挿入を超えた動作を実現するために、ある種の式言語を備える必要があります。次に、条件レンダリングのシナリオを有効にする必要があります。そして最も重要なこととして、レンダリングされたマークアップに対して、リッチな動作を単純に追加できる必要があります。つまり、エンジンは、作成したマークアップに付加するコンポーネントを単純な方法でインスタンス化できる必要があるのです。
ASP.NET AJAX の次のバージョンでは、テンプレートを再度取り上げ、それらを単純な宣言コンポーネント構文と共に AJAX フレームワークに再導入する予定です。単純なリスト ビューは、次のようなものになります。
<body xmlns:sys="javascript:Sys" xmlns:dv=  "javascript:Sys.UI.DataView">
...
<div id="dataview1" sys:type="dv" dv:data="{{someArray}}">
  <div class="sys-template">
    <h2><a href="{{ 'products/' + id }}">{{name}}</a></h2>
    <!--% if (description) { %-->
      <p>{{description}}</p>
    <!--% } %-->
  </div>
</div>
ここではいくつか注目すべき点があります。式は区切り記号 {{ }} を使用してマークアップに埋め込まれます。これらの式は、現在のデータ項目フィールド (名前、説明など) への直接参照であるか、または複雑な JavaScript 式 ('products/' + id) を使用できます。body タグでは、DataView JavaScript コンポーネントにマッピングされる "dv" 名前空間を定義しています。その後、"dataview1" div の sys:type を "dv" に設定することで、DataView コントロールをインスタンス化してその要素にアタッチすることを指定します。
同じタグで、dv:data="{{someArray}}" は DataView コントロールの data プロパティを someArray グローバル変数に設定します。テンプレートの内部で同じ宣言構文を使用することにより、任意のコントロールおよび動作をインスタンス化してアタッチできます。ここでテンプレート自体は dataview1 div 内部のマークアップですが、これを外部コンテンツから構築することもできます。最後に、エンジンでは <!--% %--> ブロックを使用してテンプレート マークアップに任意のコードを埋め込むことができます。ここではそれを使用して、description データ列を含む段落を条件レンダリングしています。
—Bertrand Le Roy
プログラム マネージャ、ASP.NET
純粋な AJAX アプローチは、リアルタイム株価ページのデザインにどのような影響を与えるのでしょうか。ページはタイマを設定し、XMLHttpRequest を使用してリモート サービスを呼び出します。このサービスは、アプリケーションのバック エンドの一部であり、標準の Microsoft® .NET Framework API を使用して財務 Web サービスを呼び出し、データを取得します。データは JavaScript オブジェクトとしてブラウザに返されます。最後に、それをユーザーにレンダリングする部分は、デザイナの仕事です。
違いは何でしょうか。まず、呼び出される URL がページ自体ではありません。ページは、スクリプト対応の Web または WCF サービスでバックアップされる HTTP エンドポイントを呼び出します。ページのライフサイクルや、ポストバック イベント、ビュー ステートの復元などはありません。その結果、ネットワークのトラフィックが大きく削減されます。このサンプル ページの場合、ペイロードは部分的なレンダリングの場合と比べて 10 倍小さくなります。アーキテクチャ的な観点から見ると、クライアント側のフロントエンドとサーバー側のバックエンドという 2 つの整然と分離されたコード ブロックが動作しているのがわかります。前者は JavaScript によって駆動され、後者はマネージ コードに基づいています。
ASP.NET AJAX は、プログラミング インターフェイスやデータ型の統一を行うため、このような状況で威力を発揮します。それにより、JavaScript クライアント開発者は、サーバー上で定義されているのとまったく同じプログラミング インターフェイスおよびコントラクトを使用できます。JavaScript Object Notation (JSON) 層により、クライアントに到着した DTO がサーバーから送信されたデータをそのまま反映していることが保証されます。
それでは、いくつかのコードで実験してみましょう。新しいページには、ユーザーがクリックして新しいデータをダウンロードできるクライアント ボタンがあります。ここでタイマの代わりにボタンを使用しているのは、単に操作性を良くするためです。
<input type="button" id="btnRefresh" 
       value="Live Quotes" 
       onclick="_getLiveQuotes()" />
これにより JavaScript ハンドラが、ラッパであるリアルタイム株価 Web サービスのメソッドを呼び出し、StockInfo オブジェクトのコレクションを取得します。
<%@ WebService Class="Samples.WebServices.LiveQuoteService" %>
図 3 に、サービスのソース コード全体を示します。
namespace Samples.WebServices
{
    [WebService(Namespace = "http://samples.ajax/")]
    [ScriptService]
    public class LiveQuoteService : System.Web.Services.WebService
    {
        private static string stocks = 
            ConfigurationManager.AppSettings["StockSymbols"];
        private static StockPicker picker = new StockPicker(stocks);

        public LiveQuoteService()
        {
        }

        [WebMethod]
        public StockInfoCollection Update()
        {
            return picker.Update();
        }

        [WebMethod]
        public string GetServiceDescription()
        {
            return picker.GetServiceDescription();
        }
    }
}
部分的なレンダリングでは、ユーザー インターフェイス要素 (ビュー) とコア アプリケーション ロジック (モデル) の間に本質的な区別はありません。すべてがサーバー上でアセンブルされ、すぐに使用できる状態でクライアントに提供されます。純粋な AJAX アーキテクチャでは、プレゼンテーション層がより洗練されて機能が追加され、ビジネス層から物理的に分離されています。そのような高度に分離されたアーキテクチャは、本質的に柔軟性が高くテストもしやすいことは言うまでもありません。さらに、図 4 に示すように、データをキャッシュできる場所が他にもいくつかあります。
図 4 AJAX アーキテクチャ内の可能なキャッシュ レベル (クリックすると拡大画像が表示されます)
データは、(Web) サービスのフロント エンドおよび実装上にあるサーバーにキャッシュできます。また、途中で HTTP プロキシによってキャッシュするか、ブラウザで実行中の JavaScript コードでキャッシュすることもできます。さらに重要なこととして、キャッシュされるのはマークアップではなく、使用可能なデータ (オブジェクトまたは JSON 文字列) であり、実行時の条件に対していつでもチェックすることができます。
レンダリングのロジックをクライアントに移すことは、負荷の高いサーバーの負担を軽減するという利点もあります。サーバーが小さなデータだけを返し、マークアップ生成とビュー ステート処理を含むページのライフサイクル全体を実行する必要がなければ、CPU サイクルおよびメモリを節約できます。
ASP.NET AJAX をクライアントから使用し、リモート サービスとやり取りしてリアルタイムのデータをブラウザに取り込むことも、同じように単純明快です。たとえば、次に示すのはリモート メソッドを呼び出すコードです。
function _getLiveQuotes()
{
    Samples.WebServices.LiveQuoteService.Update(onDataAvailable);
}
function onDataAvailable(results)
{
    // Update the UI
}
ASP.NET AJAX では、サーバー側サービスと同じ名前を持つ JavaScript プロキシ オブジェクトと、いくつかの静的メソッドが提供されます。このコード スニペットでは、Update メソッドが最終的に JavaScript ドキュメント型定義 (DTD) の配列をフェッチし、ユーザー インターフェイスを最終的に更新するコールバック関数にそれを渡します。コード内の results という変数には、サービスからの応答が含まれます。これは、グリッドにバインドする必要のあるデータそのものです。
残念ながら、サーバー ベースの ASP.NET アプローチで使用できるものと同様な、クライアント側の GridView コントロールはありません。何らかの形のクライアント側データ バインド (できればテンプレートをサポートするもの) が必要となります。現在のところ、ASP.NET AJAX はこの目的にはあまり役立ちません。この問題に関するマイクロソフト ASP.NET チームからの直接の説明については、「インサイト : ASP.NET AJAX」のリンクを参照してください。
いくつかの主要な ASP.NET コントロール ベンダは最近、等価なクライアント側オブジェクト モデルを備えたサーバー コントロールの提供を開始しました。これらのライブラリのいずれかを使用すると、クライアントのデータ ソース プロパティと DataBind メソッドを備えた、サーバー側コントロールを模倣する JavaScript オブジェクトが得られる可能性があります。ASP.NET AJAX は、クライアントから Web サービスを呼び出すために必要な基盤を提供しますが、AJAX を考慮してデザインされた完全な UI フレームワークは提供せず、クライアント データの構築やテンプレート作成もサポートしません。UI の観点から見た各種の AJAX アプローチについての興味深い記事が、Miljan Braticevic 氏によって投稿されています (go.microsoft.com/fwlink/?LinkID=116054)。

ASP.NET AJAX でのクライアント側テンプレート
"Atlas" を覚えているでしょうか。後に ASP.NET AJAX Extensions となる、その初期のビルドでは、クライアント側のデータ バインドおよびテンプレートを提供しようという試みがなされました。そして、それは実際にかなり良いものでした。しかし、何かの理由で、ASP.NET の最終リリースには組み込まれず、ASP.NET Futures ライブラリに押し込まれています。
基本的に、古い Atlas バインド モデルは、HTML テンプレートと、マークアップ要素とデータ フィールドの間のバインドを表す宣言構文に基づいていました。そしてテンプレートとバインドは、リスト ビュー コントロールというクライアント コントロールで結合されます。XML を使用して構成された Atlas リスト ビュー コントロールは、受信したすべての情報を使用して多くの解析作業を行い、動作、コントロール、およびバインドをマークアップのさまざまな部分にアタッチします。
この最後の段階から始めて、データ バインドおよびテンプレートに対する小さく効果的な独自のフレームワークを構築する方法を見ていきましょう。最終的な出力は HTML マークアップの文字列でなければならないことに注意してください。最初の入力は、いくつかのパブリック プロパティを持つオブジェクトのコレクションです。必要な作業は、ユーザーの期待を満足させる HTML コードを構築することです。一般には、バインドされたコレクションに対してループを実行し、テンプレートのテキストをオブジェクトのプロパティの値と連結することから開始します。
このアプローチの悪いところはどこでしょうか。実際には、何も悪いところはありません。同じ段階でこれとまったく同じことを行わないような、まったく別のアプローチは思いつきません。これは、この基本的なルーチンを抽象化するフレームワークが確実に作成できることを意味します。AJAX パターンのカタログ (www.ajaxpatterns.org を参照) には、クライアント側のテンプレート モデルを定義する主要な手順が、ブラウザ側テンプレート (BST) パターンという名前で紹介されています。

ブラウザ側テンプレート パターン
ブラウザ側テンプレート (BST) パターンの目的は、データのビューを生成するコードをデータ自体から分離することです。ソフトウェア システムで従来からの問題の 1 つである、ビューとデータ間の分離には、Model View Controller (MVC) パターンのいずれかのバリエーションを用いれば、標準的な解決策が見つかります。
MVC や BST のような表示パターンは、決して相互に排他的なものではなく、BST はそれ自体が MVC のようなものと考えることができます。ただし、コントローラという固有の概念はなく、ブラウザとサーバーが実際に分離されていることでビューとモデルが別々に保持されます。図 5 に、BST に関連した手順を示しています。ユーザーは、データをクライアントにダウンロードするリモート呼び出しをトリガします。このデータは、新しい種類のコンポーネント (マークアップ ビルダ) をインスタンス化する JavaScript コールバックによって管理されます。
図 5 ブラウザ側テンプレートの動作 (クリックすると拡大画像が表示されます)
マークアップ ビルダは、ページのドキュメント オブジェクト モデル (DOM) 内の HTML テンプレートへの参照とダウンロードされたデータに基づく、HTML 文字列を返します。最後に、コールバックはページの DOM に文字列を挿入します。
ここで、いくつかのコードを見てみましょう。この実装では、BST の中核は JavaScript MarkupBuilder クラス内にあり、これは最大 3 つの HTML テンプレート (ヘッダー、フッター、および項目) を受け取ります。これらのテンプレートは、直接 DOM から参照するか、またはプレーンな文字列リテラルとして指定できます。
function pageLoad()
{
    if (builder === null)
    {
        builder = new Samples.MarkupBuilder();
        builder.loadHeader($get("header"));
        builder.loadFooter($get("footer"));
        builder.loadItemTemplate($get("item"));
   }
}
HTML テンプレートは、非表示の DIV を使用して直接ページに埋め込むこともできます。ただし、これはブロック内のマークアップが十分に整っている場合にだけ機能します。より適切なオプションは、次に示すように、テンプレートを XML データ アイランドとして埋め込むことです。
<xml id="item">
    <tr style="background-color:#F0FAFF;">
        <td align="left">#Symbol</td>
        <td align="right">#Quote</td>
        <td align="right">#Change</td>
    </tr> 
</xml>
このコードからわかるように、テンプレートは、カスタム表記を使用してバインド フィールドを参照する HTML のチャンクです。ここでは、#PropertyName 式を使用して、バインドされた値のプレースホルダを示しています。データが使用可能であれば、単にマークアップ ビルダで bind メソッドを呼び出します。
function _getLiveQuotes()
{
    Samples.WebServices.LiveQuoteService.Update(onDataAvailable);
}
function onDataAvailable(results)
{
    var temp = builder.bind(results);
    $get("grid").innerHTML = temp;
}
言うまでもありませんが、grid という名前の要素は、この例の最終的な出力に対するプレースホルダです。MarkupBuilder クラスは、Microsoft AJAX クライアント ライブラリに基づいています。ソース コードの全体を図 6 に示します。
Type.registerNamespace('Samples');

// Class ctor
Samples.MarkupBuilder = function Samples$MarkupBuilder() 
{
    Samples.MarkupBuilder.initializeBase(this);

    // Initializes private members
    this._header = "";
    this._footer = "";
    this._itemTemplate = "";
}
Samples.MarkupBuilder = function Samples$MarkupBuilder(header, footer) 
{
   Samples.MarkupBuilder.initializeBase(this);

    // Initializes the private members
    this._header = header;
    this._footer = footer;
    this._itemTemplate = "";
}

// PROPERTY:: header (String)
function Samples$MarkupBuilder$get_header() { 
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._header;
}
function Samples$MarkupBuilder$set_header(value) {
    var e = Function._validateParams(arguments, [{name: 'value', 
        type: String}]);
    if (e) throw e;

    this._header = value;
}

// PROPERTY:: footer (String)
function Samples$MarkupBuilder$get_footer() { 
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._footer;
}
function Samples$MarkupBuilder$set_footer(value) {
    var e = Function._validateParams(arguments, [{name: 'value', 
        type: String}]);
    if (e) throw e;

    this._footer = value;
}

// PROPERTY:: itemTemplate (String)
function Samples$MarkupBuilder$get_itemTemplate() { 
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._itemTemplate;
}
function Samples$MarkupBuilder$set_itemTemplate(value) {
    var e = Function._validateParams(arguments, [{name: 'value', 
        type: String}]);
    if (e) throw e;

    this._itemTemplate = value;
}

// METHOD:: bind()
function Samples$MarkupBuilder$bind(data) {
   var temp = this._generate(data);
   return temp;
}
// METHOD:: loadHeader()
function Samples$MarkupBuilder$loadHeader(domElement) {
   var temp = domElement.innerHTML;
   this._header = temp;
}

// METHOD:: loadFooter()
function Samples$MarkupBuilder$loadFooter(domElement) {
   var temp = domElement.innerHTML;
   this._footer = temp;
}

// METHOD:: loadItemTemplate()
function Samples$MarkupBuilder$loadItemTemplate(domElement) {
   var temp = domElement.innerHTML;
   this._itemTemplate = temp;
}

///////                     ///////
///////  PRIVATE members    ///////
///////                     ///////

function Samples$MarkupBuilder$_generate(data) {
    var _builder = new Sys.StringBuilder(this._header);

    for(i=0; i<data.length; i++)
    {
        var dataItem = data[i];
        var template = this._itemTemplate;


        var pattern = /#\w+/g;  // Finds all #word occurrences
        var matches = template.match(pattern); 
        for (j=0; j<matches.length; j++)
        {
            var name = matches[j];
            name = name.slice(1);
            template = template.replace(matches[j], dataItem[name]);
        }

        _builder.append(template);
    }

    _builder.append(this._footer);
    return _builder.toString();
}

// PROTOTYPE
Samples.MarkupBuilder.prototype = 
{
    get_header:         Samples$MarkupBuilder$get_header,
    set_header:         Samples$MarkupBuilder$set_header,
    get_footer:         Samples$MarkupBuilder$get_footer,
    set_footer:         Samples$MarkupBuilder$set_footer,
    get_itemTemplate:   Samples$MarkupBuilder$get_itemTemplate,
    set_itemTemplate:   Samples$MarkupBuilder$set_itemTemplate,
    bind:               Samples$MarkupBuilder$bind,
    _generate:          Samples$MarkupBuilder$_generate,
    loadHeader:         Samples$MarkupBuilder$loadHeader,
    loadFooter:         Samples$MarkupBuilder$loadFooter,
    loadItemTemplate:   Samples$MarkupBuilder$loadItemTemplate
}

Samples.MarkupBuilder.registerClass('Samples.MarkupBuilder');
内部では、マークアップ ビルダは Sys.StringBuilder オブジェクトを使用して HTML 文字列を作成します。最初に、ヘッダー テンプレート (ある場合) を追加してから、バインドされたデータのループに進みます。最後に、フッター テンプレートを追加します。この実装では、ヘッダー テンプレートとフッター テンプレートはデータ バインドされていません (図 7 に、マークアップ ビルダを使用したページを示します)。
図 7 マークアップ ビルダを使用した新しいページ (クリックすると拡大画像が表示されます)

その他の考慮事項
テンプレートをプレーンな文字列として指定するのと、埋め込まれた DOM サブツリーまたは XML データ アイランドとして指定するのとでは、何か違いがあるのでしょうか。マークアップ ビルダの観点から言えば、その違いはあまり問題となりません。必要なのは、StringBuilder のバッファに追加する文字列だけです。開発者の観点からは、DOM テンプレートを使用する方が優れた選択肢かもしれません。なぜなら、テンプレートの検証が容易であり、テンプレートを URL からダウンロードできる可能性もあるためです。
ここに示したソリューションは、データのコレクションだけを扱うようにデザインされているため、クライアント側のテンプレートに関しては他のフリー フレームワークのガイドラインに従っています。これらのフレームワークのうち、具体的に挙げておきたいものの 1 つが、PrototypeJS です。PrototypeJS の詳細、サンプル、およびソース コードについては、prototypejs.org/api/template を参照してください。
さらに何か改善できる点はあるでしょうか。もちろん、あります。このコラムで示したコードは、ブラウザ側テンプレートおよびバインド エンジンから期待される最小限の機能セットだけを実装していることに注意してください。
図 7 を見るとわかるように、現在の実装には、図 2 のような上昇した株価と下降した株価を区別するための色分けがありません。図 2 のコードでは、GridView コントロールの RowDataBound イベントを処理して、関連したセルのスタイルを直接変更しています。
void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.Cells[2].Text.StartsWith("+"))
        e.Row.Cells[2].ForeColor = Color.Green;
    if (e.Row.Cells[2].Text.StartsWith("-"))
        e.Row.Cells[2].ForeColor = Color.Red;
}
部分的なレンダリングのソリューションでは、このスタイル変更はサーバー上で行われるため、サーバーのデータ バインド コントロールの機能豊富なプログラミング モデルを利用できます。クライアント側のソリューションでは、関数デリゲートに対応した、より高度なマークアップ ビルダが必要となります。この関数デリゲートは、任意のバインドされたデータに対して、バッファに付加する前にスタイルを設定します。
2 と図 7 を生成したコードの間には、印刷された図には表れていない、より重要な違いが他にもあります。この違いを見つけるために、両方のページを実行して、表示される他のボタン ([Weather Forecasts] (天気予報) と [Start Some Task] (タスクの開始)) をクリックしてみてください。図 7 に示されるページでは、株価の取得中に他のタスクは停止せず、ユーザーが何か他のタスクを開始するためにボタンをクリックしても、サービス呼び出しは中断されません。
ポイントは何でしょうか。部分的なレンダリングでは、通常の非 AJAX ページの場合と同様に、セッションごとに一度に 1 つの要求しか保留できなかったことを思い出してください。もちろん、直接 XMLHttpRequest で制御すると、同時にいくつでも呼び出しを行うことができます。さらに、直接のサービス呼び出しを行った場合は、ビュー ステートが存在しないため、複数の部分的なレンダリング呼び出し間でその一貫性を保つことを気にしなくて済みます (これが、部分的なレンダリング実装で同時操作を実行できない真の理由です)。

HTML Message パターン
このコラムの締めくくりとして、将来のコラムで取り上げる価値のある別のパターンについて簡単に述べさせてください。これは、HTML Message パターンと呼ばれるものです。HTML Message パターンの目的は、ブラウザで表示される HTML マークアップのブロックをサーバーで生成させることです。お気付きのように、これは、部分的なレンダリングと、真の AJAX アプリケーションを構築する他のモデルとの中間に位置するものです。
このパターンはどのように使用するのでしょうか。可能な実装の 1 つは、リモート URL (サービスまたはアドホック HTTP ハンドラ) への呼び出しを行い、すぐに表示できる HTML スニペットを受信することです。これは、プレーンなサービス呼び出しよりも多くのトラフィックを生成しますが、部分的なレンダリングによるトラフィックよりは少なくなります。
サーバー上では、サーバー コントロールの新しいインスタンス (ビュー ステートなし) を使用して、必要な任意の出力を好みのスタイルで出力できます。このパターンについては、今後のコラムで取り上げるつもりです。
ここで、ASP.NET AJAX を使用するときには、AJAX サービス層を定義してそれをクライアント ブラウザから呼び出すためのプログラミング ツールが必要であることを思い出してください。また、JavaScript ベースのデータ バインドやテンプレートなど、クライアント上のデータを効果的に操作するための強力なツールも必要です。
読者の皆さんが、このコラムで紹介した BST AJAX パターンのサンプル実装をご覧になり、それを部分的なレンダリングに基づいたソリューションと比較することで、必然的に著者と同じ結論に到達されることを期待しています。

ご意見やご感想は、cutting@microsoft.com まで英語でお送りください。

Dino Esposito は、『Programming ASP.NET 3.5 Core References』の著者です。Dino はイタリアに在住し、世界各国で開催される業界のイベントで頻繁に講演しています。ブログは weblogs.asp.net/despos で読むことができます。

Page view tracker