書籍転載
独習ASP.NET 第3版

ASP.NETを理解する3つの仕組み
― 第2章 ASP.NET の基礎 2.3 ―

WINGSプロジェクト 山田 祥寛
2011/07/06
Page1 Page2 Page3

2.3.2 イベントドリブンモデル

 ASP.NETのページでは、さまざまなイベントが発生します。イベントとは、たとえば、「ボタンがクリックされた」「テキストボックスの内容が変更された」「チェックボックスがチェックされた」など、ページで発生するできごとのことです。

 イベントドリブンモデル(イベント駆動モデル)とは、この随所で発生したイベントに応じて実行すべきコードを記述するプログラミングモデルのことを言います。たとえば、リスト2.1(Hello.aspx.vb)の例であれば、

[送信]ボタンがクリックされたら(=イベント)、
「こんにちは、<テキストボックスの内容>さん!」
という文字列をラベルに表示しなさい(=実行すべきコード)

というわけです。

 このとき、ボタンクリックというイベントに対して、その処理を定義するコードのことを、イベントハンドラー、またはイベントプロシージャと言います(図2.30)。

図2.30 イベントとイベントハンドラー

 表2.9に、ASP.NETでサポートされている主なイベントをまとめておきます。詳細は改めて後述しますので、ここではまず、こんなものがあるんだな、という程度の感覚で眺めてみてください。

イベント サポートするコントロール 発生要件
Click Button、ImageButton、LinkButton マウスがクリックされたとき
CheckChanged CheckBox、RadioButton 選択内容が変更されたとき
DayRender Calendar 日付が描画されるとき
Load Page ページがロードされたとき
RowDeleted、ItemDeleted GridView、FormView 項目が削除されたとき
RowUpdated、ItemUpdated GridView、FormView 項目が編集されたとき
SelectedIndexChanged CheckBoxList、DropDownList、ListBox、RadioButtonList 選択されたリスト項目に変更があったとき
SelectionChanged Calendar 選択された日付が変更されたとき
ServerChange HtmlInputCheckBox、HtmlInputRadioButton、HtmlInputText、HtmlSelect、HtmlTextArea 内容に変更があったとき
ServerClick HtmlAnchor、HtmlButton、HtmlInputButton、HtmlInputImage マウスがクリックされたとき
Sorted GridView ソートが行われたとき
TextChanged TextBox テキストボックスの内容に変更があったとき
VisibleMonth Calendar 表示されている月が変更になったとき
表2.9 ASP.NETでサポートされる主要なイベント

イベントハンドラーの生成

 イベントとイベントハンドラーを紐付けるには、いくつかの方法があります。

デザインビューでコントロールをダブルクリックする

 最もシンプルな方法です。

 これによって、それぞれのサーバーコントロールであらかじめ決められたデフォルトのイベントハンドラー(最もよく利用されるイベントハンドラー)が自動生成されます。デフォルトのイベントハンドラーは、使用するサーバーコントロールによって異なります(たとえば、ButtonコントロールではClickイベントがデフォルトです)。

 なお、デザインビューのなにもない場所でダブルクリックした場合には、Page_Loadイベントハンドラー(ページロード時に実行)が自動生成されます。

ソースビューから選択する

 デフォルト以外のイベントハンドラーを作成したい場合には、この方法を利用します。

 ソースビュー上部の選択ボックスから対象となるコントロールと、そのイベントを選択することで、自動的に対応するイベントハンドラーが作成されます。

 たとえば図2.31では、TextBoxコントロールtxtNameのTextChangedイベントハンドラー(テキストが変更されたときに実行)が生成されます。

図2.31 対象のコントロールとイベントを選択

* ソースビューからイベントハンドラーを生成した場合、イベントハンドラーのアクセス修飾子はPrivateに、それ以外の場合にはProtectedになります。しかし、両者に実質的な違いはありませんので、まずは生成されるコードが微妙に異なるという事実さえおさえておけば十分でしょう。

