构建一个真实的应用电子商务SportsStore(六)
构建一个真实的应用电子商务SportsStore(六)
添加Navigation控件
上篇我们已经对UI部分做了整理,但是我们网站看起来仍然很奇怪,因为用户无法选择他们想看的商品类别,必须要一页一页的浏览,直到找到自己想要买的东西。我经常在网上浏览一些技术站点,并添加他们到我的收藏夹,但收藏夹里的条目太多了,还是不能方便的找到自己想看的网址,偶然发现了一个网站,叫做开发者导航( http://HdhCmsTestdevseek.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://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于构建一个真实的应用电子商务SportsStore(六)的详细内容...