好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

构建一个真实的应用电子商务SportsStore(八)

构建一个真实的应用电子商务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测试数据/?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吧:

 

 

 @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文件中,运行一下吧!

 

 

 

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

 

http://vdisk.weibo测试数据/s/EOJ5b/1370615290

 

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

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于构建一个真实的应用电子商务SportsStore(八)的详细内容...

  阅读:63次