zoukankan      html  css  js  c++  java
  • 构建一个真实的应用电子商务SportsStore(八)

    使用MVC4,Ninject,EF,Moq,构建一个真实的应用电子商务SportsStore(八)

    我们喜欢使用session state在Cart控制器中存储和管理我们Cart对象,但是我们不喜欢这种做事的方式,而且那些基于action方法参数的应用模块也不适用这种方式,我们无法测试控制器类,除非我们Mock基类的Session参数,这就意味着要mock整个控制器类和我们所有需要的东西,这太不现实了。为了解决这个问题,我们就必须使用MVC的另一个重要特性Model binders,MVC框架使用Model binding从Http请求中创建C# 对像,传递给action方法作为参数,我们现在就创建一个自定义的model binder,去获取session data中包含的Cart对像。 

     

    创建自定义的Model Binder

     

    要创建自定义的model binder,就要实现IModelBinder 接口.在你的SportsStore.WebUI工程中建一个文件夹叫做Binders,并且创建一个叫做CartModelBinder的类:

     

    复制代码
    using System;
    using System.Web.Mvc;
    using SportsStore.Domain.Entities;
    
    namespace SportsStore.WebUI.Binders
    {
        public class CartModelBinder : IModelBinder {
            private const string sessionKey = "Cart";
            public object BindModel(ControllerContext controllerContext,
            ModelBindingContext bindingContext)
            {
                // get the Cart from the session
                Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
                // create the Cart if there wasn't one in the session data
                if (cart == null)
                {
                    cart = new Cart();
                    controllerContext.HttpContext.Session[sessionKey] = cart;
                }
                // return the cart
                return cart;
            }
        }
    }
    复制代码

     

     

    IModelBinder 接口定义了一个方法: BindModel. ControllerContext提供了访问控制器所有信息的能力,包括来自客户端请求的详细信息, ModelBindingContext 给了你关于你将要绑定的模块的信息。ControllerContext类有一个HttpContext属性,它又包含了一个Session属性,我们可以操作session data. 现在,我们要通知MVC使用我们的CartModelBinder类去创建Cart实例,我们需要修改一下Global.asax文件的 Application_Start方法。

     

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Http;
    using System.Web.Mvc;
    using System.Web.Optimization;
    using System.Web.Routing;
    using SportsStore.WebUI.Infrastructure;
    using SportsStore.Domain.Entities;
    using SportsStore.WebUI.Binders;
    namespace SportsStore.WebUI
    {
        // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
        // 请访问 http://go.microsoft.com/?LinkId=9394801
    
        public class MvcApplication : System.Web.HttpApplication
        {
            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
    
                WebApiConfig.Register(GlobalConfiguration.Configuration);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
    
                //Added by wangzhiyue 
                //We need to tell MVC that we want to use the NinjectController 
                //class to create controller objects
                ControllerBuilder.Current.SetControllerFactory(new
                             NinjectControllerFactory());
    
                ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());
                //Added end
    
                AuthConfig.RegisterAuth();
            }
        }
    }
    复制代码

     

     

    现在我们需要更新CartController 类,删除GetCart方法:

     

    复制代码
    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(Cart cart, string returnUrl)
            {
                return View(new CartIndexViewModel
                {
                   // Cart = GetCart(),
                    Cart = cart,
                    ReturnUrl = returnUrl
                });
            }
    
            public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)
            {
                Product product = repository.Products
                .FirstOrDefault(p => p.ProductID == productId);
                if (product != null)
                {
                   // GetCart().AddItem(product, 1);
                    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)
                {
                    //GetCart().RemoveLine(product);
                    cart.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;
            //}
        }
    }
    复制代码

     

     

    现在去完善一下CartTests.cs文件吧:

     

    复制代码
    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using SportsStore.Domain.Entities;
    using System.Linq;
    using Moq;
    using SportsStore.Domain.Abstract;
    using SportsStore.WebUI.Controllers;
    using System.Web.Mvc;
    using SportsStore.WebUI.Models;
    
    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);
                }
    
                [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);
                }
    
                [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);
                }
    
                [TestMethod]
                public void Can_Add_To_Cart() {
                        // Arrange - create the mock repository
                        Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
                        mock.Setup(m => m.Products).Returns(new Product[] {
                        new Product {ProductID = 1, Name = "P1", Category = "Apples"},
                        }.AsQueryable());
                        // Arrange - create a Cart
                        Cart cart = new Cart();
                        // Arrange - create the controller
                        CartController target = new CartController(mock.Object);
                        // Act - add a product to the cart
                        target.AddToCart(cart, 1, null);
                        // Assert
                        Assert.AreEqual(cart.Lines.Count(), 1);
                        Assert.AreEqual(cart.Lines.ToArray()[0].Product.ProductID, 1);
                }
    
                [TestMethod]
                public void Adding_Product_To_Cart_Goes_To_Cart_Screen()
                {
                    // Arrange - create the mock repository
                    Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
                    mock.Setup(m => m.Products).Returns(new Product[] {
                    new Product {ProductID = 1, Name = "P1", Category = "Apples"},
                    }.AsQueryable());
                    // Arrange - create a Cart
                    Cart cart = new Cart();
                    // Arrange - create the controller
                    CartController target = new CartController(mock.Object);
                    // Act - add a product to the cart
                    RedirectToRouteResult result = target.AddToCart(cart, 2, "myUrl");
                    // Assert
                    Assert.AreEqual(result.RouteValues["action"], "Index");
                    Assert.AreEqual(result.RouteValues["returnUrl"], "myUrl");
                }
    
                [TestMethod]
                public void Can_View_Cart_Contents()
                {
                    // Arrange - create a Cart
                    Cart cart = new Cart();
                    // Arrange - create the controller
                    CartController target = new CartController(null);
                    // Act - call the Index action method
                    CartIndexViewModel result
                    = (CartIndexViewModel)target.Index(cart, "myUrl").ViewData.Model;
                    // Assert
                    Assert.AreSame(result.Cart, cart);
                    Assert.AreEqual(result.ReturnUrl, "myUrl");
                }
    
          }
    }
    复制代码

     

    我们已经定义了RemoveFromCart方法,所以从购物车中删除商品,只是要暴露这个方法给用户,我们修改一下Views/Cart/Index.cshtml文件,去实现这个功能:

     

    复制代码
    @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>
        <td>
                @using (Html.BeginForm("RemoveFromCart", "Cart")) {
                @Html.Hidden("ProductId", line.Product.ProductID)
                @Html.HiddenFor(x => x.ReturnUrl)
                <input class="actionButtons" type="submit"
                value="Remove" />
                }
                </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>
    复制代码

     

     

    还有个问题,就是用户只能在每次添加商品时才看到自己消费的summary,这实在不方便,我们应该为CartController添加一个Summary的action方法,让用户随时可以查看:

     

            public PartialViewResult Summary(Cart cart)
            {
                return PartialView(cart);
            }

     

    现在就去创建一个强类型的partial view吧:

     

    image

     

    复制代码
    @model SportsStore.Domain.Entities.Cart
    <div id="cart">
        <span class="caption">
        <b>Your cart:</b>
        @Model.Lines.Sum(x => x.Quantity) item(s),
        @Model.ComputeTotalValue().ToString("c")
        </span>
        @Html.ActionLink("Checkout", "Index", "Cart",
        new { returnUrl = Request.Url.PathAndQuery }, null)
    </div>
    复制代码

     

     

    我们还要把这个View渲染到_Layout.cshtml文件中:

     

    复制代码
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/Site.css" type="text/css" rel="stylesheet" />
    </head>
    <body>
        <div id="header">
            @{Html.RenderAction("Summary", "Cart");}
            <div class="title">SPORTS STORE</div>
        </div>
        <div id="categories">
           @{ Html.RenderAction("Menu", "Nav"); }
        </div>
        <div id="content">
            @RenderBody()
        </div>
    </body>
    </html>
    复制代码

     

     

    DIV#cart { float:right; margin: .8em; color: Silver;
    background-color: #555; padding: .5em .5em .5em 1em; }
    DIV#cart A { text-decoration: none; padding: .4em 1em .4em 1em; line-height:2.1em;
    margin-left: .5em; background-color: #333; color:White; border: 1px solid black;}

     

    把上面的样式单添加到你的Site.css文件中,运行一下吧!

     

    image

     

     

    为了方便大家调试跟踪,我把截至到本篇的项目源代码发布到了网盘上,这是全量包,去下载吧:

     

    http://vdisk.weibo.com/s/EOJ5b/1370615290

     

    哦,忘记了最重要的一件事,我们还没收款的功能,这我们可亏大了!不过今天实在太累了,下篇我们再继续开发收款的模块吧!请继续关注我们续篇!

  • 相关阅读:
    linux tar 压缩解压缩
    JS获取图片上传地址
    ipython notebook
    docker build lnmp(未完成。。。)
    centos6.7 install chrome
    centos6.6 install
    centos 安装mysqldb 记录
    centos emacs安装
    第三周-第08章节-Python3.5-文件修改详解
    第三周-第06章节-Python3.5-文件读与写详解
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3125625.html
Copyright © 2011-2022 走看看