驗證編輯方法(Edit method)和編輯檢視(Edit view)
在本節中,您將驗證電影控制器生成的編輯方法(Edit action methods)和檢視。但是首先將修改點程式碼,使得釋出日期屬性(ReleaseDate)看上去更好。開啟Models \ Movie.cs檔案,並新增高亮行如下所示:
using System; using System.ComponentModel.DataAnnotations; using System.Data.Entity; namespace MvcMovie.Models { public class Movie { public int ID { get; set; } public string Title { get; set; } [Display(Name = "Release Date")] [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime ReleaseDate { get; set; } public string Genre { get; set; } public decimal Price { get; set; } } public class MovieDBContext : DbContext { public DbSet<Movie> Movies { get; set; } } }
在接下來的教程中,我們將討論DataAnnotations。Display屬性指明要顯示的欄位的名稱(在本例中“Release Date”來代替“ReleaseDate”)。DataType屬性用於指定型別的資料,在本例它是一個日期,所以不會顯示存放在該欄位時間詳情。DisplayFormat屬性在Chrome瀏覽器裡有一個bug:呈現的日期格式不正確。
在瀏覽器位址列裡追加/Movies, 瀏覽到Movies頁面。並進入編輯(Edit)頁面。
Edit(編輯)連結是由Views\Movies\Index.cshtml檢視
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
Html
物件是一個Helper, 以屬性的形式在System.Web.Mvc.WebViewPage基類上公開。 ActionLink是一個幫助方法(Helper),便於動態生成指向Controller中操作方法 的HTML 超連結連結。ActionLink
方法的第一個引數是想要呈現的連結文字 (例如,<a>Edit Me</a>
)。第二個引數是要呼叫的操作方法的名稱(在本例中, Edit方法)。最後一個引數是一個匿名物件(anonymous object),用來生成路由資料 (在本例中,ID 為 4 的)。
在上圖中所生成的連結是http://localhost:xxxxx/Movies/Edit/4。預設的路由 (在App_Start\RouteConfig.cs 中設定) 使用的 URL 匹配模式為: {controller}/{action}/{id}
。因此,ASP.NET 將http://localhost:xxxxx/Movies/Edit/4轉化到Movies
控制器中Edit
操作方法,引數ID
等於 4 的請求。檢視App_Start\RouteConfig.cs檔案中的以下程式碼。
MapRoute方法是使用HTTP請求路由查詢到正確的控制器(controller)和行動方法,並提供了可選ID的引數。MapRoute方法也被用於通過HtmlHelpers如ActionLink的控制器,操作方法及任何路由資料,以生成URL。
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 } ); }
您還可以使用QueryString來傳遞操作方法的引數。例如,URL: http://localhost:xxxxx/Movies/Edit?ID=3還會將引數ID
為 3的請求傳遞給Movies
控制器的Edit
操作方法。
開啟Movies
控制器。如下所示的兩個Edit
操作方法。
// GET: /Movies/Edit/5 public ActionResult Edit(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Movie movie = db.Movies.Find(id); if (movie == null) { return HttpNotFound(); } return View(movie); } // POST: /Movies/Edit/5 // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see http://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] 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
屬性,從而作為HttpGet
方法。) 繫結(Bind)屬性是另一個重要安全機制,可以防止黑客攻擊(從over-posting資料到你的模型)。您應該只包含在bind屬性屬性,您想要更改。您可以閱讀有關在我overposting security note。我們將在本教程中使用的簡單模型,模型中繫結所有資料。ValidateAntiForgeryToken屬性是用來防止偽造的請求,並配對@Html.AntiForgeryToken()檔案 (Views\Movies\Edit.cshtml),如下圖所示,部分在編輯view檔案:
@model MvcMovie.Models.Movie @{ ViewBag.Title = "Edit"; } <h2>Edit</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Movie</h4> <hr /> @Html.ValidationSummary(true) @Html.HiddenFor(model => model.ID) <div class="form-group"> @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title) </div> </div>
@Html.AntiForgeryToken() 生成隱藏的窗體, 防偽令牌必須匹配的的Movies控制器的Edit方法。在我的教程XSRF/CSRF Prevention in MVC,你可以讀到更多關於跨站點請求偽造(也稱為XSRF或CSRF)。
HttpGet
Edit
方法會獲取電影ID引數、 查詢影片使用Entity Framework 的Find方法,並返回到選定影片的編輯檢視。如果不帶引數呼叫Edit
方法,ID 引數被指定為預設值 零。如果找不到一部電影,則返回HttpNotFound 。當scaffolding自動建立編輯檢視時,它會檢視Movie
類併為類的每個屬性建立用於Render的<label>
和<input>
的元素。下面的示例為visual studio scaffolding自動建立的編輯檢視:
@model MvcMovie.Models.Movie @{ ViewBag.Title = "Edit"; } <h2>Edit</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Movie</h4> <hr /> @Html.ValidationSummary(true) @Html.HiddenFor(model => model.ID) <div class="form-group"> @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.ReleaseDate) @Html.ValidationMessageFor(model => model.ReleaseDate) </div> </div> @*Genre and Price removed for brevity.*@ <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Save" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
注意,檢視模板在檔案的頂部有 @model MvcMovie.Models.Movie
的宣告,這將指定檢視期望的模型型別為Movie
。
scaffolded自動生成的程式碼,使用了Helper方法的幾種簡化的 HTML 標記。 Html.LabelFor
用來顯示欄位的名稱("Title"、"ReleaseDate"、"Genre"或"Price")。 Html.EditorFor
用來呈現 HTML <input>
元素。Html.ValidationMessageFor
用來顯示與該屬性相關聯的任何驗證訊息。
執行該應用程式,然後瀏覽URL,/Movies。單擊Edit連結。在瀏覽器中檢視頁面原始碼。HTML Form中的元素如下所示:
<form action="/movies/Edit/4" method="post"> <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" /> <fieldset class="form-horizontal"> <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="control-group"> <label class="control-label" for="Title">Title</label> <div class="controls"> <input class="text-box single-line" id="Title" name="Title" type="text" value="GhostBusters" /> <span class="field-validation-valid help-inline" data-valmsg-for="Title" data-valmsg-replace="true"></span> </div> </div> <div class="control-group"> <label class="control-label" for="ReleaseDate">Release Date</label> <div class="controls"> <input class="text-box single-line" data-val="true" data-val-date="The field Release Date must be a date." data-val-required="The Release Date field is required." id="ReleaseDate" name="ReleaseDate" type="date" value="1/1/1984" /> <span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span> </div> </div> <div class="control-group"> <label class="control-label" for="Genre">Genre</label> <div class="controls"> <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Comedy" /> <span class="field-validation-valid help-inline" data-valmsg-for="Genre" data-valmsg-replace="true"></span> </div> </div> <div class="control-group"> <label class="control-label" for="Price">Price</label> <div class="controls"> <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="7.99" /> <span class="field-validation-valid help-inline" data-valmsg-for="Price" data-valmsg-replace="true"></span> </div> </div> <div class="form-actions no-color"> <input type="submit" value="Save" class="btn" /> </div> </fieldset> </form>
被<form>
HTML 元素所包括的 <input>
元素會被髮送到,<form>的action
屬性所設定的URL:/Movies/Edit。單擊Save按鈕時,from資料將會被髮送到伺服器。第二行顯示隱藏XSRF通過@Html.AntiForgeryToken()呼叫生成的令牌。
處理 POST 請求
下面的程式碼顯示了Edit
操作方法的HttpPost
處理:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie) { if (ModelState.IsValid) { db.Entry(movie).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(movie); }
ASP.NET MVC model binder
接收form所post的資料,並轉換所接收的Movie請求資料從而建立一個Movie
物件。ModelState.IsValid
方法用於驗證提交的表單資料是否可用於修改(編輯或更新)一個Movie
物件。如果資料是有效的電影資料,將儲存到資料庫的Movies
集合(MovieDBContext
例項)。通過呼叫MovieDBContext
的SaveChanges
方法,新的電影資料會被儲存到資料庫。資料儲存之後,程式碼會把使用者重定向到MoviesController
類的Index
操作方法,頁面將顯示電影列表,同時包括剛剛所做的更新。
一旦客戶端驗證確定某個欄位的值是無效的,將顯示出現錯誤訊息。如果禁用JavaScript,則不會有客戶端驗證,但伺服器將檢測回傳的值是無效的,而且將重新顯示錶單中的值與錯誤訊息。在本教程的後面,我們驗證更詳細的審查。Edit.cshtml檢視模板中的Html.ValidationMessageFor
Helper將用來顯示相應的錯誤訊息。
所有HttpGet方法遵循類似的模式。他們得到一個電影物件(或物件列表中,如本案例的Index),並把模型資料傳遞給檢視。Create方法傳遞一個空的影片物件給Create檢視。所有的create, edit, delete方法,或其他的方法: 用HttpPost過載的方法修改資料。修改資料在HTTP GET方法, 存在安全風險,如部落格文章ASP.NET MVC Tip #46 – Don’t use Delete Links because they create Security Holes. 在HTTP GET方法中修改資料也違反HTTP的最佳實踐和REST模式架構,指明GET請求不應該改變你的應用程式的狀態。換句話說,執行GET操作應該是一個安全,操作,無任何副作用,不會修改你的持久化資料。
如果您的電腦是是US-English的語言設定,可以跳過這一節,直接進入下一個教程。
注意,為了使jQuery支援使用逗號的非英語區域的驗證 ,需要設定逗號(",")來表示小數點,你需要引入globalize.js並且你還需要具體的指定cultures/globalize.cultures.js檔案 (地址在https://github.com/jquery/globalize) 在 JavaScript 中可以使用 Globalize.parseFloat
。你可以從NuGet中安裝非英語的jQuery的驗證、外掛。 (如果您使用的是英語語言環境,不要安裝全球化 (Globalize)。)
1. 在工具(Tools)選單,點選庫程式包管理器( Library Package Manager),選擇解決方案程式包管理器(Manage NuGet Packages for Solution).
2. 在左邊面板上,選擇聯機庫(Online,見下圖)
3. 在搜尋已安裝庫( Search Installed packages ),輸入 Globalize搜尋
點選安裝(Install). JavaScript指令碼 \jquery.globalize\globalize.js 檔案將會新增到您的當前工程下. 指令碼\jquery.globalize\cultures\ 資料夾的下面會包含很多不同文化的JavaScript檔案
注意事項:安裝這個包,預計花費5分鐘時間(取決於您的網速).
下面的程式碼展示了在"FR-FR" Culture下的 Views\Movies\Edit.cshtml 檢視:
@section Scripts { @Scripts.Render("~/bundles/jqueryval") <script src="~/Scripts/jquery.globalize/globalize.js"></script> <script src="~/Scripts/jquery.globalize/cultures/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> <script> $.validator.methods.date = function (value, element) { return this.optional(element) || Globalize.parseDate(value); } </script> }
作為一個臨時解決辦法,如果您不能驗證當前的區域設定,可以強制你的計算機使用US English,或者你可以在瀏覽器中禁用JavaScript。為了強制您的電腦使用美國英語,你可以在專案根目錄Web.config檔案裡面新增的全球化設定。
下面的程式碼演示設定為美國英語的全球化文化設定。
<system.web> <globalization culture ="en-US" /> <!--elements removed for clarity--> </system.web>
在接下來的教程,我們將實現搜尋功能。
新增一個搜尋方法(Search Method)和搜尋檢視(Search View)
在本節中,您將新增Index操作方法,可以讓你按照電影流派(genre)或名稱搜尋電影。
升級 Index窗體
我們開始在方法現有MoviesController類中,更新Index方法。程式碼如下:
public ActionResult Index(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); }
Index方法的第一行建立以下的LINQ查詢,以選擇看電影:
var movies = from m in db.Movies select m; 如果searchString引數包含一個字串,可以使用下面的程式碼,修改電影查詢要篩選的搜尋字串: if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); }
上面s => s.Title
程式碼是一個Lambda 表示式。Lambda 是基於方法的LINQ查詢,例如上面的where查詢。在上面的程式碼中使用了標準查詢引數運算子的方法。當定義LINQ查詢或修改查詢條件時,如呼叫Where
或OrderBy
方法時,不會執行 LINQ 查詢。相反,查詢執行會被延遲,這意味著表示式的計算延遲,直到取得實際的值或呼叫ToList
方法。在Search示例中,Index.cshtml檢視中執行查詢。有關延遲的查詢執行的詳細資訊,請參閱Query Execution.
注:Contains 方法是執行在的資料庫,而不是C#程式碼上面。在資料庫中,Contains對映到to SQL LIKE,這是大小寫不敏感的。
現在,您可以實現Index檢視並將其顯示給使用者。
執行這個應用程式和導航到 /Movies/Index。追加一個查詢字串,URL如 ?searchString=ghost。篩選的影片會被顯示。
如果你改變了Index方法簽名引數名為id的,這個id引數將匹配{ id }的佔位符。App_Start\ RouteConfig.cs檔案中設定的預設路由定義如下。
{controller}/{action}/{id}
原來的 Index 方法看起來如下所示:
public ActionResult Index(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); } 修改後的 Index 方法看起來如下所示: public ActionResult Index(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段)的標題搜尋了,而不是作為查詢字串值,截圖如下:
然而,你不能期望使用者可以每次要搜尋一部電影都會去修改URL。所以,現在你將新增使用者介面,幫助他們來過濾影片。如果你改變Index方法來測試如何通過路由繫結ID引數的簽名,Index方法需要一個字串引數searchString:
public ActionResult Index(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\Index.cshtml, 在這段程式碼@Html.ActionLink("Create New", "Create")後之後, 新增如下高亮的: @model IEnumerable<MvcMovie.Models.Movie> @{ ViewBag.Title = "Index"; } <h2>Index</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助手會導致窗體post到它本身。
Visual Studio2013中有一個很好的改善: 顯示和編輯檢視檔案時。當你執行應用程式開啟檢視檔案時,Visual Studio2013的將呼叫正確的控制器操作方法來展示檢視。
在Visual Studio中開啟使用Index檢視(在上面的圖片所示),點選Ctr F5或F5執行應用程式,然後試試搜尋一部電影。
該Index 方法的HttpPost沒有過載。 你不需要它,因為該方法不改變application的狀態,只是過濾資料。
您可以新增以下httppost Index方法。在這種情況下,函式呼叫將匹配的HttpPost Index方法,的HttpPost Index方法執行的如下面的圖片所示。
[HttpPost] public string Index(FormCollection fc, string searchString) { return "<h3> From [HttpPost]Index: " + searchString + "</h3>"; }
但是,即使您新增此HttpPost
Index方法,這一實現其實是有侷限的。想象一下您想要新增書籤給特定的搜尋,或者您想要把搜尋連結傳送給朋友們,他們可以通過單擊看到一樣的電影搜尋列表。請注意 HTTP POST 請求的 URL 和GET 請求的URL 是相同的(localhost:xxxxx/電影/Index)— — 在 URL 中沒有搜尋資訊。現在,搜尋字串資訊作為窗體欄位值,傳送到伺服器。這意味著您不能在 URL 中捕獲此搜尋資訊,以新增書籤或傳送給朋友。
解決方法是使用過載的BeginForm,它指定 POST 請求應新增到 URL 的搜尋資訊,並應該路由到 HttpGet版的 Index方法。將現有的無引數BeginForm
方法,修改為以下內容
@using (Html.BeginForm("Index","Movies",FormMethod.Get))
現在當您提交搜尋,該 URL 將包含搜尋的查詢字串(query string)。搜尋還會請求到 HttpGet
Index操作方法,即使您也有一個HttpPost
Index方法。
按照電影流派新增搜尋
如果您添加了HttpPost
的Index方法,請立即刪除它。
接下來,您將新增功能可以讓使用者按流派搜尋電影。將Index方法替換成下面的程式碼:
public ActionResult Index(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)) { movies = movies.Where(x => x.Genre == movieGenre); } return View(movies); }
這個版本的Index方法將接受一個附加的movieGenre
引數。前幾行的程式碼會建立一個List
物件來儲存資料庫中的電影流派。
下面的程式碼是從資料庫中檢索所有流派的 LINQ 查詢。
var GenreQry = from d in db.Movies orderby d.Genre select d.Genre;
該程式碼使用泛型 List集合的 AddRange方法將所有不同的流派,新增到集合中的。(使用 Distinct
修飾符,不會新增重複的流派 -- 例如,在我們的示例中添加了兩次喜劇)。
該程式碼然後在ViewBag
物件中儲存了流派的資料列表。的SelectList物件在ViewBag作為儲存類資料(這樣的電影流派),然後在下拉列表框中的資料訪問類別,是一個典型的MVC applications的方法。
下面的程式碼演示如何檢查movieGenre
引數。如果它不是空的,程式碼進一步指定了所查詢的電影流派。
if (!string.IsNullOrEmpty(movieGenre)) { movies = movies.Where(x => x.Genre == movieGenre); }
如前所述,查詢資料不會在資料庫上執行,直到電影列表迭代結束(恰發生在View,Index方法返回後)。
Index檢視新增標記,以支援按流派搜尋電影
在Views\Movies\Index.cshtml 檔案中,新增Html.DropDownList輔助方法,在TextBox前。完成的程式碼如下圖所示:
@model IEnumerable<MvcMovie.Models.Movie> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @Html.ActionLink("Create New", "Create") @using (Html.BeginForm("Index", "Movies", FormMethod.Get)) { <p