好得很程序员自学网

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

构建一个真实的应用电子商务SportsStore4

构建一个真实的应用电子商务SportsStore4

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

上篇中,我们将数据库中的数据显示到了 UI上,在这里我要强调一点,在上篇中我们应用了强类型的View,不要与model业务混淆,有关强类型view的知识点,不在本实例范畴之内,请参阅相关文档。对于任何一个电子商务网站来说,都需要使用户能方便的浏览所有的商品,并能够从一页迁移到另一页,这是个非常实用、也非常基本的功能,但在MVC4中,怎么实现它呢,现在就让我们一步一步的完善这个功能。

 

首先,我们要为我们的Product控制器的List 方法添加一个参数,用它来代表浏览的页号,代码如下:

 using   System;
  using   System.Collections.Generic;
  using   System.Linq;
  using   System.Web;
  using   System.Web.Mvc;
  using   SportsStore.Domain.Abstract;
  using   SportsStore.Domain.Entities;
  using   SportsStore.WebUI.Models;

  namespace   SportsStore.WebUI.Controllers
{
      public   class   ProductController : Controller
    {
          private   IProductsRepository repository;
          public   int  PageSize =  4  ;

          public   ProductController(IProductsRepository productRepository)
        {
              this .repository =  productRepository;
        }

          public  ViewResult List( int  page =  1  ) {

              return  View(repository.Products.OrderBy(p =>  p.ProductID)
                                .Skip((page  -  1 ) *  PageSize)
                                .Take(PageSize));
        }

    }
} 

PageSize字段指定了每一页要显示的产品数量,稍后我们将使用更好的机制来替换它,现在你只需要理解它。

我们还添加了一个可选的参数到List方法,这就表示如果我们调用的方法没有参数(List()), 我们将会使用(List(1)) 来处理,就是默认为显示第一页。

 

现在我们添加一个测试文件到你的SportsStore.UnitTests工程

 using   Microsoft.VisualStudio.TestTools.UnitTesting;
  using   Moq;
  using   SportsStore.Domain.Abstract;
  using   SportsStore.Domain.Entities;
  using   SportsStore.WebUI.Controllers;
  using   System.Collections.Generic;
  using   System.Linq;
  using   SportsStore.WebUI.Models;
  using   System;

  namespace   SportsStore.UnitTests {
    [TestClass]
      public   class   UnitTest1 {
        [TestMethod]
          public   void   Can_Paginate() {
                  //   Arrange 
                Mock<IProductsRepository> mock =  new  Mock<IProductsRepository> ();
                mock.Setup(m  => m.Products).Returns( new   Product[] {
                  new  Product {ProductID =  1 , Name =  "  P1  "  },
                  new  Product {ProductID =  2 , Name =  "  P2  "  },
                  new  Product {ProductID =  3 , Name =  "  P3  "  },
                  new  Product {ProductID =  4 , Name =  "  P4  "  },
                  new  Product {ProductID =  5 , Name =  "  P5  "  }
                }.AsQueryable());
                ProductController controller  =  new   ProductController(mock.Object);
                controller.PageSize  =  3  ;
                  //   Act 
                IEnumerable<Product> result = 
                (IEnumerable <Product>)controller.List( 2  ).Model;
                  //   Assert 
                Product[] prodArray =  result.ToArray();
                Assert.IsTrue(prodArray.Length  ==  2  );
                Assert.AreEqual(prodArray[  0 ].Name,  "  P4  "  );
                Assert.AreEqual(prodArray[  1 ].Name,  "  P5  "  );
        }


    }
} 

这里请注意看,我们是如何轻松的从一个控制器的结果集中获得数据的,在这里,我们反转了这个结果集到一个数组,并检查单个对象的长度和值。

运行工程,可以看到如下结果:

 

添加View Model

View Model 不是我们领域模型的一部分,只是为了方便在控制器和View 之间传递数据,所以我们把它放在SportsStore.WebUI工程的Models文件夹中,命名为PagingInfo,代码如下:

 using   System;

  namespace   SportsStore.WebUI.Models {

      public   class   PagingInfo {

          public   int  TotalItems {  get ;  set  ; }
          public   int  ItemsPerPage {  get ;  set  ; }
          public   int  CurrentPage {  get ;  set  ; }

          public   int   TotalPages
        {
              get  {  return  ( int )Math.Ceiling(( decimal )TotalItems /  ItemsPerPage); }
        }

    }
} 

