使用樣板(Template)綁定(Binding)整個List資料進行編輯
緣起
通常在設計上都會希望一次針對單筆資料進行異動,但有時候確實需要針對一系列的資訊進行編輯(例如:修改購物車內各商品購買數量),這時候就需要對List中的各筆資料進行資料綁定(Data Binding)後,將所有Input資料Post傳回Server以接續後端資料庫異動作業。
實作
當筆者初次接觸MVC時,針對使用者待做事項清單的異動需求寫了以下的Code。
介面如下圖所示,按下Save後會將所有待做事項資料Post到Server端以更新清單內所有資料。
Models
// 使用者
public class User
{
public string UserId { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
// 使用者待做事項
public class UserTask
{
public string UserTaskId { get; set; }
public string TaskName { get; set; }
public DateTime CompletedDate { get; set; }
// FK
public string UserId { get; set; }
// Navigation
public virtual User User { get; set; }
}
Controller
public ActionResult Edit()
{
// prepare default view model for testing
var viewModel = new ViewModels.Task.IndexTaskViewModel();
viewModel.User = new Models.User() { Name = "Chirs Chen", Phone = "0800-000-000", UserId = "Chris" };
viewModel.UserTasks = new List<Models.UserTask>()
{
new Models.UserTask() {UserId = "Chris", TaskName="do something 1", CompletedDate = System.DateTime.Now},
new Models.UserTask() {UserId = "Chris", TaskName="do something 2", CompletedDate = System.DateTime.Now},
new Models.UserTask() {UserId = "Chris", TaskName="do something 3", CompletedDate = System.DateTime.Now},
new Models.UserTask() {UserId = "Chris", TaskName="do something 4", CompletedDate = System.DateTime.Now},
};
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(ViewModels.Task.IndexTaskViewModel viewModel)
{
// save ...
return RedirectToAction("Index");
}
View Model
public class IndexTaskViewModel
{
public User User { get; set; }
public List<UserTask> UserTasks { get; set; }
}
View
@model JsWebApp.ViewModels.Task.IndexTaskViewModel
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].TaskName)
</th>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].CompletedDate)
</th>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].UserId)
</th>
<th></th>
</tr>
@foreach (var item in Model.UserTasks)
{
@Html.HiddenFor(model => item.UserTaskId)
<tr>
<td>
@Html.EditorFor(model => item.TaskName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => item.TaskName, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => item.CompletedDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => item.CompletedDate, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => item.UserId, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => item.UserId, "", new { @class = "text-danger" })
</td>
</tr>
}
</table>
<input type="submit" value="Save" class="btn btn-default" />
}
問題發生
依照上述方式實作後,畫面完全依照需求呈現;但是,當筆者要著手處理Controller中接收之View Model資料時,卻赫然發現內容都是空白!! 查詢了一下頁面Html後發現,透過Razor語法產出清單內各筆Element的Name都是相同的,所以在Post資料到Server端的時候,MVC無法將所有資料解析綁定至View Model中。
解決方式
方法一、使用 For Loop 取代 Foreach Loop 呈現資料
如此即可以透過Razor語法以陣列方式產生Element Name,並藉由MVC剖析Post至Server的資料,直接Binding至ViewModel中供Controller使用。
@model JsWebApp.ViewModels.Task.IndexTaskViewModel
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].TaskName)
</th>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].CompletedDate)
</th>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].UserId)
</th>
<th></th>
</tr>
<!-- Using For Loop to Get Correct Element Name -->
@for (int i = 0; i < Model.UserTasks.Count; i++)
{
@Html.HiddenFor(model => Model.UserTasks[i].UserTaskId)
<tr>
<td>
@Html.EditorFor(model => Model.UserTasks[i].TaskName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => Model.UserTasks[i].TaskName, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => Model.UserTasks[i].CompletedDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => Model.UserTasks[i].CompletedDate, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => Model.UserTasks[i].UserId, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => Model.UserTasks[i].UserId, "", new { @class = "text-danger" })
</td>
</tr>
}
</table>
<input type="submit" value="Save" class="btn btn-default" />
}
方法二、使用Edit Template進行實作
於Views/Shared中建立EditorTemplates資料夾,並在該資料夾下建立我們所需之Template View (注意名稱必須與對應類別名稱相符),並在此Template中明確定義Model以及希望在修改此類別物件時所呈現的畫面。
@model JsWebApp.Models.UserTask
@Html.HiddenFor(model => model.UserTaskId)
<tr>
<td>
@Html.EditorFor(model => model.TaskName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.TaskName, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => model.CompletedDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.CompletedDate, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => model.UserId, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UserId, "", new { @class = "text-danger" })
</td>
</tr>
在View中我們僅需要使用 Html.EditorFor() 方法引入需修改的物件,MVC將會自動依照類別型態找到對應的Template,並依照Template中定義的修改畫面進行呈現。另外,在轉換的過程中當發現目標修改物件為集合時,MVC也會很聰明地將集合中的每一筆資料依照Template中制定的方式逐一呈現,如同此例筆者並未使用任何迴圈來處理集合內的各物件,卻可以在畫面上呈現出集合內所有資料。
@model JsWebApp.ViewModels.Task.IndexTaskViewModel
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].TaskName)
</th>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].CompletedDate)
</th>
<th>
@Html.DisplayNameFor(model => model.UserTasks[0].UserId)
</th>
</tr>
<!-- Using Edit Template to Edit List Data -->
@Html.EditorFor(model => model.UserTasks)
</table>
<input type="submit" value="Save" class="btn btn-default" />
}
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !