好得很程序员自学网

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

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

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

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

添加Navigation控件

上篇我们已经对UI部分做了整理,但是我们网站看起来仍然很奇怪,因为用户无法选择他们想看的商品类别,必须要一页一页的浏览,直到找到自己想要买的东西。我经常在网上浏览一些技术站点,并添加他们到我的收藏夹,但收藏夹里的条目太多了,还是不能方便的找到自己想看的网址,偶然发现了一个网站,叫做开发者导航( http://www.devseek.net ),它收录了我所需要的所有网址,这正是我想要的,于是我今天也用这个导航的字眼,来为我们的网站添加一个分类过滤的功能。我们今天的内容主要有三个部分:

1.增强ProductController类的List action功能,使它能够分类商品。

2.修改并加强URL scheme 和我们的rerouting策略。

3.在边条上创建分类列表,并高亮当前的分类和连接。

过滤产品列表

为了渲染我们的边条,我们需要和我们ProductsListViewModel类沟通,过滤出产品分类的列表,现在就打开这个文件,让我们为它做个Enhancement。

 using   System.Collections.Generic;
  using   SportsStore.Domain.Entities;

  namespace   SportsStore.WebUI.Models {

      public   class   ProductsListViewModel {

          public  IEnumerable<Product> Products {  get ;  set  ; }
          public  PagingInfo PagingInfo {  get ;  set  ; }
          public   string  CurrentCategory {  get ;  set  ; }
    }
} 

我们为这个类添加了一个当前分类的属性,用来显示当前用户选择的分类。我们要更新我们ProductController,使它能够使用这个属性:

 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( string  category,  int  page =  1  )
        {

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

    }
} 

上面的代码我们做了3个改变。第一,我们添加了一个新的参数叫做category. 这个参数通过我们的第二个变化被使用,这第二个变化就是我改进了Linq查询,如果category参数不是null,只有匹配这个分类的产品才能被选择。这最后一个改变就是设置CurrentCategory 属性的值,然而,我们这3点改变,就意味着PagingInfo.TotalItems的值是不正确的,我们必须解决这个问题。

更新现有的测试方法

我们改变了List的参数列表,这使得我们必须更新我们现有的测试方法,为了保证我们的测试方法都可用,我们要为他们添加一个null值,作为第一个参数传递,找到Can_Paginate方法,将List(2).Model改成List(null, 2).Model。运行你的应用,能看到如下画面:

这和我们上篇最后的结果是一样的,现在你在地址栏中添加如下参数:?category=Soccer ,是你的地址栏看上去像这样 http://localhost:47072/?category=Soccer  你会看到这样的画面:

我们的测试文件现在需要添加一个功能,使它能够正确的过滤一个分类并接收一个指定分类的产品:

 [TestMethod]
          public   void   Can_Filter_Products() {
                  //   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 =  "  Cat1  "  },
                  new  Product {ProductID =  2 , Name =  "  P2  " , Category =  "  Cat2  "  },
                  new  Product {ProductID =  3 , Name =  "  P3  " , Category =  "  Cat1  "  },
                  new  Product {ProductID =  4 , Name =  "  P4  " , Category =  "  Cat2  "  },
                  new  Product {ProductID =  5 , Name =  "  P5  " , Category =  "  Cat3  "  }
                }.AsQueryable());
                  //   Arrange - create a controller and make the page size 3 items 
                ProductController controller =  new   ProductController(mock.Object);
                controller.PageSize  =  3  ;
                  //   Action 
                Product[] result = ((ProductsListViewModel)controller.List( "  Cat2  " ,  1  ).Model)
                .Products.ToArray();
                  //   Assert 
                Assert.AreEqual(result.Length,  2  );
                Assert.IsTrue(result[  0 ].Name ==  "  P2  "  && result[ 0 ].Category ==  "  Cat2  "  );
                Assert.IsTrue(result[  1 ].Name ==  "  P4  "  && result[ 1 ].Category ==  "  Cat2  "  );
        } 

