摘要:
上一篇文章,我建立了SportsStore应用程序的核心架构。现在我将使用这个架构向这个应用程序添加功能,你将开始看到这个基础架构的作用。我将添加重要的面向客户的简单功能,在这个过程中,你将看到MVC框架提供的额外功能。
如果客户能够根据目录导航产品,SportsStore应用程序可用性将更高。
- 改进ProductController控制器中的List方法,让他能够过滤在repository里的产品对象。
- 修改改进URL格式,修改routing策略。
- 在网站首页旁边创建一个目录列表,高亮显示当前页。
过滤产品列表
修改视图模型ProductsListViewModel,添加当前目录信息。
1 using SportsStore.Domain.Entities; 2 using System.Collections.Generic; 3 4 namespace SportsStore.WebUI.Models 5 { 6 public class ProductsListViewModel 7 { 8 public IEnumerable<Product> Products { get; set; } 9 public PagingInfo PagingInfo { get; set; } 10 public string CurrentCategory { get; set; } 11 } 12 }
修改ProductController控制器的List方法,添加参数category,根据传入的category过滤产品。
1 using SportsStore.Domain.Abstract; 2 using SportsStore.WebUI.Models; 3 using System.Web.Mvc; 4 using System.Linq; 5 6 namespace SportsStore.WebUI.Controllers 7 { 8 public class ProductController : Controller 9 { 10 private IProductRepository repository; 11 12 public int PageSize = 4; 13 14 public ProductController(IProductRepository productRepository) 15 { 16 this.repository = productRepository; 17 } 18 19 public ViewResult List(string category, int page = 1) 20 { 21 ProductsListViewModel model = new ProductsListViewModel 22 { 23 Products = repository.Products.Where(p => string.IsNullOrEmpty(category) || p.Category == category) 24 .OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize), 25 PagingInfo = new PagingInfo 26 { 27 CurrentPage = page, 28 ItemsPerPage = PageSize, 29 TotalItems = category == null ? repository.Products.Count() : repository.Products.Where(e => e.Category == category).Count() 30 }, 31 CurrentCategory = category 32 }; 33 return View(model); 34 } 35 } 36 }
- 如果传入的category是空字符串,则返回所有产品列表。否则根据linq的where条件获取category下的产品列表。
- 如果传入的category是空字符串,则返回所有产品数量。否则根据linq的where条件获取category下的产品数量。
- 将category赋值给视图模型对象的CurrentCategory属性。
执行程序,如果将浏览器地址栏修改成下面的形式:
http://localhost:17596/?category=Soccer
将返回category是Soccer的产品列表。
很明显,用户将不会自己使用URL导航产品目录,但是你可以看到,一旦基础架构做好了,一个小的改动在一个MVC框架的应用程序起到了大的作用。
修改定义URL格式
没有人想看到或者使用丑陋的像/?category=Soccer一样的URL。为了改进这个,我将修改路由格式,创建更适合我和客户的生成URL的方法。
修改RouteConfig.cs文件的RegisterRoutes方法。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 using System.Web.Routing; 7 8 namespace SportsStore 9 { 10 public class RouteConfig 11 { 12 public static void RegisterRoutes(RouteCollection routes) 13 { 14 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 16 routes.MapRoute( 17 name: null, 18 url: "", 19 defaults: new { controller = "Product", action = "List", category = (string)null, page = 1 } 20 ); 21 22 routes.MapRoute( 23 name: null, 24 url: "Page{page}", 25 defaults: new { controller = "Product", action = "List", category = (string)null }, 26 constraints: new { page = @"d+" } 27 ); 28 29 routes.MapRoute( 30 name: null, 31 url: "{category}", 32 defaults: new { controller = "Product", action = "List", page = 1 } 33 ); 34 35 routes.MapRoute( 36 name: null, 37 url: "{category}/Page{page}", 38 defaults: new { controller = "Product", action = "List" }, 39 constraints: new { page = @"d+" } 40 ); 41 42 routes.MapRoute( 43 name: "Default", 44 url: "{controller}/{action}/{id}", 45 defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional } 46 ); 47 } 48 } 49 }
下面来解释添加的路由信息。
第一个路由函数:
url: "":表示这个是默认路由。defaults设置参数默认值,将category的空字符串设置为默认值,page的默认值是1。这样,如果访问URL:http://localhost:17596/,category为空字符串,page值为1。
第二个路由函数:
url: "Page{page}":表示路由的URL格式是/Page{page}。defaults设置参数category的默认值为空字符串。这样,如果访问URL:http://localhost:17596/Page2,category为空字符串,page值为传入的值2。
第三个路由函数:
url: "{category}":表示路由的URL格式是/{category}。defaults设置参数page的默认值为1。这样,如果访问URL:http://localhost:17596/Soccer,category为字符串Soccer,page值为默认值1。
第四个路由函数:
url: "{category}/Page{page}":表示路由的URL格式是/{category}/Page{page}。defaults不用设置参数默认值。这样,如果访问URL:http://localhost:17596/Soccer/Page2,category为字符串Soccer,page值为传入的值2。
MVC使用ASP.NET路由系统来处理客户端到来的请求,但是它还用来向外生成符合URL定义格式的URL字符串,嵌入到Web页面上。于是,我可以保证应用程序的所有的URL都是一致的。
定义好路由表后,需要修改List视图上生成链接字符串的方法调用。
1 @model SportsStore.WebUI.Models.ProductsListViewModel 2 3 @{ 4 ViewBag.Title = "Products"; 5 } 6 7 @foreach (var p in Model.Products) 8 { 9 Html.RenderPartial("ProductSummary", p); 10 } 11 <div class="btn-group pull-right"> 12 @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x, category = Model.CurrentCategory })) 13 </div>
Url.Action方法是向外产生链接最方便的方法。这里添加将category传入生成链接的代理方法中。
运行程序,改变浏览器地址栏上的URL,得到各路由URL页面结果。
创建目录导航菜单
首先创建MenuController控制器。
1 using SportsStore.Domain.Abstract; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web.Mvc; 5 6 namespace SportsStore.WebUI.Controllers 7 { 8 public class NavController : Controller 9 { 10 private IProductRepository repository; 11 12 public NavController(IProductRepository productRepository) 13 { 14 repository = productRepository; 15 } 16 17 public PartialViewResult Menu(string category = null) 18 { 19 ViewBag.SelectedCategory = category; 20 IEnumerable<string> categories = repository.Products.Select(x => x.Category).Distinct().OrderBy(x => x); 21 return PartialView("Menu", categories); 22 } 23 } 24 }
- 这个控制器返回PartialViewResult类型对象。PartialViewResult类跟之前使用的ViewResult类型都是继承自基类ViewResultBase。
- ViewBag动态类型属性SelectedCategory设置为当前目录字符串。
- 参数category将获取路由产生的URL上的目录字符串。
- categories变量返回Product下所有目录。
- 调用方法PartialView方法,返回PartialViewResult类型对象,传入的参数是视图名称和视图模型的变量。
然后创建Menu视图。
1 @model IEnumerable<string> 2 3 <div> 4 @Html.RouteLink("Home", new { controller = "Product", action = "List" }, new { @class = "btn btn-block btn-default btn-lg" }) 5 @foreach (var link in Model) 6 { 7 @Html.RouteLink(link, new 8 { 9 controller = "Product", 10 action = "List", 11 category = link, 12 page = 1 13 }, new 14 { 15 @class = ("btn btn-block btn-default btn-lg") + (link == ViewBag.SelectedCategory ? " btn-primary" : "") 16 }) 17 } 18 </div>
这里调用Html扩展方法RouteLink,生成链接字符串。当然,你也可以使用ActionLink方法。
- 方法的第一个参数是链接显示的文本字符串。
- 第二个参数是动态数据类型,用来指定控制器,控制器方法以及方法传入的参数。
- 第三个参数也是动态数据类型,使用@class来指定链接的样式。这里使用bootstrap的样式,这里的设置顺序是:btn指定按钮样式,btn-block指定按钮呈现的外观占整行,btn-default指定按钮的皮肤颜色等,btn-lg指定按钮的大小。
- 如果是当前页,将添加btn-primary样式,将该按钮高亮显示。
最后,修改_Layout.cshtml文件,将Menu显示在页面上。
1 <!DOCTYPE html> 2 3 <html> 4 <head> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <link href="~/Content/bootstrap.css" rel="stylesheet" /> 7 <link href="~/Content/bootstrap-theme.css" rel="stylesheet" /> 8 <title>@ViewBag.Title</title> 9 </head> 10 <body> 11 <div class="navbar navbar-inverse" role="navigation"> 12 <a class="navbar-brand" href="#">SPORTS STORE</a> 13 </div> 14 <div class="row panel"> 15 <div class="col-xs-3"> 16 @Html.Action("Menu", "Nav") 17 </div> 18 <div class="col-xs-8"> 19 @RenderBody() 20 </div> 21 </div> 22 </body> 23 </html>
这里调用Html的扩展方法Action,传入控制器名字和方法名字,生成HTML字符串。它将Menu视图生成的字符串嵌入到div里面,生成导航栏。
注意这里不是调用RenderPartial方法,RenderPartial方法返回的void,它跟C#语句嵌套使用,在视图上嵌入视图。
运行程序,得到运行结果。
如果点击Soccer目录,将返回Category是Soccer的产品列表。并将Soccer这个按钮高亮显示。