添加HTML Helper 方法

现在,我们需要添加一个文件夹命名为HtmlHelpers,并添加一个文件,命名为PagingHelpers。

 using   System;
  using   System.Text;
  using   System.Web.Mvc;
  using   SportsStore.WebUI.Models;
  namespace   SportsStore.WebUI.HtmlHelpers
{
      public   static   class   PagingHelpers
    {
          public   static  MvcHtmlString PageLinks( this   HtmlHelper html,
        PagingInfo pagingInfo,
        Func < int ,  string >  pageUrl)
        {
            StringBuilder result  =  new   StringBuilder();
              for  ( int  i =  1 ; i <= pagingInfo.TotalPages; i++ )
            {
                TagBuilder tag  =  new  TagBuilder( "  a  " );  //   Construct an <a> tag 
                tag.MergeAttribute( "  href  "  , pageUrl(i));
                tag.InnerHtml  =  i.ToString();
                  if  (i ==  pagingInfo.CurrentPage)
                    tag.AddCssClass(  "  selected  "  );
                result.Append(tag.ToString());
            }
              return   MvcHtmlString.Create(result.ToString());
        }
    }
} 

这个PageLinks 扩展方法为页链接集合产生HTML,这个页链接集合使用了PagingInfo对象, Func 参数提供了传递代理的能力,代理被用来产生链接到其他页面的链接。

 

测试我们的HtmlHelpers

在我们测试文件中添加如下引用和方法:

 using   Microsoft.VisualStudio.TestTools.UnitTesting;
  using   Moq;
  using   SportsStore.Domain.Abstract;
  using   SportsStore.Domain.Entities;
  using   SportsStore.WebUI.Controllers;
  using   System.Collections.Generic;
  using   System.Linq;
  using   SportsStore.WebUI.Models;
  using   System;
  using   System.Web.Mvc;
  using   SportsStore.WebUI.HtmlHelpers;

  namespace   SportsStore.UnitTests {
    [TestClass]
      public   class   UnitTest1 {
        [TestMethod]
          public   void   Can_Paginate() {
                  //   Arrange 
                Mock<IProductsRepository> mock =  new  Mock<IProductsRepository> ();
                mock.Setup(m  => m.Products).Returns( new   Product[] {
                  new  Product {ProductID =  1 , Name =  "  P1  "  },
                  new  Product {ProductID =  2 , Name =  "  P2  "  },
                  new  Product {ProductID =  3 , Name =  "  P3  "  },
                  new  Product {ProductID =  4 , Name =  "  P4  "  },
                  new  Product {ProductID =  5 , Name =  "  P5  "  }
                }.AsQueryable());
                ProductController controller  =  new   ProductController(mock.Object);
                controller.PageSize  =  3  ;
                  //   Act 
                IEnumerable<Product> result = 
                (IEnumerable <Product>)controller.List( 2  ).Model;
                  //   Assert 
                Product[] prodArray =  result.ToArray();
                Assert.IsTrue(prodArray.Length  ==  2  );
                Assert.AreEqual(prodArray[  0 ].Name,  "  P4  "  );
                Assert.AreEqual(prodArray[  1 ].Name,  "  P5  "  );
        }


        [TestMethod]
          public   void   Can_Generate_Page_Links()
        {
              //   Arrange - define an HTML helper - we need to do this
              //   in order to apply the extension method 
            HtmlHelper myHelper =  null  ;
              //   Arrange - create PagingInfo data 
            PagingInfo pagingInfo =  new   PagingInfo
            {
                CurrentPage  =  2  ,
                TotalItems  =  28  ,
                ItemsPerPage  =  10  
            };
              //   Arrange - set up the delegate using a lambda expression 
            Func< int ,  string > pageUrlDelegate = i =>  "  Page  "  +  i;
              //   Act 
            MvcHtmlString result =  myHelper.PageLinks(pagingInfo, pageUrlDelegate);
              //   Assert 
            Assert.AreEqual(result.ToString(),  @"  <a href=""Page1"">1</a>  " 
            +  @"  <a class=""selected"" href=""Page2"">2</a>  " 
            +  @"  <a href=""Page3"">3</a>  "  );
        }
    }
} 

