自学MVC——查看Edit方法和Edit视图
在本节中,我们将查看为影片控制器生成的方法和视图。
运行程序,在浏览器中访问/Movies。鼠标悬停在影片列表某条记录的Edit链接上方,会看到链接类似于http://localhost:54454/Movies/Edit/1。
Edit的链接是由Views\Movies\Index.cshtml文件中的Html.ActionLink方法生成的,如下所示:@Html.ActionLink("Edit","Edit",new{id=item.ID})
Html对象是System.Web.Mvc.WebViewPage基类暴露出来的一个属性,作为助手来使用。助手的ActionLink方法令动态生成与控制器中的方法关联的HTML超链接变得容易。ActionLink的第一个参数是显示的文本(例如,<a>编辑</a>),第二个参数是要调用的方法名,最后一个参数是生成的匿名路由数据对象(在这个例子中,是指值为1的ID)。
在上面生成的链接地址是http://localhost:54454/Movies/Edit/1。默认路由(在App_Start\RouteConfig.cs中创建)解析URL的模式为{controller}/{action}/{id}。因此,ASP.NET将http://localhost:54454/Movies/Edit/1装换成Movies控制器的Edit方法的请求,并携带一个值为1参数ID。在App_Start\Route.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 } ); } }
我们也可以使用查询字符串来传递方法参数。例如,http://localhost:54454/Movies/Edit?ID=1同样传递值为1的ID参数给Movies控制器的Edit方法。
打开Movies控制器文件。两个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] [ValidateAntiForgeryToken] 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属性应用在第一个Edit方法,但那不是必须的,因为它是默认的。(我们将为HttpGet方法隐性地指定HttpGet属性)。
HttpGet属性的Edit方法获取影片ID参数,使用Entity Framework的Find方法查找影片,并返回选中的影片给Edit视图。当Edit方法被调用时没有参数传入时,ID参数指定0作为默认值。当无法找到影片时,返回HttpNotFound。当脚手架系统创建Edit视图时,查看Movie类并为该类的每个属性创建<label>和<input>元素。下面的例子展示了生成的Edit视图:
@model Mvc_Movie.Models.Movie @{ ViewBag.Title = "Edit"; } <h2>Edit</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <fieldset> <legend>Movie</legend> @Html.HiddenFor(model => model.ID) <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.Genra) </div> <div class="editor-field"> @Html.EditorFor(model => model.Genra) @Html.ValidationMessageFor(model => model.Genra) </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.Date) </div> <div class="editor-field"> @Html.EditorFor(model => model.Date) @Html.ValidationMessageFor(model => model.Date) </div> <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
注意视图模板在文件顶部有@model Mvc_Movie.Models.Movie语句--指定了视图期望的模型是Movie类型。
脚手架代码使用几个Helper方法来生成流式的Html标记。Html.LabelFor显示字段名称(片名、价格、风格等)。Html.EditorFor生成<input>元素。Html.ValidationMessageFor显示属性关联的验证信息。
运行程序,导航到/Movies地址,点击Edit超链接。在浏览器中查看页面源码。表单元素的Html源码如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Edit - Movie Application</title> <link href="/favicon.ico" rel="shortcut icon" type="image/x-icon" /> <meta name="viewport" content="width=device-width" /> <link href="/Content/site.css" rel="stylesheet"/> <script src="/Scripts/modernizr-2.6.2.js"></script> </head> <body> <header> <div class="content-wrapper"> <div class="float-left"> <p class="site-title"><a href="/">MVC Movie</a></p> </div> <div class="float-right"> <section id="login"> <ul> <li><a href="/Account/Register" id="registerLink">Register</a></li> <li><a href="/Account/Login" id="loginLink">Log in</a></li> </ul> </section> <nav> <ul id="menu"> <li><a href="/">Home</a></li> <li><a href="/Home/About">About</a></li> <li><a href="/Home/Contact">Contact</a></li> </ul> </nav> </div> </div> </header> <div id="body"> <section class="content-wrapper main-content clear-fix"> <h2>Edit</h2> <form action="/Movies/Edit/1" method="post"><input name="__RequestVerificationToken" type="hidden" value="WfqpXrJkSauucf7KsDJPVyHH97eqAMMUjDgbe7K_EqaSs3_P_h-WCGNqCDiNQybG3nuh3byFl-IJoSzMVZV54_1zEcnoIShhQ-3YM-YYxOI1" /> <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="1" /> <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="私人定制" /> <span class="field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span> </div> <div class="editor-label"> <label for="Genra">Genra</label> </div> <div class="editor-field"> <input class="text-box single-line" id="Genra" name="Genra" type="text" value="123" /> <span class="field-validation-valid" data-valmsg-for="Genra" 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="123.00" /> <span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span> </div> <div class="editor-label"> <label for="Date">Date</label> </div> <div class="editor-field"> <input class="text-box single-line" data-val="true" data-val-date="The field Date must be a date." data-val-required="The Date field is required." id="Date" name="Date" type="datetime" value="12/31/2013 12:00:00 AM" /> <span class="field-validation-valid" data-valmsg-for="Date" 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> </div> <footer> <div class="content-wrapper"> <div class="float-left"> <p>© 2014 - My ASP.NET MVC Application</p> </div> </div> </footer> <script src="/Scripts/jquery-1.8.2.js"></script> <script src="/Scripts/jquery.unobtrusive-ajax.js"></script> <script src="/Scripts/jquery.validate.js"></script> <script src="/Scripts/jquery.validate.unobtrusive.js"></script> </body> </html>
包含<input>元素的HTML<form>元素的action属性设置为/Movies/Edit,方法为post。点击Edit按钮,表单数据将被post到服务器,处理post请求。
下面列表显示了处理HttpPost请求版本的Edit方法
// // POST: /Movies/Edit/5 [HttpPost] [ValidateAntiForgeryToken] 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模型绑定机制获取post表单值,创建Movie对象作为参数。ModelState.IsValid方法验证表单提交的数据可以用来修改(编辑或更新)Movie对象。如果数据有效,则影片数据将被保存到MovieDbContext实例集合中。新的影片数据在调用MovieDbContext的SaveChanges时保存到数据库,保存完数据后,代码引导用户到MoviesController的Index方法,在那展示包括刚刚修改的影片在内的影片集合。如果post的数据无效,将在表单中重新显示。Edit.cshtml视图模板中的Html.ValidationMessageFor助手负责显示错误信息。
所有的HttpGet方法遵循相似的模式,获取一个movie对象(或者对象集合,比如在Index例子中),从模型传递到视图。Create方法传递空对象给Create视图,创建、编辑、删除或者修改数据等所有操作放在HttpPost重载方法中。在HttpGet方法中修改数据存在安全隐患,详细信息。在Get方法中修改数据同样违法Http最佳实践以及Rest模式(指定Get请求不应当变更你的应用程序状态)。换句话说,执行Get操作应该是一个不影响以及不会修改此前数据的安全操作。