檢查編輯方法與編輯檢視 (VB)
本教學課程將說明基本概念,簡介如何使用 Microsoft Visual Web Developer 2010 Express Service Pack 1 (Visual Studio Microsoft 的免費版本) 建置 ASP.NET MVC Web 應用程式。 開始之前,請確定您已安裝下列必要項目。 您可按以下連結安裝所有專案:Web Platform Installer。 或者可使用下列連結個別安裝必要條件:
- Visual Studio Web Developer Express SP1 必要條件
- ASP.NET MVC 3 工具更新
- SQL Server Compact 4.0 (執行階段 + 工作支援)
如果您使用 Visual Studio 2010 而非 Visual Web Developer 2010,請按以下連結安裝必要條件:Visual Studio 2010 必要條件。
本主題隨附內含 VB.NET 原始程式碼的 Visual Web Developer 專案。 下載 VB.NET 版本。 如果您偏好使用 C#,請改參閱本教學課程的 C# 版。
在本節中,我們將檢查針對電影控制器產生的動作方法和檢視。 接著我們將新增自訂搜尋頁面。
執行應用程式並瀏覽至 Movies
控制器,方法是將 /Movies 附加到瀏覽器網址列中的 URL。 將滑鼠指標停留在 [編輯] 連結上,查看將連至的 URL。
Edit 連結是由 Html.ActionLink
Views\Movies\Index.vbhtml 檢視中的 方法所產生:
@Html.ActionLink("Edit", "Edit", New With {.id = currentItem.ID}) |
Html
物件是使用 WebViewPage
基底類別的屬性公開的協助程式。 協助程式的 ActionLink
方法可動態產生 HTML 超連結,輕鬆連結至控制器的動作方法。 ActionLink
方法的第一個引數是需要轉譯的連結文字 (例如 <a>Edit Me</a>
)。 第二個引數是要叫用的動作方法名稱。 最後一個引數是匿名物件,會產生路由資料 (在此例中,ID 為 4)。
上圖顯示的已產生連結為 http://localhost:xxxxx/Movies/Edit/4
。 預設路由{controller}/{action}/{id}
會採用 URL 模式。 因此,ASP.NET 會將 http://localhost:xxxxx/Movies/Edit/4
轉譯為對 Movies
控制器提出 Edit
動作方法要求,其中參數 ID
等於 4。
您也可以使用查詢字串傳遞動作方法參數。 例如,URL http://localhost:xxxxx/Movies/Edit?ID=4
也會將參數 ID
4 傳遞給 Movies
控制器的 Edit
動作方法。
開啟 Movies
控制器。 以下顯示兩種 Edit
動作方法。
'
' GET: /Movies/Edit/5
Function Edit(id As Integer) As ViewResult
Dim movie As Movie = db.Movies.Find(id)
Return View(movie)
End Function
'
' POST: /Movies/Edit/5
<HttpPost()>
Function Edit(movie As Movie) As ActionResult
If ModelState.IsValid Then
db.Entry(movie).State = EntityState.Modified
db.SaveChanges()
Return RedirectToAction("Index")
End If
Return View(movie)
End Function
請注意,第二個 Edit
動作方法的前面是 HttpPost
屬性。 此屬性指定 Edit
方法的多載只能由 POST 要求叫用。 您可以將 HttpGet
屬性套用至第一個編輯方法,但因為這是預設值,因此不一定要執行此動作。 (我們會參照將 HttpGet
屬性隱含指派為 HttpGet
方法的動作方法)。
HttpGet
Edit
方法會採用電影 ID 參數,運用 Entity Framework Find
方法查詢電影,並將選取的電影傳回「編輯」檢視。 當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie
類別,並建立程式碼為類別的每個屬性轉譯 <label>
和 <input>
元素。 以下範例示範產生的編輯檢視:
@ModelType MvcMovie.Movie
@Code
ViewData("Title") = "Edit"
End Code
<h2>Edit</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@Using Html.BeginForm()
@Html.ValidationSummary(True)
@<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(Function(model) model.ID)
<div class="editor-label">
@Html.LabelFor(Function(model) model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(Function(model) model.Title)
@Html.ValidationMessageFor(Function(model) model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(Function(model) model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(Function(model) model.ReleaseDate)
@Html.ValidationMessageFor(Function(model) model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(Function(model) model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(Function(model) model.Genre)
@Html.ValidationMessageFor(Function(model) model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(Function(model) model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(Function(model) model.Price)
@Html.ValidationMessageFor(Function(model) model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
End Using
<div>
@Html.ActionLink("Back to List", "Index")
</div>
請注意檢視範本的檔案頂端有 @ModelType MvcMovie.Models.Movie
陳述式,用來指定預期的檢視範本模型為 Movie
類型。
已建立結構的程式碼會使用數個協助程式方法來簡化 HTML 標記。 Html.LabelFor
協助程式會顯示欄位的名稱 (「Title」、「ReleaseDate」、「Genre」 或 「Price」)。 Html.EditorFor
協助程式會顯示 HTML <input>
元素。 Html.ValidationMessageFor
協助程式則會顯示與該屬性相關聯的任何驗證訊息。
執行應用程式,並前往 /Movies URL。 按一下 Edit 連結。 在瀏覽器中,檢視頁面的原始檔。 頁面顯示的 HTML 外觀類似以下範例。 (為了清楚起見,已排除功能表標記)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Edit</title>
<link href="/Content/Site.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
</head>
<body>
<div class="page">
<header>
<div id="title">
<h1>MVC Movie App</h1>
</div>
...
</header>
<section id="main">
<h2>Edit</h2>
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number."
data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number."
data-val-required="The Price field is required." id="Price" name="Price" type="text" value="9.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
<div>
<a href="/Movies">Back to List</a>
</div>
</section>
<footer>
</footer>
</div>
</body>
</html>
<input>
元素位於 HTML <form>
元素中,後者的 action
屬性設為張貼到 /Movies/Edit URL。 按一下 [編輯] 按鈕,表單資料就會張貼至伺服器。
處理 POST 要求
下列清單顯示 HttpPost
版本的 Edit
動作方法。
'
' POST: /Movies/Edit/5
<HttpPost()>
Function Edit(movie As Movie) As ActionResult
If ModelState.IsValid Then
db.Entry(movie).State = EntityState.Modified
db.SaveChanges()
Return RedirectToAction("Index")
End If
Return View(movie)
End Function
ASP.NET 架構模型繫結器會採用已張貼的表單值,並建立 Movie
物件 (以 movie
參數形式傳遞)。 ModelState.IsValid
會檢查程式碼,確認表單中提交的資料可用於修改 Movie
物件。 如果資料有效,程式碼會將電影資料儲存至 MovieDBContext
執行個體的 Movies
集合。 接著,程式碼會呼叫 MovieDBContext
的 SaveChanges
方法,將新的電影資料儲存至資料庫,以保存資料庫變更。 儲存資料之後,程式碼會將使用者重新導向至 MoviesController
類別的 Index
動作方法,因此電影清單會顯示已更新的電影。
如果張貼的值無效,表單會重新顯示值。 Html.ValidationMessageFor
Edit.vbhtml 檢視範本中的協助程式會負責顯示適當的錯誤訊息。
地區設定的注意事項若您平常都使用英文以外的地區設定,請參閱「支援使用非英文地區設定的 ASP.NET MVC 3 驗證」。
強化編輯方法
建構系統產生的 HttpGet
Edit
方法不會檢查其中傳遞的識別碼是否有效。 如果使用者從 URL (http://localhost:xxxxx/Movies/Edit
) 移除識別碼區段,就會顯示下列錯誤:
使用者也可以傳遞不存在於資料庫的識別碼,例如 http://localhost:xxxxx/Movies/Edit/1234
。 您可以對 HttpGet
Edit
動作方法進行兩項變更,以因應這項限制。 首先,若系統未明確傳遞識別碼,請將 ID
參數變更為預設值等於零。 將電影物件傳回至檢視範本之前,您也可以檢查 Find
方法是否已實際找到電影。 更新的 Edit
方法顯示如下。
Public Function Edit(Optional ByVal id As Integer = 0) As ActionResult
Dim movie As Movie = db.Movies.Find(id)
If movie Is Nothing Then
Return HttpNotFound()
End If
Return View(movie)
End Function
若找不到電影,則呼叫 HttpNotFound
方法。
所有 HttpGet
方法都遵循類似的模式。 它們會取得電影物件 (在 Index
的案例為物件清單),並將模型傳遞至檢視。 Create
方法會將空白電影物件傳遞至「建立」檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 HttpPost
多載中執行這個動作。 修改 HTTP GET 方法中的數據是安全性風險。 修改 GET 方法的資料也不符合 HTTP 最佳做法及架構 REST 模式,此模式會指定 GET 要求不應變更應用程式的狀態。 換句話說,執行 GET 作業應該要安全無虞,沒有其他風險考量。
新增搜尋方法和搜尋檢視
在本節中,您將新增 SearchIndex
動作方法,其可依「類型」或「名稱」搜尋電影。 使用 /Movies/SearchIndex URL 即可取得。 要求會顯示 HTML 表單,其中包含使用者可填入的輸入元素,以便搜尋電影。 使用者提交表單時,動作方法會取得使用者張貼的搜尋值,並將值用於搜尋資料庫。
顯示 SearchIndex 表單
首先,將 SearchIndex
動作方法新增至現有的 MoviesController
類別。 方法會傳回包含 HTML 表單的檢視。 程式碼如下:
Public Function SearchIndex(ByVal searchString As String) As ActionResult
Dim movies = From m In db.Movies
Select m
If Not String.IsNullOrEmpty(searchString) Then
movies = movies.Where(Function(s) s.Title.Contains(searchString))
End If
Return View(movies)
End Function
SearchIndex
方法的第一行會建立以下 LINQ 查詢來選取電影:
Dim movies = From m In db.Movies Select m
目前我們已定義查詢,但尚未對資料存放區執行查詢。
如果 searchString
參數包含字串,則系統會使用下列程式碼修改電影查詢,以篩選搜尋字串的值:
如果不是 String.IsNullOrEmpty(searchString) 則
movies = movies。Where(Function(s) s.Title.Contains(searchString))
結束 If
在您定義 LINQ 查詢,或藉由呼叫 Where
或 OrderBy
這類方法修改 LINQ 查詢時,它並不會執行。 反之,查詢的執行作業將會延遲,即評估運算式會延遲,直到系統逐一查看實現值或呼叫 ToList
方法。 在 SearchIndex
範例中,查詢將於 SearchIndex 檢視中執行。 如需延後查詢執行的詳細資訊,請參閱查詢執行。
接下來,您可以實作 SearchIndex
檢視,向使用者顯示表單。 在 SearchIndex
方法內按滑鼠右鍵,然後按一下 [新增檢視]。 在 [新增檢視] 對話方塊中,指定您要將 Movie
物件傳遞至檢視範本做為模型類別。 在「Scaffold 範本」清單中選擇 [清單],然後按一下 [新增]。
當您按兩下 [ 新增 ] 按鈕時, 會建立 Views\Movies\SearchIndex.vbhtml 檢視範本。 由於您在 Scaffold 範本清單中選取 [清單],因此 Visual Web Developer 會自動在檢視中產生 (建構) 預設內容。 Scaffolding 建立了 HTML 表單。 其中檢查了 Movie
類別,並建立程式碼呈現類別中每個屬性的 <label>
元素。 下列清單顯示產生的建立檢視:
@ModelType IEnumerable(Of MvcMovie.Movie)
@Code
ViewData("Title") = "SearchIndex"
End Code
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Title
</th>
<th>
ReleaseDate
</th>
<th>
Genre
</th>
<th>
Price
</th>
<th></th>
</tr>
@For Each item In Model
Dim currentItem = item
@<tr>
<td>
@Html.DisplayFor(Function(modelItem) currentItem.Title)
</td>
<td>
@Html.DisplayFor(Function(modelItem) currentItem.ReleaseDate)
</td>
<td>
@Html.DisplayFor(Function(modelItem) currentItem.Genre)
</td>
<td>
@Html.DisplayFor(Function(modelItem) currentItem.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", New With {.id = currentItem.ID}) |
@Html.ActionLink("Details", "Details", New With {.id = currentItem.ID}) |
@Html.ActionLink("Delete", "Delete", New With {.id = currentItem.ID})
</td>
</tr>
Next
</table>
執行應用程式,並瀏覽至 /Movies/SearchIndex。 將查詢字串 (例如 ?searchString=ghost
) 附加至 URL。 隨即顯示篩選過的電影。
如果您將 SearchIndex
方法的簽章變更為包含名為 id
的參數,則 id
參數會針對 Global.asax 檔案中設定的預設路由,比對 {id}
預留位置。
{controller}/{action}/{id}
已修改的 SearchIndex
方法如下所示:
Public Function SearchIndex(ByVal id As String) As ActionResult
Dim searchString As String = id
Dim movies = From m In db.Movies
Select m
If Not String.IsNullOrEmpty(searchString) Then
movies = movies.Where(Function(s) s.Title.Contains(searchString))
End If
Return View(movies)
End Function
您現在可以將搜尋標題作為路由資料 (URL 區段) 傳遞,而不是作為查詢字串值。
但是,您不能期望使用者在每次想要搜尋電影時修改 URL。 現在您可新增 UI 來協助篩選電影。 如果您已變更 SearchIndex
方法的簽章,藉此測試如何傳遞路由繫結的 ID 參數,請改回原狀,使 SearchIndex
方法採用名為 searchString
的字串參數:
開啟 Views\Movies\SearchIndex.vbhtml 檔案,然後在 之後@Html.ActionLink("Create New", "Create")
新增下列內容:
@Code
ViewData("Title") = "SearchIndex"
Using (Html.BeginForm())
@<p> Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
End Using
End Code
Html.BeginForm
協助程式會建立開頭的 <form>
標記。 使用者按一下 [篩選] 按鈕提交表單後,Html.BeginForm
協助程式會將表單張貼至表單本身。
執行應用程式並嘗試搜尋電影。
SearchIndex
方法沒有 HttpPost
多載。 您不需要多載,因為此方法不會變更應用程式的狀態,只會篩選資料。 如果您新增了下列HttpPost
SearchIndex
方法,動作叫用程式會符合 SearchIndex
HttpPost
方法,而且 HttpPost
SearchIndex
方法會執行,如下圖所示。
<HttpPost()>
Public Function SearchIndex(ByVal fc As FormCollection, ByVal searchString As String) As String
Return "<h3> From [HttpPost]SearchIndex: " & searchString & "</h3>"
End Function
新增以類型為依據的搜尋
如果您已新增 SearchIndex
方法的 HttpPost
版本,請先將它刪除。
接下來,您會新增一項功能,讓使用者依據類型搜尋電影。 以下列程式碼取代 SearchIndex
方法:
Public Function SearchIndex(ByVal movieGenre As String, ByVal searchString As String) As ActionResult
Dim GenreLst = New List(Of String)()
Dim GenreQry = From d In db.Movies
Order By d.Genre
Select d.Genre
GenreLst.AddRange(GenreQry.Distinct())
ViewBag.movieGenre = New SelectList(GenreLst)
Dim movies = From m In db.Movies
Select m
If Not String.IsNullOrEmpty(searchString) Then
movies = movies.Where(Function(s) s.Title.Contains(searchString))
End If
If String.IsNullOrEmpty(movieGenre) Then
Return View(movies)
Else
Return View(movies.Where(Function(x) x.Genre = movieGenre))
End If
End Function
這個版本的 SearchIndex
方法會採用額外參數,也就是 movieGenre
。 前幾行程式碼會建立 List
物件,藉此保存資料庫中的電影類型。
下列程式碼是一種 LINQ 查詢,其會從資料庫中擷取所有的內容類型。
Dim GenreQry = From d In db.Movies
Order By d.Genre
Select d.Genre
程式碼會使用一般的 List
集合的 AddRange
方法,將所有不同類型新增至清單。 (如果沒有 Distinct
修飾詞,系統會新增重複的類型,例如在我們的範例中,喜劇就會新增兩次)。 程式碼接著會將類型清單儲存在 ViewBag
物件中。
下列程式碼示範如何檢查 movieGenre
參數。 如果參數不是空白,程式碼會進一步限制電影查詢,將選取的電影限為指定類型。
If String.IsNullOrEmpty(movieGenre) Then
Return View(movies)
Else
Return View(movies.Where(Function(x) x.Genre = movieGenre))
End If
將標記新增到 SearchIndex 檢視,支援依類型搜尋
Html.DropDownList
將協助程式新增至 Views\Movies\SearchIndex.vbhtml 檔案,就在協助程式之前TextBox
。 完成的標記如下所示:
<p>
@Html.ActionLink("Create New", "Create")
@Code
ViewData("Title") = "SearchIndex"
Using (Html.BeginForm())
@<p> Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
End Using
End Code
</p>
執行應用程式,並瀏覽至 /Movies/SearchIndex。 嘗試依類型、電影名稱搜尋,以及同時用這兩個條件搜尋。
在本節中,您檢查了由架構產生的 CRUD 動作方法和檢視。 您建立了搜尋動作方法和檢視,讓使用者依電影標題和類型進行搜尋。 下一節,您將瞭解如何將屬性新增至 Movie
模型,以及如何新增初始設定式,自動建立測試資料庫。