上一篇文章建立了SportsStore应用程序的核心架构。本文将利用这一基础架构,将一些关键性特性添加到该应用程序上。
一:添加导航部件
如果让客户通过茶品分类对产品进行导航,SportsStore应用程序会更加适用,这需要从三个方面着手。
1. 增强Product控制器中List动作方法,以使它能够过滤存储库中的Product对象。
2. 重新考察并增强URL方案,并修订路由策略。
3. 创建一个产品分类列表,并将其放入网站工具条,高亮当前分类,并对其他分类进行链接。
1.1过滤产品列表
本例打算从增强视图模型类ProductsListViewModel开始,为了渲染导航条目,需要将当前分类传给视图。对ProductsListViewModel类进行如下修改。
添加了一个新的属性,叫做CurrentCategory。下一步是更新Product控制器类,以使List动作方法能通过分类来过滤Product对象,并利用这个添加到视图模型的新属性,以指示已选择了哪个分类。修改如下图。
做了如下三处修改,一是添加一个名为category的新参数;二是使用这个category参数,以使Linq查询得到增强,如果category非空,则只选出与Category属性匹配的那些Product对象;最后一个修改是添加到ProductsListViewModel类上的CurrentCategory属性的值。
运行应用程序,并用查询字符串选择一个分类,如:/?category=足球,便会只看到足球分类中的产品。
此时将会看到,足球类别下只有3个产品,分页个数仍为3页,这个问题将在稍后处理。
1.2调整URl方案
没人愿意看到像“/?category=足球”这种丑陋的URL,我们需要重新考察之前的路由方案,以创建一种更友好的URL方法。修改RegisterRoutes方法,如下图所示。
Url.Action方法是生成输出链接最方便的方法,在上一篇文章中,为了显示分页链接,已在List.cshtml视图中使用过这个辅助器方法,现在添加了对分类过滤的支持,就需要把这个信息传递给该辅助器方法,如下图所示。
在修改之前,所生成的分页连接是这样的:“http://<服务器>:<端口号>/Page2”,如果用户点击这样的链接,分类过滤器不会起作用,显示出来的将是一个包含所有分类产品的页面。通过添加当前分类,它取自视图模型,可以生成如下URL。
http://<服务器>:<端口号>/足球/Page2
当用户点击这样的链接时,当前分类会被传递给List动作方法,过滤就起作用了。访问“/足球”或“/国际象棋”这样的URL,也可以看到分页链接会正确地包含分类。
1.3建立分类导航菜单
现在需要给客户提供一种选择一个分类的方法,即需要展示一个可用分类的列表,并指出它们之中哪个是被选中的。随着应用程序的扩展,将在多个控制器中使用这个分类列表,因此需要确保它是自包含且可重用的。
ASP.NET MVC框架有一种叫做“子动作(Child Action)”的概念,它特别适用于创建诸如可重用导航控件之类的东西。子动作依赖于叫做“RenderAction”的HTML辅助器方法,它让用户能够在当前视图中包含一个任意动作方法的输出。
1.创建导航控制器
在Controllers文件夹下新建一个名为NavController的空控制器,删除默认创建的Index方法,并添加为名Menu的动作方法,如下所示。
这个方法返回一个静态字符串,但它足以把这个子动作集成到应用程序的其余部分。我们希望分类列表出现在所有页面上,因此需要在布局中渲染这个子动作。编辑_Layout.cshtml文件,以使它调用RenderAction辅助器方法,如下图所示。
运行应用程序,将看到每个页面都包含了这个Menu动作方法的输出,如下所示。
2.生成分类列表
现在回到该控制器,并生成一组实际的分类。本例不希望在控制器中生成分类的URL。控制器的职责是为视图准备数据或处理从视图而来的数据,不负责为视图表现数据。所以在Menu动作方法中所要做的是创建分类的列表,如下所示。
所作的第一个修改是添加了构造器,它接收一个IPorductRepository实现作为其参数,该实现将由Ninject提供,这是在控制器实例化时,使用上文建立的绑定来实现的。
第二个修改是Menu动作方法,它现在使用了一个LINQ查询,以获取存储库中的分类列表,并将它们传递给视图。注意:由于在这个控制器中使用的是一个分部视图,所以在动作方法中调用了PartialView方法,且结果是一个PartialViewResult对象。
3.创建分部视图
右击Menu动作方法,从弹出菜单选择添加视图,视图命名为Menu,选中创建分部视图复选框,如下图所示。
首先使用ActionLink辅助器方法添加了一个首页链接,用于显示无分类过滤时所有的产品列表。然后枚举了分类名,并用RouteLink方法为每个分类创建了链接。
运行应用程序,就可以看到如下分类链接了。点击一个分类,就会看到此分类下的产品。
4.高亮当前分类
需要为客户指明他们正在查看的是哪一个分类,其做法是创建一个视图模型,它包含视图模型和所选分类,事实上,这正是常规做法,但是之前已经用过,本例使用ViewBag来实现。修改Menu动作方法,如下所示。
给Menu方法添加了一个名为category的参数,此参数的值将由路由配置自动提供,在方法体中,将此值赋给ViewBag的SelectedCate动态属性。
现在更新Menu视图,利用ViewBag信息来设置被选中分类的样式,如下所示。
运行应用程序,分类被高亮的效果如下图所示。
1.4修正页面计数
当前分页链接的数目是由产品总数决定的,而不是被选中分类下的产品数所决定的。这意味着客户如果点击足球分类的第二页,得到的却是一个空白页面,这是因为没有足够的产品来填充第二页。
修改Product控制器中的List方法,以使分页信息把分类考虑进来,如下图所示。
如果选中了一个分类,则返回该分类中的物品总数,如果没有选中分类,则返回产品总数。运行应用程序,现在可以看到,分页条数正确地反应了该分类中的产品数目,如下所示。
二:建立购物车
完成购物从加入产品到购物车开始,所以每个产品都要有一个加入购物车按钮,点击此按钮将进入购物车页面,显示该客户已选产品的摘要和总费用,客户点击继续购物可以回到产品页面,或者点击立即结算进入下一步操作。以上即为在线购物的基本流程。
2.1定义购物车实体
购物车是应用程序业务域的一部分,因此应该在域模型中建立这个表现购物车的实体。在SportsStore.Domain项目的Entities文件夹中添加一个名为Cart的类,如下图所示。
此Cart类使用了在同文件中定义的CartLine类,以表示由客户所选的一个产品和客户想要购买的数量。还定义了一些方法,包括把产品添加到购物车,删除购物车内的产品,计算总价等。还提供了一个属性,他使用IEnumerable<CartLine>对购物车的内容进行访问。
2.2添加“加入购物车”按钮
编辑ProductSummary分部视图,如下所示。
添加了一个Razor代码块,为列表中的每一个产品创建一个小型的HTML表单,当该表单提交时,将调用Cart控制器中的AddCart动作方法。
2.3实现购物车的控制器
此时,需要创建一个控制器来处理加入购物车的按钮点击,创建一个名为CartController的新控制器,如下图所示。
此控制器运用了ASP.NET会话(Session)状态特性来存储和接收Cart对象,这是GetCart方法的意图。ASP.NET有一个很好的会话特性,它使用重写cookie或URL的办法将一个用户的各个请求关联在一起,形成一个单一的浏览会话。一个相关的特性时会话状态,它允许开发者把数据与会话关联起来。这对Cart类很适合,希望每个用户有自己的购物车,而且购物车在各次请求之间时保持的,当会话过期时,与会话关联的数据会被删除,这意味着不需要对Cart对象的存储或声明周期进行管理。
2.4添加购物车内容
Cart控制器的AddToCart和RemoveFromCart方法都调用了RedirectToAction方法。其效果是把一个HTTP的重定向指令发送到客户端浏览器,要求浏览器请求一个新的URL。在此例中,要求浏览器请求的URL是调用Cart控制器的Index方法。我们需要实现这个Index方法,并用它显示购物车内容。
需要把两个数据片段传递给显示购物车内容的视图:Cart对象,以及如果用户点击继续购物按钮时要显示的URL。下面将为此目的建一个简单的视图模型类。在SportsStore.WebUI的Models文件夹下新建一个名为CartIndexViewModel的新类,如下所示。
现在用这个视图模型来实现Cart的Index动作方法,如下图所示。
接下来时创建视图,右击Index方法,从菜单中选中添加视图,选择强类型视图,模型类选择CartIndexViewModel。编辑内容如下图所示。
现在,已经实现了购物车的基本功能,运行应用程序,添加到购物车按钮,相应产品将会添加到购物车,并在购物车显示相关内容,点击继续购物,将回到原来的产品页面。如下图所示。