在上一文章中由Entity Framework(實體框架)去實現了對資料庫的CURD操作。在本篇文章中,主要是除錯修改自動生成的動作方法和檢視,以及除錯編輯功能與編輯功能的Book控制器。
首先,在Visual Studio中執行一下上次的應用程式,通過瀏覽器訪問http://localhost:36878/Book。將滑鼠指標移到瀏覽器中的一個“Edit”連結上,就可以看到這個“Edit”指向的URL。如下圖紅框所示。
“Edit” 連結通過Html.ActionLink方法在瀏覽中生成指向Views\Book\Edit.cshtml 檢視的連結。程式碼如下。
@Html.ActionLink("Edit", "Edit", new { id=item.BookID })
如上圖中所示,這個“HTML”物件是通過System.Web.Mvc.WebViewPage基類的屬性暴露出來的。這個ActionLink方法可以很容易地動態生成一個連結,這個連結指向控制器的一個動作方法。ActionLink方法的第一個引數是顯示連結的文字資訊(例如,“Edit”)。第二個引數是指動作方法要呼叫控制器中的方法名稱。最後一個引數是生成的路由資料(如示例中的ID=8)的一個匿名物件。
在上圖中所示的生成的“Edit”連結是http://localhost:36878/Book/Edit/8。預設路由URL模板是{controller}/{action}/{id}(模板程式碼位於App_Start\ RouteConfig.cs)。因此,瀏覽器會發出http://localhost:36878/Book/Edit/8 這個URL請求,同時傳遞引數ID=8給Asp.net MVC的Book控制器的“Edit”方法。下面就是App_Start\ RouteConfig.cs裡面的路由模板設定。
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 } ); }
您還可以使用action方法後面帶上查詢引數。例如,URL http://localhost:36878/Book/Edit?ID=8,通過ID=8,把引數ID的資料(8)傳遞給了Book控制器的“Edit”方法。以上兩種方式的執行結果如下圖。
其次,我們開啟BookController.cs檔案,來仔細看看裡面的兩個“Edit”方法。程式碼如下所示。
// // GET: /Book/Edit/8 public ActionResult Edit(int id = 0) { Book book = db.Books.Find(id); if (book == null) { return HttpNotFound(); } return View(book); } // // POST: /Book/Edit/8 [HttpPost] public ActionResult Edit(Book book)
{ if (ModelState.IsValid) { db.Entry(book).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(book); }
請注意,第二個“Edit”方法的上面有一個“HttpPost”屬性。這個屬性指定了此方法只能通過POST請求來呼叫。也可以給第一個方法上面加上“HTTPGET”屬性,但這不是必要的,因為HTTPGET屬性是預設的。 (Visual Studio將會給所有沒有指明的方法,預設且隱性的分配HTTPGET屬性,這種隱性分配了“HTTPGET”屬性的方法稱為HTTPGET操作方法。)
第一個“HTTPGET”的“ Edit”方法是將書籍的ID做為引數,使用Entity Framework查詢方法找到指定ID的書籍,並返回找到的書籍資料給編輯檢視。 此方法中的ID引數被指定了預設值,如果當呼叫時不帶引數,則ID會使用預設值0。如果書籍記錄沒有找到,將會返回HttpNotFound。由基架系統建立的編輯檢視通過<label>與<input>來呈現Book類中的每個屬性。示例程式碼如下。
@model MvcApplication1.Models.Book @{ ViewBag.Title = "Edit"; } <h2>Edit</h2> @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>Book</legend> @Html.HiddenFor(model => model.BookID) <div class="editor-label"> @Html.LabelFor(model => model.Category) </div> <div class="editor-field"> @Html.EditorFor(model => model.Category) @Html.ValidationMessageFor(model => model.Category) </div> <div class="editor-label"> @Html.LabelFor(model => model.Name) </div> <div class="editor-field"> @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) </div> <div class="editor-label"> @Html.LabelFor(model => model.Numberofcopies) </div> <div class="editor-field"> @Html.EditorFor(model => model.Numberofcopies) @Html.ValidationMessageFor(model => model.Numberofcopies) </div> <div class="editor-label"> @Html.LabelFor(model => model.AuthorID) </div> <div class="editor-field"> @Html.EditorFor(model => model.AuthorID) @Html.ValidationMessageFor(model => model.AuthorID) </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> <div class="editor-label"> @Html.LabelFor(model => model.PublishDate) </div> <div class="editor-field"> @Html.EditorFor(model => model.PublishDate) @Html.ValidationMessageFor(model => model.PublishDate) </div> <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
請注意,這個檢視模板檔案的頂部中有一句@model MvcApplication1.Models.Book,這一句程式碼的意思是在檔案中宣告瞭物件的型別定義 ——即指定了本檢視中的物件型別是Book。
由VS自動生成的基架程式碼中使用了幾個輔助方法,這幾個輔助方法用來減少輸入HTML標記。
第一個輔助方法是:Html.LabelFor輔助方法用於顯示欄位的名稱(例如:“Name”,“PublishDate”,“Price”)。
第二個輔助方法是:Html.EditorFor輔助方法用於自動生成一個HTML<input>元素。
第三個輔助方法是:Html.ValidationMessageFor輔助方法用於顯示與該屬性關聯的驗證訊息。
再次,按F5執行應用程式,並在瀏覽器中瀏覽/Book網址,在頁面中,使用滑鼠左鍵單擊“Edit”連結。瀏覽器會自動導航到編輯頁面,在頁面中,單擊滑鼠右鍵,在彈出選單中選擇“檢視原始碼”,你會看到已經生成的HTML表單元素。如下。
<form action="/book/Edit/8" method="post"> <fieldset> <legend>Book</legend> <input data-val="true" data-val-number="欄位 BookID 必須是一個數字。" data-val-required="BookID 欄位是必需的。" id="BookID"
name="BookID" type="hidden" value="8" /> <div class="editor-label"> <label for="Category">Category</label> </div> <div class="editor-field"> <input class="text-box single-line" id="Category" name="Category" type="text" value="SAP" /> <span class="field-validation-valid" data-valmsg-for="Category" data-valmsg-replace="true"></span> </div> <div class="editor-label"> <label for="Name">Name</label> </div> <div class="editor-field"> <input class="text-box single-line" id="Name" name="Name" type="text" value="SAP" /> <span class="field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span> </div> <div class="editor-label"> <label for="Numberofcopies">Numberofcopies</label> </div> <div class="editor-field"> <input class="text-box single-line" data-val="true" data-val-number="欄位 Numberofcopies 必須是一個數字。"
data-val-required="Numberofcopies 欄位是必需的。" id="Numberofcopies" name="Numberofcopies" type="number" value="12" /> <span class="field-validation-valid" data-valmsg-for="Numberofcopies" data-valmsg-replace="true"></span> </div> <div class="editor-label"> <label for="AuthorID">AuthorID</label> </div> <div class="editor-field"> <input class="text-box single-line" data-val="true" data-val-number="欄位 AuthorID 必須是一個數字。"
data-val-required="AuthorID 欄位是必需的。" id="AuthorID" name="AuthorID" type="number" value="12" /> <span class="field-validation-valid" data-valmsg-for="AuthorID" 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="1000000.00" /> <span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span> </div> <div class="editor-label"> <label for="PublishDate">PublishDate</label> </div> <div class="editor-field"> <input class="text-box single-line" data-val="true" data-val-date="欄位 PublishDate 必須是日期。"
data-val-required="PublishDate 欄位是必需的。" id="PublishDate" name="PublishDate" type="datetime" value="2013/1/1 0:00:00" /> <span class="field-validation-valid" data-valmsg-for="PublishDate" data-valmsg-replace="true"></span> </div> <p> <input type="submit" value="Save" /> </p> </fieldset> </form>
其中最後一個<input>元素設定為Submit,用於提交HTML <form>中的表格資料到/Book/Edit URL。
處理POST請求
下面的程式碼示例,顯示了Edit操作方法的HttpPost版本。
[HttpPost] public ActionResult Edit(Book book) { if (ModelState.IsValid) { db.Entry(book).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(book); }
ASP.NET MVC Model Binder簡單的說就是控制器中的Action方法需要引數資料;而這些引數資料包含在HTTP請求中,包括表單上的Value和URL中的引數等。通過Model Binder的功能將表單上的Value和URL中的引數換成Book物件,然後將Book物件繫結到Action操作方法中的引數上面。其中ModelState.IsValid方法驗證使用者提交的表單中的資料是否可以被用於修改(編輯或更新)一個Book物件。如果資料是有效的,Book資料將被儲存到資料庫的Book集合中(BookDBContext例項)。新的Book資料是通過呼叫BookDBContext的SaveChanges方法儲存到資料庫中。儲存資料後,程式碼將使用者重定向到BookController類的Index操作,同時在瀏覽器中顯示最新的Book列表。
如果提交過來的表單中的值是無效的,這些值將重新顯示在瀏覽器的頁面中。在Edit.cshtml檢視模板的Html.ValidationMessageFor助手方法顯示相應的錯誤訊息。如下圖。
請注意,ASP.NET MVC 4.0 已經支援jQuery的驗證方法,在非英語語言環境中使用逗號(“,”)代替小數點,你必須包括globalize.js和你自己所需要的特定語言的globalize.cultures.js檔案(從https://github.com/ jQuery/ globalize 下載)和JavaScript中使用Globalize.parseFloat。下面的程式碼顯示了修改Views\Book\ Edit.cshtml檔案中顯示法語(“fr-FR”):
@section Scripts { @Scripts.Render("~/bundles/jqueryval") <script src="~/Scripts/globalize.js"></script> <script src="~/Scripts/globalize.culture.fr-FR.js"></script> <script> $.validator.methods.number = function (value, element) { return this.optional(element) || !isNaN(Globalize.parseFloat(value)); } $(document).ready(function () { Globalize.culture('fr-FR'); }); </script> <script> jQuery.extend(jQuery.validator.methods, { range: function (value, element, param) { //Use the Globalization plugin to parse the value var val = $.global.parseFloat(value); return this.optional(element) || ( val >= param[0] && val <= param[1]); } }); </script> }
小數字段可能需要使用一個逗號,不是小數點。作為一個臨時的解決辦法,你可以在全球化的元素新增到專案的根目錄下的Web.config檔案中。下面的程式碼顯示了全球化設定為”en-US”。
<system.web> <globalization culture ="en-US" /> <!--elements removed for clarity--> </system.web>
說明:所有的建立,編輯,刪除或以其他方式修改資料的方法都是通過HttpPost將資料傳給相應控制器的相應方法。
HTTPGET方法遵循類似的模式。他們獲取一個書籍(Book)物件(或是物件列表,在Index方法情況下) ,同時將物件資料傳遞給檢視,由檢視進行呈現。 Create方法傳遞一個空的書籍物件給Create檢視。通過HTTP GET方式修改資料存在一個安全隱患。使用GET方法修改資料也違反了HTTP的最佳實踐和REST的架構模式,它指定了GET請求不應該改變你的應用程式的狀態。換句話說,執行GET操作應該是一個安全的操作,沒有任何副作用,不會修改你的持久化的資料。