假設有一個購物網站有一個CartController控制器使用Session存取Cart物件的實例
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);
}