原址:http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/getting-started-with-mvc3-part6-cs
這個教程將會使用Microsoft Visual Web Developer 2010 Express Service Pack 1來教會您構建一個基於ASP.NET MVC Web應用。 在您開始之前,請確保已經安裝了下面羅列的必備條件。您可以點擊接下來的鏈接來下載它們:Web Platform Installer。或者您可以使用下面的鏈接來單個安裝:
如果您使用的是Visual Studio 2010, 可以點擊接下來的鏈接來安裝這些必備條件: Visual Studio 2010 prerequisites.
在這個Visual Web Developer項目中將會全程使用c#. Download the C# version.。如果您比較擅長VB, 可以在這個教程中改爲VB Visual Basic version
在這次章節中,您將會檢查一下在movie控制器(Controller)中生成的方法和視圖(View)。 之後,您將會添加一個定製的搜索頁面。
啓動應用和瀏覽器,在瀏覽器中工具欄的地址欄中追加/Movies ,然後鏈接到Movies控制器。將鼠標指針停留在Edit鏈接上,看看這時顯示的URL鏈接到哪裏。
這個Edit鏈接是Views\Movies\Index.cshtml 視圖(View)中通過Html.ActionLink
方法生成的。
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
這個Html對象是一個幫助器,這個幫助器是在WebViewPage
基類中暴露的一個屬性。這個幫助器中的ActionLink
方法簡單的動態生成了一段HTML代碼去鏈接到控制器(controllers)中的action方法。ActionLink方法的第一個參數是在頁面中呈現一段鏈接文本(例如,<a>Edit
Me</a>
) ,第二個參數是需要調用的action方法,最後的一個參數是一個匿名對象 ,用於生成路由數據(在這個列子中,就是ID 4)。
在上一張圖片中生成的鏈接是http://localhost:xxxxx/Movies/Edit/4 。這個默認的路由匹配的URL格式是{controller}/{action}/{id}。因此,ASP.NET解釋了http://localhost:xxxxx/Movies/Edit/4
轉化成一個請求,帶着ID是4的參數鏈接到Movies控制器(controller)中的一個Edit
action方法 。
您也能使用一段查詢字符串來傳遞action方法中的參數。例如,URL http://localhost:xxxxx/Movies/Edit?ID=4 也是傳遞ID是4的參數到Movies控制器(controller)中的Edit actin方法。
打開Movies控制器(controller)。這兩個Edit action方法顯示如下。
//
// GET: /Movies/Edit/5
public ActionResult Edit(int id)
{
Movie movie = db.Movies.Find(id);
return View(movie);
}
//
// POST: /Movies/Edit/5
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
注意第2個Edit action方法,在方法之前有一個HttpPost
屬性。這個屬性指定了Edit的重載方法僅僅被一個POST的請求調用。您可以應用HttpGet
屬性在第一個edit方法中,但是這不是必須需的,因爲默認就是這樣。(我們提交的action方法,默認的是在方法上加了HttpGet屬性的HttpGet方法。)
這個HttpGet方法擁有一個電影ID參數,使用Entity Framework找到方法查找電影,並且將查詢後的電影返回到Edit視圖(view)。當這個系統在創建了Edit視圖(view)時,它檢查了Movie類並且爲每個屬性創建了<label>
和<input>
標籤用於在視圖中呈現。下面的例子展現了生成後的Edit視圖(view):
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Edit";
}
<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(model => model.ID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
注意在這個視圖(view)模板文件的頂部怎麼會有一段@model MvcMovie.Models.Movie聲明
— 這指明瞭在視圖(view)模板中mode的類型。
這段代碼使用了幾個幫助器(helper)方法去生成HTML標籤。這個Html.LabelFor
幫助器(helper)用來顯示字段的名稱(Title", "ReleaseDate", "Genre", or "Price")。這個TheHtml.EditorFor
幫助器(helper)顯示一段HTML<input>元素。這個Html.ValidationMessageFor
幫助器(helper)用來顯示關聯屬性的驗證信息。
啓動應用導航到/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>
在一段HTML <form>元素中包含了一個<input>元素,它觸發一個表單提交,通過<form>元素的action屬性將信息透過post的方式請求到/Movies/Edit URL.。當點擊Edit按鈕的時候,這個表單信息將會發送到服務器。
POST請求過程
下面的監聽顯示了action方法的一個HttpPost版本。
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
ASP.NET framework模型綁定器(model binder)攜帶了一個創建的Movie對象(這個對象被當做參數)進行傳遞。ModelState.IsValid在代碼中是用來執行驗證,因爲數據從表單中提交,時長會修改一個Movie對象,如果這個數據是有效的,這段代碼將把電影信息保存到
MovieDBContext實例中,在調用
MovieDBContext中的
SaveChanges方法後,這個新的電影信息將會保存到數據庫中。在保存數據以後,這段代碼將會重定向用戶到
MoviesController類的Index
action方法中,然後會更新電影信息,在電影列表中顯示
。
如果傳遞的值無效,將會在表單中重新顯示這些值。在Edit.cshtml 視圖(view)模板中的Html.ValidationMessageFor
幫助器(helpers)會顯示這些錯誤信息。
讓Edit方法變得更加穩健
通過系統自動生成的這個HttpGet Edit方法沒有檢查ID的有效性。如果一個用戶從URL(http://localhost:xxxxx/Movies/Edit)中移除了ID,就會顯示下面的錯誤信息:
一個用戶可能也會輸入一個數據庫中不存在的ID,比如http://localhost:xxxxx/Movies/Edit/1234 。您可以在HttpGet Edit action方法中改變兩個地方來改善這個問題。首先,當沒有準確的輸入一個ID的時候,讓ID參數具有一個默認值0,您也能在返回一個movie對象到視圖(view)前,通過Find方法檢查這個movie是否存在,更新後的Edit方法如下所示。
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
如果沒有電影被找到,這個HttpNotFound
方法會被調用。
所有的HttpGet方法都有一個相似的模式, 他們獲得一個movie對象(或者在Index中,是一個對象列表),並且傳遞model到視圖(view)。這個Create方法傳送一個空的movie對象到Create視圖(view)。所有的方法(添加, 修改, 刪除或其他的數據修改)都是HttpPost重載方法。 通過HTTP GET方法來修改數據會有安全風險,如在ASP.NET MVC Tip #46 – Don’t use Delete Links because they create Security Holes這篇博文中有相關的描述。通過一個GET方法來修改數據也違反了HTTP最佳實踐和REST架構。指定GET請求將不會改變您的應用的狀態,換句話說,執行一個GET操作是一個沒有效果的安全操作。
增加搜索方法和視圖(View)
在這個章節中,您將增加一個SearchIndex的action方法,這樣就可以根據電影的類型或名稱搜索電影,之後Movies/SearchIndex這個URL將會變得可用。
這個請求將會顯示一個包含了input元素的HTML表單,用戶可以填寫信息來搜索一部電影。當一個用戶提交了表單,這個action方法將會使用用戶輸入的信息去數據庫中搜索相關的電影,並返回。
顯示SearchIndex表單
向已存在的MoviesController類中增加一個
SearchIndex的action方法。這個方法將會返回包含了一段HTML表單的視圖(view),下面是具體的代碼:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
SearchIndex方法的第一行使用了下面的LINQ 查詢語句去篩選相關的電影:
var movies = from m in db.Movies
select m;
在這個地方的查詢表達式,至今還沒有跟數據庫打交道。
如果這個searchString
參數包含了一段查詢所用的字符串,電影查詢就要修改成根據搜索的字符串進行篩選,使用下面的代碼:
if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); }
當定義LINQ查詢的時候或者當LINQ查詢調用瞭如Where或者OrderBy語句進行修改時,這些語句還沒有被執行,換句話說,LINQ查詢是延遲執行的,這意味着要麼重複這個表達式或者直到調用了ToList
方法後,纔會被執行。在這個SearchIndex示例中,查詢是在SearchIndex視圖(view)中被執行了。 要查閱更多的延遲查詢資料,看看Query
Execution。
現在您能夠完成SearchIndex視圖(view),用來給用戶顯示錶單信息。在這個SearchIndex方法內右鍵,然後點擊Add View。在Add View 對話框中,指定您要傳送到視圖(view)模板的Movie對象。在Scaffold template 列表中選擇List,最後點擊Add。
當您單擊Add 按鈕時,Views\Movies\SearchIndex.cshtml 視圖(view)模板被創建了,因爲您在Scaffold template 列表中選擇了List,Visual Web Developer 會給視圖(view)自動的生成一些默認的內容,生成器創建了一個表單,它分析了Movie類,並用代碼給每個屬性創建了<Label>元素。下面的代碼展示了生成後的視圖(view):
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<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>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
啓動應用導航到/Movies/SearchIndex,在URL中追加?searchString=ghost查詢字符串,篩選後的電影顯示如下。
如果您將SearchIndex
方法的參數名改爲id,這個參數就會匹配到Global.asax文件中定義的默認路由的{id}。
{controller}/{action}/{id}
改變SearchIndex
方法後如下所示:
public ActionResult SearchIndex(string id)
{
string searchString = id;
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
您現在可以按照路由規則輸入搜索信息來代替之前的搜索字符串了。
然而,您不能期望用戶在他們想要搜索一部電影的時候每次去修改這個URL。因此,現在您將要增加UI來幫助他們篩選電影。如果您剛剛爲了測試路由規則把參數改成了ID,那麼現在還是把參數改回成原來的searchString,如下所示:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
打開Views\Movies\SearchIndex.cshtml 文件,並且在@Html.ActionLink("Create New", "Create")之後添加下面的代碼:
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
下面的例子展示了在Views\Movies\SearchIndex.cshtml中添加篩選器後的部分代碼。
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
這個Html.BeginForm
幫助器(helper)創建了一個<form>標籤。當用戶單擊Filter按鈕提交表單時,這個Html.BeginForm幫助器(helper)會將表單信息提交給控制器(Cotroller)。
啓動這個應用並且嘗試搜索一部電影。
這兒沒有重載HttpPost的SearchIndex方法,您不需要它,因爲這個方法不會改變應用的狀態,僅僅是用來篩選數據。
您可以增加下面的HttpPost的SearchIndex方法。在這次示例中,action調用了匹配的
SearchIndex方法,並且這個方法將會顯示下面的圖片信息。HttpPost的
[HttpPost]
public string SearchIndex(FormCollection fc, string searchString)
{
return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>";
}
然而,即使您給SearchIndex
方法增加了這個HttpPost版本, 有一個疑問,它是怎麼被完全執行的, 想象一下,您想要標記一個特殊的搜索或者您想要給一個朋友發送一個鏈接,這樣,他們可以看到一些篩選後的電影列表。注意到通過HTTP POST請求的URL和通過HTTP GET請求的URL是相同的(localhost:xxxxx/Movies/SearchIndex)--在它自己的URL中沒有搜索信息,現在,這個搜索字符串信息是被作爲一個表單字段值發送到服務器,這意味着您不能在一個URL中捕獲這個搜索信息去標記或者發送給朋友。
這個解決方案是在一個重載的BeginForm上指定了POST請求,把搜索信息增加到URL上並且之後路由到
。像下面一樣替換SearchIndex
方法的一個HttpGet版本上BeginForm
中的參數:
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))
現在,當您提交一個搜索時,這個URL包含了搜索查詢字符串。即使您現在有一個HttpPost的SearchIndex方法,搜索也只會提交到一個HttpGet的SearchIndex action方法。
按照類型進行搜索
如果您給SearchIndex
方法增加了一個HttpPost版本,那麼,現在刪除它。
接下來,您將會增加一個特性,來根據類型搜索電影。把SearchIndex
方法替換成下面的代碼:
這個版本的SearchIndex
方法增加了一個參數,參數名稱是movieGenre
,代碼的第一行創建了一個List對象去存放從數據庫中篩選的電影類型。
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
下面的代碼是用來從數據庫中查詢出所有類型的LINQ查詢語句。
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
代碼使用了List集合的
AddRange方法去增加所有的不重複的類型到列表中。
(沒有這個Distinect修飾語,重複的類型將會被添加—例如,喜劇片在這個示例中將會被添加兩次)。接下來的代碼將這個篩選後的類型集合存放到ViewBag對象中。
下面的代碼展示了怎麼檢查movieGenre
參數。如果這個參數不是空的,就需要在電影中根據指定的類型進行篩選。
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
在視圖(View)中增加標籤,來支持根據類型進行搜索
在Views\Movies\SearchIndex.cshtml文件中,在TextBox幫助器(helper)前
增加一個Html.DropDownList
幫助器(helper),完成後的標籤如下所示:
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p>Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
</p>
啓動應用和瀏覽器鏈接到/Movies/SearchIndex。嘗試根據類型和電影名稱進行搜索。
在這章中您檢查了CRUD操作方法並且使用framework來生成視圖(view)。您創建了一個搜索的action方法和視圖,讓用戶可以根據電影名稱和類型進行搜索。在下一章節中,您將會看到怎麼在Movie模型(model)中增加一個屬性和怎麼增加一個初始化程序去自動的創建一個測試數據庫。