7.9 从控制器访问模型数据
本节,您将创建一个新的MoviesController类并编写代码读取电影数据并用视图模板在浏览器中显示他们。在继续前,请先编译你的应用程序,不然下面找不到模型类。
右键Controllers文件夹新建一个MoviesController控制器。选择下面的选项:
· Controller name: MoviesController. (默认. )
· Template: Controller with read/write actions and views, using Entity Framework.
· Model class: MOVIE (MvcMovie.DAL)
· Data context class: MovieEntities (MvcMovie.DAL)
· Views: Razor (CSHTML). (默认.)
单击“Add”。Visual Studio创建了下面的文件夹和文件:
1. 在项目的Controller文件夹下MoviesController.cs文件
2. 在项目的View文件夹下Movies文件夹
3. 在新创建的文件夹Views\Movies下Create.cshtml, Delete.cshtml,
Details.cshtml, Edit.cshtml, and Index.cshtml。
ASP.NET MVC 3架构机制自动创建CRUD(create, read, update, and delete)响应方法和视图。您现在拥有了全部的web应用程序的功能,支持增加、显示、编辑、删除电影作品。
运行应用程序并通过在浏览器地址栏中URL后追加/Movies来浏览Movies控制器。因为应用程序依托默认的路由(在Global.asax文件中定义),浏览请求http://localhost:xxxxx/Movies被路由到Movies控制器的默认方法Index。换句话说,http://localhost:xxxxx/Movies实际上和http://localhost:xxxxx/Movies/Index是一样的。因为您还没有添加任何东西,所以电影列表是空的。
7.10 创建电影
选择“Create New”链接。输入一个电影的详细信息然后单击“Create”按钮。
单击“Create”按钮使页面回发到服务器端(那里的电影信息将会保存在数据库中)。您将重定向到/Movies URL,在列表里您能看到刚被添加的电影信息。
此时,Visual Studio还自动实现了数据验证,在上图的创建页面,不输入信息,然后点击“Create”会出现如下结果(后面我们会介绍自定义模型验证):
运行结果:
查看数据库:
您可以创建一些电影作品,测试全部的功能,编辑、明细、删除。
7.11 审视代码
打开Controllers\MoviesController.cs文件并审视生成的Index方法代码。部分控制器Index方法的代码如下所示:
public class MoviesController : Controller { private MovieEntities db = new MovieEntities(); // // GET: /Movies/ public ViewResult Index() { return View(db.MOVIEs.ToList()); } }
如前面所述,下面的行在MoviesController类中实例化了一个电影数据库的内容。
private MovieEntities db = new MovieEntities();
Movies控制器返回数据库中的所有电影资料实体并把结果传递给Index视图。
强类型模型和@model关键字
在教程的前面部分,您了解了如何使用ViewBag对象把数据通过控制器传递给视图。ViewBag是一个动态对象,提供了方便的迟绑定方式将信息传递给视图。
ASP.NET MVC也支持传递强类型数据给视图模板。这种强类型的方式支持编译时检查代码和丰富的智能感知。我们将在MoviesController类和Index.cshtml视图模板中采用这种方式。请注意该代码创建一个List对象时调用Index方法中View Helper方法。代码通过控制器传递电影列表给视图:
public ViewResult Index() { return View(db.MOVIEs.ToList()); }
通过在视图模板文件的顶部包含@model表达式,您可以指定在视图中您期望使用的对象类型。当您创建电影控制器的时候,Visual Studio自动在视图模板文件Index.cshtml的顶部包含@model表达式:@model IEnumerable<MvcMovie.DAL.MOVIE>
@model指令允许您访问由控制器使用强类型Model对象传递给视图的电影列表。比如,Index.cshtml模板,代码通过foreach表达式遍历了基于强类型的电影资料。
@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> }
由于实体对象是强类型(被用作可枚举对象),循环中的每个item对象都是强类型的Movie。其他好处指的是编译时检查代码和全部的智能感知支持。
您现在拥有了数据库并通过简单的列表展现了他们。
审视编辑方法和视图
在这一节中,您将审视Movies控制器生成的响应方法和视图。然后您将添加
一个自定义搜索页面。运行程序并通过在URL追加/Moives浏览movie控制器。把鼠标悬停在Edit链接上,看看它执行的URL.
Edit的链接由视图Views\Movies\Index.cshtml 的Html.ActionLink方法生成。
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
Html对象是一个助手,它是WebViewPage基类暴露的属性。助手的ActionLink方法可以很容易的生成HTML超链接,它指向控制器的响应方法。ActionLink的第一个参数表示超链接的文本呈现(比如:<a>Edit Me</a>),第二个参数是要调用的响应方法的名称,最后一个参数生成路由数据的匿名对象(anonymous object,这里指ID=4)。
您可以使用查询字符串(query string)传递参数给响应方法。比如URLhttp://localhost:xxxxx/Movies/Edit?ID=4传递ID=4给Movies控制器的Edit方法。
打开Movies控制器。有两个Edit方法,如下所示:
// GET: /Movies/Edit/5 public ActionResult Edit(decimal id) { MOVIE movie = db.MOVIEs.Single(m => m.ID == id); return View(movie); } // // POST: /Movies/Edit/5 [HttpPost] public ActionResult Edit(MOVIE movie) { if (ModelState.IsValid) { db.MOVIEs.Attach(movie); db.ObjectStateManager.ChangeObjectState(movie, EntityState.Modified); db.SaveChanges(); return RedirectToAction("Index"); } return View(movie); }
注意第二个Edit方法前面是HttpPost属性。它表明这个重载的Edit方法只能被
POST请求调用。您也可以给第一个Edit方法采用HttpGet属性,但是这不是必须的,应为方法默认为HttpGet(响应方法隐含的了HttpGet属性将被认为是HttpGet方法)。
HttpGet方法将电影的ID作为参数,并使用实体框架的Find方法查找电影,然后返回被找到的电影给视图模板。当架构体系创建编辑模板时,它检查Movie类并为每个属性生成<label><input>代码去呈现元素。下面的代码展示了自动生成的Edit视图模板:
@model MvcMovie.DAL.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>
<form>中的<input>元素表示一个提交按钮,点击时将提交页面数据到Controller中的Edit方法。
处理POST请求
由架构体系生成的属性为HttpGet的Edit方法没有检查传给它的ID的有效性。如果用户删除URL的ID字段,错误信息如下所示:
用户还可以传递一个不存在的ID,比如:
http://localhost:xxxxx/Movies/Edit/1234.您可以给HttpGet Edit方法做两点修改来限制URL。首先,把ID参数改为默认值为0 (id不是必须传递)。
您也可以在回传电影对象给视图模板之前,检查Find方法是否真正的找到了电影信息。
public ActionResult Edit(decimal id=0) { MOVIE movie = db.MOVIEs.FirstOrDefault(m => m.ID == id); if(movie==null) { return HttpNotFound(); } return View(movie); }
如果没有找到,HttpNotFound方法被调用。
所有的HttpGet方法都遵循类似的模式。它们获取一个电影对象(在Index中返回对象列表),然后传递模型给视图。Create方法传递一个空电影对象给Create视图。所有的方法(创建、编辑、删除)都有一个HttpPost的重载方法。
在HTTP GET方法中修改数据存在安全风险,在博客ASP.NET MVC Tip #46 – Don’t use Delete Links because they create Security Holes中有描述。在HTTP GET方法中修改数据违反了HTTP的最佳实践和REST架构模式(其中规定GET请求不应改变应用程序状态)。换句话,执行GET操作应该是一个无副作用的安全操作。