这个测试创建了一个mock repository,它包含了类别中的一个Product对象,一个被指定使用在Action方法中的分类,并且结果被check,确保在右侧的产品对象都是正确的。

改善URL Scheme

我们的URL地址看上去太丑了,也不专业,现在我们必须花点时间去改善一下App_Start/RouteConfig.cs文件:

 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(  null  ,
              ""  ,
              new   {
            controller  =  "  Product  " , action =  "  List  "  ,
            category  = ( string ) null , page =  1  
            } );

            routes.MapRoute(  null  ,
              "  Page{page}  "  ,
              new  { controller =  "  Product  " , action =  "  List  " , category = ( string ) null   },
              new  { page =  @"  \d+  "   }
            );

            routes.MapRoute(  null  ,
              "  {category}  "  ,
              new  { controller =  "  Product  " , action =  "  List  " , page =  1   }
            );

            routes.MapRoute(  null  ,
              "  {category}/Page{page}  "  ,
              new  { controller =  "  Product  " , action =  "  List  "   },
              new  { page =  @"  \d+  "   }
            );

            routes.MapRoute(  null ,  "  {controller}/{action}  "  );

        }
    }
} 

URL

导航到

/

列出说有产品的第一页列表

列出所有产品的指定页

/Soccer

列出指定类别的产品的第一页

/Soccer/Page2

列出指定类别的产品的指定页

/Anything/Else

调用Anything 控制器的Else方法

这是我们URL Scheme的具体含义。MVC使用ASP.NET routing 系统去处理从用户端发来的请求,同时,它也向外发出URL scheme,这就是我们能够嵌入到网页中的地址,我们要做的就是这些应用中的地址都是被组装起来的。

现在我们就去添加一些对分类过滤的支持:

 @model SportsStore.WebUI.Models.ProductsListViewModel
@{
    ViewBag.Title  =  "  Products  "  ;
}
@foreach (  var  p  in   Model.Products)
{
    Html.RenderPartial(  "  ProductSummary  "  , p);
}
 <div  class = "  pager  " > 
        @Html.PageLinks(Model.PagingInfo, x  => Url.Action( "  List  "  ,
                 new  {page = x, category =  Model.CurrentCategory}))
 </div>

构建一个分类导航菜单

我们需要提供给用户一种途径,使用户能够选择某种分类,这就需要我们必须提供分类信息给用户,让他们去选择,而这个分类的信息必须要在多个控制器中运用,这就要求它必须是自包含的并且可重用的。在ASP.NET MVC框架中有个child actions的概念, 大家都喜欢用它来创建诸如可重用的导航控件之类的东西。一个child action 依赖与HTML helper方法,这个方法被称为RenderAction,它让我们从当前view的任意action方法中包含输出,现在我们创建一个新的Controller(我们称它为NavController) 和一个action方法 (菜单) ,并且渲染一个导航菜单,然后从这个方法中注入output到layout中。这个方法给了我们一个真正的控制器,无论我们的应用逻辑是什么都可以使用它,并且我们都能像其他控制器那用去测试它。

创建Navigation控制器

右击WebUI中的Controllers文件夹,创建一个名为NavController的控制器,选择空的MVC模板,删除自动生成的index方法,添加代码如下:

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

  namespace   SportsStore.WebUI.Controllers
{
      public   class   NavController : Controller
    {
          // 
         //   GET: /Nav/ 

         public   string   Menu()
        {
              return   "  Hello from NavController  "  ;
        }

    }
} 

这个方法返回一个消息字符串,但这对于我们整合一个child action到这个应用的其他部分已经足够用了。我们希望这个分类列表展现在所有页面上,所以我们将在layout中渲染这个child action,而不是在一个指定的View中。 现在我们编辑Views/Shared/_Layout.cshtml 文件,让它调用RenderAction helper方法。

