使用MVC4,Ninject,EF,Moq,構建一個真實的應用電子商務SportsStore(七)


我們的項目進展相當的不錯,但是現在還不能真正的出售商品,因為我們沒有為顧客提供購物車。今天,我們就加入購物車的功能,畢竟賺錢才是贏道理啊!購物車的邏輯看起來應該像這樣:

image

我們需要在每件商品的旁邊都加一個"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方法,選擇添加視圖:

image

修改代碼如下:

@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;
}

運行程序並選擇產品添加到購物車,你將看到如下畫面:

image

現在我們已經有了一個基本的購物功能,但它還有很多問題,在下一篇中,我們將進一步改進並完善我們的購物車,如果你已經想到了更好的完善方案,在下一篇中,我們可以對比一下,看看我們想的是否一致!請繼續關注我的續篇!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM