第2回 ASP.NETによるCRUD処理(パート1:管理用ページの作成):連載:ASP.NETによる軽量業務アプリ開発(3/4 ページ)
前回はなぜASP.NETで軽量な業務アプリを開発するのか、そのために必要な準備について話をした。これを受けて、今回は管理用ページの実装までを見ていくことにしよう。
管理用ページの実装
管理用ページは1つのファイルで全ての機能を実現するために、次のネストした状態を処理する。
1. 認証前
- 認証処理を実行する。認証できた場合は、認証済みであることをセッション変数に保存する
- 認証できなかった場合は、認証用のフォームをHTMLとして出力する(次の画面)
2. 認証後
- SQL入力用のフォームをHTMLとして出力する
- リクエストにSQLが含まれていればデータベース処理を実行する
2-1. [Execute]ボタンのsubmitであれば、SQL文を実行し、結果を含んだHTMLコードを出力する(次の画面)
2-2. [Query]ボタンのsubmitであれば、SQL文を実行してデータを読み取り、その結果を含んだHTMLコードを出力する(次の画面)
2-3. SQL文の実行時例外を受けたら、例外情報を含んだHTMLコードを出力する(次の画面)
以下に管理用ページ(admin.aspxファイル)のソースコードを示す。
このファイルを、直接C:\inetpub\wwwroot\testディレクトリに作成する。
自分で打ち込んで試す場合、文字コードには、CP932(WindowsのシフトJIS。メモ帳ではANSI)、UTF-8のいずれを利用してもよい。ただし、UTF-8で記述する場合は、必ずBOMを付ける必要がある点には注意が必要である*5。エディターがUTF-8のBOMをサポートしていない場合は、一度メモ帳で開いてから上書き保存することでBOMを付けることが可能である。
*5 BOM(Byte Order Mark)とはユニコードの種類を示すためにファイルの先頭に埋め込む特殊な文字コード。本来UTF-8にはBOMは不要であるが、複数エンコーディングのサポートを単純化するためにそのような仕様としたのだと推測できる。
<%@ Page Language="C#" EnableViewState="false" Debug="true" %> ← (1)
<%@ Import namespace="System.Data.SqlClient" %>
<%@ Import namespace="System.Web" %>
<!DOCTYPE html> ← (2)
<html lang="ja">
<meta charset="utf-8">
<% ← (3)
var loggedIn = (Session["LoggedIn"] == null) ? false : (bool)Session["LoggedIn"]; ← (4)
if (!loggedIn)
{
loggedIn = Request.Form["user"] == "test" && Request.Form["pwd"] == "Test24989"; ← (5)
Session["LoggedIn"] = loggedIn;
}
if (!loggedIn)
{
%> ← (6)
<form action="admin.aspx" method="post">
<label>ユーザー<input type="text" name="user" size="16"></label>
<label>パスワード<input type="password" name="pwd" size="24"></label>
<input type="submit" value="ログイン">
</form>
<%
}
else
{
%>
<section>
<form action="admin.aspx" method="post">
<label>SQL</label><br>
<textarea name="sql" cols="80" rows="8"><%= (Request["sql"] == null) ? string.Empty : HttpUtility.HtmlEncode(Request["sql"])%></textarea> ← (7)
<div>
<input type="submit" name="execute" value="Execute">
<input type="submit" name="query" value="Query">
</div>
</form>
</section>
<section>
<%
if (!string.IsNullOrEmpty(Request["sql"])) ← (8)
{
using (var connection = new SqlConnection("Data Source=.\\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|testdb.mdf;User Instance=true"))
{
connection.Open();
using(var command = connection.CreateCommand())
{
command.CommandText = Request["sql"];
try
{
if (string.IsNullOrEmpty(Request["query"]))
{
%>
<div>
結果:<%= command.ExecuteNonQuery() %> ← (9)
</div>
<%
}
else
{
using (var reader = command.ExecuteReader()) ← (10)
{
%>
<table>
<tr>
<%
for (var i = 0; i < reader.FieldCount; i++)
{
%>
<th><%= HttpUtility.HtmlEncode(reader.GetName(i)) %></th>
<%
}
%>
</tr>
<%
while (reader.Read())
{
%>
<tr>
<%
for (var i = 0; i < reader.FieldCount; i++)
{
%>
<td><%= HttpUtility.HtmlEncode(reader[i]) %></td>
<%
}
}
%>
</table>
<%
}
}
}
catch (SqlException e)
{
%>
<div><%= HttpUtility.HtmlEncode(e.Message) %></div> ← (11)
<pre><%= HttpUtility.HtmlEncode(e.StackTrace) %></pre>
<%
}
}
connection.Close();
}
}
}
%>
</section>
</html>
(1)Pageディレクティブで、C#で記述することを示す。EnableViewState属性にfalseを与えてASP.NET Webフォームフレームワーク用の特殊な<hidden>タグを作成しないことを指定する。Debug属性をtrueに設定すると、エラー時にデバッグシンボル情報が利用されるのでエラー発生時の行番号などを正しく参照できる。
(2)ここから3行は<%……%>で囲まれていないため、そのままクライアントに送られる地のHTMLコードである。
(3)インラインコードブロックを開始する。
(4)すでに認証済みならばセッション変数loggedInにtrueを設定する(4行下)。null(未設定)またはfalse(認証失敗)ならばloggedIn変数にはfalseが設定される。
(5)認証されていなければ認証を実行する。ここでは直接ユーザーIDとパスワードを文字列定数と比較している。管理者が1人かつ他のWebページでは利用しないことから、単純な認証はこのような実装で十分である。パスワードを変えたければ、ASPXファイルを編集すればよい。拡張子が「.aspx」のファイルそのものの外部からの読み取りはIISによって防御されているため、直接パスワードなどを埋め込んでも外部からは参照できない*6。
(6)(3)からのインラインコードの終端。次行からはloggedIn変数がfalseの場合、つまり未認証時の処理となる。ここでは次行からの5行を使って認証用のフォームを出力する。対応するelse節(9行下)では最初に認証済みの場合のSQL入力用のフォームを出力する。
(7)フォーム変数はRequestコレクションのインデクサーに変数名を指定することで取得できる。ただし認証直後はSQLを入力するテキストエリア(name属性が"sql"の<textarea>タグ)がまだないため、nullが返る。ここではインライン式を使ってこのテキストエリアに入力内容(フォーム変数sqlの内容)をエコーバックするようにしている。文字列の出力時はHttpUtilityクラスのHtmlEncodeメソッドを利用してエスケープする必要がある。
(8)フォーム変数sql(9行上の<textarea>タグ)に有効な文字列が入力されているので、以降データベース処理を実行する。
(9)インライン式を使って、ExecuteNonQueryメソッド(SqlCommandクラス)の結果をHTMLコードとして出力する。ExecuteNonQueryメソッドはint値を返すが、インライン式は自動的に文字列化するのでToStringメソッドを呼び出す必要はない。
(10)データの読み取りにはSqlDataReaderクラスを利用する((SqlCommand.ExecuteReaderメソッドはSqlDataReaderオブジェクトを返す)。
(11)例外を捕捉しない場合、既定でIISと同一ホストからのアクセス時は例外メッセージとスタックトレースが出力される。リモート実行の場合は、次節で説明するリモートコンピューターのエラー表示となる。ここでは管理者自身によるアクセスと想定できるので、特別な設定抜きですぐに例外を確認できるようにHTMLコードへ出力している。
*6 今となってはトリビアであるが、ASPの当初の実装には「asp.」と末尾に「.」を付けると、IISがソースそのものをテキストとして返すというデバッグ用なのか何なのかよく分からない豪快な機能があり、大問題となった(現在は当然修正されている)。参考:「[IIS] ASP スクリプトがブラウザ上に表示される」。
[コラム]ASPXファイルの読み取りについての補遺
直接サーバー上でASPXファイルを編集した場合、エディターによっては同一ディレクトリに拡張子「.bak」や「~」が付加されたファイルが作成される。拡張子「.aspx」のファイルの読み取りをIISが保護していても、これらが保護されていなければ、内容を読み取られる危険性があることに変わりはない。
実際に試してみると分かるが、IISは拡張子「.bak」などの読み取りを既定の設定では禁止していてファイルが存在してもHTTPエラー404を返す(次の画面)。
Copyright© Digital Advantage Corp. All Rights Reserved.