<!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  " >
        <div  class = "  title  " >SPORTS STORE</div>
    </div>
    <div id= "  categories  " > 
       @{ Html.RenderAction(  "  Menu  " ,  "  Nav  "  ); }
     </div>
    <div id= "  content  " > 
        @RenderBody()
     </div>
</body>
</html>

运行应用,你将看到我们的边条上已经出现了这条消息字符串:

产生分类列表

现在,我们就在Menu action方法中创建分类列表:

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

  namespace   SportsStore.WebUI.Controllers
{
      public   class   NavController : Controller
    {
          // 
         //   GET: /Nav/ 

         private   IProductsRepository repository;
          public   NavController(IProductsRepository repo)
        {
            repository  =  repo;
        }
          public   PartialViewResult Menu()
        {
            IEnumerable < string > categories =  repository.Products
            .Select(x  =>  x.Category)
            .Distinct()
            .OrderBy(x  =>  x);
              return   PartialView(categories);
        }

    }
} 

我们的控制器现在接受一个IProductsRepository的实现,这个实现是通过Ninject提供的,还有一个变化,就是我们使用Linq从repository中获得分类信息,请注意,我们调用了一个PartialView的方法,返回了一个PartialViewResult对象。现在让我们去更新一下我们的测试文件吧!添加如下代码到你的测试文件:

         [TestMethod]
          public   void   Can_Create_Categories() {
                  //   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  "  },
                  new  Product {ProductID =  2 , Name =  "  P2  " , Category =  "  Apples  "  },
                  new  Product {ProductID =  3 , Name =  "  P3  " , Category =  "  Plums  "  },
                  new  Product {ProductID =  4 , Name =  "  P4  " , Category =  "  Oranges  "  },
                }.AsQueryable());
                  //   Arrange - create the controller 
                NavController target =  new   NavController(mock.Object);
                  //   Act = get the set of categories 
                 string [] results = ((IEnumerable< string > )target.Menu().Model).ToArray();
                  //   Assert 
                Assert.AreEqual(results.Length,  3  );
                Assert.AreEqual(results[  0 ],  "  Apples  "  );
                Assert.AreEqual(results[  1 ],  "  Oranges  "  );
                Assert.AreEqual(results[  2 ],  "  Plums  "  );
        } 

现在让我们去创建这个PartialView吧!

创建PartialView

在NavController中,右击Menu方法,选择添加View,并输入IEnumerable<string>在模型类输入框中。

修改Menu.cshtml文件如下:

@model IEnumerable< string > 
@Html.ActionLink(  "  Home  " ,  "  List  " ,  "  Product  "  )

    @foreach (  var  link  in   Model) {
            @Html.RouteLink(link,   new   {
            controller  =  "  Product  "  ,
            action  =  "  List  "  ,
            category  =  link,
            page  =  1  
    })
} 

在Site.css文件中添加如下代码:

 DIV#categories A
 { 
font :  bold 1.1em "Arial Narrow","Franklin Gothic Medium",Arial ;  display :  block ; 
text-decoration :  none ;  padding :  .6em ;  color :  Black ; 
border-bottom :  1px solid silver ;
} 
DIV#categories A.selected  {  background-color :  #666 ;  color :  White ; } 
DIV#categories A:hover  {  background-color :  #CCC ; } 
DIV#categories A.selected:hover  {  background-color :  #666 ; }

运行你的应用,能应该能看到如下画面:

我们还要需要进一步完善,因为我们现在还不能让用户清楚的看出当前选择了那种分类,我们要高亮当前选中的分类,这样看起来才更加友好、实用。

修改我们的NavController中的Menu方法如下:

 public  PartialViewResult Menu( string  category =  null  )
        {
            ViewBag.SelectedCategory  =  category;

            IEnumerable < string > categories =  repository.Products
            .Select(x  =>  x.Category)
            .Distinct()
            .OrderBy(x  =>  x);
              return   PartialView(categories);
        } 

