#title(ASP.NETでAjaxを使用したWebアプリケーションを作成する 2) #contents *ASP.NETでAjaxを使用したWebアプリケーションを作成する 2 [#cad07051] 前号では.NETの機能を一切使用せず、自分でJavaScriptを書くことでAjaxを使用する方法を紹介しました。今回は、.NET 2.0からの新機能を使ってAjaxを実現する方法を紹介します。 **ICallbackEventHandlerインターフェイスを実装することにより、Ajaxを使用する方法 [#t33028ff] .NET Framework 2.0からは、ICallbackEventHandlerインターフェイスを実装することにより、より簡単にAjaxを利用することができるようになりました。この方法を使えば、Webサービスを自分で作成する必要がなく、自分で書かなければならないJavaScriptのコードもかなり減ります。 なおこの方法は、「クライアント コールバック」と呼ばれ、MSDNでは「[[ASP.NET Web ページでポストバックせずにクライアント コールバックを実装する>http://msdn2.microsoft.com/ja-jp/library/ms178208(VS.80).aspx]]」などで紹介されています。 補足:以前「[[ASP.NET Web ページでポストバックせずにクライアント コールバックを実装する>http://msdn2.microsoft.com/ja-jp/library/ms178208(VS.80).aspx]]」に記述されていた説明とコードには、間違いがありました。RaiseCallbackEventメソッドの戻り値がStringになっており、GetCallbackResultメソッドがありませんでした。オンラインのMSDNでは修正されています。 ***コード [#g8f5b109] 早速ですが、まず具体的なコードを示し、コードの説明は後に回します。作成しているアプリケーションは前回と同じで、ゆうパックの料金を調べるものです。 まずメインのページとなるASPXファイルは、次のようにします。ここでは、単一ファイルページとしています。 #code(vbnet){{ <%@ Page Language="VB" %> <%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) 'callbackServer関数を作成する Dim callbackReference As String = _ Page.ClientScript.GetCallbackEventReference( _ Me, "arg", "receiveServerData", "", "callbackError", True) Dim callbackScript As String = _ "function callbackServer(arg, context) {" + _ callbackReference + "; }" 'callbackServer関数のJavaScriptブロックをページに追加する Page.ClientScript.RegisterClientScriptBlock( _ Me.GetType(), "callbackServer", callbackScript, True) End Sub Dim callbackResult As String 'クライアントから呼び出されるメソッド Sub RaiseCallbackEvent(ByVal eventArgument As String) _ Implements System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent Dim fee As Integer = Me.GetYoupackFee(Integer.Parse(eventArgument)) Me.callbackResult = fee.ToString() End Sub '結果を返す Function GetCallbackResult() As String _ Implements System.Web.UI.ICallbackEventHandler.GetCallbackResult Return Me.callbackResult End Function Public Function GetYoupackFee(ByVal packSize As Integer) As Integer If packSize <= 60 Then Return 600 ElseIf packSize <= 80 Then Return 800 ElseIf packSize <= 100 Then Return 1000 ElseIf packSize <= 120 Then Return 1200 ElseIf packSize <= 140 Then Return 1400 ElseIf packSize <= 160 Then Return 1600 ElseIf packSize <= 170 Then Return 1700 Else Return -1 End If End Function </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>ゆうパック送料検索</title> <script type="text/javascript" src="callback.js"></script> </head> <body> <h1>ゆうパック送料検索</h1> <p>同一都道府県内への配達、重量30kgまで</p> <form id="form1" runat="server"> <div> <asp:Label ID="Label1" runat="server" Text="荷物の大きさ(縦・横・高さの合計): " AssociatedControlID="PackageSizeList"></asp:Label> <asp:DropDownList ID="PackageSizeList" runat="server"> <asp:ListItem Value="0">(選択してください)</asp:ListItem> <asp:ListItem Value="60">60cmまで</asp:ListItem> <asp:ListItem Value="80">80cmまで</asp:ListItem> <asp:ListItem Value="100">100cmまで</asp:ListItem> <asp:ListItem Value="120">120cmまで</asp:ListItem> <asp:ListItem Value="140">140cmまで</asp:ListItem> <asp:ListItem Value="160">160cmまで</asp:ListItem> <asp:ListItem Value="170">170cmまで</asp:ListItem> </asp:DropDownList> <br /> <asp:Label ID="ResultLabel" runat="server"></asp:Label> </div> </form> </body> </html> }} #code(csharp){{ <%@ Page Language="C#" %> <%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { //callbackServer関数を作成する string callbackReference = Page.ClientScript.GetCallbackEventReference( this, "arg", "receiveServerData", "", "callbackError", true); string callbackScript = "function callbackServer(arg, context) {" + callbackReference + "; }"; //callbackServer関数のJavaScriptブロックをページに追加する Page.ClientScript.RegisterClientScriptBlock( this.GetType(), "callbackServer", callbackScript, true); } #region ICallbackEventHandler メンバ string callbackResult; //クライアントから呼び出されるメソッド void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) { int fee = this.GetYoupackFee(int.Parse(eventArgument)); this.callbackResult = fee.ToString(); } //結果を返す string ICallbackEventHandler.GetCallbackResult() { return this.callbackResult; } public int GetYoupackFee(int packSize) { if (packSize <= 60) return 600; else if (packSize <= 80) return 800; else if (packSize <= 100) return 1000; else if (packSize <= 120) return 1200; else if (packSize <= 140) return 1400; else if (packSize <= 160) return 1600; else if (packSize <= 170) return 1700; else return -1; } #endregion </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>ゆうパック送料検索</title> <script type="text/javascript" src="callback.js"></script> </head> <body> <h1>ゆうパック送料検索</h1> <p>同一都道府県内への配達、重量30kgまで</p> <form id="form1" runat="server"> <div> <asp:Label ID="Label1" runat="server" Text="荷物の大きさ(縦・横・高さの合計): " AssociatedControlID="PackageSizeList"></asp:Label> <asp:DropDownList ID="PackageSizeList" runat="server"> <asp:ListItem Value="0">(選択してください)</asp:ListItem> <asp:ListItem Value="60">60cmまで</asp:ListItem> <asp:ListItem Value="80">80cmまで</asp:ListItem> <asp:ListItem Value="100">100cmまで</asp:ListItem> <asp:ListItem Value="120">120cmまで</asp:ListItem> <asp:ListItem Value="140">140cmまで</asp:ListItem> <asp:ListItem Value="160">160cmまで</asp:ListItem> <asp:ListItem Value="170">170cmまで</asp:ListItem> </asp:DropDownList> <br /> <asp:Label ID="ResultLabel" runat="server"></asp:Label> </div> </form> </body> </html> }} JavaScriptは外部ファイル"callback.js"に記述しています。"callback.js"の中身は、次のようにします。 #code(javascript){{ var msgElement, listElement; //onloadイベントハンドラを追加 if (window.addEventListener) { window.addEventListener("load", onLoad, false); } else if (window.attachEvent) { window.attachEvent("onload", onLoad); } else { window.onload = onLoad; } //onloadイベントハンドラ function onLoad() { msgElement = document.getElementById("ResultLabel"); listElement = document.getElementById("PackageSizeList"); if (window.addEventListener) { listElement.addEventListener("change", onChange, false); } else if (window.attachEvent) { listElement.attachEvent("onchange", onChange); } else { listElement.onchange = onChange; } } //リストのonchangeイベントハンドラ function onChange() { if (listElement.value == "0") { msgElement.innerHTML = ""; return; } msgElement.innerHTML = "読み込み中..."; //サーバーのメソッドを呼び出す callbackServer(listElement.value, ""); } //サーバーが返したデータを取得するイベントハンドラ function receiveServerData(arg, context) { msgElement.innerHTML = "料金は " + arg + "円"; } //サーバーでエラーが発生した時に結果を取得するイベントハンドラ function callbackError(arg, context) { msgElement.innerHTML = "エラーが発生しました"; } }} ***JavaScriptのコードの説明 [#ge0c5feb] まずは、JavaScriptのコードの説明をします。前回のJavaScriptのコードと比べて、かなりさっぱりしたのがお分かりいただけるでしょう。 まず、XMLHttpRequestを使用してサーバーと通信を行う部分が、callbackServer関数を呼び出すだけのコードに変わりました。つまり、この面倒な処理をcallbackServer関数が行ってくれます。このcallbackServer関数がどこから来たのかというと、ASPXファイル(サーバーコード)のPage_Loadメソッドで作成しています。とりあえず今は、callbackServer関数のはじめのパラメータに荷物のサイズを表す「listElement.value」を指定していることを確認してください。 補足:実は、callbackServer関数にパラメータを全く指定しない方法もあります。詳しくは、後述します。 callbackServer関数を呼び出した後、サーバーからデータが正しく返されると、receiveServerData関数が実行されます。この時、argパラメータにサーバーが返した結果が入ります。 サーバーがエラーを返した時(例えばこの例では、サーバーに数字以外の文字を渡すと、エラーが発生します)は、callbackError関数が実行されます。エラー時の処理が必要なければ、callbackError関数を省略できます。 なお、ここで登場したcallbackServer、receiveServerData、callbackErrorという関数名は私が勝手につけたもので、自由な名前に変更することができます。以下の説明をお読みいただければ、お分かりいただけるでしょう。 ***ICallbackEventHandlerインターフェイスの実装 [#u796a99e] 次に、ASPXファイルのコードについて説明します。 まず、ページクラスにICallbackEventHandlerインターフェイスを実装します。ここでは単一ファイルページのため、@Implementsディレクティブを使用していますが、分離コードの場合は、例えば次のようになります。 #code(vbnet){{ Partial Class _Default Inherits System.Web.UI.Page Implements System.Web.UI.ICallbackEventHandler '(省略) End Class }} #code(csharp){{ partial class _Default : System.Web.UI.Page, ICallbackEventHandler { //(省略) } }} 補足:ICallbackEventHandlerインターフェイスはユーザーコントロールで実装します。GridView、DetailsView、TreeViewなどのコントロールがICallbackEventHandlerインターフェイスを実装しています。 ICallbackEventHandlerインターフェイスのメンバには、RaiseCallbackEventメソッドとGetCallbackResultメソッドがあります。JavaScriptから呼び出すコード(前回Webサービスで行っていた処理)をRaiseCallbackEventメソッドに記述します。しかし、その結果を返すのは、GetCallbackResultメソッドです。上記の例では、RaiseCallbackEventメソッドでゆうパックの料金を調べ、その結果をGetCallbackResultメソッドで返しています。 返す結果は、必ず文字列(String型)です。もし複数の値を返したいのであれば、それらを区切り文字を使って連結し、クライアントで分解するなどとすればよいでしょう。詳しくは、「[[クライアント・コールバックで複数の値をクライアントへ渡す方法>http://www.microsoft.com/japan/msdn/asp.net/tips/ClientCallback2/]]」で紹介されています。 ***GetCallbackEventReferenceメソッド [#t43915de] 最後に、Page_Loadメソッド内の説明をします。ここで、callbackServer関数を作成しています。 まず、ClientScriptManagerオブジェクトのGetCallbackEventReferenceメソッドを呼び出して、JavaScriptのcallbackServer関数の中身を作成します。 GetCallbackEventReferenceメソッドの1番目のパラメータには、ICallbackEventHandlerインターフェイスが実装され、JavaScriptが呼び出すRaiseCallbackEventメソッドがあるコントロールを指定します。ここでは、VB.NETではMe、C#ではthisです。 3番目のパラメータには、サーバーから返された結果を受け取るJavaScript側の関数名を指定します。ここでは、"receiveServerData"です。 同様に、5番目のパラメータには、サーバーで発生したエラーを受け取るJavaScript側の関数名を指定します。ここでは、"callbackError"です。必要ないならば、null(VB.NETでは、Nothing)とすることができます。 2番目のパラメータには、サーバーのRaiseCallbackEventメソッドにeventArgumentパラメータとして渡すデータを指定します。ここでは"arg"としていますが、この理由は、JavaScriptからcallbackServer関数を呼び出す時に、1番目のパラメータにサーバーに渡すデータを指定しており、さらに、この次の行で行っているcallbackServer関数を作成しているところで、一番目のパラメータを"arg"としているからです。 この2番目のパラメータは、必ずしも"arg"とする必要はありません。例えば、上記の例では、"listElement.value"とすることもできます。このようにすると、JavaScript側でcallbackServer関数を呼び出す時に、パラメータとして"listElement.value"を指定する必要がなくなります。また、例えば、JavaScript側でサーバーに渡すデータを返す関数"getValue()"を作成し、GetCallbackEventReferenceメソッドの2番目のパラメータに"getValue()"を指定することもできます。 4番目のパラメータには、コンテクストとするデータを指定します。ここで指定されたデータは、JavaScript側のreceiveServerData関数とcallbackError関数の2番目のパラメータ"context"として渡されます。このパラメータの指定の仕方は、先の2番目のパラメータと同様です。つまり、上記の例でここを"context"としているのは、作成しているcallbackServer関数の2番目のパラメータが"context"であり、JavaScript側でcallbackServer関数を呼び出す時に2番目のパラメータにコンテクストを指定すると、receiveServerData関数あるいはcallbackError関数でそのコンテクストを取得できるようにするためです。 このようにしておけば、例えば複数の箇所からcallbackServer関数が呼び出される可能性があるときに、callbackServer関数の2番目のパラメータを変えることで、どのcallbackServer関数によってreceiveServerData関数やcallbackError関数が呼び出されたかを知ることができます。 また、receiveServerData関数やcallbackError関数に渡したい値をここに指定することもできます。さらに、MSDNの「[[ClientScriptManager.GetCallbackEventReference メソッド (Control, String, String, String)>http://msdn2.microsoft.com/ja-jp/library/ms153103(VS.80).aspx]]」では、この4番目のパラメータにreceiveServerData関数を記述する例が紹介されています。 最後の6番目のパラメータには、非同期的に実行するか否かを指定します。上記のようにTrueを指定することにより、非同期で実行されます。GetCallbackEventReferenceメソッドの別のオーバーロードにはこのパラメータがないものもありますが、その場合は、同期的に実行されます。 上記のコードでは、GetCallbackEventReferenceメソッドは次のような文字列を返します。 #pre{{ WebForm_DoCallback('__Page',arg,receiveServerData,context,callbackError,true) }} GetCallbackEventReferenceメソッドを呼び出した後は、このメソッドが返したコードを使って、callbackServer関数を作成します。上記の例では、作成されたcallbackServer関数のコードは、次のようになります。(長いため、途中で改行しています。) #pre{{ function callbackServer(arg, context) { WebForm_DoCallback('__Page',arg,receiveServerData,"context",callbackError,true); } }} 最後にこれをRegisterClientScriptBlockメソッドを使って、ページに挿入します。挿入されるHTMLは、例えば次のようになります。 #pre{{ <script type="text/javascript"> <!-- function callbackServer(arg, context) { WebForm_DoCallback('__Page',arg,receiveServerData,"context",callbackError,true); }// --> </script> }} これで、callbackServer関数を呼び出せるようになりました。 補足:callbackServer関数以外に、srcが"/AjaxWebSite/WebResource.axd?.."のようなJavaScriptブロックが挿入されます。 なおこのような「クライアントコールバック」において、サーバーとクライアントがどのようにデータをやり取りしているかについて補足しておきます。私の調べた限りでは、クライアントがサーバーにPOSTでデータを送信し、サーバーはクライアントに、XMLでもJSONでもない、普通のテキストを返しているようでした。 クライアントがPOSTするデータには、"__CALLBACKID"や"__CALLBACKPARAM"というものが含まれ、それぞれGetCallbackEventReferenceメソッドの1番目のパラメータで指定されたコントロールのUniqueIDと、callbackServer関数の1番目のパラメータで指定された値が指定されていました。それ以外に、__VIEWSTATEや__EVENTVALIDATION、__EVENTTARGET、__EVENTARGUMENTなどのデータも送信されていました。 サーバーが返す値には、GetCallbackResultメソッドが返す文字列だけではなく、__EVENTVALIDATIONの値なども含まれているようでした。 **参考 [#s2bc10b2] -[[An Introduction to AJAX Techniques and Frameworks for ASP.NET - The Code Project>http://www.codeproject.com/Ajax/IntroAjaxASPNET.asp]] -[[ページをリロードせずにサーバーとやり取りする方法>http://www.microsoft.com/japan/msdn/asp.net/tips/ClientCallback/]] -[[クライアント・コールバックで複数の値をクライアントへ渡す方法>http://www.microsoft.com/japan/msdn/asp.net/tips/ClientCallback2/]] -[[クライアント・コールバックでデータベースの値を表示する方法>http://www.microsoft.com/japan/msdn/asp.net/tips/ClientCallback3/]] **コメント [#def6338e] #comment //これより下は編集しないでください #pageinfo([[:Category/.NET]] [[:Category/ASP.NET]],2007-10-16 (火) 01:54:25,DOBON!,2008-02-02 (土) 17:56:23,DOBON!) |