プロパティウィンドウから選択する

 イベントハンドラーは、プロパティウィンドウからも生成ここをクリックできます。デザインビューでコントロールを選択してからプロパティウィンドウ上部の(イベント)ボタンをクリックします。対応するイベント一覧が表示されますので、目的のイベント名をダブルクリックしてください。

 イベントハンドラー名が自動的にセットされ、同時に自動生成されたイベントハンドラーがコードエディターに表示されます。

 なお、イベントハンドラーをすでに用意している場合には、選択ボックスに候補となるイベントハンドラーが表示されますので、そこから選択してもかまいません。

イベントハンドラーの構文

 VWDを利用していれば、イベントハンドラーの最低限の骨組みは自動生成されます。そのため、特に最初のうちは構文そのものを意識する機会はないかもしれません。しかし、イベントハンドラーの構文(シグニチャ)を最低限理解しておくことは、今後、プログラミングを行っていく中で欠かせないステップです。2.1節で作成したHello.aspx.vbを再掲してみましょう(リスト2.3)。

リスト2.3 Hello.aspx.vb

図2.32  プロパティウィンドウから選択

イベントとイベントハンドラーの紐付けを理解する

 イベントとイベントハンドラーとの関連付けを行っているのは、「Handles btnSend.Click」の部分です。この場合、ButtonコントロールbtnSendがクリックされたタイミングで(Clickイベントが発生したタイミングで)、btnSend_Clickイベントハンドラーが呼び出されることを示します。

 イベントハンドラーの名前は、

<コントロールのID値>_<イベント名>

とするのが一般的であり、また、VWDで自動生成した場合にはこの命名規則でイベントハンドラーが生成されます。これは構文規則ではありませんが、コードを読みやすくするという意味でも従っておくに越したことはないでしょう。

 イベントハンドラーの名前自体には、イベントとイベントハンドラーとを結び付けるキーの意味合いはまったくありませんので、(たとえば)MyDisplayのような名前を付けても間違いではありません。

イベントハンドラーの引数を理解する

 続いて、イベントハンドラーに対して渡される引数に注目してみましょう。

 まず、第1引数のObject(sender)はイベントの発生元となったオブジェクトを指します。Hello.aspx.vbの例で言うならば、Buttonコントロールです。これはすべてのイベントハンドラーにおいて共通の引数です。

 一方、第2引数はイベントハンドラーの種類によって異なります。発生したイベントに関する情報を格納するEventArgsクラス、もしくはEventArgsクラスの機能を引き継ぐ派生クラス(EventArgs派生クラス)を指定できます。

 たとえば、後述するGridViewコントロール(グリッド表)では、表内の[削除]ボタンをクリックしたタイミングでRowDeletingイベントが発生しますが、そのときに呼び出されるイベントハンドラーのシグニチャは、以下のとおりです。

Protected Sub grid_RowDeleting(ByVal sender As Object, ByVal e As System.RWeb.UI.WebControls.GridViewDeleteEventArgs) Handles grid.RowDeleting
  ' 任意のコード
End Sub

 RowDeletingイベントハンドラーでは、第2引数としてGridViewDeleteEventArgsオブジェクトeを受け取ります。イベントハンドラーでは、(たとえば)そのRowIndexプロパティにアクセスすることで、削除行のインデックス番号を取得できます。

lblResult.Text = (e.RowIndex + 1) & "行目が削除されました。"

 GridViewコントロールそのものについては、改めて第4章で解説します。ここではEventArgs派生クラスを利用することで、イベントハンドラーの中でイベントに関係するさまざまな情報を知ることができるということをおさえておいてください。

 イベントとイベントハンドラーで受け取ることができるEventArgs派生クラスの関係は、あらかじめ決まっています。主な対応関係については、それぞれ関連するサンプルで触れていますので、そちらを参照してください。

