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

目次
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 コントロール内にグリッド コントロールをラップしています。

図 1 リアルタイム株価ページの UI
<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 アプローチを用いて書き直すには何が必要かを見ていきます。
リアルタイム株価ページを再検討する
純粋な 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 に、サービスのソース コード全体を示します。

図 3 リアルタイム株価 Web サービス
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 に示します。

図 6 MarkupBuilder クラス
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 パターンのサンプル実装をご覧になり、それを部分的なレンダリングに基づいたソリューションと比較することで、必然的に著者と同じ結論に到達されることを期待しています。
Dino Esposito は、『Programming ASP.NET 3.5 Core References』の著者です。Dino はイタリアに在住し、世界各国で開催される業界のイベントで頻繁に講演しています。ブログは
weblogs.asp.net/despos で読むことができます。