今回は ASP.NET のイベントがどのように発生しているのかを簡単な例で説明しながら、ASP.NET のプログラミングでの注意点について解説します。
● クライアント側からのイベント通知
ブラウザでボタンがクリックされた時に、Hello World という文字列を表示するにはどうすればいいでしょうか。
○ 今までの Web プログラミングでのイベント処理
今までの Web プログラミングでは、[リスト 0000] のようなコードになります。
[リスト 0000] クラシックASP で記述した場合(Sample1.asp, Sample2.asp)
(sample1.asp)
<%@ Language="VBScript" %>
<html>
<head>
<title>Sample1.asp</title>
</head>
<body>
<form id="form1" method="post" action="Sample2.asp">
<!-- 処理結果を表示 -->
<span><%= Session("Hello") %></span>
<!-- sample2.asp にポスト -->
<input type="submit" value="Button1" />
</form>
</body>
</html>
-------------------------------------------------------------------------------
(sample2.asp)
<%@ Language="VBScript" %>
<%
' 処理結果を保存
Session("Hello") = "Hello World"
' リダイレクト
Response.Redirect("Sample1.asp")
%>
このサンプルの処理順序は、
1.Sample1.asp から Sample2.asp にフォームの内容をポスト
2.Sample2.asp で処理を行い、結果を Session 変数に保存
3.Sample2.asp から Sample1.asp を呼び出す
4.Sample1.asp で処理結果を表示する
となります。
Windows アプリケーションになれていると、sample1.asp で起きたイベントを sampel2.asp が処理して sampel1.asp に戻すという処理に違和感があり、ボタンが 2 つ以上あったらどうするか、どのページでイベントが発生したか、などいろいろと考える必要があります。
○ ASP.NET のイベント処理
ASP.NET で記述する場合、内容自体はとても簡単です。ページに配置されたボタンのクリックイベントを作成し、イベントハンドラに Hello World という文字列を表示するコードを記述するだけです。
まずは簡単なイベント処理から復習していきましょう。
Visual Studio で新規 Web サイトを作成して、[リスト 0001] のようにテキストとボタンを追加した後に、追加したボタンをダブルクリックしてイベントハンドラを作成してください。
[リスト 0001] テキストとボタンを配置した例(上が Visual Basic 版、下が C# 版)
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>
<!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>標準のテキストとボタンを配置したASP.NETのページ</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Text ID="TextBox1" runat="server" />
<asp:Button ID="Button1" runat="server" Text="Button1" />
<asp:Button ID="Button2" runat="server" Text="Button2" />
</div>
</form>
</body>
</html>
-------------------------------------------------------------------------------
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>標準のテキストとボタンを配置したASP.NETのページ</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Text ID="TextBox1" runat="server" />
<asp:Button ID="Button1" runat="server" Text="Button1"
onclick="Button1_Click" />
<asp:Button ID="Button2" runat="server" Text="Button2" />
</div>
</form>
</body>
</html>
ASP.NET では、ASPX 側に配置されたコントロールの runat 属性が server と指定された場合、コード側(VB や C#)で対応するインスタンスが作成され、コード側から ASPX 側の要素へアクセスすることができるようになります。
コード側では、Button1 の Text プロパティに値を代入することで、ASPX 側の Button1 の Text 属性にコード側で設定した値が反映されます。
[リスト 0002] はボタンがクリックされた時に Hello World を表示する例です。
[リスト 0002] ボタンクリックのイベントハンドラ
Partial Class _Default
Inherits System.Web.UI.Page
''' <summary>
''' ボタンButton1をクリックした時に実行されるイベントハンドラ
''' </summary>
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
' テキストにメッセージを表示
TextBox1.Text = "Hello World"
End Sub
End Class
-------------------------------------------------------------------------------
using System;
public partial class _Default : System.Web.UI.Page
{
/// <summary>
/// ボタンButton1をクリックした時に実行されるイベントハンドラ
/// </summary>
protected void Button1_OnClick(object sender, EventArgs e)
{
// テキストにメッセージを表示
TextBox1.Text = "Hello World";
}
}
プログラムを実行後、表示されたブラウザに表示されているボタンをクリックすると、ブラウザには Hello World が表示されるはずです。
Visual Basic も C# もほとんど記述は同じですが、イベントハンドラの設定方法が少し異なっています。C# では ASPX 側で Button1 の OnClick に Button1_Click が指定されているのに対し、Visual Basic ではコード側に Button1 がクリックされたら Button1_Click を呼び出すように指定(Handle 句)されています。
ASPX 側にイベントの指定があった場合は、対象のイベントと同じシグネチャ(この場合引数に object と EventArgs を引数に持つ型)のメソッドを探し実行されます。もちろん Visual Basic でも C# と同じように ASPX 側でイベントハンドラの指定をすることができます。
○ ASPX と出力される HTML
ASP.NET では、ASPX にリクエストがあると、 ASP.NET が ASPX やプログラムコードを元に HTML を構成してクライアントに送り返します。
[リスト 0001] を IE7 で表示した場合、[リスト 0003] のような HTML がクライアントに返されます。
[リスト 0003] [リスト 0001] を呼び出した結果出力された HTML
<!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 id="Head1"><title>
標準のテキストとボタンを配置した ASP.NET のページ
</title></head>
<body>
<form name="form1" method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTExNjMzNDIxNjRkZAX4nBs76QDzigkK7h8ze12y5ksG" />
</div>
<div>
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwKrq7CKAgKM54rGBgK7q7GGCKdLJgc6RQYGtU7fhdOsSvtoCd6i" />
</div>
<div>
<span id="TextBox1"></span>
<input type="submit" name="Button1" value="Button1" id="Button1" />
<input type="submit" name="Button2" value="Button2" id="Button2" />
</div>
</form>
</body>
</html>
はき出された HTML を確認するといろいろなことが解ってきます。
例えば、form タグの action 属性では、自分自身(Default.aspx)にデータをポストするように指定されていますし、元からある Button1 や Button2 以外にも __VIEWSTATE や __EVENTVALIDATION などの見慣れないタグも出力されています。
○ ポストバック
従来の Web アプリケーション開発では、下図のようにイベントの発生元ページ(sample1.asp)でボタンがクリックされた場合は、イベントを処理するページ(sample2.asp)に値をポストすることでサーバー側のイベント処理を行っていました。
ASP.NET のアプリケーション開発では、イベントが発生した場合、元のページに対して再度ポストしてイベントを処理する事で、Windows アプリケーションと同じようなイベントモデルでプログラミングを実現しています。
【イベントの比較】

この仕組みはポストバックと呼ばれ、ASP.NET のイベント処理の特徴となっています。
ただ、従来の Web アプリケーションと全く異なる仕組みなので、従来の Web アプリケーション開発に慣れていると、その動きの違いに混乱するかもしれません。
○ コントロールのイベントが呼び出されるのはなぜ?
ボタンがクリックされた時に sample1.aspx がポストバックで再度呼び出されるとして、ASP.NET では、ページが初めて読み込まれたのか、ページ上でイベントが発生したためのポストバックなのかをどのように判断しているのでしょうか。
下記は、Button1 がクリックされた時の通信内容をキャプチャーした結果です。
通信のキャプチャーには Web Web Development Helper という通信状況をキャプチャーするソフトを使用します。インストールや使い方などはここでは説明しませんが、インストールする場合は以下の URL からダウンロードしてインストールしてください。
【Button1 がクリックされたときの通信状況】

HTTP の通信には、リクエストの設定(Request Headers)とリクエストの本体(Request Body)があります。ここでは、ページでポストした値を確認したいので Request Body タブの Name と Value の値に注目してください。
ページに送っている情報の中には、ページの状態を保存している変数や、どのボタンによってイベントが引き渡されたのか(この場合 Button1)という情報が一緒に含まれています。たとえば[リスト 0001]のページで Button2 がクリックされた場合は、Button2=Button2 が代わりにポストされることで、どのコントロールでイベントが起きたかを ASP.NET に通知しているということになります。
では、ほかのページから Button1=Button1 というリクエストをページにポストしたら sample1.aspx の Button1_Click イベントを呼び出すことができるのでしょうか?
この場合 [リスト 0004] のような例外が発生し、イベントを発生させるとはできません。
[リスト 0004] EventValidation なしでのポストバック]
「アプリケーションでサーバー エラーが発生しました。
--------------------------------------------------------------------------------無効なポストバックまたはコールバック引数です。イベントの検証は、構成の <pages enableEventValidation="true"/>、またはページの <%@ Page EnableEventValidation="true" %> を使用して有効にされます。セキュリティの目的により、この機能は、イベントをポストバックまたはコールバックする引数が、それらを最初に表示したサーバーコントロールから発行されていることを確認します。データが有効であり、予期されている場合、検証のためのポストバックまたはコールバック データを登録するために ClientScriptManager.RegisterForEventValidation メソッドを使用してください。
...略...
上図を見ると、Button1 と一緒に __EVENTVALIDATION や __VIEWSTATE という値も同時にサーバーにポストされています。
__EVENTVALIDATION の動作に関する詳しい説明は MSDN にも見当たりませんが、読み込まれたページ以外からイベントの呼び出しがあっても ASP.NET でブロックしているため、安全なプログラミングが行えるような仕組みが組み込まれているということです。
ページの動作から __EVENTVALIDATION や __VIEWSTATE の動きを予想してみると、 __EVENTVALIDATION はイベントがそのページから発生しているかを検査し、 __VIEWSTATE はページの現在の状態を保持している隠し変数として動作しているようです。
● ASP.NET イベントモデルの注意点
ASP.NET では、ポストバックのおかげで、まるで Windows アプリケーションのように Web アプリケーションを作成できます。ただし、Web の仕組みを使っている以上、いくつかの点で注意が必要です。
○ 初期化イベントはどう書くの?
掲示板でよくある質問で、テキストボックスに値を入力したはずなのに何故か反映されないというものがあります。
例えば、[リスト 0002] のコードに [リスト 0005] を追加したらどうなるでしょうか。
[リスト 0005] PostBack を考慮していないコード
Partial Class _Default
Inherits System.Web.UI.Page
''' <summary>
''' ページロード時に実行されるイベントハンドラ
''' </summary>
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
TextBox1.Text = "Hello"
End Sub
''' <summary>
''' ボタンButton1をクリックした時に実行されるイベントハンドラ
''' </summary>
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
' TextBox の値に現在時刻を付けた物で更新する。
TextBox1.Text &= DateTime.Now.ToString()
End Sub
End Class
-------------------------------------------------------------------------------
using System;
public partial class _Default : System.Web.UI.Page
{
/// <summary>
/// ページロード時に実行されるイベントハンドラ
/// </summary>
protected void Page_Load(object sender, EventArgs e)
{
TextBox1.Text = "Hello";
}
/// <summary>
/// ボタンButton1をクリックした時に実行されるイベントハンドラ
/// </summary>
protected void Button1_OnClick(object sender, EventArgs e)
{
// TextBox の値に現在時刻を付けた物で更新する。
TextBox1.Text += DateTime.Now.ToString();
}
}
ボタンのクリック時に TextBox の値に現在時刻を追加して表示しようとしています。
この場合、いくらユーザーがテキストボックスの値を変更しても、Hello +現在時刻が表示されるだけで、テキストボックスに入力した値+現在時刻にはなりません。
ASP.NET では、ASPX にリクエストがあった場合はページライフサイクル(後編で解説)の中で決まった処理が実行されます。毎回初期化されてしまうのは、ボタンのクリックなどのポストバックイベントが Page.Load などのイベントが実行された後に発生するためです。
Page.Load で初回ロード時のみ値を初期化したい場合は [リスト 0006] のように Page_Load メソッドの中で Page の IsPostBack プロパティを利用して、現在のポストが PostBack かどうかを判断する必要があります。
[リスト 0006] PostBack を考慮したコード
''' <summary>
''' ページロード時に実行されるイベントハンドラ
''' </summary>
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Page.IsPostBack Then
TextBox1.Text = "Hello"
End If
End Sub
-------------------------------------------------------------------------------
/// <summary>
/// ページロード時に実行されるイベントハンドラ
/// </summary>
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
TextBox1.Text = "Hello";
}
}
実行すると初回ロード時のみ Hello が表示され、それ以外の ロード(PostBack) では期待したように、TextBox の値+現在時刻が表示されるはずです。
■まとめ
既存の Web プログラミングに比べると、Windows のようなイベントモデルでプログラミングができる分 ASP.NET のプログラミングはとっつきやすくなっています。
後編では、アプリケーションライフサイクルと、ページライフサイクルについて触れながら、ASP.NET がサーバー側でイベントを処理する仕組みについて説明します。