`
king_tt
  • 浏览: 2259633 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

使用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

现在我们已经有了一个基本的购物功能,但它还有很多问题,在下一篇中,我们将进一步改进并完善我们的购物车,如果你已经想到了更好的完善方案,在下一篇中,我们可以对比一下,看看我们想的是否一致!请继续关注我的续篇!

分享到:
评论

相关推荐

    Ninject & Moq for MVC3

    在Ninject和Moq结合使用时,它们可以一起为MVC3应用提供强大的测试支持。开发者可以使用Ninject来注入Mock对象,这样在单元测试中,就可以控制依赖的行为和期望,而不会受到真实环境的影响。例如,对于一个依赖...

    【ASP.NET编程知识】ASP.NET MVC使用Ninject的方法教程.docx

    本文总结了使用 Ninject 框架在 ASP.NET MVC 中实现依赖注入的方法,介绍了 Ninject 的优点、基本概念和使用步骤,并提供了实践示例代码。 一、为什么选择 Ninject? Ninject 是一个轻量级的基于 .NET 平台的 IOC ...

    在MVC5中如何使用Ninject

    Ninject是一个流行的依赖注入(DI)框架,它可以帮助开发者实现松耦合的代码,提高应用程序的可测试性和可维护性。在ASP.NET MVC5中,Ninject可以被用来管理控制器的依赖关系,使你的应用更加灵活。以下是如何在MVC5...

    ASP.NetMVC4Ninject

    ASP.NET MVC 4 是微软开发的一个用于构建动态网站的框架,它基于模型-视图-控制器(MVC)设计模式,提供了对Web应用程序的灵活控制和强大的测试支持。Ninject是一个流行的轻量级依赖注入(DI)容器,它在.NET环境中...

    mvc下简单使用Ninject

    它能够帮助你把应用程序分离成一个个松耦合、高内聚的模块,然后用一种灵活的方式组装起来。通过使用Ninject配套你的软件架构,那么代码将会变得更加容易编写、重用性强、易于测试和修改。 MVC4 配合 Ninject 3 更...

    在ASP.NET Web API和MVC中使用Ninject

    另一方面,ASP.NET MVC(Model-View-Controller)是微软提供的一个用于构建Web应用程序的模式,它鼓励分离关注点,提高代码的可测试性和可维护性。 **Ninject介绍** Ninject是一款轻量级的依赖注入(DI)容器,...

    ASP.NET MVC使用Ninject的方法教程

    ASP.NET MVC是一个强大的Web应用程序开发框架,它支持多种设计模式,包括依赖注入(Dependency Injection,简称DI)。依赖注入是一种设计模式,它可以帮助我们构建松耦合的代码,提高可测试性和可维护性。Ninject是...

    MVC3+EF4.1框架

    MVC3(Model-View-Controller)和Entity Framework 4.1(简称EF4.1)是两个在Web开发领域广泛使用的框架,它们分别处理应用程序的结构设计和数据访问层。在这个主题中,我们将深入探讨这两个框架的核心概念、功能...

    Professional ASP.NET MVC 4 英文原版 全本

    ASP.NET MVC(Model-View-Controller)是一个开源的Web应用程序框架,由Microsoft开发,用于构建可维护且高度分化的Web应用。MVC模式鼓励分离关注点,使得代码更加模块化,更容易测试和维护。ASP.NET MVC 4是该框架...

    MVC.net + IOC(Ninject) 示例源码

    **MVC .NET + IOC (Ninject) 示例源码详解** **一、MVC .NET 框架介绍** MVC(Model-View-Controller)是一种设计模式,广泛应用于Web开发,它将业务逻辑、数据处理和用户界面分离,提高了代码的可维护性和可测试性...

    mvc EF框架 开发项目的绝佳选择

    在IT行业中,开发高效且易于维护的Web应用程序是一项重要的任务,而MVC(Model-View-Controller)架构模式和Entity Framework (EF) 框架是实现这一目标的强大工具。本文将深入探讨“mvc EF框架”作为开发项目的绝佳...

    MVC4在先考试截图

    - **Web API**:MVC4引入了ASP.NET Web API,这是一个用于构建RESTful服务的强大框架,可以轻松地创建供移动应用和Web应用使用的数据接口。 - **Bundling and Minification**:MVC4提供了资源打包和压缩功能,可以...

    ASP.NET MVC4开发指南.pdf

    ASP.NET MVC4是一种基于微软.NET Framework的开源web应用程序框架,专为构建可维护性和测试性的动态网站而设计。它结合了Model-View-Controller(MVC)设计模式、ASP.NET的功能性和HTML5的新特性,提供了高效、灵活...

    Ninject .net mvc DI容器

    ### Ninject 在 ASP.NET MVC 中的应用 #### 一、依赖注入与 IOC 概念 依赖注入(Dependency Injection,简称 DI)是一种设计模式,用于降低组件之间的耦合度,提高代码的可测试性和可维护性。它允许外部实体管理...

    itera-virtual-book-library:一个使用 EntityFramework 6.0、OWIN、Ninject、NLog 和各种框架的 ASP.NET MVC 5 应用程序

    itera-virtual-book-library 使用 EntityFramework 6.0 beta、OWIN、Ninject、NLog 和各种其他框架的 ASP.NET MVC 5 应用程序。 这是我在 2013 年为 Itera 编写的一个简单项目,用于培训目的。 背后的源代码 。

    ninject demo

    在“ninject demo”中,我们可以看到多个文件和目录,它们共同构成了一个使用NInject的示例项目。下面将逐一解释这些文件和目录的作用,以及如何在实际开发中运用NInject的知识点。 1. **ConsoleApp.sln**: 这是...

    C#MVC+IOC+EF+SQLite源码,学习用

    学习这个项目,你可以了解如何将 C#、MVC、IOC 和 EF 结合使用来构建一个完整的 Web 应用程序,并利用 SQLite 存储数据。同时,它还提供了测试和项目组织的良好示例,对于提升你的 .NET 开发技能非常有帮助。为了...

    ninject3.0.0.15-net-4.0.zip

    Ninject 是一个流行的依赖注入(Dependency Injection,DI)框架,属于控制反转(Inversion of Control,IoC)设计模式的一种实现。在.NET开发环境中,它为开发者提供了方便的方式来管理对象的生命周期,解耦代码,...

Global site tag (gtag.js) - Google Analytics