要使扩展方法有效,在代码中,我们需要确保引用它所在的namespace,我使用了using语句,但是对于一个 Razor View,我们必须配置web.config文件,而一个MVC工程,有2个web.config文件,一个是在工程的根目录下,另一个在View文件夹中,我们需要配置文件夹中的这个,添加如下语句到namespace标签中:

<add namespace="SportsStore.WebUI.HtmlHelpers"/>

<namespaces>
        <add  namespace = "  System.Web.Mvc  "  />
        <add  namespace = "  System.Web.Mvc.Ajax  "  />
        <add  namespace = "  System.Web.Mvc.Html  "  />
        <add  namespace = "  System.Web.Optimization  " />
        <add  namespace = "  System.Web.Routing  "  />
        <add  namespace = "  SportsStore.WebUI.HtmlHelpers  " />
      </namespaces>

 

添加View Model数据

我们并没有打算完全使用HTML helper方法,我们依然需要提供一个PagingInfo view model类的实例到View,我们可以使用view bag的特性,但我们更倾向于打包所有的Controller数据发送到一个单一的View,要实现这一点,我们要添加一个新类到Model文件夹,命名为ProductsListViewModel。

 

好了,现在我们再更新一下ProductController的代码,用ProductsListViewModel类提供给View更详细的数据。

 

 using   System;
  using   System.Collections.Generic;
  using   System.Linq;
  using   System.Web;
  using   System.Web.Mvc;
  using   SportsStore.Domain.Abstract;
  using   SportsStore.Domain.Entities;
  using   SportsStore.WebUI.Models;

  namespace   SportsStore.WebUI.Controllers
{
      public   class   ProductController : Controller
    {
          private   IProductsRepository repository;
          public   int  PageSize =  4  ;

          public   ProductController(IProductsRepository productRepository)
        {
              this .repository =  productRepository;
        }

          public  ViewResult List( int  page =  1  ) {

            ProductsListViewModel model  =  new   ProductsListViewModel
            {
                Products  =  repository.Products
                                    .OrderBy(p  =>  p.ProductID)
                                    .Skip((page  -  1 ) *  PageSize)
                                    .Take(PageSize),
                                    PagingInfo  =  new   PagingInfo
                                    {
                                        CurrentPage  =  page,
                                        ItemsPerPage  =  PageSize,
                                        TotalItems  =  repository.Products.Count()
                                    }
            };
              return   View(model);
        }

    }
} 


