public class CartController : Controller { private IProductRepository repository; public CartController(IProductRepository repo) { this.repository = repo; } public RedirectToRouteResult AddToCart(int productId, string returnUrl) { Product product = repository.Products.FirstOrDefault(p => p.ProductId == productId); if (product != null) { GetCart().AddItem(product, 1); } return RedirectToAction("Index", new { returnUrl }); } public RedirectToRouteResult RemoveFromCart(int productId, string returnUrl) { Product product = repository.Products.FirstOrDefault(p => p.ProductId == productId); if (product != null) { GetCart().RemoveItem(product); } return RedirectToAction("Index", new { returnUrl }); } private Cart GetCart() { Cart cart = (Cart)Session["Cart"]; if (cart == null) { cart = new Cart(); Session["Cart"] = cart; } return cart; } }
現在要自定義一個 ModelBinder 來獲取 Session 數據中的 Cart 實例,通過實作 System.Web.Mvc.IModelBinder 介面
public class CartModelBinder : IModelBinder { private const string sessionKey = "Cart"; public object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext) { // 通過 session 取得 Cart Cart cart = null; if (controllerContext.HttpContext.Session != null) { cart = (Cart)controllerContext.HttpContext.Session[sessionKey]; } // 若 session 中沒有 Cart, 則創建一個 if (cart == null) { cart = new Cart(); if (controllerContext.HttpContext.Session != null) { controllerContext.HttpContext.Session[sessionKey] = cart; } } return cart; } }
需要告訴 MVC 框架,使用 CartModelBinder 來創建 Cart 實例,在Global.asax 的 Application_Start 方法中設置
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); // 告訴 MVC 框架使用 CartModelBinder 類來創建 Cart 實例 ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder()); } }
現在更新 CartController,刪除 GetCart 方法,依靠 CartModelBinder 為 CartController 提供 Cart 物件
public class CartController : Controller { private IProductRepository repository; public CartController(IProductRepository repo) { this.repository = repo; } public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl) { Product product = repository.Products.FirstOrDefault(p => p.ProductId == productId); if (product != null) { cart.AddItem(product, 1); } return RedirectToAction("Index", new { returnUrl }); } public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl) { Product product = repository.Products.FirstOrDefault(p => p.ProductId == productId); if (product != null) { cart.RemoveItem(product); } return RedirectToAction("Index", new { returnUrl }); } }
用來創建 Cart 物件與 CartController 的邏輯方離開來了,這樣開發者能夠修改存取 Cart 物件的方法而不需要修改 CartController,可以方便對 CartController 做單元測試而不需要模仿大量的 ASP.NET 通道。
[TestMethod] public void Can_Add_To_Cart() { // Arrange Mock<IProductRepository> mock = new Mock<IProductRepository>(); mock.Setup(m => m.Products).Returns(new Product[] { new Product{Id=1,Name="P1",Category = "Apples"}, }.AsQueryable()); Cart cart = new Cart(); CartController target = new CartController(mock.Object); // Act target.AddToCart(cart, 1, null); // Asert Assert.AreEqual(1, cart.Products.Count(), 1); Assert.AreEqual(1, cart.Products.ToArray()[0].Id); }