我們的項目已經進入了非常好的良性循環,項目中涵蓋了多數現在的主流開源框架的使用。就Ninject而言,我們的運用是非常的成功,沒有任何一點多余的代碼,你不在每個控制器的構造函數中去調用Ninject的任何代碼,控制器工廠類會自動為你注入你想要的對象,這一點希望大家能記住並運用到你今后的項目中,之所以作為重點提及它,是因為網上有很多錯誤的教程和做法,既沒有顯示出Ninject的本質,也誤導了讀者。今天,我們就對該項目的剩余功能做個完結,下篇我們將把注意力集中在網絡安全上,沒有跟上進度的兄弟,或沒有理解的很透的兄弟,一定要多讀幾遍,多調試下代碼,有很多細節的東西,我們沒有寫太多的筆墨,這並不是我懶,而是我覺得那些對你沒有問題,你搞得定它!
添加模塊驗證
我們的管理員也是人,所以也有犯錯誤的可能,而且幾乎是一定會犯錯誤。所以,我們也要添加些驗證的規則,去規避錯誤,例如在價格中輸入了負數,商品簡介沒有填寫等,而我們還是使用系統的功能和特性去完成這些工作,這也有助於我們對系統的理解,現在我們就在Product類上添加以下特性:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; namespace SportsStore.Domain.Entities { public class Product { [HiddenInput(DisplayValue = false)] public int ProductID { get; set; } [Required(ErrorMessage = "請輸入商品名稱")] public string Name { get; set; } [DataType(DataType.MultilineText)] [Required(ErrorMessage = "請輸入商品介紹")] public string Description { get; set; } [Required] [Range(0.01, double.MaxValue, ErrorMessage = "請輸入一個有效的價格")] public decimal Price { get; set; } [Required(ErrorMessage = "請指定一個商品類別")] public string Category { get; set; } } }
編輯一個Product時,Html.EditorForModel helper 方法就創建一個form元素,MVC框架添加markup並加載了CSS類,這些都是顯示驗證錯誤所必需的,運行一下程序,我們的驗證規則開始發揮作用了:
結果並非我們所預期的,而是拋出了一個異常,仔細分析原因,原來是因為我們改動了數據模型,導致EFDbContext不再適用於原來的數據庫,我們需要做數據遷移。
Entity Framework Migrations
處理這種情況,我們可能有下面幾種選擇方案:
- 刪除數據庫並重建它, 當這將丟失數據庫中的數據, 而且你的老板和客戶都會不喜歡這種方案。
- 根據錯誤提示做Code-Migration。
那么,我們怎么該怎么做呢?
Step 1:在Global.asax, 在Application_Start 事件中添加下行代碼:
Database.SetInitializer<EFDbContext>(null); //For Changes
哦對了,如果你的程序不能認出Database這個名字,你要添加一條引用語句在代碼的引用部分
using System.Data.Entity;
Step 2:打開程序包管理控制台,工具/庫程序包管理器/程序包管理控制台,執行下列命令:
PM> Enable-Migrations -ProjectName SportsStore.Domain -ContextTypeName SportsStore.Domain.Concrete.EFDbContext
執行命令后, 你應該在工程文件夾下,看到一個新文件夾被命名為 ‘Migrations’ 的文件夾。
- Configuration - ‘Seed’ 方法.
- InitialCreate - Up 和Down 方法.
Step 3 執行下面的命令:
PM> add-migration -ProjectName SportsStore.Domain Initial
這個命令將會在‘Migrations’ 文件夾中添加一個命名為<TimeStamp>_Initial.cs的文件。
Step 4:PM> update-database -ProjectName SportsStore.Domain
更新數據庫,現在運行程序,你應該可以看到預期的結果了
現在我們可以把剛才添加到Global.asax的Application_Start 事件中的代碼再完善一下:
Database.SetInitializer<EFDbContext>(new CreateDatabaseIfNotExists<EFDbContext>()); //For Changes
我們的驗證方法並不科學,不但導致程序不可讀,而且,每次驗證都要在服務器端處理,而不能直接在瀏覽器端解決,這很浪費資源和時間,之所以繞出這么遠,主要是為了掩飾EF框架數據遷移的用法,如果你覺得這是有助於學習,你得到了幫助,那么請為我留下個推薦吧!
改用客戶端校驗
打開_AdminLayout.cshtml文件,添加如下語句:
<script src="~/Scripts/jquery-1.7.1.js"></script> <script src="~/Scripts/jquery.validate.min.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> <title></title>
你可以在通過設置web.config文件,去關閉整個工程的客戶端驗證:
<appSettings> <add key="webpages:Version" value="2.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="PreserveLoginUrl" value="true" /> <add key="ClientValidationEnabled" value="false" /> <add key="UnobtrusiveJavaScriptEnabled" value="false" /> <add key="Email.WriteAsFile" value="true"/> </appSettings>
添加新產品
創建一個新的action方法到AdminController類:
public ViewResult Create() { return View("Edit", new Product()); }
我們這個創建的方法沒有自己對應的View,我們重用了Edit view,一個action方法鏈接到一個存在的view上,是個完美的設計,在這里,我們注入一個新的產品對象作為View model,使Edit view彈出一個空窗體,然而,我們還是過於樂觀了,現在的代碼還不是我們所期望的結果。因為它不能返回到初始的Edit action,我們需要修改一下Views/Admin/Edit.cshtml文件:
@model SportsStore.Domain.Entities.Product @{ ViewBag.Title = "Admin: 編輯" + @Model.Name; Layout = "~/Views/Shared/_AdminLayout.cshtml"; } <h1>Edit @Model.Name</h1> @using (Html.BeginForm("Edit", "Admin")) { @Html.EditorForModel() <input type="submit" value="保存" /> @Html.ActionLink("取消並返回", "Index") }
我們為BeginForm()添加了兩個參數,保證返回的地址是正確的。
刪除商品
添加一個新的方法到IProductRepository接口:
Product DeleteProduct(int productID);
然后到EFProductRepository類中去實現這個方法:
public Product DeleteProduct(int productID) { Product dbEntry = context.Products.Find(productID); if (dbEntry != null) { context.Products.Remove(dbEntry); context.SaveChanges(); } return dbEntry; }
最后一步,我們要在AdminController中添加一個刪除的action方法,這里我們只要支持Post,因為這個刪除的操作不是冪等操作,冪等操作不在本系了教程之內,如果有朋友感興趣,可以去查閱相關資料,這里你只需知道,Get方法make一個請求時,不需要用戶的顯示同意,就會釋放瀏覽器和緩存,這個極為危險的,為了回避這一點,我們必須使用Post。
[HttpPost] public ActionResult Delete(int productId) { Product deletedProduct = repository.DeleteProduct(productId); if (deletedProduct != null) { TempData["message"] = string.Format("{0} was deleted", deletedProduct.Name); } return RedirectToAction("Index"); }
到AdminTest文件中,添加測試方法吧:
[TestMethod] public void Can_Delete_Valid_Products() { // Arrange - create a Product Product prod = new Product { ProductID = 2, Name = "Test" }; // Arrange - create the mock repository Mock<IProductsRepository> mock = new Mock<IProductsRepository>(); 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.ProductID)); }
運行它去看看今天的成果吧!如果你完全理解和掌握了這個系列的所有知識點,現在你已經是MVC的專家了,你可以應用這些技術到你的項目中。今天就到這里吧,下篇我們將進行最后的潤色,爭取做到完美收官!有任何問題可直接留言或Email給我,請繼續保持你熱情,讓我們善始善終吧!