在上节中添加控制器以后,项目中自动生成了增删改查视图。本节中我们将对其进行修改。
虽然项目中有Detail视图,但是我们无法查看学生的选课信息。所以修改Detail视图页如下,添加Enrollments信息。
1 @model ContosoUniversity.Models.Student 2 3 @{ 4 ViewBag.Title = "Details"; 5 } 6 7 <h2>Details</h2> 8 9 <div> 10 <h4>Student</h4> 11 <hr /> 12 <dl class="dl-horizontal"> 13 <dt> 14 @Html.DisplayNameFor(model => model.LastName) 15 </dt> 16 17 <dd> 18 @Html.DisplayFor(model => model.LastName) 19 </dd> 20 21 <dt> 22 @Html.DisplayNameFor(model => model.FirstMidName) 23 </dt> 24 25 <dd> 26 @Html.DisplayFor(model => model.FirstMidName) 27 </dd> 28 29 <dt> 30 @Html.DisplayNameFor(model => model.EnrollmentDate) 31 </dt> 32 33 <dd> 34 @Html.DisplayFor(model => model.EnrollmentDate) 35 </dd> 36 <dt> 37 @Html.DisplayNameFor(model=>model.Enrollments); 38 </dt> 39 <dd> 40 <table class="table"> 41 <tr> 42 <th>Course Title</th> 43 <th>Grade</th> 44 </tr> 45 @foreach(var item in Model.Enrollments) 46 { 47 <tr> 48 <td> 49 @Html.DisplayFor(modelItem=>item.Course.Title) 50 </td> 51 <td> 52 @Html.DisplayFor(modelItem=>item.Grade); 53 </td> 54 </tr> 55 } 56 </table> 57 </dd> 58 </dl> 59 </div> 60 <p> 61 @Html.ActionLink("Edit", "Edit", new { id = Model.ID }) | 62 @Html.ActionLink("Back to List", "Index") 63 </p>
运行效果如下:
Detail页面完成。
修改控制器中的Create(HttpPost)方法:添加try catch语句
/// <summary> /// 通过模型绑定添加Student实体,asp.net mvc中的模型绑定将post表单 /// 转为CLR类型,并且将其以参数的形式传递到Action方法 /// 因为ID字段是主键,所以添加数据的时候ID字段会自动创建。不需要模型绑定 /// </summary> /// <param name="student"></param> /// <returns></returns> public ActionResult Create([Bind(Include="LastName, FirstMidName, EnrollmentDate")] Student student) { try {
//服务器端验证,稍后将会看到客户端验证 if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException) { ModelState.AddModelError("","创建失败!"); } return View(student); }
安全提示:ValidateAntiForgeryToken
属性可以防止跨站点请求伪造攻击。它需在视图中有相应的Html.AntiForgeryToken()声明,稍后将会看到。
如果Student实体包含一个Secret属性,但你不想在页面设置这个字段,即使你页面上没有这字段,黑客也可以通过工具或者写一些JavaScript传递这个字段值。如果没有模型绑定的Bind属性,黑客就可以添加Secret字段到你的实体中。你可以通过Include参数设置白名单,也可以通过Exclude设置黑名单。
另一种防止过度发布的方法就是使用视图模型而不是模型绑定的实体类。在视图模型中仅包含你想要修改的属性。
Create页面完成。
Student控制器中有两个Edit方法,这里我们只修改带有HttpPost的Edit方法。
[HttpPost,ActionName("Edit")] [ValidateAntiForgeryToken] public ActionResult EditPost(int ?id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } var studentToUpdate = db.Students.Find(id); //调用TryUpdateModel方法修改用户传入的字段,EF会自动跟踪修改 Modified标志, if (TryUpdateModel(studentToUpdate,"",new string []{ "LastName", "FirstMidName", "EnrollmentDate"})) { try { //调用此方法后 Modified会调用sql修改数据行,忽略并发冲突,所有的数据都会被修改,包括那些用户没有修改的 //稍后将会涉及如何处理并发冲突,如果你指仅修改数据库的个别字段,你可以将实体设置成Unchanged设置个别字段为Modified db.SaveChanges(); return RedirectToAction("Index"); } catch (DataException) { ModelState.AddModelError("","修改失败"); } } return View(studentToUpdate); }
因为HttpPost的Edit方法和HttpGet的Edit方法签名相同,所以修改方法名称为EditPost加以区分。目前为止,没有我们想要保护的字段,如果要防止过度发布,可以在TryUpdateModel参数中添加白名单。
数据库上下文跟踪内存中的实体是否与其数据库的内容同步。这决定了当你调用Savechanges方法将会发生什么。例如:当你向Add方法中添加一个实体的时候,这个实体的状态就会被设置成Added,然后你调用Savechanges方法,数据库上下文就会发出一个sql的INSERT命令。
实体可能是如下状态:
Added:数据库中不攒在该实体,Savechanges方法会引发一个Insert。
UnChanged:Savechanges方法不会对该实体做任何处理,当你从数据库中读取一个实体的时候,就是以这种状态开始的。
Modified:实体的一些或者全部属性被修改后,Savechanges方法会引发一个Update声明
Deleted:实体已经被标记为删除,Savechanges方法会引发一个Delete声明。
Detached:实体未被数据上下文跟踪。
在桌面程序中,通常都是自动设置状态变化,在桌面程序中,读取实体并修改属性值,这会将实体状态自动设置成Modified,然后调用Savechanges方法,EF会生成Update语句仅修改你想修改的属性。
断开连接的web app不会发生以上这些连续序列。页面渲染以后DbContext会读取实体,调用HttpPost Edit方法时,生成一个新请求和一个新的DbContext声明。因此你必须手动设置实体状态为Modified。当调用Savechanges方法时,EF修改了数据库中的所有行,因为上下文无法知道你修改的是哪个属性。
如果你只想让sql 的update语句只修改你要修改的属性。可以用一些方法保存原始值(例如隐藏字段)。可以使用原始值创建实体并调用Attach方法。修改实体值为新值然后调用Savechanges方法。有关信息请查看:Entity states and SaveChanges 和Local Data
Edit页面完成。
同前面一样,delete操作也有两个方法,Get请求的方法会展现一个视图给用户确定是否删除,如果用户删除,那么就执行Post请求。当post请求执行后才会真正删除数据。
Get请求的delete方法中,通过Find方法找到实体,这里对这个稍作修改,如果查找实体失败,给与提示用户。
在post请求的delete方法中,添加try-catch。
// GET: /Student/Delete/5
/// <summary>
/// 添加可选参数设置为false,在post请求的Delete方法中会调用get方法,如果删除成功状态为true,否则为false
/// </summary>
/// <param name="id"></param>
/// <param name="saveChangesError"></param>
/// <returns></returns>
public ActionResult Delete(int? id,bool? saveChangesError=false) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } if (saveChangesError.GetValueOrDefault()) { ViewBag.ErrorMessage = "删除失败!"; } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); }
// POST: /Student/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public ActionResult DeleteConfirmed(int id) { try { //视图生成代码 //Student student = db.Students.Find(id); //db.Students.Remove(student); //为了提高性能可以使用下面代码避免不需要的sql查询,这涉及到了之前讲过的实体状态 Student studentToDelete = new Student() {ID=id }; db.Entry(studentToDelete).State = EntityState.Deleted; db.SaveChanges(); } catch (DataException) { return RedirectToAction("Delete", new { id = id, saveChangesError = true }); } return RedirectToAction("Index"); }
修改视图代码,添加ViewBag
<h2>Delete</h2> <p class="error">@ViewBag.ErrorMessage</p> <h3>Are you sure you want to delete this?</h3>
Delete功能完成!
关闭连接释放资源。代码提供在控制器最后提供了一个Dispose方法,基础的Controller类已经实现了IDisposable接口,因此只需重写Dispose(bool)
方法销毁数据库上下文实例。
当你修改多行数据执行Savechanges方法的时候,使用了事务。EF自动确保你所做更改要么全部成功,要么全部失败。如果先修改数据然后报错,这些修改就会自动回滚。有关事务请查看: Working with Transactions。
原文:http://www.cnblogs.com/peaceOfMind/p/5316049.html