【補足】イベントハンドラーの関連付け(C#の場合)

 本書では、プログラミング言語としてVisual Basicを利用していますが、C#を利用している場合、イベントハンドラーの関連付けの方法がやや違いますので、注意してください。リスト2.4〜5は、C#で生成した場合のHello.aspxとHello.aspx.csのコードです。

<asp:TextBox ID="txtName" runat="server"></asp:TextBox>
<asp:Button ID="btnSend" runat="server" onclick="btnSend_Click" Text="送信" />
リスト2.4 Hello.aspx(C#)

protected void btnSend_Click(object sender, EventArgs e)
{
  ……中略……
}
リスト2.5 Hello.aspx.cs

 分離コード(.aspx.cs)側に「Handles〜」のような関連付けがない代わりに、.aspxファイルのほうに「onclick="btnSend_Click"」のような記述があるのが確認できます。

 この違いは、イベントハンドラーを生成する際には意識する必要はありませんが、削除する際には留意する必要があります。というのも、Visual Basicでは分離コード側のイベントハンドラーを削除するだけで済みますが、C#では.aspxファイルにOnXxxxx属性の記述が残ってしまうのです。C#でイベントハンドラーを削除する場合には、必ずプロパティウィンドウから削除するようにしてください。これによって、関連付けを切断することができます。

「ASP.NET式」イベントドリブンモデルの特殊性

 イベントドリブンモデルによる開発の基本が理解できたところで、もう少し詳細を見ていくことにしましょう。

 イベントドリブンモデルは、もともとVisual Basicなどに代表されるWindowsアプリケーション開発で採用されていたプログラミングモデルです。Windowsアプリケーション開発と限りなく同じ感覚でWebアプリケーションを開発できるのは、ASP.NETの最大の特長です。

 もっとも、ASP.NETのイベントドリブンモデルはあくまで疑似的なものであり、Windowsアプリケーションのそれとはあくまで異なる点に注意してください。

 具体的には、イベントを処理する場所が違います。Windowsアプリケーションでは、クライアントマシンの上でイベントが発生したら、それはそのままクライアント側で処理されます。つまり、イベントが発生する場所とイベントを処理する場所は、同一です。

 しかし、ASP.NETでは、クライアントで発生したイベントは、いったんサーバー側に送信(ポスト)されたうえで処理されます。たとえば、ブラウザー上でボタンがクリックされたとき、「ボタンがクリックされた」という情報は、いったんサーバー上にある現在のWebページ(自分自身)に対して送信されます。ASP.NETでは、これをイベントハンドラーで処理した後、その結果を反映したページを応答するわけです(図2.33)。

図2.33 ポストバック処理

 このように自分自身に対してページの内容を送信し、処理することを、通常のポストと区別する意味でポストバック(Postback)と言います。また、イベントの発生に伴って、処理がサーバー/クライアント間を行き来することをサーバーラウンドトリップと言います。

 このような「ASP.NET式」イベントドリブンモデルを理解しておくことは、ASP.NETプログラミングで思わぬ不具合に遭遇しないためにも、とても重要なことです。以下では、「ASP.NET式」イベントドリブンモデル固有の注意点をいくつかまとめておきます。

* ここで注目していただきたいのは、ポストバックもサーバーラウンドトリップも、ASP.NET独自の仕組みではないということです。これらは、いずれもHTTPによる一般的なWebアプリケーションの仕組みにすぎません。ASP.NETの魅力とは、独自のアーキテクチャではない、ごく汎用的なプロトコル(手続き)の範疇で、新たな開発モデルを実現している点にあるとも言えます。

ページイベントは常に発生する

 たとえば、2.1節で作成したHello.aspx.vbを、リスト2.6のように書き換えてみましょう。Page_Loadイベントハンドラーは、ASP.NETページが呼び出されたときにコールされるイベントハンドラーで、ここではTextBoxコントロールtxtNameに初期値として「権兵衛」をセットしています(イベントハンドラーそのものはデフォルトで用意されています)。

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  txtName.Text = "権兵衛"
End Sub
リスト2.6 書き換え後のHello.aspx.vb(配布サンプルではHello2.aspx.vb)

 ページを呼び出したタイミングで、今度はテキストボックスに「権兵衛」と表示されます。ここまでは正しい動作です。しかし、テキストボックスに「山田」と入力して[送信]ボタンをクリックするとどうでしょう。テキストボックスに入力した文字列がラベルに反映されません(図2.34)。

図2.34 修正したHello.aspx.vbの実行結果

 これはどういうことでしょうか。

 本来、意図した動作ではボタンクリックによってbtnSend_Clickイベントハンドラーが呼び出され、「こんにちは、山田さん!」という文字列がラベル上に表示されるはずです。しかし、画面上には「こんにちは、権兵衛さん!」と表示されてしまうのです。テキストボックスの表示もいつの間にか「権兵衛」に戻ってしまっています。

 一見、プログラムに誤りはないように見えます。しかし、これこそが「ASP.NET式」イベントドリブンモデルの落とし穴なのです。

 さて、突然ですが、ここで皆さんにちょっとしたクイズです。

【クイズ】

Hello.aspx上でボタンをクリックしたとき、以下のイベントハンドラーのうち、Q実行されるのはどれでしょう。

 Page_Loadイベントハンドラー(ページがロードされたタイミングで発生)
 Button_Clickイベントハンドラー(ボタンがクリックされたタイミングで発生)
 TextBox_Changedイベントハンドラー(テキストボックスの内容が変更されたタイミングで発生)

 Windowsアプリケーションの開発になじみのある方ならば、おそらくは即座に.と答えることでしょう。また、その回答は多くの人々の直感にも一致する回答のはずです。しかし、ASP.NETにおいて、これは「間違い」です。

 正解は、「Page_Load」「Button_Click」「TextBox_Changed」のすべてです。ASP.NETではポストバックしたタイミングで、常にPage_Loadイベントハンドラーが呼び出されます。

 ここで、ASP.NETではどのようにイベント処理が行われるのか、図2.35に整理してみます。

図2.35 ASP.NETページにおける主なイベント処理([※]はページイベント)

 [※]が付いているイベントがページイベントと呼ばれるものです。

 ASP.NETでは、ページが最初に呼び出されたときだけでなく、ポストバック時にも常にページイベントがひととおり呼び出されるのです。これは、イベント処理が常にサーバー側で行われるものであり、「イベントが処理される」ということは「ページが新たに呼び出される」ことと同じ意味であるからです(そもそもPageオブジェクトはリクエストのたびにインスタンス化されるのでした)。

 これを理解してしまえば、先ほどの不具合の原因も理解できるはずです。まずリスト2.6のPage_Loadイベントハンドラーによってテキストボックスの値が「権兵衛」と初期化された後に、btnSend_Clickイベントハンドラーが呼び出されます。そのため、せっかく入力した「山田」という文字は無視されてしまうというわけです。

 では、「山田」という入力文字を認識させるためには、どのようにしたら良いのでしょう。それは「ページが最初に呼び出された」タイミングと「ポストバックによって呼び出された」タイミングとを、コード上で明示的に区別することです。ASP.NETは、この区別のために便利なプロパティを用意しています。それがPage.IsPostBackプロパティです。

 先のコードについて、Page_Loadイベントハンドラーをリスト2.7のように書き換えてみてください。

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  If Not Page.IsPostBack Then
    txtName.Text = "権兵衛"
  End If
End Sub
リスト2.7 書き換え後のHello.aspx.vb(配布サンプルではHello2.aspx.vb)

 同じようにテキストボックスに「山田」と入力し、[送信]ボタンをクリックすると、今度は「こんにちは、山田さん!」という文字列が表示されるはずです。テキストボックスの入力値も「山田」のままです(図2.36)。

図2.36 再修正したHello.aspx.vbの実行結果

 IsPostBackプロパティは、現在のリクエストがポストバックされたものであるかどうかをTrue/Falseのいずれかで返します。つまり、上記のコードではIsPostBackプロパティがFalseの場合(ポストバックでない、最初のページ要求の場合)にのみテキストボックスの値を初期化します。そして、ポストバックされた場合にはこの部分は無視され、入力された「山田」という文字列がそのまま採用されるという仕組みです。

 このIsPostBackプロパティは、このようなイベントの矛盾を未然に防ぐ場合だけでなく、ポストバックのたびに行われる余計な処理を防ぎ、パフォーマンスを向上させる目的でも使用できます。たとえば、Page_Loadイベントハンドラーでデータベース検索のようにオーバーヘッドの大きな処理を行っているとしたらどうでしょう。そのままでも特に動作の問題はありませんが、常に同じ内容をセットしているだけなのに、ポストバックのたびに処理を繰り返すのは、いかにも無駄の多い処理です。

 このような場合にも、IsPostBackプロパティを利用してページが最初に呼び出された場合にだけデータベース検索を行うようにしてやることで、ポストバック時の重複した処理を省くことができます。

変更系イベントの発生タイミングは選択できる

 先ほどの説明を見て、「あれ?」と思った方もいるかもしれません。ラジオボタンが変更された、テキストボックスの内容が変更されたなどのイベントも、ページイベントやクリック系のイベントと同時に発生するのでしょうか。従来のイベントドリブンの考え方ならば、変更系のイベントはその変更が発生したタイミングで発生することになっているので、Windowsアプリケーションのイベントドリブンモデルに慣れている方ほど違和感を感じるところでしょう。

 しかし、ASP.NETではデフォルト状態で、変更系のイベントはいったんプール(保存)されます。つまり「変更があった」という状態だけが記憶され、最終的にボタンがクリックされるなどのイベントが発生したタイミングで、まとめて処理されるのです(図2.37)。

図2.37 変更系イベントはプールされる

* イベントがプールされた場合、複数のイベントハンドラーが特定の順序で処理されることを期待してはいけません。唯一期待して良いのは、変更系イベント→クリックイベントの順で処理されるということだけで、変更系イベントが複数発生した場合、いずれが先に処理されるかは保証されません。

 これは、まさにASP.NET式イベント処理の特殊性を強く意識した仕様です。ASP.NETでは、イベント処理のたびにクライアント/サーバー間のトラフィックが発生しますから、どうしても処理に際するオーバーヘッドは大きくなります。そこでASP.NETでは、ユーザーがあまり意識しないままに頻繁に発生する可能性がある(ということは、パフォーマンス悪化の原因となりやすい)変更系イベントでのポストバックは、デフォルトで無効にしているのです。

 同じ理由から、

  • OnMouseOver(マウスポインターがコントロールの上に載ったときに発生)
  • OnMouseOut(マウスポインターがコントロールから外れたときに発生)
  • OnFocus(コントロールにフォーカスが当たったときに発生)

などのイベントについて、ASP.NETではサポートされていません。こうしたイベントについては、必要に応じてクライアントサイドスクリプトを使って実装してください。

 ただし例外的に、変更系のイベントを発生のたびにポストバックさせるよう設定することもできます。それが一部のサーバーコントロールに用意されているAutoPostBackプロパティの役割です。AutoPostBackプロパティは、変更系イベントをサポートするTextBoxやDropDownList、CheckBoxコントロールなどで利用できます。

 AutoPostBackプロパティをTrueに設定することで、コントロール内で発生した変更系イベントは、クリックイベントを待たずにポストバックを発生させることができます。ただし、繰り返しますが、変更系イベントの即時ポストバックを有効にするということは、それだけサーバー/クライアント間の行き来も増える(可能性がある)ということです。見かけ上のプログラミングモデルがWindowsアプリケーションと似通っているだけに、無意識にWindowsアプリケーションと同じ動作をさせたくなってしまいますが、WindowsアプリケーションとWebアプリケーションの差を認識し、本当に必要な場合にだけ用いるようにしたいものです。

 なお、AutoPostBackプロパティが正しく動作するためには、クライアントサイドのスクリプト機能が有効になっている必要がありますので、注意してください。クライアントサイドスクリプトがなんらかの事情で無効に設定されている場合、変更系イベントの即時ポストバックは行われません。

【練習問題】2.2
  1. ポストバックの意味について、簡単に説明してみましょう。

  2. 以下の文章は、変更系イベントが処理されるタイミングについて説明したものです。適切なものをすべて選択してください。

    常に、該当するコントロールの内容が変更されたタイミングで処理される
    デフォルトでは、クリック系イベントと同時に処理される
    常に、クリック系イベントと同時に処理される
    特定条件を満たせば、対象のコントロールの内容が変更されたタイミングで処理される



 INDEX
  [書籍転載]独習ASP.NET 第3版
  ASP.NETを理解する3つの仕組み
    1.サーバーコントロール
  2.イベントドリブンモデル
    3.ビューステート

インデックス・ページヘ  「独習ASP.NET 第3版」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間