构建一个真实的应用电子商务SportsStore(七)
构建一个真实的应用电子商务SportsStore(七)
我们的项目进展相当的不错,但是现在还不能真正的出售商品,因为我们没有为顾客提供购物车。今天,我们就加入购物车的功能,毕竟赚钱才是赢道理啊!购物车的逻辑看起来应该像这样:
我们需要在每件商品的旁边都加一个"Add to cart”的按钮,客户可以随时的添加自己选择的商品。现在就让我们到Domain工程的Entities文件夹去添加一个Cart类吧:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SportsStore.Domain.Entities { public class Cart { private List<CartLine> lineCollection = new List<CartLine> (); public void AddItem(Product product, int quantity) { CartLine line = lineCollection .Where(p => p.Product.ProductID == product.ProductID) .FirstOrDefault(); if (line == null ) { lineCollection.Add( new CartLine { Product = product, Quantity = quantity }); } else { line.Quantity += quantity; } } public void RemoveLine(Product product) { lineCollection.RemoveAll(l => l.Product.ProductID == product.ProductID); } public decimal ComputeTotalValue() { return lineCollection.Sum(e => e.Product.Price * e.Quantity); } public void Clear() { lineCollection.Clear(); } public IEnumerable<CartLine> Lines { get { return lineCollection; } } } public class CartLine { public Product Product { get ; set ; } public int Quantity { get ; set ; } } }
Cart类使用CartLine去展示客户选择的产品和想购买的数量,我们已经定义了添加商品到购物车的方法, 从购物车删除商品的方法,计算购物车中的商品总额的方法和重置购物车,清空说有商品的方法。我们也提供了一个属性,使用IEnumerble<CartLine>.去访问购物车的内容,所有这些通过LinQ很容易实现。
测试购物车
购物车是我们网站中非常重要的功能,我们必须确保它能正常的运行。为此我们必须要对它进行测试,我们现在就在测试工程中添加一个购物车的测试文件,叫做CartTests.cs。我们要测试的第一项内容是,当一个商品第一次被添加到购物车时,我们希望购物车能添加一个新的CartLine。
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using SportsStore.Domain.Entities; using System.Linq; namespace SportsStore.UnitTests { [TestClass] public class CartTests { [TestMethod] public void Can_Add_New_Lines() { // Arrange - create some test products Product p1 = new Product { ProductID = 1 , Name = " P1 " }; Product p2 = new Product { ProductID = 2 , Name = " P2 " }; // Arrange - create a new cart Cart target = new Cart(); // Act target.AddItem(p1, 1 ); target.AddItem(p2, 1 ); CartLine[] results = target.Lines.ToArray(); // Assert Assert.AreEqual(results.Length, 2 ); Assert.AreEqual(results[ 0 ].Product, p1); Assert.AreEqual(results[ 1 ].Product, p2); } } }
然而,这必然存在一个隐患,如果顾客已经添加过这个商品,再次添加时,我们希望增加的是购物车中的数量,而不是创建一个新的CartLine.
[TestMethod] public void Can_Add_Quantity_For_Existing_Lines() { // Arrange - create some test products Product p1 = new Product { ProductID = 1 , Name = " P1 " }; Product p2 = new Product { ProductID = 2 , Name = " P2 " }; // Arrange - create a new cart Cart target = new Cart(); // Act target.AddItem(p1, 1 ); target.AddItem(p2, 1 ); target.AddItem(p1, 10 ); CartLine[] results = target.Lines.OrderBy(c => c.Product.ProductID).ToArray(); // Assert Assert.AreEqual(results.Length, 2 ); Assert.AreEqual(results[ 0 ].Quantity, 11 ); Assert.AreEqual(results[ 1 ].Quantity, 1 ); }
用户也会随时改变想法,从购物车中删除商品,这个方法我们应实现了,但我们还是需要去Check,这里也需要测试。
[TestMethod] public void Can_Remove_Line() { // Arrange - create some test products Product p1 = new Product { ProductID = 1 , Name = " P1 " }; Product p2 = new Product { ProductID = 2 , Name = " P2 " }; Product p3 = new Product { ProductID = 3 , Name = " P3 " }; // Arrange - create a new cart Cart target = new Cart(); // Arrange - add some products to the cart target.AddItem(p1, 1 ); target.AddItem(p2, 3 ); target.AddItem(p3, 5 ); target.AddItem(p2, 1 ); // Act target.RemoveLine(p2); // Assert Assert.AreEqual(target.Lines.Where(c => c.Product == p2).Count(), 0 ); Assert.AreEqual(target.Lines.Count(), 2 ); }
我们还要测试一下对商品总额的计算是否正确,说干就干,添加如下测试方法:
[TestMethod] public void Calculate_Cart_Total() { // Arrange - create some test products Product p1 = new Product { ProductID = 1 , Name = " P1 " , Price = 100M}; Product p2 = new Product { ProductID = 2 , Name = " P2 " , Price = 50M}; // Arrange - create a new cart Cart target = new Cart(); // Act target.AddItem(p1, 1 ); target.AddItem(p2, 1 ); target.AddItem(p1, 3 ); decimal result = target.ComputeTotalValue(); // Assert Assert.AreEqual(result, 450M); }
我们还要测试一下,当我们重置购物车的时候,购物车里的内容是否正确:
[TestMethod] public void Can_Clear_Contents() { // Arrange - create some test products Product p1 = new Product { ProductID = 1 , Name = " P1 " , Price = 100M }; Product p2 = new Product { ProductID = 2 , Name = " P2 " , Price = 50M }; // Arrange - create a new cart Cart target = new Cart(); // Arrange - add some items target.AddItem(p1, 1 ); target.AddItem(p2, 1 ); // Act - reset the cart target.Clear(); // Assert Assert.AreEqual(target.Lines.Count(), 0 ); }
好了,现在我们得为添加购物车设置一个触发点了,那就是为商品加上“Add to cart” 按钮,编辑Views/Shared/ProductSummary.cshtml 文件,为它添加一个按钮。
@model SportsStore.Domain.Entities.Product <div class = " item " > <h3>@Model.Name</h3> @Model.Description @using(Html.BeginForm( " AddToCart " , " Cart " )) { @Html.HiddenFor(x => x.ProductID) @Html.Hidden( " returnUrl " , Request.Url.PathAndQuery) <input type= " submit " value= " + Add to cart " /> } <h4>@Model.Price.ToString( " c " )</h4> </div>
我们添加了一个Razor语句块,为每个产品链接创建一个小HTML,当我们提交时,它将调用Cart控制器的AddToCart action 方法,现在我们就去看看Cart控制器吧!哦,差点忘了,我们还得为我们这个BeginForm生成的表单添加一个css,不然它实在太丑了,打开你的Site.css文件,添加如下代码:
FORM { margin : 0 ; padding : 0 ; } DIV.item FORM { float : right ; } DIV.item INPUT { color : White ; background-color : #333 ; border : 1px solid black ; cursor : pointer ; }
在每个产品相上使用Html.BeginForm helper意味着每个“Add to cart”按钮都将被渲染在自己分离的Html表单元素中,这可能让你惊讶,ASP.NET MVC每一页的forms没有数量限制吗?的确是这样,你可以有尽可能多的表单,只要你需要绝对没问题。这不是什么技术需求,然而,我们的表单都提交给同一个控制器,并且带有不同的参数,这使得按钮的处理非常简单。
实现Cart控制器
右击控制器文件夹,添加一个名为iCartController的控制器:
using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace SportsStore.WebUI.Controllers { public class CartController : Controller { private IProductsRepository repository; public CartController(IProductsRepository repo) { 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().RemoveLine(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; } } }
在这个控制器中有几点需要注意:首先,我们使用了ASP.NET session state特性去存取Cart对象,这也是GetCart方法的用意所在。 ASP.NET有很好的session特性,它能使用cookies 或 URL rewriting与来自用户的请求结合在一起,去形成一个单一的浏览session。一个相关的特性是session state, 它允许我们使用session去整合数据。
我们还需要传递信息到View,当用户点击了继续购物按钮时,购物车对象和链接地址需要显示出来,我们想在Model文件夹创建一个简单的Viewmodel,命名为CartIndexViewModel:
using SportsStore.Domain.Entities; namespace SportsStore.WebUI.Models { public class CartIndexViewModel { public Cart Cart { get ; set ; } public string ReturnUrl { get ; set ; } } }
现在我们需要在Cart控制器中实现Index方法:
using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SportsStore.WebUI.Models; namespace SportsStore.WebUI.Controllers { public class CartController : Controller { private IProductsRepository repository; public CartController(IProductsRepository repo) { repository = repo; } public ViewResult Index( string returnUrl) { return View( new CartIndexViewModel { Cart = GetCart(), ReturnUrl = returnUrl }); } 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().RemoveLine(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; } } }
最后一步,我们要创建一个强类型的Index View,右击Index方法,选择添加视图:
修改代码如下:
@model SportsStore.WebUI.Models.CartIndexViewModel @{ ViewBag.Title = " Sports Store: 你的购物车 " ; } <h2>你的购物车</h2> <table width= " 90% " align= " center " > <thead><tr> <th align= " center " >Quantity</th> <th align= " left " >Item</th> <th align= " right " >Price</th> <th align= " right " >Subtotal</th> </tr></thead> <tbody> @foreach( var line in Model.Cart.Lines) { <tr> <td align= " center " >@line.Quantity</td> <td align= " left " >@line.Product.Name</td> <td align= " right " >@line.Product.Price.ToString( " c " )</td> <td align= " right " >@((line.Quantity * line.Product.Price).ToString( " c " ))</td> </tr> } </tbody> <tfoot> <tr> <td colspan= " 3 " align= " right " >Total:</td> <td align= " right " > @Model.Cart.ComputeTotalValue().ToString( " c " ) </td> </tr> </tfoot> </table> <p align= " center " class = " actionButtons " > <a href= " @Model.ReturnUrl " >Continue shopping</a> </p>
添加样式单代码:
H2 { margin-top : 0.3em } TFOOT TD { border-top : 1px dotted gray ; font-weight : bold ; } .actionButtons A, INPUT.actionButtons { font : .8em Arial ; color : White ; margin : .5em ; text-decoration : none ; padding : .15em 1.5em .2em 1.5em ; background-color : #353535 ; border : 1px solid black ; }
运行程序并选择产品添加到购物车,你将看到如下画面:
现在我们已经有了一个基本的购物功能,但它还有很多问题,在下一篇中,我们将进一步改进并完善我们的购物车,如果你已经想到了更好的完善方案,在下一篇中,我们可以对比一下,看看我们想的是否一致!请继续关注我的续篇!
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于构建一个真实的应用电子商务SportsStore(七)的详细内容...