前回は自作の社員リストモデル(EmployeeList)をビューで表示しました。今回は表示したデータを利用し、社員テーブルの追加・更新・削除の機能を実装してみましょう。
まずは面倒な方からいきます。追加・更新です。これらは別々に作っても良いのですがページ自体が同じイメージになるので一緒にしちゃいます。処理の流れとしてはIndexビューのActionLinkからIdを受け取り、Idが「-1」等の存在しない値だったら新規用を、それ以外なら社員テーブルからデータを抽出して更新用を表示、保存時は既存Idだったら更新、なければ新規として扱うって感じでどうでしょう?
あとせっかくなんで配属先IDの入力はドロップダウンリストにしてみようかと思います。…なんて簡単に考えてたんですが、実際やってみるとちょっとモデルをよく考えないといけなそうです。MVCでないASP.Net等だったら別にドロップダウンリストのソースだけ配属先テーブルから取ってきてバインドなりすりゃすんだわけなんですが、MVCはModelViewControllerなわけでモデルに無いものは扱えませんよっていうか扱っちゃいけませんよってのが基本ですよね。当然、配属先テーブルは社員テーブルには無いわけです。なので追加・更新用に新たに配属先テーブルを持つモデルを作成します。
追記:別の方法もありました(ViewBagを追加ってDropDownListを作ってみる)
社員編集用モデル
public class EmployeeUpdate { /// <summary> /// 社員ID /// </summary> public int Id { get; set; } /// <summary> /// 社員名 /// </summary> public string EmployeeName { get; set; } /// <summary> /// 配属先ID /// </summary> public int AssignId { get; set; } /// <summary> /// 配属先テーブルリスト /// </summary> public List<Assign> Assigns { get; set; } }
モデルは見ての通り、社員テーブルのデータと配属先テーブルのリストで作られています。それではこれを使ってコントローラーに表示用メソッドを追加します。
Updateメソッド
public ActionResult Update(int id = -1) { using (var context = new MvcTestContext()) { var overlap = context.Employees.Find(id); if (overlap == null) { var result = new EmployeeUpdate { Assigns = context.Assigns.ToList() }; return View(result); } else { var result = new EmployeeUpdate { Id = overlap.EmployeeId, EmployeeName = overlap.EmployeeName, AssignId = overlap.AssignId, Assigns = context.Assigns.ToList() }; return View(result); } } }
いまいちスマートじゃないけど中身はシンプルなメソッドなので解説なしで、一端これを元にビューを作成しますか。
Updateビュー
@model MVCTestProject.Models.EmployeeUpdate @{ ViewBag.Title = "Update"; } @if (Model.Id != 0) { <h2>更新</h2> }else { <h2>追加</h2> } @using (Html.BeginForm()) { <!-- IdはPost時に利用する必要があるので隠しときます --> @Html.HiddenFor(model => model.Id) <div class="editor-label">社員名</div> <div class="editor-field"> @Html.EditorFor(model => model.EmployeeName) </div> <div class="editor-label">配属先</div> <div class="editor-field"> @Html.DropDownListFor(model => model.AssignId, new SelectList(Model.Assigns,"AssignId", "AssignName")) </div> <p> <input type="submit" value="保存" /> </p> } <div> @Html.ActionLink("戻る", "Index") </div>
ドロップダウンリストの書き方、資料がなさ過ぎて苦労しました。MSDN(SelectExtensions.DropDownListFor(TModel, TProperty) メソッド (HtmlHelper(TModel), Expression(Func(TModel, TProperty)), IEnumerable(SelectListItem)) (System.Web.Mvc.Html))分かりにくいです。「@Html.DropDownListFor(モデルの値, 使用するリスト(値フィールド, 表示フィールド))」って感じです。これで保存ボタンをクリックするとPostが発生するようになるので次はコントローラーに戻り、Post時のメソッドを書きます。
Post時のUpdateメソッド
[HttpPost] public ActionResult Update(EmployeeUpdate employeeUpdate) { using (var context = new MvcTestContext()) { var overlap = context.Employees.Find(employeeUpdate.Id); if (overlap == null) { context.Employees.Add(new Employee { EmployeeName = employeeUpdate.EmployeeName, AssignId = employeeUpdate.AssignId, SortNumber = (from p in context.Employees select p.SortNumber).Max() + 1 }); } else { overlap.EmployeeName = employeeUpdate.EmployeeName; overlap.AssignId = employeeUpdate.AssignId; } context.SaveChanges(); return RedirectToAction("Index"); } }
[HttpPost]を忘れないよう。これないとPost時に動いてくれません。あれPost時にモデルって渡したっけ?って感じですがどうやら自動的に渡してくれてる模様です。処理自体は最初に考えてたとおりに処理しています。IDの重複があれば更新、なければ追加です。んで最終的にIndexに移動します。深い意味はありませんがSortNumberフィールドは追加時は最大値を取るようにしてます。
以下、実行結果です。
実行結果
追加
更新
追加時、更新時でURLのパラメータの違うのが分かりますね。さて追加・更新ができたので次は削除です。ちょっと疲れてきましたが一気にやってしまいましょう。
社員削除用モデル
public class EmployeeDelete { /// <summary> /// ID /// </summary> public int Id { get; set; } /// <summary> /// 社員名 /// </summary> public string EmployeeName { get; set; } /// <summary> /// 配属先名 /// </summary> public string AssignName { get; set; } }
Indexで使ったEmployeeListモデルと内容が同じになっちゃいましたが一応使い分けが効くように別にモデルを作りました。
Deleteメソッド
public ActionResult Delete(int id = -1) { using (var context = new MvcTestContext()) { var overlap = (from p in context.Employees join g in context.Assigns on p.AssignId equals g.AssignId into gg from ggg in gg.DefaultIfEmpty() where p.EmployeeId == id select new { p.EmployeeId, p.EmployeeName, AssignName = (ggg != null ? ggg.AssignName : string.Empty) }).FirstOrDefault(); if (overlap == null) { return HttpNotFound(); } else { var result = new EmployeeDelete { Id = overlap.EmployeeId, EmployeeName = overlap.EmployeeName, AssignName = overlap.AssignName }; return View(result); } } }
対象IDが見つからなかった場合、「HttpNotFund」を呼び出し、オブジェクトが見つからなかったぞと通知するようにしてます。
Deleteメソッド(Post時)
[HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { using (var context = new MvcTestContext()) { var overlap = context.Employees.Find(id); context.Employees.Remove(overlap); context.SaveChanges(); return RedirectToAction("Index"); } }
前述のDeleteメソッドとメソッド名とパラメータが被っちゃうので別名にし、ActionName属性でアクション(”Delete")を指定してます。
Deleteビュー
@model MVCTestProject.Models.EmployeeDelete @{ ViewBag.Title = "Delete"; } <h2>このデータを削除してもよろしいですか?</h2> <fieldset> <legend>社員データ</legend> <div class="display-label">社員名</div> <div class="display-field"> @Html.DisplayFor(model => model.EmployeeName) </div> <div class="display-label">配属先</div> <div class="display-field"> @Html.DisplayFor(model => model.AssignName) </div> </fieldset> @using (Html.BeginForm()) { <input type="submit" value="Delete" /> @Html.ActionLink("戻る", "Index") }
これは特に言うことも無いでしょう。次に実行結果です。
実行結果
あー、疲れた。とりあえずは思った通りにできたので今回はここまでにします。それにしても予想以上にシンプルな流れで作れるので素直にMVCに感動しました。