摘要:
SportsStore应用程序进展很顺利,但是我不能销售产品直到设计了一个购物车。在这篇文章里,我就将创建一个购物车。
在目录下的每个产品旁边添加一个添加到购物车按钮。点击这个按钮将显示客户到目前为止选择的产品摘要,包含总价格。这时候,用户可以点击继续购物按钮返回产品目录,或者点击现在下单按钮完成订单结束购物过程。
定义Cart实体类
在SportsStore.Domain工程的Entities文件夹下,创建代码文件Cart.cs。
1 using System.Collections.Generic; 2 using System.Linq; 3 4 namespace SportsStore.Domain.Entities 5 { 6 public class Cart 7 { 8 private List<CartLine> lineCollection = new List<CartLine>(); 9 public void AddItem(Product product, int quantity) 10 { 11 CartLine line = lineCollection.Where(p => p.Product.ProductID == product.ProductID).FirstOrDefault(); 12 if (line == null) 13 { 14 lineCollection.Add(new CartLine 15 { 16 Product = product, 17 Quantity = quantity 18 }); 19 } 20 else { 21 line.Quantity += quantity; 22 } 23 } 24 25 public void RemoveLine(Product product) 26 { 27 lineCollection.RemoveAll(l => l.Product.ProductID == product.ProductID); 28 } 29 30 public decimal ComputeTotalValue() 31 { 32 return lineCollection.Sum(e => e.Product.Price * e.Quantity); 33 } 34 35 public void Clear() 36 { 37 lineCollection.Clear(); 38 } 39 40 public IEnumerable<CartLine> CartLines 41 { 42 get { return lineCollection; } 43 } 44 } 45 46 public class CartLine 47 { 48 public Product Product { get; set; } 49 public int Quantity { get; set; } 50 } 51 }
Cart类使用了CartLine类,他们定义在同一个代码文件内,保存一个客户选择的产品,以及客户想买的数量。我定义了添加条目到购物车的方法,从购物车删除之前已经添加的条目的方法,计算购物车内条目总价格,以及删除所有条目清空购物车的方法。我还提供了一个通过IEnumrable<CartLine>访问购物车内容的属性。这些都很直观,通过一点点LINQ很容易用C#实施。
定义视图模型类
在SportsStore.WebUI工程的Models文件夹内,创建代码文件CartIndexViewModel。
1 using SportsStore.Domain.Entities; 2 3 namespace SportsStore.WebUI.Models 4 { 5 public class CartIndexViewModel 6 { 7 public Cart Cart { get; set; } 8 public string ReturnUrl { get; set; } 9 } 10 }
该模型类有两个属性。Cart属性保存了购物车信息,ReturnUrl保存了产品目录的URL,需要这个信息是因为,客户可以随时点击继续购物按钮,返回之前的产品目录URL。
添加购物车控制器CartController
1 using SportsStore.Domain.Abstract; 2 using SportsStore.Domain.Entities; 3 using SportsStore.WebUI.Models; 4 using System.Linq; 5 using System.Web.Mvc; 6 7 namespace SportsStore.WebUI.Controllers 8 { 9 public class CartController : Controller 10 { 11 private IProductRepository repository; 12 13 public CartController(IProductRepository productRepository) 14 { 15 repository = productRepository; 16 } 17 18 public ActionResult Index(string returnUrl) 19 { 20 return View(new CartIndexViewModel 21 { 22 Cart = GetCart(), 23 ReturnUrl = returnUrl 24 }); 25 } 26 27 public RedirectToRouteResult AddToCart(int productId, string returnUrl) 28 { 29 Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId); 30 if (product != null) 31 { 32 GetCart().AddItem(product, 1); 33 } 34 return RedirectToAction("Index", new { returnUrl = returnUrl }); 35 } 36 37 public RedirectToRouteResult RemoveFromCart(int productId, string returnUrl) 38 { 39 Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId); 40 if (product != null) 41 { 42 GetCart().RemoveLine(product); 43 } 44 return RedirectToAction("Index", new { returnUrl }); 45 } 46 47 private Cart GetCart() 48 { 49 Cart cart = (Cart)Session["Cart"]; 50 if (cart == null) 51 { 52 cart = new Cart(); 53 Session["Cart"] = cart; 54 } 55 56 return cart; 57 } 58 } 59 }
该控制器的一些解释:
- GetCart方法:从Session里获取购物车对象,如果该对象为空,则创建这个对象,添加到Session,并返回该对象。
- Index方法:传入returnUrl参数,返回购物车摘要信息视图。该视图的模型类是CartIndexViewModel,模型类对象的Cart属性通过调用方法GetCart返回,ReturnUrl属性使用方法参数赋值。
- AddToCart方法:传入productId参数和returnUrl参数,添加产品到购物车,并返回重定向的购物车摘要信息视图。方法的返回类型是RedirectToRouteResult,该类的基类是ActionResult。
- RemoveFromCart方法:传入productId参数和returnUrl参数,从购物车中删除产品,并返回重定向的购物车摘要信息视图。
- AddToCart方法和RemoveFromCart方法都是通过调用Controller基类的RedirectToAction方法,返回重定向视图类RedirectToRouteResult的对象。
- RedirectToAction方法的第一个参数是Action名称,第二个无类型对象参数提供传入Action的参数值。这里将重定向到Cart控制器的Index方法。
添加到购物车按钮
修改ProductSummary.cshtml视图,添加Add to Cart按钮。
1 @model SportsStore.Domain.Entities.Product 2 3 <div class="well"> 4 <h3> 5 <strong>@Model.Name</strong> 6 <span class="pull-right label label-primary">@Model.Price.ToString("c")</span> 7 </h3> 8 @using (Html.BeginForm("AddToCart", "Cart")) 9 { 10 <div class="pull-right"> 11 @Html.HiddenFor(x => x.ProductID) 12 @Html.Hidden("returnUrl", Request.Url.PathAndQuery) 13 <input type="submit" class="btn btn-success" value="Add to cart" /> 14 </div> 15 } 16 <span class="lead"> @Model.Description</span> 17 </div>
- 使用Html.BeginForm帮助方法,生成AddToCart表单。方法的第一个参数是Action名称AddToCart,第二个参数是控制器名称Cart。
- 使用Html.HiddenFor帮助方法,生成表单的hidden html元素,该元素的name属性是字符串ProductID,值是该产品的ProductID值。
- 使用Html.Hidden帮助方法,生成表单的hidden html元素,该元素的name属性是字符串returnUrl,值是当前页面的Url。
- 控制器的AddToCart方法将通过表单元素的名称,获取要传入该方法的参数productID和returnUrl的值(大小写不敏感)。
添加购物车摘要视图
在Views文件夹的Cart文件夹内,添加Index.cshtml。
1 @model SportsStore.WebUI.Models.CartIndexViewModel 2 3 @{ 4 ViewBag.Title = "Sports Store: Your Cart"; 5 } 6 <style> 7 #cartTable td { 8 vertical-align: middle; 9 } 10 </style> 11 <h2>Your cart</h2> 12 <table id="cartTable" class="table"> 13 <thead> 14 <tr> 15 <th>Quantity</th> 16 <th>Item</th> 17 <th class="text-right">Price</th> 18 <th class="text-right">Subtotal</th> 19 </tr> 20 </thead> 21 <tbody> 22 @foreach (var line in Model.Cart.CartLines) 23 { 24 <tr> 25 <td class="text-center">@line.Quantity</td> 26 <td class="text-left">@line.Product.Name</td> 27 <td class="text-right"> 28 @line.Product.Price.ToString("c") 29 </td> 30 <td class="text-right"> 31 @((line.Quantity * line.Product.Price).ToString("c")) 32 </td> 33 <td> 34 @using (Html.BeginForm("RemoveFromCart", "Cart")) 35 { 36 @Html.Hidden("ProductId", line.Product.ProductID) 37 @Html.HiddenFor(x => x.ReturnUrl) 38 <input class="btn btn-sm btn-warning" type="submit" value="Remove" /> 39 } 40 </td> 41 </tr> 42 } 43 </tbody> 44 <tfoot> 45 <tr> 46 <td colspan="3" class="text-right">Total:</td> 47 <td class="text-right"> 48 @Model.Cart.ComputeTotalValue().ToString("c") 49 </td> 50 </tr> 51 </tfoot> 52 </table> 53 <div class="text-center"> 54 <a class="btn btn-primary" href="@Model.ReturnUrl">Continue shopping</a> 55 </div>
- 这个视图以表格的形式,展示了购物车摘要产品信息,包含了产品名称、购买数量、单价、价格信息。
- 每个产品条目后面,添加删除表单和删除按钮,这里的表单和按钮,同之前添加到购物车按钮一样。
- 表格底部,调用ComputeTotalValue方法,返回总价格。
- 页面底部中间,显示一个Continue Shopping按钮,ReturnUrl属性指向之前的产品目录Url,点击后返回产品目录页面。
运行程序,得到运行结果。
这里我选择了Chess目录,浏览器地址栏上的URL变成了:http://localhost:17596/Chess
如果我点击Human Chess Board产品的Add To Cart按钮,得到页面:
注意这时候的浏览器地址栏的地址变成了:http://localhost:17596/Cart/Index?returnUrl=%2FChess,包含的购物车的Cart/Index,以及以问号?开始的参数?returnUrl=%2FChess。returnUrl的值就是刚才的页面地址。
如果再点击Continue Shoppinga按钮,将返回到returnUrl指向的页面,既是刚才的页面:http://localhost:17596/Chess