ASP.NETでAjaxを使用したWebアプリケーションを作成する 2 †
前号では.NETの機能を一切使用せず、自分でJavaScriptを書くことでAjaxを使用する方法を紹介しました。今回は、.NET 2.0からの新機能を使ってAjaxを実現する方法を紹介します。
ICallbackEventHandlerインターフェイスを実装することにより、Ajaxを使用する方法 †
.NET Framework 2.0からは、ICallbackEventHandlerインターフェイスを実装することにより、より簡単にAjaxを利用することができるようになりました。この方法を使えば、Webサービスを自分で作成する必要がなく、自分で書かなければならないJavaScriptのコードもかなり減ります。
なおこの方法は、「クライアント コールバック」と呼ばれ、MSDNでは「ASP.NET Web ページでポストバックせずにクライアント コールバックを実装する」などで紹介されています。
補足:以前「ASP.NET Web ページでポストバックせずにクライアント コールバックを実装する」に記述されていた説明とコードには、間違いがありました。RaiseCallbackEventメソッドの戻り値がStringになっており、GetCallbackResultメソッドがありませんでした。オンラインのMSDNでは修正されています。
コード †
早速ですが、まず具体的なコードを示し、コードの説明は後に回します。作成しているアプリケーションは前回と同じで、ゆうパックの料金を調べるものです。
まずメインのページとなるASPXファイルは、次のようにします。ここでは、単一ファイルページとしています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
| | <%@ 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)
Dim callbackReference As String = _
Page.ClientScript.GetCallbackEventReference( _
Me, "arg", "receiveServerData", "", "callbackError", True)
Dim callbackScript As String = _
"function callbackServer(arg, context) {" + _
callbackReference + "; }"
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>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
| | <%@ 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)
{
string callbackReference =
Page.ClientScript.GetCallbackEventReference(
this, "arg", "receiveServerData", "", "callbackError", true);
string callbackScript = "function callbackServer(arg, context) {" +
callbackReference + "; }";
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"の中身は、次のようにします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| | var msgElement, listElement;
if (window.addEventListener)
{
window.addEventListener("load", onLoad, false);
}
else if (window.attachEvent)
{
window.attachEvent("onload", onLoad);
}
else
{
window.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;
}
}
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のコードの説明 †
まずは、JavaScriptのコードの説明をします。前回のJavaScriptのコードと比べて、かなりさっぱりしたのがお分かりいただけるでしょう。
まず、XMLHttpRequestを使用してサーバーと通信を行う部分が、callbackServer関数を呼び出すだけのコードに変わりました。つまり、この面倒な処理をcallbackServer関数が行ってくれます。このcallbackServer関数がどこから来たのかというと、ASPXファイル(サーバーコード)のPage_Loadメソッドで作成しています。とりあえず今は、callbackServer関数のはじめのパラメータに荷物のサイズを表す「listElement.value」を指定していることを確認してください。
補足:実は、callbackServer関数にパラメータを全く指定しない方法もあります。詳しくは、後述します。
callbackServer関数を呼び出した後、サーバーからデータが正しく返されると、receiveServerData関数が実行されます。この時、argパラメータにサーバーが返した結果が入ります。
サーバーがエラーを返した時(例えばこの例では、サーバーに数字以外の文字を渡すと、エラーが発生します)は、callbackError関数が実行されます。エラー時の処理が必要なければ、callbackError関数を省略できます。
なお、ここで登場したcallbackServer、receiveServerData、callbackErrorという関数名は私が勝手につけたもので、自由な名前に変更することができます。以下の説明をお読みいただければ、お分かりいただけるでしょう。
ICallbackEventHandlerインターフェイスの実装 †
次に、ASPXファイルのコードについて説明します。
まず、ページクラスにICallbackEventHandlerインターフェイスを実装します。ここでは単一ファイルページのため、@Implementsディレクティブを使用していますが、分離コードの場合は、例えば次のようになります。
1
2
3
4
5
| | Partial Class _Default
Inherits System.Web.UI.Page
Implements System.Web.UI.ICallbackEventHandler
End Class
|
1
2
3
4
| | partial class _Default : System.Web.UI.Page, ICallbackEventHandler
{
}
|
補足:ICallbackEventHandlerインターフェイスはユーザーコントロールで実装します。GridView、DetailsView、TreeViewなどのコントロールがICallbackEventHandlerインターフェイスを実装しています。
ICallbackEventHandlerインターフェイスのメンバには、RaiseCallbackEventメソッドとGetCallbackResultメソッドがあります。JavaScriptから呼び出すコード(前回Webサービスで行っていた処理)をRaiseCallbackEventメソッドに記述します。しかし、その結果を返すのは、GetCallbackResultメソッドです。上記の例では、RaiseCallbackEventメソッドでゆうパックの料金を調べ、その結果をGetCallbackResultメソッドで返しています。
返す結果は、必ず文字列(String型)です。もし複数の値を返したいのであれば、それらを区切り文字を使って連結し、クライアントで分解するなどとすればよいでしょう。詳しくは、「クライアント・コールバックで複数の値をクライアントへ渡す方法」で紹介されています。
GetCallbackEventReferenceメソッド †
最後に、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)」では、この4番目のパラメータにreceiveServerData関数を記述する例が紹介されています。
最後の6番目のパラメータには、非同期的に実行するか否かを指定します。上記のようにTrueを指定することにより、非同期で実行されます。GetCallbackEventReferenceメソッドの別のオーバーロードにはこのパラメータがないものもありますが、その場合は、同期的に実行されます。
上記のコードでは、GetCallbackEventReferenceメソッドは次のような文字列を返します。
WebForm_DoCallback('__Page',arg,receiveServerData,context,callbackError,true)
GetCallbackEventReferenceメソッドを呼び出した後は、このメソッドが返したコードを使って、callbackServer関数を作成します。上記の例では、作成されたcallbackServer関数のコードは、次のようになります。(長いため、途中で改行しています。)
function callbackServer(arg, context) {
WebForm_DoCallback('__Page',arg,receiveServerData,"context",callbackError,true);
}
最後にこれをRegisterClientScriptBlockメソッドを使って、ページに挿入します。挿入されるHTMLは、例えば次のようになります。
<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の値なども含まれているようでした。
参考 †
コメント †