第3回 ASP.NETによるCRUD処理(パート2:ユーザー用ページの作成):連載:ASP.NETによる軽量業務アプリ開発(2/3 ページ)
コマンドラインとエディターのみでWeb開発する方法を説明。今回は一般ユーザーがtestデータベースをCRUD処理するためのページを実装していく。
新規作成ページ
新規作成ページには、id、create_dateなど、自動的に値を設定するもの以外のカラムについてデータの入力フォームを用意する。
下のサンプルでは、入力フォームとフォームの送信先として実際にinsertを実行する処理を共通のページで実現している。
<%@ Page Language="C#" EnableViewState="false" Debug="true"%>
<%@ Import namespace="System.Data.SqlClient"%>
<%
if (Request.Form["save"] == null) ← (1)
{
%>
<!DOCTYPE html>
<html lang="ja">
<meta charset="utf-8">
<section>
<form action="create.aspx" method="post"> ← (2)
<label>件名
<input type="text" name="subject" placeholder="件名を入力します">
</label>
<br/>
<textarea name="content" rows="5" placeholder="本文を入力します"></textarea>
<br/>
<input type="submit" name="save" value="保存"> ← (3)
</form>
<form action="index.aspx" method="get">
<input type="submit" value="戻る">
</form>
</section>
</html>
<%
}
else ← (4)
{
using (var conn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|testdb.mdf;User Instance=true"))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "insert into test values (@subject,@content,sysdatetime(),sysdatetime())";
cmd.Parameters.Add(new SqlParameter("@subject", Request.Form["subject"]));
cmd.Parameters.Add(new SqlParameter("@content", Request.Form["content"]));
cmd.ExecuteNonQuery();
}
conn.Close();
}
Response.Redirect("index.aspx"); ← (5)
}
%>
(1)(3)のフォームのPOSTであれば、<input>タグ(type="submit")のname属性の値「save」がRequest.Formプロパティに含まれる。一方、index.aspxファイルから([新規作成]ボタンによって)遷移された場合には含まれないためフォーム変数saveの値はnullとなる。従って後続のHTML(新規データの入力フォーム)はindex.aspxファイルから遷移した場合に表示される。
(2)フォームデータの送信先をこのASPXファイルとしている。その場合(データを入力して[保存]ボタンをクリックした場合)は(4)以降に制御が移る。
(3)(1)でこのボタンが押されたかどうかを判定しているのでname属性が必要である。
(4)フォーム変数(subject、content)に設定された各入力項目を利用してinsertを実行する。
(5)insert後はこのページにとどまる必要はないので、index.aspxファイルへ制御を移す。
詳細ページ
詳細ページでは指定された行の内容を表示する。このページから該当する行の更新、削除を実行できるようにする(以下の画面)。
<%@ Page Language="C#" EnableViewState="false" Debug="true"%>
<%@ Import namespace="System.Data.SqlClient"%>
<%@ Import namespace="System.Web" %>
<!DOCTYPE html>
<html lang="ja">
<meta charset="utf-8">
<%
using (var conn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|testdb.mdf;User Instance=true"))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "select * from test where id=@id";
cmd.Parameters.Add(new SqlParameter("@id", Request["id"]));
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
%>
<section>
<form action="update.aspx" method="post">
<input type="hidden" name="id" value="<%= reader["id"] %>"> ← (1)
<input type="hidden" name="update_date" value="<%= ((DateTime)reader["update_date"]).Ticks %>"> ← (2)
<input type="submit" name="update" value="更新">
<input type="submit" name="delete" value="削除">
</form>
</section>
<section>
<article>
<header>
<h1><%= HttpUtility.HtmlEncode(reader["subject"])%></h1>
<p><time><%= ((DateTime)reader["create_date"]).ToString("d")%></time></p>
</header>
<p><%= HttpUtility.HtmlEncode(reader["content"]).Replace("\r\n", "<br/>")%></p>
<footer>
<p>最終更新 <time><%= ((DateTime)reader["update_date"]).ToString("G")%></time>
</footer>
</article>
</section>
<%
}
else
{
%>
<section>
記事が削除されました。 ← (3)
</section>
<%
}
}
}
conn.Close();
}
%>
<section>
<form action="index.aspx" method="get">
<input type="submit" value="戻る">
</form>
</section>
</html>
(1)更新や削除処理で使用するために主キーを「type="hidden"」の非表示データとして埋め込む。
(2)楽観ロックのために更新日時をTick*3として埋め込む。
(3)他のユーザーが削除した場合の考慮をしておく。
*3 .NET FrameworkのDateTime構造体の最小単位で紀元1年1月1日からの100ナノ秒単位での経過時数。
サンプルでは更新ページ(削除処理を含む)用に、行の主キーとは別に更新日時も与えている。
Webアプリでは、更新のための読み込みから、実際の更新までの間にデータベースとの接続を維持しない。このため、データベースが持つロック機構を利用できない。従って、更新ページであるユーザーが編集をしている間に、他のユーザーがそれとは異なる編集を行うことが可能である。このような場合、どのように制御するかをあらかじめ決めておくことは重要である。
例として、2人のユーザーが同時に編集してしまった場合を考える。
- ユーザーAがレコード1を読み込み、編集を開始する
- ユーザーBがレコード1を読み込み、編集を開始する
- ユーザーAがレコード1の編集を完了し保存する。レコード1はレコード1'となる
このタイミングで、ユーザーBが編集しているレコード1(保存するとレコード1"となる)と、現在データベース上に存在するレコード1'は異なるものである。
これに対して取り得る選択肢は以下のいずれかである。
(1) ユーザーBはレコード1からレコード1"を作成したいのであるから、ユーザーAのレコード1'は無視してそのままレコード1"を保存する。結果としてユーザーAの変更は無視される
(2) ユーザーBの編集の前提となるレコード1はすでに存在しないのであるから、ユーザーBの現在の編集は取り消す。結果としてユーザーBはユーザーAが変更したレコードを基に再度編集を行う
ここでは(2)の方法として、編集開始時点の更新日付と保存時点の更新日付を比較して同一かどうかを判定するようにする。
このような更新時の制御を、通常はユーザー間の更新の衝突が発生しないことを前提することから楽観ロックと呼ぶ。
Copyright© Digital Advantage Corp. All Rights Reserved.