为我们的测试文件添加一个选中的测试方法:

         [TestMethod]
          public   void   Indicates_Selected_Category()
        {
              //   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  "  },
                            new  Product {ProductID =  4 , Name =  "  P2  " , Category =  "  Oranges  "  },
                         }.AsQueryable());
              //   Arrange - create the controller 
            NavController target =  new   NavController(mock.Object);
              //   Arrange - define the category to selected 
             string  categoryToSelect =  "  Apples  "  ;
              //   Action 
             string  result =  target.Menu(categoryToSelect).ViewBag.SelectedCategory;
              //   Assert 
             Assert.AreEqual(categoryToSelect, result);
        } 

更新Menu.cshtml如下:

@model IEnumerable< string > 
@Html.ActionLink(  "  Home  " ,  "  List  " ,  "  Product  "  )

    @foreach (  var  link  in   Model) {
            @Html.RouteLink(link,   new   {
            controller  =  "  Product  "  ,
            action  =  "  List  "  ,
            category  =  link,
            page  =  1  
    },
      new   {
    @class  = link == ViewBag.SelectedCategory ?  "  selected  "  :  null  
    })
} 

运行一下看看结果吧!

纠正页码

从上图中我们很轻易就能看出,我们的页码是错的,我们只有两个产品,却显示了有3页,我们必须纠正这个错误!打开ProductController,找到List方法,修改如下:

 public  ViewResult List( string  category,  int  page =  1  )
        {

            ProductsListViewModel model  =  new   ProductsListViewModel
            {
                Products  =  repository.Products
                           .Where(p  => category ==  null  || p.Category ==  category)
                           .OrderBy(p  =>  p.ProductID)
                           .Skip((page  -  1 ) *  PageSize)
                           .Take(PageSize),
                           PagingInfo  =  new   PagingInfo
                           {
                               CurrentPage  =  page,
                               ItemsPerPage  =  PageSize,
                               TotalItems  = category ==  null  ? 
                                      repository.Products.Count() :
                           repository.Products.Where(e  => e.Category ==  category).Count()
                                    },
                                    CurrentCategory  =  category
            };
              return   View(model);
        } 

添加测试方法到测试文件:

         [TestMethod]
          public   void   Generate_Category_Specific_Product_Count() {
                  //   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 =  "  Cat1  "  },
                  new  Product {ProductID =  2 , Name =  "  P2  " , Category =  "  Cat2  "  },
                  new  Product {ProductID =  3 , Name =  "  P3  " , Category =  "  Cat1  "  },
                  new  Product {ProductID =  4 , Name =  "  P4  " , Category =  "  Cat2  "  },
                  new  Product {ProductID =  5 , Name =  "  P5  " , Category =  "  Cat3  "  }
                }.AsQueryable());
                  //   Arrange - create a controller and make the page size 3 items 
                ProductController target =  new   ProductController(mock.Object);
                target.PageSize  =  3  ;
                  //   Action - test the product counts for different categories 
                 int  res1 =  ((ProductsListViewModel)target
                .List(  "  Cat1  "  ).Model).PagingInfo.TotalItems;
                  int  res2 =  ((ProductsListViewModel)target
                .List(  "  Cat2  "  ).Model).PagingInfo.TotalItems;
                  int  res3 =  ((ProductsListViewModel)target
                .List(  "  Cat3  "  ).Model).PagingInfo.TotalItems;
                  int  resAll =  ((ProductsListViewModel)target
                .List(  null  ).Model).PagingInfo.TotalItems;
                  //   Assert 
                Assert.AreEqual(res1,  2  );
                Assert.AreEqual(res2,   2  );
                Assert.AreEqual(res3,   1  );
                Assert.AreEqual(resAll,   5  );
        } 

运行一下,现在看下我们成果吧!

