ASP.NETサーバーコントロールパッケージ-【イベント】-1.1【イベント再送.非同期コールバック】


1.イベントおよびなぜイベント駆動メカニズムが必要なのか
イベントの定義は、「イベントとは、オブジェクトまたはクラスに通知を与えることができるメンバーである」というC#言語の詳細である.ここでは、ページに登録されているイベントのオブジェクトが、ユーザの操作をキャプチャして処理することができる.では、なぜイベントメカニズムを引用する必要があるのでしょうか.
クラスAのインスタンスオブジェクトにクラスBのインスタンスオブジェクトが作成されると、クラスAのインスタンスオブジェクトでは、クラスBが開示している方法や属性などをそのクラスBのインスタンスオブジェクトから呼び出すことができることはよく知られている.ユーザPageオブジェクトにTextBoxオブジェクトが作成されているように、PageオブジェクトはTextBoxオブジェクトを介してTextプロパティを呼び出すことができます.しかし、上記のTextBoxオブジェクトでPageオブジェクトのいくつかの属性やメソッドを呼び出す必要がある場合は、どうすればいいのでしょうか.呼び出しを含めるとうまくいかないのは明らかで、イベントメカニズムはちょうどこの問題を解決します.
ここで、TextBoxのTextChangedイベントについて説明する.
まずTextBoxでTextChanged依頼を宣言し,その依頼をページ登録によりページクラス処理イベント関数に関連付ける必要がある.
<asp:TextBox ID="TextBox1" runat="server"  OnTextChanged="TextBox1_TextChanged"></asp:TextBox>

 
(上記コードは、TextBox 1のTextChangedイベントをページクラスに登録することである).
そしてTextBoxクラス内でTextChanged依頼関連のイベント関数を呼び出せばよい.このように、ページクラスオブジェクトにおけるTextBoxコントロールのTextが変更されると、TextBoxはまず自分の内部論理処理を実行すると、ページクラスにおけるOnTextChangedイベント関数TextBox 1_を呼び出すTextChanged()、一方TextBox 1_TextChanged()関数はページクラスの1つのメソッドであるため、TextBoxはこの関数内でページクラスの他のメソッドと属性を呼び出すことができる.(もちろんTextBox 1自身を呼び出すこともできます).
 
2.再送の原理
Web開発者は,クライアントがサービス側に返信するイベントが1つしかないことを知っているが,サービス側ではユーザがどのような操作を行ったかをどのように区別しているのだろうか.ここではまずASP.NETのページリクエストについてお話ししました.
ASP.NETでページを処理する場合,前後2ページ間は無状態に接続されている,つまりクライアントの前後2回の要求は互いに独立しており,サービス側は前回の要求のページ状態を保存しない.これにより、前後2回の要求を処理するためのビューステータスメカニズム(ASP.NETが開発したビューステータスとコントロールステータスメカニズムの研究の楽しさを後述する)が導入され、その原理は、前回の要求が発生した後、サービス側がページ面の論理情報を隠れたフィールドに保存してクライアントに返信することである.次のリクエストが発生すると、サービス側はまず非表示フィールドの値を取り出し、各ビューコントロールに復元し、論理処理が完了してから新しいデータを非表示フィールドに保存するとクライアントに送られ、2回のページ間のステータス情報が継続されます.
本明細書で説明する再送は、ビューステータスメカニズムに基づいて行われます.すなわち、サービス側に送信されたコントロールの現在値と非表示フィールドに保存されている古い値を比較することで、どのイベントがトリガーされるかを決定します.もちろん、カスタムコントロールクラスはIPostBackDataHanderインタフェースを継承することでイベントの再送機能を完了する必要があります.
 
3.非同期コールバックの原理
コールバックとは異なり,コールバックはクライアントからサービス側へ,サービス側で処理に関する論理が完了して処理したデータをクライアントに返すことであり,クライアントがサービス側のメソッド(これはWebサービスとよく似ている)を呼び出したように,処理したデータをクライアントプログラムに返す結果として処理することに相当する.
非同期コールバック中、ASP.NETはページの通常のライフサイクルを変更して要求を処理し、返信はASP.NETは完全なページライフサイクルでリクエストを処理します.
また、非同期コールバック中に、クライアントページが再リフレッシュされない他の操作を行うこともできます.
 
