在這一小節,我們將會仔細查看一下為MoviesController自動生成的action和view。然后我們會添加一個自定義的查找頁面。
運行程序,通過在URL后面添加/Movies來查看MoviesController。把鼠標放到Edit超鏈接上來看一下這個超鏈接指向哪里。
Eidt超鏈接是由Views\Movies\Index.cshtml視圖里的Html.ActionLink方法生成的。
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
Html對象是作為System.Web.Mvc.WebViewPage基類的一個屬性暴露出來的,它是一個輔助類,這個輔助類的ActionLink方法使動態創建一個指向controller中的action方法的HTML超鏈接變得十分容易(注:ActionLink實際上是一個擴展方法,擴展了HtmlHelper類)。ActionLink方法的第一個參數是要展示出來的超鏈接的文本內容(例如,<a>Edit Me</a>)。第二個參數是要調用的action的名字,最后一個參數是一個匿名類型用來生成路由數據(route data,不知道怎么翻譯)??
==============================我是華麗的分隔符,一下內容是博主自己加===========================================
注:其實我們的cshtml文件會被編譯為WebViewPage<TModel>的一個子類,這個類中的代碼如下
namespace System.Web.Mvc { public abstract class WebViewPage<TModel> : WebViewPage { public new AjaxHelper<TModel> Ajax { get; set; } public new HtmlHelper<TModel> Html { get; set; } public new TModel Model { get; } public new ViewDataDictionary<TModel> ViewData { get; set; } public override void InitHelpers(); protected override void SetViewData(ViewDataDictionary viewData); } }
而WebViewPage中的代碼如下
using System.Web; using System.Web.WebPages; namespace System.Web.Mvc { public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild { public AjaxHelper<object> Ajax { get; set; } public override HttpContextBase Context { get; set; } public object Model { get; } public TempDataDictionary TempData { get; } public dynamic ViewBag { get; } #region IViewDataContainer Members public ViewDataDictionary ViewData { get; set; } #endregion #region IViewStartPageChild Members public HtmlHelper<object> Html { get; set; } public UrlHelper Url { get; set; } public ViewContext ViewContext { get; set; } #endregion protected override void ConfigurePage(WebPageBase parentPage); public override void ExecutePageHierarchy(); public virtual void InitHelpers(); protected virtual void SetViewData(ViewDataDictionary viewData); } }
所以我們才能在cshtml中使用ViewBag
===================================分隔符=============================================================
生成的超鏈接在前面的截圖里展示了,它指向了http://localhost:xxxx/Movies/Edit/3。默認的路由(在App_Start\RouteConfig.cs文件里定義)使用{controller}/{action}/{id}這種URL模式。因此,ASP.NET將對http://localhost:xxxx/Movies/Edit/3的請求轉換成了對MoviesController中Edit方法的調用,並且給參數ID賦值3,下面是App_Start\RouteConfig.cs文件里的內容
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
同樣也可以使用查詢字符串的方式來傳遞參數.例如URL http://localhost:xxxx/Movies/Edit?ID=4同樣給MoviesController中的Edit方法的參數ID賦值為3
打開MoviesController類,下面是兩個Edit方法的代碼:
// // GET: /Movies/Edit/5 public ActionResult Edit(int id = 0) { Movie movie = db.Movies.Find(id); if (movie == null) { return HttpNotFound(); } 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); }
需要注意的是第二個Edit方法附加了HttpPost特性。這個特性指出這個重載的Edit方法只能響應post請求。我們也可以給第一個方法加上HttpGet特性,但是並不需要這么做,因為這個特性是默認特性。(也就是說如果方法上沒有加HttpGet或者HttpPost特性的話,默認都是HttpGet)。
HttpGet的Edit方法接收一個ID作為參數,然后使用Entity Framework的Find方法查找數據,然后返回選定的數據到Edit視圖中。參數ID有一個默認值0,如果找不到指定ID的數據,會返回HttpNotFound。當scaffolding機制創建Edit視圖時,它會檢查Movie類的代碼然后生成代碼來為Movie類的每一個屬性生成<label>和<input>標簽。下面是生成的Edit視圖里的代碼:
@model MvcMovie.Models.Movie @{ ViewBag.Title = "Edit"; } <h2>Edit</h2> @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> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
注意到我們在文件內容的第一行有@model MvcMovies.Models.Movie語句,指定了我們可以在視圖模板中使用的model是Movie類型。
自動生成的代碼使用了好幾個輔助方法來生成HTML標簽。Html.LabelFor方法展示了name字段("Title","ReleaseDate","Genre","Price"),Html.EditorFor方法會渲染成<input>標簽。Html.ValidationMessageFor方法會展示於Movie的屬性有關的驗證信息。
運行程序,瀏覽/Movies,點擊Edit超鏈接。在瀏覽器中查看這個頁面的源文件,下面是form表單中的html內容
<form action="/Movies/Edit/3" method="post"> <fieldset> <legend>Movie</legend> <input data-val="true" data-val-number="字段 ID 必須是一個數字。" data-val-required="ID 字段是必需的。" id="ID" name="ID" type="hidden" value="3" /> <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="瘋狂的石頭" /> <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-date="字段 ReleaseDate 必須是日期。" data-val-required="ReleaseDate 字段是必需的。" id="ReleaseDate" name="ReleaseDate" type="datetime" value="2009/10/10 0:00:00" /> <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="喜劇" /> <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="字段 Price 必須是一個數字。" data-val-required="Price 字段是必需的。" id="Price" name="Price" type="text" value="25.00" /> <span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span> </div> <p> <input type="submit" value="Save" /> </p> </fieldset> </form>
form表單的action屬性被設置為了/Movies/Edit/3,表單中的數據會以post的方式提交到服務器。
處理POST請求
下面是HttpPost版本的Edit方法
[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 MVC model binder 獲取post給服務器的表單數據並且創建一個Movie對象,將這個Movie對象傳遞給Edit方法。ModelState.IsValid方法確保表單中提交的數據可以用來修改一個Movie對象。如果數據通過驗證,新的數據會被保存到db(MovieDBContext實例)的Movies集合中。MovieDBContext的SaveChanges方法將新數據保存到了數據庫里。保存完數據之后,代碼會重定向到Index方法,就會將所有的數據展示出來。
注:原文中接下來的內容是和javascript中的Globalization有關的內容,在這里沒有翻譯。
添加一個Search方法和Search視圖
在這一小節,我們將會添加一個SearchIndex方法來可以按照名字或者分來來查找影片。通過/Movies/SearchIndex這個URL來實現。這個請求將會展現為一個包含了幾個Input標簽的html表單頁面,用戶可以在表單中輸入數據來查找電影。當用戶提交表單時,這個actionn方法將會獲得用戶傳入的數據並且根據這些數據來從數據庫進行檢索。
展示SearchIndex表單
我們從在MoviesController中添加一個SearchIndex方法開始。這個方法返回一個包含html表單的視圖,下面是代碼:
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參數不為空的話,movies查詢將會根據searchString的值來過濾數據:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
代碼中的s=>s.Title是Lambda表達式,基於方法的linq查詢使用Lambda表達式作為參數。linq查詢在定義的時候或者使用Where,OrderBy方法進行修改的時候並不會立即執行,相反,查詢會被延遲執行,也就是說查詢表達式的求值會被推遲,直到真正的值被迭代時(使用foreach)或者ToList之類的方法被調用。在SearchIndex示例中,這個查詢會在SearchIndex的視圖中執行。更多的關於延遲查詢執行的信息,請參考Query Execution。
現在我們可以來實現SearchIndex視圖,右鍵在SearchIndex方法的內部單擊,然后選擇"Add View"。在彈出的Add View對話框中,指定我們要傳遞一個Movie對象到視圖模板中作為視圖的model。在Scaffold template列表中,選擇List,然后點擊Add。
點擊Add按鈕后,Views\Movies\SearchIndex.cshtml視圖模板就會被創建出來。因為我們在Scaffold template模板中選擇了List,Visual Studio會自動在視圖中生成以下標簽。scaffolding機制會創建一個HTML表單,它會檢測Movie類然后生成代碼來為Movie的每一個屬性渲染一個<label>標簽,下面是生成的代碼:
@model IEnumerable<MvcMovie.Models.Movie> @{ ViewBag.Title = "SearchIndex"; } <h2>SearchIndex</h2> <p> @Html.ActionLink("Create New", "Create") </p> <table> <tr> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.ReleaseDate) </th> <th> @Html.DisplayNameFor(model => model.Genre) </th> <th> @Html.DisplayNameFor(model => model.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,然后再后面追加一個查詢字符串,比如?searchString=%e8%a5%bf%e6%b8%b8(注:這里比較囧的一點是我前面輸入的數據都是中文的,而中文在URL里是要被編碼的,所以這一串奇怪的%e8%a5%bf%e6%b8%b8其實是中文"西游"的utf-8編碼),被篩選出來的數據如下:
如果我們更改了SearchIndex方法的簽名把參數名字換為id,這樣就會符合在Global.asax文件里定義的默認路由模板
{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元素來幫助用戶篩選數據。如果你之前把SearchIndex方法的參數名改成了id,現在需要改回searchString,因為接下來的例子中要使用查詢字符串。
打開Views\Movies\SearchIndex.cshtml文件,在@Html.ActionLink("Create New","Create")后面添加如下的代碼:
@using (Html.BeginForm()) { <p>Title:@Html.TextBox("SearchString")<br> <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輔助方法會創建一個開放的<form>標簽。Html.BeginForm會在用戶單擊Filter按鈕來提交表單時將表單提交到自身。(前面通過在SearchIndex方法內部右鍵添加視圖SearchIndex.cshtml,所以該視圖對應的action方法是SearchIndex,這個無參數的BeginForm會將表單提交給這個action處理)。
現在運行程序,我們來試着搜索一部電影(有了UI的幫助我們不必對中文進行URL編碼了)。
我們並沒有對應HttpPost請求的SearchIndex方法,因為我們不需要,這個方法並不會更改應用程序的狀態,只是檢索數據而已。
我們也可以添加如下的HttpPost版本的SearchIndex方法。這樣做的話,請求將會調用HttpPost版本的SearchIndex方法,程序執行起來的效果如截圖所示。
[HttpPost] public string SearchIndex(FormCollection fc, string searchString) { return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>"; }
但是,即使你添加了HttpPost版本的SearchIndex方法,這一實現仍然是有限制的。想象一下你想要為特定的搜索添加一個書簽,或者想要給朋友發送一個鏈接,通過這個鏈接他能看到同樣篩選出來的數據。值得注意的是使用Http Post發送請求時,URL是和Get請求一樣的(都是localhost:xxxx/Movies/SearchIndex),在URL里並沒有與搜索信息。現在,搜索的字符串信息是作為一個表單的字段值提交到服務器的,這就意味着你無法將搜索信息保存在書簽里或者通過URL發送給朋友。
解決的方法是使用BegionForm的一個重載,這個重載會向服務器發送Get請求,這樣就可以將搜索信息添加到URL中。
Html.BeginForm("SearchIndex","Movies",FormMethod.Get)
BeginForm的這個重載,第一個參數是action方法名,第二個參數是controller名,第三個參數指定表單的method。
現在當你提交一此搜索,URL中將會包含一個查詢字符串。查詢將會調用HttpGet版本的SearchIndex方法,而不會調用HttpPost版本的方法。
按照分類(Genre)來查詢
如果實現添加了HttpPost版本的SearchIndex方法,先刪除它。
接下來,讓我們添加一個新特性來允許用戶根據電影的分類來進行查詢,使用下面的SearchIndex方法替換掉原來的代碼:
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)); } }
這個版本的SearchIndex方法多了一個參數,叫做movieGenre。方法前面的幾行代碼創建了一個List對象來保存從數據庫查出來的所有分類。
下面的linq語句從數據庫中檢索了所有的分類。
var GenreQry = from d in db.Movies orderby d.Genre select d.Genre;
代碼使用了泛型集合List的AddRange方法來添加了所有的不重復的分類(如果不使用Distinct方法的話,集合里將會有很多重復值)。然后將這個泛型List存儲到了ViewBag中。
下面的代碼展示了如何核查movieGenre參數,如果這個參數不為空,代碼就會按照電影的分類(Genre屬性)來進一步篩選數據。
if (string.IsNullOrEmpty(movieGenre)) { return View(movies); } else { return View(movies.Where(x => x.Genre == movieGenre)); }
在SearchIndex的視圖中添加相應標簽來支持按照分類進行查詢
在Views\Movies\SearchIndex.cshtml文件中加入一個Html.DropDownList方法,就加載TextBox方法的前面,完整的代碼如下
<p> @Html.ActionLink("Create New", "Create") @using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){ <p>Genre: @Html.DropDownList("movieGenre", "All") Title: @Html.TextBox("SearchString") <input type="submit" value="Filter" /></p> } </p>
運行程序,瀏覽/Movies/SearchIndex。試一試同時使用分類和名字來篩選數據
這一小節我們深入查看了自動生成的CRUD方法和視圖文件。並且我們自己創建了一個SearchIndex方法和對應的視圖來允許用戶按照指定的條件搜索數據。在下一個小街里,我們將會看看如何為模型Movie添加一個新屬,以及如何添加一個初始設定並且自動創建測試數據庫。