第 5 部分:編輯表單和範本
作者 :JonGaloway
MVC 音樂市集是一個教學課程應用程式,會介紹並說明如何使用 ASP.NET MVC 和 Visual Studio 進行 Web 開發。
MVC 音樂市集是輕量型的市集實作,可線上銷售音樂相簿,並實作基本網站管理、使用者登入和購物車功能。
本教學課程系列詳細說明建置 ASP.NET MVC 音樂市集範例應用程式所採取的所有步驟。 第 5 部分涵蓋編輯表單和範本化。
在過去一章中,我們正在從資料庫載入資料並加以顯示。 在本章中,我們也會啟用編輯資料。
建立 StoreManagerController
我們將從建立名為 StoreManagerController的新控制器開始。 在此控制器中,我們將利用 ASP.NET MVC 3 工具更新中提供的 Scaffolding 功能。 設定 [新增控制器] 對話方塊的選項,如下所示。
當您按一下 [新增] 按鈕時,您會看到 ASP.NET MVC 3 Scaffolding 機制會為您執行良好的工作:
- 它會使用本機 Entity Framework 變數建立新的 StoreManagerController
- 它會將 StoreManager 資料夾新增至專案的 Views 資料夾
- 它會將 Create.cshtml、Delete.cshtml、Details.cshtml、Edit.cshtml 和 Index.cshtml 檢視新增至 Album 類別的強型別
新的 StoreManager 控制器類別包含 CRUD (建立、讀取、更新、刪除) 控制器動作,這些動作知道如何使用相簿模型類別,並使用 Entity Framework 內容進行資料庫存取。
修改 Scaffolded 檢視
請務必記住,雖然我們已為我們產生此程式碼,但這是標準 ASP.NET MVC 程式碼,就像我們在本教學課程中一樣。 其旨在節省您在撰寫重複使用控制器程式碼和手動建立強型別檢視時所花費的時間,但這不是您可能在批註中看到有 dire 警告的產生程式碼類型。 這是您的程式碼,您應該變更它。
因此,讓我們從快速編輯 StoreManager 索引檢視開始, (/Views/StoreManager/Index.cshtml) 。 此檢視會顯示資料表,其中列出我們存放區中具有編輯/詳細資料/刪除連結的相簿,並包含相簿的公用屬性。 我們將移除 [AlbumArtUrl] 欄位,因為它在此顯示中並不實用。 在 < 檢視程式碼的資料表 > 區段中,移除 < AlbumArtUrl 參考周圍的 th > 和 < td > 元素,如以下醒目提示的行所示:
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th>
AlbumArtUrl
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.AlbumArtUrl)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>
修改過的檢視程式碼會顯示如下:
@model IEnumerable<MvcMusicStore.Models.Album>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create
New", "Create")
</p>
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>
第一次查看市集管理員
現在執行應用程式並流覽至 /StoreManager/。 這會顯示我們剛才修改的市集管理員索引,其中顯示市集中的相簿清單,其中包含 [編輯]、[詳細資料] 和 [刪除] 的連結。
按一下 [編輯] 連結會顯示包含 [相簿] 欄位的編輯表單,包括 [內容類型] 和 [作者] 的下拉式清單。
按一下底部的 [返回清單] 連結,然後按一下 [相簿的詳細資料] 連結。 這會顯示個別相簿的詳細資訊。
同樣地,按一下 [返回清單] 連結,然後按一下 [刪除] 連結。 這會顯示確認對話方塊,顯示相簿詳細資料,並詢問我們是否確定要刪除它。
按一下底部的 [刪除] 按鈕將會刪除相簿,並返回 [索引] 頁面,其中顯示已刪除的相簿。
我們不會使用市集管理員,但我們有工作控制器和檢視 CRUD 作業的程式碼,以便從中開始。
查看市集管理員控制器程式碼
市集管理員控制器包含大量的程式碼。 讓我們從上到下進行此動作。 控制器包含 MVC 控制器的一些標準命名空間,以及模型命名空間的參考。 控制器具有 MusicStoreEntities 的私人實例,由每個控制器動作用於資料存取。
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;
namespace MvcMusicStore.Controllers
{
public class StoreManagerController : Controller
{
private MusicStoreEntities db = new MusicStoreEntities();
市集管理員索引和詳細資料動作
索引檢視會擷取一份相簿清單,包括每個相簿參考的內容類型和藝術師資訊,如我們先前在市集流覽方法上所見。 [索引] 檢視會遵循連結化物件的參考,以便顯示每個相簿的 [內容類型] 名稱和 [作者名稱],因此控制器在原始要求中有效並查詢此資訊。
//
// GET: /StoreManager/
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist);
return View(albums.ToList());
}
StoreManager 控制器的詳細資料控制器動作與先前撰寫的市集控制器詳細資料動作完全相同-它會使用 Find () 方法來查詢相簿,然後將它傳回檢視。
//
// GET: /StoreManager/Details/5
public ViewResult Details(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
建立動作方法
建立動作方法與我們到目前為止看到的動作方法稍有不同,因為它們會處理表單輸入。 當使用者第一次造訪 /StoreManager/Create/時,將會顯示空白表單。 此 HTML 頁面會包含 < 表單 > 元素,其中包含下拉式清單和文字方塊輸入元素,可在其中輸入相簿的詳細資料。
當使用者填入 [相簿] 表單值之後,他們可以按 [儲存] 按鈕,將這些變更提交回我們的應用程式,以儲存在資料庫內。 當使用者按下 [儲存] 按鈕時, < 表單 > 會執行 HTTP-POST 回到 /StoreManager/Create/ URL,並將表單 > 值提交 < 為 HTTP-POST 的一部分。
ASP.NET MVC 可讓我們輕鬆地分割這兩個 URL 叫用案例的邏輯,方法是讓我們在 StoreManagerController 類別內實作兩個不同的「建立」動作方法–一個用來處理初始 HTTP-GET 流覽至 /StoreManager/Create/ URL,另一個用來處理提交變更的 HTTP-POST。
使用 ViewBag 將資訊傳遞至檢視
我們稍早在本教學課程中使用 ViewBag,但尚未討論過。 ViewBag 可讓我們在不使用強型別模型物件的情況下,將資訊傳遞至檢視。 在此情況下,我們的 [編輯 HTTP-GET 控制器] 動作需要將內容類型清單和藝術師清單傳遞至表單,以填入下拉式清單,最簡單的方式就是將其傳回為 ViewBag 專案。
ViewBag 是動態物件,這表示您可以輸入 ViewBag.Foo 或 ViewBag.YourNameHere,而不需撰寫程式碼來定義這些屬性。 在此情況下,控制器程式碼會使用 ViewBag.GenreId 和 ViewBag.ArtistId,讓以表單提交的下拉式清單值會是 GenreId 和 ArtistId,也就是它們將會設定的相簿屬性。
這些下拉式清單值會使用 SelectList 物件傳回至表單,此物件只是為了該目的而建置。 這是使用類似以下的程式碼來完成:
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
如您在動作方法程式碼中所見,使用三個參數來建立此物件:
- 下拉式清單將會顯示的專案清單。 請注意,這不只是字串 - 我們會傳遞內容類型清單。
- 要傳遞至 SelectList 的下一個參數是 [選取的值]。 這讓 SelectList 知道如何預先選取清單中的專案。 當我們查看 [編輯] 表單時,這會更容易瞭解,這很類似。
- 最後一個參數是要顯示的 屬性。 在此情況下,這表示 Genre.Name 屬性會顯示給使用者。
請記住,HTTP-GET Create 巨集指令相當簡單 - 兩個 SelectList 會新增至 ViewBag,而且沒有模型物件傳遞至表單 (,因為它尚未建立) 。
//
// GET: /StoreManager/Create
public ActionResult Create()
{
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");
return View();
}
在建立檢視中顯示下拉式清單的 HTML 協助程式
由於我們已討論如何將下拉式清單值傳遞至檢視,讓我們快速查看檢視,以查看這些值如何顯示。 在檢視程式碼 (/Views/StoreManager/Create.cshtml) 中,您會看到下列呼叫顯示 [內容類型] 下拉式清單。
@Html.DropDownList("GenreId",
String.Empty)
這稱為 HTML 協助程式 - 執行一般檢視工作的公用程式方法。 HTML 協助程式在保持我們的檢視程式碼簡潔且易讀時非常有用。 Html.DropDownList 協助程式是由 ASP.NET MVC 提供,但如稍後所見,我們可以建立自己的協助程式以檢視應用程式內重複使用的程式碼。
Html.DropDownList 呼叫只需要告知兩件事: 要顯示清單的位置,以及應該預先選取任何) 時 (哪些值。 第一個參數 GenreId 會告訴 DropDownList 在模型或 ViewBag 中尋找名為 GenreId 的值。 第二個參數是用來指出值,以在下拉式清單中顯示為最初選取的值。 由於此表單是建立表單,因此不會預先選取任何值,而且會傳遞 String.Empty。
處理張貼的表單值
如先前所述,每個表單都有兩個相關聯的動作方法。 第一個會處理 HTTP-GET 要求,並顯示表單。 第二個會處理 HTTP-POST 要求,其中包含提交的表單值。 請注意,控制器動作具有 [HttpPost] 屬性,告知 ASP.NET MVC 應該只回應 HTTP-POST 要求。
//
// POST: /StoreManager/Create
[HttpPost]
public ActionResult Create(Album album)
{
if (ModelState.IsValid)
{
db.Albums.Add(album);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
此動作有四個責任:
-
- 讀取表單值
-
- 檢查表單值是否通過任何驗證規則
-
- 如果表單提交有效,請儲存資料並顯示更新的清單
-
- 如果表單提交無效,請重新顯示含有驗證錯誤的表單
使用模型系結讀取表單值
控制器動作正在處理表單提交,其中包含從下拉式清單) 和 Title、Price 和 AlbumArtUrl 的文字方塊值 (的 GenreId 和ArtId 值。 雖然可以直接存取表單值,但更好的方法是使用內建 ASP.NET MVC 中的模型系結功能。 當控制器動作採用模型類型做為參數時,ASP.NET MVC 會嘗試使用表單輸入 (填入該類型的物件,以及路由和查詢字串值) 。 它會尋找名稱符合模型物件屬性的值,例如,設定新 Album 物件的 GenreId 值時,它會尋找名稱為 GenreId 的輸入。 當您在 ASP.NET MVC 中使用標準方法建立檢視時,表單一律會使用屬性名稱轉譯為輸入功能變數名稱,因此功能變數名稱只會相符。
驗證模型
模型會使用對 ModelState.IsValid 的簡單呼叫進行驗證。 我們尚未將任何驗證規則新增至 [相簿] 類別,我們會稍微這麼做,因此現在這項檢查並不需要太多動作。 重要的是,此 ModelStat.IsValid 檢查會適應我們在模型上放置的驗證規則,因此未來對驗證規則所做的變更不需要對控制器動作程式碼進行任何更新。
儲存送出的值
如果表單提交通過驗證,就可以將值儲存至資料庫。 使用 Entity Framework 時,只需要將模型新增至 Albums 集合並呼叫 SaveChanges。
db.Albums.Add(album);
db.SaveChanges();
Entity Framework 會產生適當的 SQL 命令來保存值。 儲存資料之後,我們會重新導向回 「相簿」清單,讓我們可以看到我們的更新。 這是透過傳回重新導向ToAction,並傳回我們想要顯示的控制器動作名稱。 在此情況下,這是 Index 方法。
顯示不正確表單提交與驗證錯誤
在表單輸入不正確情況下,下拉式清單值會新增至 ViewBag (,如 HTTP-GET 案例) ,而系結的模型值會傳回檢視以供顯示。 驗證錯誤會使用 @Html.ValidationMessageFor HTML 協助程式自動顯示。
測試建立表單
若要測試此內容,請執行應用程式並流覽至 /StoreManager/Create/ - 這會顯示 StoreController Create HTTP-GET 方法所傳回的空白表單。
填寫一些值,然後按一下 [建立] 按鈕以提交表單。
處理編輯
[編輯] 動作組 (HTTP-GET 和 HTTP-POST) 非常類似我們剛才查看的建立動作方法。 由於編輯案例牽涉到使用現有的相簿,所以 Edit HTTP-GET 方法會根據透過路由傳入的 「id」 參數載入相簿。 此程式碼可擷取由 AlbumId 擷取相簿的程式碼,與先前在詳細資料控制器動作中查看的程式碼相同。 如同 Create / HTTP-GET 方法,下拉式清單值會透過 ViewBag 傳回。 這可讓我們將「相簿」當做模型物件傳回檢視 (,此檢視) ,同時傳遞其他資料 (例如透過 ViewBag 傳遞內容類型清單) 。
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
[編輯 HTTP-POST] 動作與建立 HTTP-POST 動作非常類似。 唯一的差別在於,不要將新相簿新增至 db。相簿集合,我們會使用 db 尋找目前相簿的實例。專案 () ,並將其狀態設定為 [已修改]。 這會告知 Entity Framework 我們正在修改現有的相簿,而不是建立新的相簿。
//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
我們可以執行應用程式並流覽至 /StoreManger/,然後按一下相簿的 [編輯] 連結來測試此專案。
這會顯示 Edit HTTP-GET 方法所顯示的 [編輯] 表單。 填寫一些值,然後按一下 [儲存] 按鈕。
這會張貼表單、儲存值,並將我們傳回至 [相簿] 清單,顯示值已更新。
處理刪除
刪除會遵循與編輯和建立相同的模式,使用一個控制器動作來顯示確認表單,以及另一個控制者動作來處理表單提交。
HTTP-GET Delete 控制器動作與先前的市集管理員詳細資料控制器動作完全相同。
//
// GET: /StoreManager/Delete/5
public ActionResult Delete(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
我們會使用 [刪除] 檢視內容範本,顯示強型別為 [相簿] 類型的表單。
[刪除] 範本會顯示模型的所有欄位,但我們可以簡化這一點。 將 /Views/StoreManager/Delete.cshtml 中的檢視程式碼變更為下列內容。
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Delete";
}
<h2>Delete Confirmation</h2>
<p>Are you sure you want to delete the album titled
<strong>@Model.Title</strong>?
</p>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" />
</p>
<p>
@Html.ActionLink("Back to
List", "Index")
</p>
}
這會顯示簡化的 [刪除] 確認。
按一下 [刪除] 按鈕會導致表單張貼回伺服器,以執行 DeleteConfirmed 巨集指令。
//
// POST: /StoreManager/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Album album = db.Albums.Find(id);
db.Albums.Remove(album);
db.SaveChanges();
return RedirectToAction("Index");
}
我們的 HTTP-POST 刪除控制器動作會採取下列動作:
-
- 依識別碼載入相簿
-
- 刪除該相簿並儲存變更
-
- 重新導向至索引,其中顯示已從清單中移除相簿
若要測試此專案,請執行應用程式並流覽至 /StoreManager。 從清單中選取一張相簿,然後按一下 [刪除] 連結。
這會顯示 [刪除] 確認畫面。
按一下 [刪除] 按鈕會移除相簿,並返回 [市集管理員索引] 頁面,其中顯示已刪除該相簿。
使用自訂 HTML 協助程式截斷文字
我們的市集管理員索引頁面有一個潛在問題。 我們的相簿標題和藝術師名稱屬性可能夠長,足以擲回我們的表格格式。 我們將建立自訂 HTML 協助程式,以讓我們輕鬆地截斷檢視中的這些和其他屬性。
Razor 的 @helper 語法可讓您輕鬆地建立自己的協助程式函式,以在檢視中使用。 開啟 /Views/StoreManager/Index.cshtml 檢視,並在行後面 @model 直接新增下列程式碼。
@helper Truncate(string
input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
這個協助程式方法會採用字串和允許的最大長度。 如果提供的文字比指定的長度短,協助程式會依原樣輸出。 如果較長,則會截斷文字並轉譯 「...」其餘部分。
現在,我們可以使用截斷協助程式來確保[相簿標題] 和 [作者名稱] 屬性都小於 25 個字元。 使用我們新的截斷協助程式的完整檢視程式碼會出現在下方。
@model IEnumerable<MvcMusicStore.Models.Album>
@helper Truncate(string input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create
New", "Create")
</p>
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Truncate(item.Artist.Name, 25)
</td>
<td>
@Truncate(item.Title, 25)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>
現在當我們流覽 /StoreManager/ URL 時,相簿和標題會維持在最大長度之下。
注意:這會顯示在一個檢視中建立和使用協助程式的簡單案例。 若要深入瞭解如何建立可在整個網站中使用的協助程式,請參閱我的部落格文章: http://bit.ly/mvc3-helper-options