4.イベント再送の実現
前述したように、クライアントからサービス側へのイベントは1つしかありません.再送イベントです.では、サービス側はどのようにして自分のコントロールに返信イベントをキャプチャしますか?
コントロールクラスをIPostBackEventHanderインタフェースに継承し、そのインタフェースの唯一の方法であるRaisePostBackEvent(string eventArgument)を実装する必要があります.
簡単な例で説明します.
 
(1).まず、サーバコントロールプロジェクトとテストプロジェクトを作成します.(ここではプロジェクトの作成方法を紹介していますが、後で説明しませんよ!)
VS 2008の新規プロジェクトを開き、ポップアップダイアログでASPを選択する.NETサーバコントロールテンプレート、プロジェクト名を入力し、次の図1のようにパスを選択します.
図1
次に、エクスプローラの「ソリューション...」上右クリックで「追加」=>「新規プロジェクト」(図2に示す)をクリックし、図1ダイアログボックスから「ASP.NET Webアプリケーション」を選択してプロジェクト名を入力します(ここではコントロールの処理をテストするためだけなのでtestと名付けます).
(2).IPostBackEventHanderインタフェースを継承し、インタフェースメソッドを実装します.
次の図に示すように、Server Control 1はIPostBackEventHanderインタフェースを継承し、マウスをこのインタフェースに一時停止すると、次の図3に示すように、インタフェースの実装方法のヒントダイアログボックスがポップアップします.
「実装インタフェース...」を選択します.インタフェースが実装する必要があるメソッドの空の関数コードを自動的に生成できます.
#region IPostBackEventHandler           
public void RaisePostBackEvent(string eventArgument)        
{           
         throw new NotImplementedException();       
 } 
#endregion

 
(3).次に,この方法の実現を開始する.
protected override void RenderContents(HtmlTextWriter output)        {            
//output.Write(Text);            
output.Write("<input type='button' name=\"{0}\" value='[   ]' ></input>",  this.UniqueID);                    
}                
#region IPostBackEventHandler           
public void RaisePostBackEvent(string eventArgument)       
{            
//throw new NotImplementedException();            
OnCliclk(EventArgs.Empty);        
}        
#endregion        
/* Click     */        
public event EventHandler Click;        
protected virtual void OnCliclk(EventArgs e)        
{           
                 if (Click != null)            
                {                
                     Click(this, e);           
                 }        
}

 
上記のコードではまず、ページ登録のクリックイベント関数に関連付けるためのClick委任とOnclickイベント関数が定義されています.RaisePostBackEvent()メソッドではOnCliclk(EventArgs.Empty)のみが呼び出されます.メソッド、ユーザーのロジックを処理します.
 
(4).コントロールのRaisePostBackEvent()メソッドでブレークポイントを押し、ツールバーから上記のコントロール(ServerControl 1)をtestプロジェクトのDefaultにドラッグします.aspページで、テストを実行します.
ボタンをクリックすると、コードの実行はブレークポイントの関数には入りません.どうして?
IPostBackEventHanderインタフェースは再送イベントのキャプチャのみを提供するため、再送がなければキャプチャとは言えません(クライアントbuttonタイプのinputボタンは再送イベントをトリガーしません).Inputタイプをsubmitタイプとして再試行して、今回はいいでしょう.
 
(5).コントロール自動再送実装
上記では、ボタンタイプがsubmitタイプに変更された場合にのみ再送イベントをキャプチャできますが、いずれのコントロールもコミットボタンを持参する必要はありません.焦らないでください.次に、コントロールのクライアントが自動的に再送することについて説明します.
コントロールクライアントの自動再送を実現するには、GetPostBackEventReferenceまたはGetPostBackClientHyperlinkメソッドを使用してクライアント再送関数の参照を取得します.
上のコードを修正するには、次のようにします.
protected override void RenderContents(HtmlTextWriter output)
{     
//output.Write(Text);     
//output.Write("<input type='button' name=\"{0}\" value='[   ]' ></input>",// this.UniqueID);     
output.Write("<input type='button' name=\"{0}\" value='[   ]' onclick=\"{1}\"></input>", this.UniqueID, Page.ClientScript.GetPostBackEventReference(this, ""));
}

 
または
protected override void RenderContents(HtmlTextWriter output)
        {
            output.Write("<input type='button' name=\"{0}\" value='[   ]' onclick=\"{1}\"></input>"
                ,this.UniqueID,this.Page.ClientScript.GetPostBackEventReference(this,""));
        }

 
プログラムを実行して、テストして、あなたのコントロールは自動的に再送することができます.
(6).ダブルクリックaspページの[私をクリック]ボタン、Default.asp.csファイルでは、このボタンを自動的に生成するクリックイベント関数です.
protected void ServerControl1_Click(object sender, EventArgs e){ }

 
ブレークポイントを打って、単一のステップで、この関数でページクラスのメソッドと属性を呼び出すことができます.これがButtonオブジェクトがイベントを介してPageオブジェクトを呼び出す方法と属性です.
 
5.非同期コールバックの実現
非同期コールバックの処理はコントロールパッケージの内容ではありません.コールバックとコールバックの違いをはっきりさせるために、説明します.
非同期コールバックはページクラスで処理され、同様に、ページクラスはICallBackEventHandleインタフェースを継承し、インタフェースメソッドを実装してページクラスの非同期コールバックの機能を増加させる必要がある.ここでは、JavaScriptから着手するか、上記の工程に沿って実例を説明する必要があります.
 
(1).上にあるaspページにボタンと結果を表示するためのSpan要素を追加します.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">    
<title>    </title>
</head>
<body>    
<form id="form1" runat="server">    
<div>        
<cc1:ServerControl1 ID="ServerControl1" runat="server"             onclick="ServerControl1_Click" />          <br /><br />        
<input id="ButtonCallBack" type="button" value="[          Button]" />        <br />        
<div>            :<br />            
Args1. <span id="ResultShowArg1"></span><br />           
 Args2. <span id="ResultShowArg2"></span>        
</div>    </div>    
</form>
</body>
</html>

 
(2).Default.aspページバックグラウンドファイルクラスは、ICallBackEventHandleインタフェースを継承し、インタフェースメソッドを実装します.
public partial class _Default : System.Web.UI.Page, ICallbackEventHandler
{    
protected void Page_Load(object sender, EventArgs e){ }    protected void ServerControl1_Click(object sender, EventArgs e){ 
}    
#region ICallbackEventHandler       
public string GetCallbackResult()   
{       throw new NotImplementedException();   
}   
public void RaiseCallbackEvent(string eventArgument)   
{        throw new NotImplementedException();   
}   
#endregion
}

 
(3).ページクラスのPage_Loadイベント関数に次のコードを追加します.
protected void Page_Load(object sender, EventArgs e)
{    
string cbRe = Page.ClientScript.GetCallbackEventReference(this, "arg", "CallBackServe", "context");   
string callbackscript;   
 callbackscript = "function CallPageServe(arg,context)" + "{" + cbRe + ";}";    
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "CallPageServe", callbackscript, true);
}

 
ここではまず、Pageオブジェクトを介してClientScriptを参照する.GetCallbackEventReference()メソッドは、ページのコールバック関数のアプリケーションを取得し、RegisterClientScriptBlock()メソッドでクライアントページに登録します.
ここで説明する必要があるのは、GetCallbackEventReferenceメソッドの最初のパラメータがコントロールオブジェクトの参照であり、通常はthisである.2番目と4番目のパラメータ(「arg」と「context」)は、クライアントがサービス側コールバックしたい関数CallPageServe関数を処理する受信パラメータである.本当に重要なのは3番目のパラメータ「CallBackServe」です.これはコールバック中にサービス側が処理した後、クライアントに戻ってデータを処理するJS関数名です.
(4).Defaultに戻るaspページ、「ButtonCallBack」ボタンをダブルクリックし、このボタンにクライアントJSを登録するイベント関数ButtonCallBack_をクリックするonclick().
<script language="javascript" type="text/javascript">            function ButtonCallBack_onclick() 
{               
 CallPageServe("args1", "args2");            
}                        
function CallBackServe(returnValue, context) 
{                
document.getElementById("ResultShowArg1").innerHTML = returnValue;                
document.getElementById("ResultShowArg2").innerHTML = context;             
}
</script>

 
その中でもButtonCallBack_onclick()で呼び出されたCallPageSever()がコールバックのトリガ関数です.CallBackServe()とは,クライアントがコールバックデータを処理するJS関数である.
(5).戻ってasp.csファイルは、ページクラスにString文字列オブジェクトを追加し、ICallBackEventHandleインタフェースメソッドの関数内で対応します.
string returnValue = "";
#region ICallbackEventHandler   
public string GetCallbackResult(){      
//throw new NotImplementedException();      
return returnValue;
}
public void RaiseCallbackEvent(string eventArgument)
{      //throw new NotImplementedException();      
returnValue = eventArgument;
} #endregion

 
(6).実行チェックの下、ButtonCallBackボタンをクリックするとコールバックの結果が表示されます.
興味のあるところにブレークポイントを打って一歩下がってください.他に面白いものがあることに気づきます.
 
