【Pro ASP.NET MVC 3 Framework】.学习笔记.8.SportsStore:管理
2013-08-31 18:01
826 查看
管理功能,如何身份认证,对controller和action方法过滤安全的访问,并在用户需要时提供证书。
1 添加分类管理
方便管理的controller,有两类页面,List页面和edit页面。
1.1 创建CRUD Controller
在Controller文件夹上点右键,创建带CRUD的controller。我们要展示如何构建controller,并解释每个步骤,删除所有的方法,只留构造函数。
publicclass AdminController : Controller { private IProductRepository repository; public AdminController(IProductRepository repo) { repository = repo; } }
1.2 用Repository中的产品渲染一个Grid
添加Index方法,显示repository中的所有产品。
public ViewResult Index() { return View(repository.Products); }
1.2.1 Index action的单元测试
Index方法能正确地返回repository中的所有Product对象。
[TestMethod] publicvoid Index_Contains_All_Products() { Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns( new Product[] { new Product{ProductID=1,Name="P1"}, new Product{ProductID=2,Name="P2"}, new Product{ProductID=3,Name="P3"} }.AsQueryable()); //Arrange - create a controller AdminController target =new AdminController(mock.Object); //Action Product[] result = ((IEnumerable<Product>)target.Index().ViewData.Model).ToArray(); //Assert Assert.AreEqual(result.Length, 3); Assert.AreEqual("P1", result[0].Name); Assert.AreEqual("P2", result[1].Name); Assert.AreEqual("P3", result[2].Name); }
1.3 创建一个新视图
在/vie/shared中创建_AdminLayout.cshtml,布局文件名约定以_开头。
<link href="@Url.Content("~/Content/Admin.css")" rel="stylesheet" type="text/css"/>
引用CSS文件。
1.3 实现List View
创建AdminController的Index方法的视图,选择强类型视图,模型类是Product,使用layout文件,选在刚刚建立的_AdminLayout布局文件。并将 scaffold view(支架模型)设为List。选择了List的支架,VS会假设你使用IEnumerable序列作为模型视图的类型。
@model IEnumerable<SportsStore.Domain.Entities.Product> @{ ViewBag.Title ="Index"; Layout ="~/Views/Shared/_AdminLayout.cshtml"; } <h1>All Products</h1> <table class="Grid"> <tr> <th>ID</th> <th> Name </th> <th class="NumericCol"> Price </th> <th> Actions </th> </tr> @foreach (var item in Model) { <tr> <td> @item.ProductID </td> <td> @Html.ActionLink(item.Name,"Edit",new{item.ProductID}) </td> <td class="NumericCol"> @item.Price.ToString("c") </td> <td> @using(Html.BeginForm("Delete","Admin")){ @Html.Hidden("ProductID",item.ProductID) <input type="submit" value="Delete"/> } </td> </tr> } </table> <p>@Html.ActionLink("Add a new product","Create")</p>
1.4 编辑Products
要提供创建和更新特性,我们将添加产品编辑页面。
显示一个页面,允许管理员改变产品属性的值
添加一个action方法,提交改变后处理
1.4.1 创建Edit Action方法
[TestMethod] publicvoid Can_Edit_Product() { Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns( new Product[] { new Product{ProductID=1,Name="P1"}, new Product{ProductID=2,Name="P2"}, new Product{ProductID=3,Name="P3"} }.AsQueryable()); //Arrange - create a controller AdminController target =new AdminController(mock.Object); //Action Product p1 = target.Edit(1).ViewData.Model as Product; Product p2 = target.Edit(2).ViewData.Model as Product; Product p3 = target.Edit(3).ViewData.Model as Product; //Assert Assert.AreEqual(1, p1.ProductID); Assert.AreEqual(2, p2.ProductID); Assert.AreEqual(3, p3.ProductID); } [TestMethod] publicvoid Cannot_Edit_Nonexistent_Product() { Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns( new Product[] { new Product{ProductID=1,Name="P1"}, new Product{ProductID=2,Name="P2"}, new Product{ProductID=3,Name="P3"} }.AsQueryable()); //Arrange - create a controller AdminController target =new AdminController(mock.Object); //Action Product result = target.Edit(4).ViewData.Model as Product; //Assert Assert.IsNull(result); }
1.4.2 创建Edit视图
使用强类型视图,模型类为Product。可以使用支架中的edit,但是我们使用Empty。使用_AdminLayout的布局文件。
@model SportsStore.Domain.Entities.Product @{ ViewBag.Title ="Admin: Edit "+@Model.Name; Layout ="~/Views/Shared/_AdminLayout.cshtml"; } <h1>Edit @Model.Name</h1> @using(Html.BeginForm()){ @Html.EditorForModel() <input type="submit" value="Save"/> @Html.ActionLink("Cancel and return to List","Index") }
与手工地写每个label和inputs相比,我们调用Html.EditorForModel helper方法。这个方法请求MVC框架,创建编辑界面,它会检查模型的类型。EditorForModel很方便,但不能产生最吸引人的结果。我们不想让管理员看到或编辑ProductID属性,并且描述属性的文本框太小。
我们可以使用model metadate(模型元数据),给MVC框架致命怎样为属性创建编辑器。这允许我们,对属性使用特性,来影响Html.EditorForModel方法的输出。
更新Product类
publicclass Product { [HiddenInput(DisplayValue=false)] publicint ProductID { get; set; } publicstring Name { get; set; } [DataType(DataType.MultilineText)] publicstring Description { get; set; } publicdecimal Price { get; set; } publicstring Category { get; set; } }
HiddenInput需要添加System.Web.Mvc的引用。DateType需要添加System.ComponentModel.DataAnnotations的引用。HiddenInput属性告诉MVC框架,将这个属性渲染为隐藏的表元素。DataType属性允许我们指定值时如何呈现和编辑。
界面依然很简陋,我们可以使用CSS改善。当MVC框架为每个属性创建input fields,它指派不同的CSS classes。textarea元素上有class=”text-box multi-line”。我们改变它,在Content文件夹下更改Admin.css。页面模板视图助手EditorForModel不是总符合我们的需求,我们将会自定义。
1.4.3 更新Product Repository
要处理编辑前,我们得增强product repository,才能保存更改。给IProductRepository接口新增方法。
publicinterface IProductRepository { IQueryable<Product> Products { get; } void SaveProduct(Product product); }
EF实现的repository,即EFProductRepository类中添加这个方法
publicvoid SaveProduct(Product product) { if(product.ProductID==0){ context.Products.Add(product); } context.SaveChanges(); }
SaveChanges方法的实现,如果ProductID是0就添加一个Product给repository。它接受任何对现存Product的更改。
1.4.4 处理Edit POST 请求
当管理员点击Save按钮,Edit action方法会处理POST请求。
[HttpPost] public ActionResult Edit(Product product) { if (ModelState.IsValid) { repository.SaveProduct(product); TempData["message"] =string.Format("{0} has been saved", product.Name); return RedirectToAction("Index"); } else { return View(product); } }
先检查模型绑定已经验证用户提交的输数据。如果一切OK,保存变更到repositoy,然后调用Index action方法,返回到产品列表页面。如果有问题,再次渲染Edit视图,让用户更正。
在我们保存变更到repository后,我们使用TempData特性存储一个消息。这是键值类型的字典,和session data和View Bag相似。关键的不同之处是TempData会在HTTP request最后被删除。
注意我们从Edit方法返回了ActionResult类型。之前偶们都是用ViewResult类型,ViewResult是派生自ActionResult,当你想让框架渲染一个视图的时候可以使用。然而,其他类型可以使用ActionResult,RedirectToAction就是其中的一个。我们在Edit action方法中,用它调用Index action方法。
在这种情况下,用户被重定向,我们可以使用VeiwBag。ViewBag在controller和view之间传递数据,它不能比当前HTTP请求更长时间地持有数据。偶们可以使用session data特性,但是消息会在偶们明确地移除它时才删除,我们不想这样做。所以,TempData特使非常适合。数据被限制为单一用户的session(所以用户看不到其他用户的TempData),并且存留到我们阅读它。我们会在视图被action方法渲染后阅读数据。
1.4.5 Edit提交的单元测试
我们要确保对Product有效的更新,模型绑定已经被创建,传递给product repository保存。我们也想检查无效的更新,不会传给repository。
[TestMethod] publicvoid Can_Save_Valid_Changes() { //Arrange - create mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); //Arrange - create the controller AdminController target =new AdminController(mock.Object); //Arrange - create the product Product product =new Product { Name ="Test" }; //Act - try to save the Product ActionResult result = target.Edit(product); //Assert - check that the repository was called mock.Verify(m => m.SaveProduct(product)); //Assert - check the method result type Assert.IsNotInstanceOfType(result, typeof(ViewResult)); } [TestMethod] publicvoid Cannot_Save_Invalid_Changes() { //Arrange - create mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); //Arrange - create the controller AdminController target =new AdminController(mock.Object); //Arrange - create a product Product product =new Product { Name ="Test" }; //Arrange - add an error to the model state target.ModelState.AddModelError("error", "error"); //Act - try to save the product ActionResult result = target.Edit(product); //Assert - check that the repository was not called mock.Verify(m => m.SaveProduct(It.IsAny<Product>()), Times.Never()); //Assert - check the method result type Assert.IsInstanceOfType(result, typeof(ViewResult)); }
1.4.6 显示确认消息
在_AdminLaout.cshtml布局上显示TempData的消息。通过处理模板上的消息,我们可以在任何使用模板的视图上创建消息,而不用创建附加的Razor块。
<div> @if(TempData["message"]!=null){ <div class="Message">@TempData["message"]</div> } @RenderBody() </div>
这样做的好处是,无论用户打开哪个页面,只要使用相同的layout,即使改变了工作流的其他页面,用户也会看到消息。如果你重新载入页面,消息会小时,因为TempData会在阅读后被删除。
1.4.7 添加模型校验
像为ShippingDetails类一样,为Product类添加模型校验
publicclass Product { [HiddenInput(DisplayValue=false)] publicint ProductID { get; set; } [Required(ErrorMessage="Please enter a product name")] publicstring Name { get; set; } [Required(ErrorMessage="Please enter a description")] [DataType(DataType.MultilineText)] publicstring Description { get; set; } [Required] [Range(0.01,double.MaxValue,ErrorMessage="Please enter a positive price")] publicdecimal Price { get; set; } [Required(ErrorMessage="Please specify a category")] publicstring Category { get; set; } }
可以将这些限制属性移动到其他类中,并告诉MVC如何找到他们。
当使用Html.EditorForModel helper方法创建表单元素时,MVC框架会给inline加进markup和CSS。
1.4.8 启用客户端校验
MVC框架可以基于我们领域模型类中使用的data annotations执行客户端校验。这个特性默认启用,但是它还没有工作,因为哦我们没有添加必须的JavaScript库的链接。在_AdminLayout.cshtml文件上链接JavaScript库,可以在任何使用这个布局的页面上客户端校验。
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script> <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>
使用客户端校验,会立即响应,并且不需要将请求发送到服务器。
如果你不想启用当前action的客户端校验,需要在view或controller中使用下面的声明
HtmlHelper.ClientValidationEnabled = false; HtmlHelper.UnobtrusiveJavaScriptEnabled = false;
要禁用整个程序的客户端校验,需要将上面的声明添加到Global.asax的Application_Start方法中。或在Web.config文件中加入下面:
<configuration> <appSettings> <add key="ClientValidationEnabled" value="false"/> <add key="UnobtrusiveJavaScriptEnabled" value="false"/> </appSettings> </configuration>
1.5 创建新产品
在AdminController中创建新方法
public ViewResult Create() { return View("Edit", new Product()); }
Create方法没有渲染它的默认视图,而是指定了Edit视图。这是完美的可接受的,一个action方法使用总是关联其他view的view。在这个例子中,我们注入一个新的Product对象,做诶视图模型,Edit视图使用空字段填充。
<form action="/Admin/Create" method="post">
此时Html.BeginForm默认产生的表单,action为条用它的action,即Create。只有action为Edit时,才能正常编辑。要修复这点,我们可以使用html.BeginForm helper方法的重载版本,来指定触发表单生成的action和congtroller是Edit和Admin。
Html.BeginForm("Edit","Admin") <form action="/Admin/Edit" method="post">
1.6 删除Products
要添加delete是非常简单,首先要在IProductRepository接口添加新方法。
publicinterface IProductRepository { IQueryable<Product> Products { get; } void SaveProduct(Product product); void DeleteProduct(Product product); } publicvoid DeleteProduct(Product product) { context.Products.Remove(product); context.SaveChanges(); }
最后需要在AdminController中实现Delete action方法。这个方法必须仅支持POST请求,因为产出对象不是一个等幂操作。浏览器和缓存可以造出GET请求,而不用用户明确地同意。所以我们必须小心避免改变Get请求的结果。
[HttpPost] public ActionResult Delete(int productId) { Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId); if(product!=null){ repository.DeleteProduct(product); TempData["message"] =string.Format("{0} was deleted", product.Name); } return RedirectToAction("Index"); }
1.6.1 删除产品的单元测试
我们想要测试两个特性,第一个是当一个有效的ProductID作为参数传递给action方法,它调用repository的DeleteProduct方法,并传递正确的、要删除的Product对象。
第二个测试是确保如果传递给Delete方法的参数值,不是repositrory中的可用Product,repository的DeleteProduct方法没有被调用。
[TestMethod] publicvoid Can_Delete_Valid_Products() { // Arrange - create a Product Product prod =new Product { ProductID =2, Name ="Test" }; // Arrange - create the mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] { new Product {ProductID =1, Name ="P1"}, prod, new Product {ProductID =3, Name ="P3"}, }.AsQueryable()); // Arrange - create the controller AdminController target =new AdminController(mock.Object); // Act - delete the product target.Delete(prod.ProductID); // Assert - ensure that the repository delete method was // called with the correct Product mock.Verify(m => m.DeleteProduct(prod)); } [TestMethod] publicvoid Cannot_Delete_Invalid_Products() { // Arrange - create the mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] { new Product {ProductID =1, Name ="P1"}, new Product {ProductID =2, Name ="P2"}, new Product {ProductID =3, Name ="P3"}, }.AsQueryable()); // Arrange - create the controller AdminController target =new AdminController(mock.Object); // Act - delete using an ID that doesn't exist target.Delete(100); // Assert - ensure that the repository delete method was // called with the correct Product mock.Verify(m => m.DeleteProduct(It.IsAny<Product>()), Times.Never()); }
1 添加分类管理
方便管理的controller,有两类页面,List页面和edit页面。
1.1 创建CRUD Controller
在Controller文件夹上点右键,创建带CRUD的controller。我们要展示如何构建controller,并解释每个步骤,删除所有的方法,只留构造函数。
publicclass AdminController : Controller { private IProductRepository repository; public AdminController(IProductRepository repo) { repository = repo; } }
1.2 用Repository中的产品渲染一个Grid
添加Index方法,显示repository中的所有产品。
public ViewResult Index() { return View(repository.Products); }
1.2.1 Index action的单元测试
Index方法能正确地返回repository中的所有Product对象。
[TestMethod] publicvoid Index_Contains_All_Products() { Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns( new Product[] { new Product{ProductID=1,Name="P1"}, new Product{ProductID=2,Name="P2"}, new Product{ProductID=3,Name="P3"} }.AsQueryable()); //Arrange - create a controller AdminController target =new AdminController(mock.Object); //Action Product[] result = ((IEnumerable<Product>)target.Index().ViewData.Model).ToArray(); //Assert Assert.AreEqual(result.Length, 3); Assert.AreEqual("P1", result[0].Name); Assert.AreEqual("P2", result[1].Name); Assert.AreEqual("P3", result[2].Name); }
1.3 创建一个新视图
在/vie/shared中创建_AdminLayout.cshtml,布局文件名约定以_开头。
<link href="@Url.Content("~/Content/Admin.css")" rel="stylesheet" type="text/css"/>
引用CSS文件。
1.3 实现List View
创建AdminController的Index方法的视图,选择强类型视图,模型类是Product,使用layout文件,选在刚刚建立的_AdminLayout布局文件。并将 scaffold view(支架模型)设为List。选择了List的支架,VS会假设你使用IEnumerable序列作为模型视图的类型。
@model IEnumerable<SportsStore.Domain.Entities.Product> @{ ViewBag.Title ="Index"; Layout ="~/Views/Shared/_AdminLayout.cshtml"; } <h1>All Products</h1> <table class="Grid"> <tr> <th>ID</th> <th> Name </th> <th class="NumericCol"> Price </th> <th> Actions </th> </tr> @foreach (var item in Model) { <tr> <td> @item.ProductID </td> <td> @Html.ActionLink(item.Name,"Edit",new{item.ProductID}) </td> <td class="NumericCol"> @item.Price.ToString("c") </td> <td> @using(Html.BeginForm("Delete","Admin")){ @Html.Hidden("ProductID",item.ProductID) <input type="submit" value="Delete"/> } </td> </tr> } </table> <p>@Html.ActionLink("Add a new product","Create")</p>
1.4 编辑Products
要提供创建和更新特性,我们将添加产品编辑页面。
显示一个页面,允许管理员改变产品属性的值
添加一个action方法,提交改变后处理
1.4.1 创建Edit Action方法
[TestMethod] publicvoid Can_Edit_Product() { Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns( new Product[] { new Product{ProductID=1,Name="P1"}, new Product{ProductID=2,Name="P2"}, new Product{ProductID=3,Name="P3"} }.AsQueryable()); //Arrange - create a controller AdminController target =new AdminController(mock.Object); //Action Product p1 = target.Edit(1).ViewData.Model as Product; Product p2 = target.Edit(2).ViewData.Model as Product; Product p3 = target.Edit(3).ViewData.Model as Product; //Assert Assert.AreEqual(1, p1.ProductID); Assert.AreEqual(2, p2.ProductID); Assert.AreEqual(3, p3.ProductID); } [TestMethod] publicvoid Cannot_Edit_Nonexistent_Product() { Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns( new Product[] { new Product{ProductID=1,Name="P1"}, new Product{ProductID=2,Name="P2"}, new Product{ProductID=3,Name="P3"} }.AsQueryable()); //Arrange - create a controller AdminController target =new AdminController(mock.Object); //Action Product result = target.Edit(4).ViewData.Model as Product; //Assert Assert.IsNull(result); }
1.4.2 创建Edit视图
使用强类型视图,模型类为Product。可以使用支架中的edit,但是我们使用Empty。使用_AdminLayout的布局文件。
@model SportsStore.Domain.Entities.Product @{ ViewBag.Title ="Admin: Edit "+@Model.Name; Layout ="~/Views/Shared/_AdminLayout.cshtml"; } <h1>Edit @Model.Name</h1> @using(Html.BeginForm()){ @Html.EditorForModel() <input type="submit" value="Save"/> @Html.ActionLink("Cancel and return to List","Index") }
与手工地写每个label和inputs相比,我们调用Html.EditorForModel helper方法。这个方法请求MVC框架,创建编辑界面,它会检查模型的类型。EditorForModel很方便,但不能产生最吸引人的结果。我们不想让管理员看到或编辑ProductID属性,并且描述属性的文本框太小。
我们可以使用model metadate(模型元数据),给MVC框架致命怎样为属性创建编辑器。这允许我们,对属性使用特性,来影响Html.EditorForModel方法的输出。
更新Product类
publicclass Product { [HiddenInput(DisplayValue=false)] publicint ProductID { get; set; } publicstring Name { get; set; } [DataType(DataType.MultilineText)] publicstring Description { get; set; } publicdecimal Price { get; set; } publicstring Category { get; set; } }
HiddenInput需要添加System.Web.Mvc的引用。DateType需要添加System.ComponentModel.DataAnnotations的引用。HiddenInput属性告诉MVC框架,将这个属性渲染为隐藏的表元素。DataType属性允许我们指定值时如何呈现和编辑。
界面依然很简陋,我们可以使用CSS改善。当MVC框架为每个属性创建input fields,它指派不同的CSS classes。textarea元素上有class=”text-box multi-line”。我们改变它,在Content文件夹下更改Admin.css。页面模板视图助手EditorForModel不是总符合我们的需求,我们将会自定义。
1.4.3 更新Product Repository
要处理编辑前,我们得增强product repository,才能保存更改。给IProductRepository接口新增方法。
publicinterface IProductRepository { IQueryable<Product> Products { get; } void SaveProduct(Product product); }
EF实现的repository,即EFProductRepository类中添加这个方法
publicvoid SaveProduct(Product product) { if(product.ProductID==0){ context.Products.Add(product); } context.SaveChanges(); }
SaveChanges方法的实现,如果ProductID是0就添加一个Product给repository。它接受任何对现存Product的更改。
1.4.4 处理Edit POST 请求
当管理员点击Save按钮,Edit action方法会处理POST请求。
[HttpPost] public ActionResult Edit(Product product) { if (ModelState.IsValid) { repository.SaveProduct(product); TempData["message"] =string.Format("{0} has been saved", product.Name); return RedirectToAction("Index"); } else { return View(product); } }
先检查模型绑定已经验证用户提交的输数据。如果一切OK,保存变更到repositoy,然后调用Index action方法,返回到产品列表页面。如果有问题,再次渲染Edit视图,让用户更正。
在我们保存变更到repository后,我们使用TempData特性存储一个消息。这是键值类型的字典,和session data和View Bag相似。关键的不同之处是TempData会在HTTP request最后被删除。
注意我们从Edit方法返回了ActionResult类型。之前偶们都是用ViewResult类型,ViewResult是派生自ActionResult,当你想让框架渲染一个视图的时候可以使用。然而,其他类型可以使用ActionResult,RedirectToAction就是其中的一个。我们在Edit action方法中,用它调用Index action方法。
在这种情况下,用户被重定向,我们可以使用VeiwBag。ViewBag在controller和view之间传递数据,它不能比当前HTTP请求更长时间地持有数据。偶们可以使用session data特性,但是消息会在偶们明确地移除它时才删除,我们不想这样做。所以,TempData特使非常适合。数据被限制为单一用户的session(所以用户看不到其他用户的TempData),并且存留到我们阅读它。我们会在视图被action方法渲染后阅读数据。
1.4.5 Edit提交的单元测试
我们要确保对Product有效的更新,模型绑定已经被创建,传递给product repository保存。我们也想检查无效的更新,不会传给repository。
[TestMethod] publicvoid Can_Save_Valid_Changes() { //Arrange - create mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); //Arrange - create the controller AdminController target =new AdminController(mock.Object); //Arrange - create the product Product product =new Product { Name ="Test" }; //Act - try to save the Product ActionResult result = target.Edit(product); //Assert - check that the repository was called mock.Verify(m => m.SaveProduct(product)); //Assert - check the method result type Assert.IsNotInstanceOfType(result, typeof(ViewResult)); } [TestMethod] publicvoid Cannot_Save_Invalid_Changes() { //Arrange - create mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); //Arrange - create the controller AdminController target =new AdminController(mock.Object); //Arrange - create a product Product product =new Product { Name ="Test" }; //Arrange - add an error to the model state target.ModelState.AddModelError("error", "error"); //Act - try to save the product ActionResult result = target.Edit(product); //Assert - check that the repository was not called mock.Verify(m => m.SaveProduct(It.IsAny<Product>()), Times.Never()); //Assert - check the method result type Assert.IsInstanceOfType(result, typeof(ViewResult)); }
1.4.6 显示确认消息
在_AdminLaout.cshtml布局上显示TempData的消息。通过处理模板上的消息,我们可以在任何使用模板的视图上创建消息,而不用创建附加的Razor块。
<div> @if(TempData["message"]!=null){ <div class="Message">@TempData["message"]</div> } @RenderBody() </div>
这样做的好处是,无论用户打开哪个页面,只要使用相同的layout,即使改变了工作流的其他页面,用户也会看到消息。如果你重新载入页面,消息会小时,因为TempData会在阅读后被删除。
1.4.7 添加模型校验
像为ShippingDetails类一样,为Product类添加模型校验
publicclass Product { [HiddenInput(DisplayValue=false)] publicint ProductID { get; set; } [Required(ErrorMessage="Please enter a product name")] publicstring Name { get; set; } [Required(ErrorMessage="Please enter a description")] [DataType(DataType.MultilineText)] publicstring Description { get; set; } [Required] [Range(0.01,double.MaxValue,ErrorMessage="Please enter a positive price")] publicdecimal Price { get; set; } [Required(ErrorMessage="Please specify a category")] publicstring Category { get; set; } }
可以将这些限制属性移动到其他类中,并告诉MVC如何找到他们。
当使用Html.EditorForModel helper方法创建表单元素时,MVC框架会给inline加进markup和CSS。
1.4.8 启用客户端校验
MVC框架可以基于我们领域模型类中使用的data annotations执行客户端校验。这个特性默认启用,但是它还没有工作,因为哦我们没有添加必须的JavaScript库的链接。在_AdminLayout.cshtml文件上链接JavaScript库,可以在任何使用这个布局的页面上客户端校验。
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script> <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>
使用客户端校验,会立即响应,并且不需要将请求发送到服务器。
如果你不想启用当前action的客户端校验,需要在view或controller中使用下面的声明
HtmlHelper.ClientValidationEnabled = false; HtmlHelper.UnobtrusiveJavaScriptEnabled = false;
要禁用整个程序的客户端校验,需要将上面的声明添加到Global.asax的Application_Start方法中。或在Web.config文件中加入下面:
<configuration> <appSettings> <add key="ClientValidationEnabled" value="false"/> <add key="UnobtrusiveJavaScriptEnabled" value="false"/> </appSettings> </configuration>
1.5 创建新产品
在AdminController中创建新方法
public ViewResult Create() { return View("Edit", new Product()); }
Create方法没有渲染它的默认视图,而是指定了Edit视图。这是完美的可接受的,一个action方法使用总是关联其他view的view。在这个例子中,我们注入一个新的Product对象,做诶视图模型,Edit视图使用空字段填充。
<form action="/Admin/Create" method="post">
此时Html.BeginForm默认产生的表单,action为条用它的action,即Create。只有action为Edit时,才能正常编辑。要修复这点,我们可以使用html.BeginForm helper方法的重载版本,来指定触发表单生成的action和congtroller是Edit和Admin。
Html.BeginForm("Edit","Admin") <form action="/Admin/Edit" method="post">
1.6 删除Products
要添加delete是非常简单,首先要在IProductRepository接口添加新方法。
publicinterface IProductRepository { IQueryable<Product> Products { get; } void SaveProduct(Product product); void DeleteProduct(Product product); } publicvoid DeleteProduct(Product product) { context.Products.Remove(product); context.SaveChanges(); }
最后需要在AdminController中实现Delete action方法。这个方法必须仅支持POST请求,因为产出对象不是一个等幂操作。浏览器和缓存可以造出GET请求,而不用用户明确地同意。所以我们必须小心避免改变Get请求的结果。
[HttpPost] public ActionResult Delete(int productId) { Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId); if(product!=null){ repository.DeleteProduct(product); TempData["message"] =string.Format("{0} was deleted", product.Name); } return RedirectToAction("Index"); }
1.6.1 删除产品的单元测试
我们想要测试两个特性,第一个是当一个有效的ProductID作为参数传递给action方法,它调用repository的DeleteProduct方法,并传递正确的、要删除的Product对象。
第二个测试是确保如果传递给Delete方法的参数值,不是repositrory中的可用Product,repository的DeleteProduct方法没有被调用。
[TestMethod] publicvoid Can_Delete_Valid_Products() { // Arrange - create a Product Product prod =new Product { ProductID =2, Name ="Test" }; // Arrange - create the mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] { new Product {ProductID =1, Name ="P1"}, prod, new Product {ProductID =3, Name ="P3"}, }.AsQueryable()); // Arrange - create the controller AdminController target =new AdminController(mock.Object); // Act - delete the product target.Delete(prod.ProductID); // Assert - ensure that the repository delete method was // called with the correct Product mock.Verify(m => m.DeleteProduct(prod)); } [TestMethod] publicvoid Cannot_Delete_Invalid_Products() { // Arrange - create the mock repository Mock<IProductRepository> mock =new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] { new Product {ProductID =1, Name ="P1"}, new Product {ProductID =2, Name ="P2"}, new Product {ProductID =3, Name ="P3"}, }.AsQueryable()); // Arrange - create the controller AdminController target =new AdminController(mock.Object); // Act - delete using an ID that doesn't exist target.Delete(100); // Assert - ensure that the repository delete method was // called with the correct Product mock.Verify(m => m.DeleteProduct(It.IsAny<Product>()), Times.Never()); }
相关文章推荐
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.4.MVC的主要工具-使用Moq
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.7.SportsStore:购物车
- 《Pro ASP.NET MVC 3 Framework》学习笔记之二十四【Controllers和Actions】
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.6.SportsStore:导航
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.12.ASP.NET MVC3的细节:URLs,Routing和Areas
- 《Pro ASP.NET MVC 3 Framework》学习笔记之二十五
- 《Pro ASP.NET MVC 3 Framework》学习笔记之二十【URL和Routing】
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.2.MVC的主要工具-Ninject
- Pro ASP.NET MVC 5 Framework.学习笔记.6.3.MVC的必备工具
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.1.主要语言特性
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.3.MVC的主要工具-单元测试
- 《Pro ASP.NET MVC 3 Framework》学习笔记之十六【示例项目SportsStore】
- 《Pro ASP.NET MVC 3 Framework》学习笔记之二十七【视图1】
- Pro ASP.NET MVC 5 Framework.学习笔记.6.4.MVC的必备工具
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.11.ASP.NET MVC3的细节:概览MVC项目
- 《Pro ASP.NET MVC 3 Framework》学习笔记之六【C#部分特性】
- 《Pro ASP.NET MVC 3 Framework》学习笔记之二十二【Controllers和Actions】
- 《Pro ASP.NET MVC 3 Framework》学习笔记之二十一【Area及URL架构的最佳实践】
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.9.SportsStore:Securing the Administration Features
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.5.SportsStore一个真实的程序