zoukankan      html  css  js  c++  java
  • 构建一个真实的应用电子商务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。运行你的应用,能看到如下画面:

    image

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

    image

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

    复制代码
    [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

    导航到

    /

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

    /Page2

    列出所有产品的指定页

    /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>
    复制代码

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

    image

    产生分类列表

    现在,我们就在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>在模型类输入框中。

    image

    修改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; }
    复制代码

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

    image

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

    修改我们的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
        })
    }
    复制代码

    运行一下看看结果吧!

    image

    纠正页码

    从上图中我们很轻易就能看出,我们的页码是错的,我们只有两个产品,却显示了有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);
            }
    复制代码

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

    image

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

    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的作用也不止这一点,它还可以做一些页面访问权限控制的工作等等,今天这里只介绍处理乱码的问题,其他的如果大家有兴趣可以自己研究,或者等小弟日后再写相关的文章跟大家交流。

     

     

     
    分类: Java编程语言
  • 相关阅读:
    笔记20200430:异常机制
    笔记20200429:面向对象
    笔记20200429:数组
    笔记20200428:方法的定义及调用、重装、命令行参数、可变参数、递归
    笔记20200427:javadoc文档、Scanner、顺序结构、选择结构、循环结构
    CSS+DIV入门第一天基础视频 CSS选择器层叠性和继承性
    网站实战 从效果图开始CSS+DIV 布局华为网站
    CSS中的间距设置与盒子模型
    CSS选择器
    CSS颜色及文本字体
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3120792.html
Copyright © 2011-2022 走看看