やはりすべてのソースコードを添付しましょう.
Default.asp.csファイル
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using PostBackEventControlDemo;
namespace test
{
    public partial class _Default : System.Web.UI.Page,ICallbackEventHandler
    {
        protected void Page_Load(object sender, EventArgs e) 
        { 
              string cbRe = Page.ClientScript
                .GetCallbackEventReference(this, "arg", "CallBackServe", ""); 
              string callbackscript; 
              callbackscript = "function CallPageServe(arg,context)" + "{" + cbRe + ";}"; 
              Page.ClientScript.RegisterClientScriptBlock(this.GetType(), 
                  "CallPageServe", callbackscript, true); 
        }


        protected void onClickHandle(object sender, EventArgs e)
        {

            String txt = ServerControl11.Text;
            int i = 0;
        }

        #region ICallbackEventHandler Members

        string returnValue = "";

        public string GetCallbackResult()
        {
            return returnValue;
            //throw new NotImplementedException();
        }

        public void RaiseCallbackEvent(string eventArgument)
        {
            returnValue = eventArgument;
            //throw new NotImplementedException();
        }

        #endregion
    }
}

 
Default.asp 
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
        Inherits="test._Default" 
        %>

<%@ Register assembly="PostBackEventControlDemo" namespace="PostBackEventControlDemo"
     tagprefix="cc2" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <cc2:ServerControl1 ID="ServerControl11" runat="server" OnClick="onClickHandle"/>
        <br />
        <br />
        <input id="ButtonCallBack" type="button" value="[          Button]"  
            onclick="return ButtonCallBack_onclick()"/>
        <div>            :<br />
        ARG1 <span id="ResultShowArg1" ></span><br />
        ARG2 <span id="ResultShowArg222" ></span>
        </div>
        
        
        <script language="javascript" type="text/javascript">
            function ButtonCallBack_onclick() {
                CallPageServe("args1|args2");
            }
            function CallBackServe(returnValue) {
                var str = returnValue.split('|');
                document.getElementById("ResultShowArg222").innerHTML = str[1];
                document.getElementById("ResultShowArg1").innerHTML = str[0];
                
            }
       </script>
    </div>
    </form>

    
</body>
</html>

 ServerControls1
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace PostBackEventControlDemo
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
    public class ServerControl1 : WebControl,IPostBackEventHandler
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Text
        {
            get
            {
                String s = (String)ViewState["Text"];
                return ((s == null) ? "[" + this.ID + "]" : s);
            }

            set
            {
                ViewState["Text"] = value;
            }
        }

        protected override void RenderContents(HtmlTextWriter output)
        {
            output.Write("<input type='button' name=\"{0}\" value='[   ]' onclick=\"{1}\"></input>"
                ,this.UniqueID,this.Page.ClientScript.GetPostBackEventReference(this,""));
        }

        #region IPostBackEventHandler Members

        public void RaisePostBackEvent(string eventArgument)
        {
            OnClick(EventArgs.Empty);
        }

        #endregion

        public event EventHandler Click;

        protected virtual void OnClick(EventArgs e)
        {
            if(Click != null)
            {
                Click(this,e);
            }
        }
    }
}