修改测试文件代码:

 using   Microsoft.VisualStudio.TestTools.UnitTesting;
  using   Moq;
  using   SportsStore.Domain.Abstract;
  using   SportsStore.Domain.Entities;
  using   SportsStore.WebUI.Controllers;
  using   System.Collections.Generic;
  using   System.Linq;
  using   SportsStore.WebUI.Models;
  using   System;
  using   System.Web.Mvc;
  using   SportsStore.WebUI.HtmlHelpers;

  namespace   SportsStore.UnitTests {
    [TestClass]
      public   class   UnitTest1 {
        [TestMethod]
          public   void   Can_Paginate() {
                  //   Arrange 
                Mock<IProductsRepository> mock =  new  Mock<IProductsRepository> ();
                mock.Setup(m  => m.Products).Returns( new   Product[] {
                  new  Product {ProductID =  1 , Name =  "  P1  "  },
                  new  Product {ProductID =  2 , Name =  "  P2  "  },
                  new  Product {ProductID =  3 , Name =  "  P3  "  },
                  new  Product {ProductID =  4 , Name =  "  P4  "  },
                  new  Product {ProductID =  5 , Name =  "  P5  "  }
                }.AsQueryable());
                ProductController controller  =  new   ProductController(mock.Object);
                controller.PageSize  =  3  ;

                  //   Action 
                ProductsListViewModel result = (ProductsListViewModel)controller.List( 2  ).Model;

                  //   Assert 
                Product[] prodArray =  result.Products.ToArray();
                Assert.IsTrue(prodArray.Length  ==  2  );
                Assert.AreEqual(prodArray[  0 ].Name,  "  P4  "  );
                Assert.AreEqual(prodArray[  1 ].Name,  "  P5  "  );
        }

        [TestMethod]
          public   void   Can_Send_Pagination_View_Model()
        {
              //   Arrange 
            Mock<IProductsRepository> mock =  new  Mock<IProductsRepository> ();
            mock.Setup(m  => m.Products).Returns( new   Product[] {
                                                              new  Product {ProductID =  1 , Name =  "  P1  "  },
                                                              new  Product {ProductID =  2 , Name =  "  P2  "  },
                                                              new  Product {ProductID =  3 , Name =  "  P3  "  },
                                                              new  Product {ProductID =  4 , Name =  "  P4  "  },
                                                              new  Product {ProductID =  5 , Name =  "  P5  "  }
                                                            }.AsQueryable());
              //   Arrange 
            ProductController controller =  new   ProductController(mock.Object);
            controller.PageSize  =  3  ;
              //   Act 
            ProductsListViewModel result = (ProductsListViewModel)controller.List( 2  ).Model;
              //   Assert 
            PagingInfo pageInfo =  result.PagingInfo;
            Assert.AreEqual(pageInfo.CurrentPage,   2  );
            Assert.AreEqual(pageInfo.ItemsPerPage,   3  );
            Assert.AreEqual(pageInfo.TotalItems,   5  );
            Assert.AreEqual(pageInfo.TotalPages,   2  );
        }

        [TestMethod]
          public   void   Can_Generate_Page_Links()
        {
              //   Arrange - define an HTML helper - we need to do this
              //   in order to apply the extension method 
            HtmlHelper myHelper =  null  ;
              //   Arrange - create PagingInfo data 
            PagingInfo pagingInfo =  new   PagingInfo
            {
                CurrentPage  =  2  ,
                TotalItems  =  28  ,
                ItemsPerPage  =  10  
            };
              //   Arrange - set up the delegate using a lambda expression 
            Func< int ,  string > pageUrlDelegate = i =>  "  Page  "  +  i;
              //   Act 
            MvcHtmlString result =  myHelper.PageLinks(pagingInfo, pageUrlDelegate);
              //   Assert 
            Assert.AreEqual(result.ToString(),  @"  <a href=""Page1"">1</a>  " 
            +  @"  <a class=""selected"" href=""Page2"">2</a>  " 
            +  @"  <a href=""Page3"">3</a>  "  );
        }
    }
} 

运行程序,你将看到如下结果:

 

改进我们的页面链接

我们的页面链接都是工作的,但它看起来是这样的:
http://localhost/?page=2
我们能做的更好些, 尤其是通过创建一个scheme,它遵循可组装URLs. 这使得用户更容易理解,并且更有效率,它看起来应该像下面的地址:
http://localhost/Page2
MVC很容易去改变URL scheme,因为它使用了ASP.NET的routing特性,我们要做的就是添加一个新的route 到RouteConfig.cs文件的RegisterRoutes,打开App_Start文件夹,找到我们要改的文件。

 using   System;
  using   System.Collections.Generic;
  using   System.Linq;
  using   System.Web;
  using   System.Web.Mvc;
  using   System.Web.Routing;

  namespace   SportsStore.WebUI
{
      public   class   RouteConfig
    {
          public   static   void   RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute(  "  {resource}.axd/{*pathInfo}  "  );

            routes.MapRoute(
                name:   null  ,
                url:   "  Page{page}  "  ,
                defaults:   new  { Controller =  "  Product  " , action =  "  List  "   }
                );

            routes.MapRoute(
                name:   "  Default  "  ,
                url:   "  {controller}/{action}/{id}  "  ,
                defaults:   new  { controller =  "  Product  " , action =  "  List  " , id =  UrlParameter.Optional }
            );
        }
    }
} 

把我的Route放在默认的Route前是很重要的,Route的处理是按照它们被列出的顺序进行的,我们需要用我们新的Route优先于默认的,现在你暂时先了解这些,以后,我们会更加详细的讲解它。

 好了,这个博客的编辑器太难用了,总是不停的刷新,无法固定页面位置,今天的内容比较多,希望大家能仔细看好每一步,这里面没有一个字是多余的!剩下没写完的下次再写吧!请继续关注的续篇。

 

 

 

 

标签:  MVC4 ,  Ninject ,  EF ,  Moq ,  IOC ,  Jquery

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

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

版权信息

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

  阅读:36次

上一篇: 产品经理之职责篇

下一篇:nopCommerce学习