好了,今天就到这里里吧!内容实在是有点多,但都是必须的,而且实用的技术,下一篇中,我们将为我们的应用添加一个购物车,这是电子商务网站上必须的功能,不然怎么卖商品呢?如果您觉得我的文章实用,对你有所帮助,请推荐它给你的朋友,请继续关注我的续篇!

Filter解决中文乱码问题

 

JavaWeb中交中文经常会出现乱码,想必各位都遇到过吧。今天跟大家聊聊一种比较常用的方式——Filter过滤。Filter就是起到一个过滤器的作用,当提交或者获取信息的时候,都会经过Filter,然后Filter会将你传递的信息转换成你设置好的编码格式,从而避免一些中文乱码的情况。

使用Filter过滤需要添加两部分代码,一是配置文件里关于Filter的配置信息;另一个就是Filter里面的过滤代码。下面一起看一下吧。

web.xml中的配置代码:

 <  filter  > 
       <  filter-name  > CharsetEncodingFilter </  filter-name  > 
       <  filter-class  >  
          com.tgb.drp.util.filter.CharsetEncodingFilter
        </  filter-class  > 
       <  init-param  > 
           <  param-name  > endcoding </  param-name  > 
           <  param-value  > GB18030 </  param-value  >   <!--  设置你想用的字符集,我这里用的是GB18030  --> 
       </  init-param  > 
   </  filter  > 
  
   <  filter-mapping  > 
       <  filter-name  > CharsetEncodingFilter </  filter-name  > 
       <  url-pattern  > *.jsp </  url-pattern  >   <!--  设置你想过滤的页面或者是Servlet,根据自己的需要配置  --> 
  </  filter-mapping  > 

Filter中的过滤代码:

 import   java.io.IOException;

  import   javax.servlet.Filter;
  import   javax.servlet.FilterChain;
  import   javax.servlet.FilterConfig;
  import   javax.servlet.ServletException;
  import   javax.servlet.ServletRequest;
  import   javax.servlet.ServletResponse;


  /**  
 * 采用Filter统一处理字符集
 *   @author   Ronaldinho
 *
   */ 
 public   class  CharsetEncodingFilter  implements   Filter {

      private   String endcoding;
    

    @Override
      public   void   destroy() {
    }
    
    @Override
      public   void   doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)   throws   IOException, ServletException {
        
        System.out.println( "CharsetEncodingFilter--->>>begin" );
        
          //  设置web.xml中配置的字符集 
         request.setCharacterEncoding(endcoding);
        
        System.out.println( "CharsetEncodingFilter--->>>doing" );
        
          //  继续执行 
         chain.doFilter(request, response);
        
        System.out.println( "CharsetEncodingFilter--->>>end" );
    }
    
    @Override
      public   void  init(FilterConfig filterConfig)  throws   ServletException {
          this .endcoding = filterConfig.getInitParameter("endcoding" );
        System.out.println( "CharsetEncodingFilter.init()-->> endcoding=" +  endcoding);
    }

} 

经过如上的设置,我们就可以避免一部分中文乱码的问题了,没错只能解决一部分乱码问题,因为导致乱码的原因很多,有可能是JSP导致的、也有可能是HTML、还有可能是URL传值导致的、也可能是Eclipse等编译器的原因所致.... 总之导致乱码的原因有很多,想做具体了解向大家推荐一篇文章—— JSP中文乱码问题终极解决方案 。

PS:Filter的方法只适合于post的提交方式,对于get的提交方式不起作用,而且get提交存在一定的安全问题,所以建议大家还是用post方式提交数据比较好一些。另外Filter的作用也不止这一点,它还可以做一些页面访问权限控制的工作等等,今天这里只介绍处理乱码的问题,其他的如果大家有兴趣可以自己研究,或者等小弟日后再写相关的文章跟大家交流。

 

 

欢迎大家光临我的CSDN博客

 

分类:  Java ,  编程语言

作者: Leo_wl

    

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

    

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

版权信息

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

  阅读:42次