#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/]]

//これより下は編集しないでください
#pageinfo(,2007-10-16 (火) 01:54:25,DOBON!,2007-11-05 (月) 02:54:17,DOBON!)
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]