第七话 Asp.Net Mvc 3.0【MVC项目实战の三】
第六话完了,我的项目只不过构建了大体的样子,接下来我们需要完成导航部分购物车部分,订单部分等。只有这些模块搞完,我们的购物流程项目才算大体的搞完。接下来,就从我们的导航开始吧!
添加导航
如果在我们的项目应用导航展示给用户,我们应该做一下的事情:
- 加强我们的模型(ProductsListViewModel),加强之后的模型必须过滤商品的属性。
- 重构我们的URL,修改我们路由机制。
- 创建类别列表,显示在网站的侧边栏里。
加强我们的模型(ProductsListViewModel),我们需要把不同类别展示在网站的侧边栏里让用户一目明了。ProductsListViewModel模型修改如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using SportsStore.Domain.Entities; namespace SportsStore.WebUI.Models { public class ProductsListViewModel { public IEnumerable<Product> Products { get; set; } public PagingInfo PagingInfo { get; set; } //添加CurrentCategory属性 public string CurrentCategory { get; set; } } }
模型(ProductsListViewModel)我们已经添加了属性进来,下来我们需要修改我们的ProductController(控制器)里的List(Action)方法,我们需要在List方法使用我们添加的属性按照分类过滤商品对象。修改ProductContrller如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SportsStore.Domain.Abstract; using SportsStore.Domain.Concrete; using SportsStore.WebUI.Models; namespace SportsStore.WebUI.Controllers { public class ProductController : Controller { public int PageSize = 4; //设置一页显示多少商品 private IProductRepository repository; public ProductController(IProductRepository productReposittory) { this.repository = productReposittory; } //返回一个视图 public ViewResult List(string category, int page = 1) { ProductsListViewModel viewModel = new ProductsListViewModel { Products = this.repository.Products .Where(h => category == null || h.Category == category) .OrderBy(h => h.ProductID) .Skip((page - 1) * PageSize) .Take(PageSize), PagingInfo = new PagingInfo { CurrentPage = page, ItemsPerPage = PageSize, TotalItems = this.repository.Products.Count() }, CurrentCategory = category }; return this.View(viewModel); } } }
上面的代码,我们添加了一个新的分类参数,我们做到这里即便我们做了稍微的改变运行一下我们的程序,自己试着拼一个URL分类的字符串。运行如下图1.
图1.可以看出我们拼的字符串可以正确的显示出我们拼的分类列表。但是像Http://localhost:XXXX/?category=people的连接我相信大家都是不喜欢的。所以我们要从新定义一套路由机制来改变这种URL访问的方式。然后在我们的Global.asax文件中配置一下几条路由,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using SportsStore.WebUI.Infrastructure; using SportsStore.WebUI.Binders; using SportsStore.Domain.Entities; namespace SportsStore.WebUI { // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明, // 请访问 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( null, "", //只匹配空的URL new { controller = "Product", action = "List", Category = (string)null, page = 1 } ); routes.MapRoute(null, "Page{page}", //匹配像 /Page2,/page123,除了/PageXYZ 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}"); //添加一条路由 routes.MapRoute( null, "Page{page}", new { controller = "Product", action = "List" } ); routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Product", action = "List", id = UrlParameter.Optional } // 参数默认值 ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); //注册路由 ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder()); } } }
总结一下我们配置的这些路由机制,如下表:
URL | 引导 |
/ | 展示列表第一页的所有商品信息 |
/page2 | 展示列表指定页面(这里指第2页)的商品信息 |
/Soccer | 展示特定分类第一页的商品信息 |
/Soccer/Page2 | 展示特定分类页特定页面(这里指第二页)的商品信息 |
/Anything/Else | 调用任何控制器里的的其他方法 |
Asp.Net路由系统使用在MVC来处理客户端的请求,外来的URL请求要是有符合我们配置的URL,就会访问到我们Web程序相应的页面。在Razor引擎的写法里,大家都应该知道URl.Action方法是最常见的添加外链的方法,那么下来就在我们的List.cshtml商品展示页面添加我们的分类信息,具体代码如下:
@model SportsStore.WebUI.Models.ProductsListViewModel @{ ViewBag.Title = "Product List"; } @foreach (var Pro in Model.Products) { Html.RenderPartial("ProductSummary", Pro); } <div class="pager"> @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { Page = x ,category = Model.CurrentCategory})) </div>
接下来我们需要构造一个类别的导航菜单,在我们的MVC Web项目里的Controllers文件夹右键添加"控制器",命名"NavController",然后干掉VS自动创建好的Index 方法(Action),然后定义我们的方法,代码如下:
using System.Web.Mvc; namespace SportsStore.WebUI.Controllers { public class NavController : Controller { public string Menu() { return "Hello from NavController"; } } }
我们的Menu方法返回的字符串,但是我们想让我们的类别出现在任何页面,所以我们要配置我们的模版(_Layout.cshtml),使它调用我们的显示导航菜单的方法,所以我们修改我们的Layout.cshtml的代码如下:
<!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> </head> <body> <div id="header"> <div class="title">SPOPTS STORE</div> </div> <div id="categories">@{Html.RenderAction("Menu", "Nav");}</div> <div id="content"> @RenderBody() </div> </body> </html>
我们现在已经在模版中给我们的导航菜单分配了一块展示的地方,现在运行一下我们的Mvc Web程序,结果如下图2.
图2.现在我们就可以去实现这个展示导航菜单的位置了,不能让这个展示导航的地方老是显示一句我们测试的字符串,接下来我们就生成我们的导航列表,但是我们有不愿意生成控制器类(NavController)的URL,所以我们将尝试用一个辅助方法来搞View(视图),所以需要Menu Action(方法)是创建一个分裂的列表,修改控制器("NavController")代码如下:
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 { private IProductRepository repository; public NavController(IProductRepository repo) { this.repository = repo; } public PartialViewResult Menu() { IEnumerable<string> categories = this.repository.Products .Select(h => h.Category) .Distinct() .OrderBy(h => h); return this.PartialView(categories); } } }
接下来我们需要创建一个局部视图,为什么是局部视图,因为我们的导航菜单是我们页面整体的一部分,所以我们就用局部视图,在Menu Action(方法)上右键添加视图,具体如下图3。
图3.我们还是创建的强类型视图模型,我们的模型类类型选择的是IEnumerable<string>,创建好我们的局部视图Menu后,修改他的内容如下:
@model IEnumerable<string> @{ this.Layout = null; } @Html.ActionLink("Home","List","Product") @foreach (var item in Model) { @Html.RouteLink(item, new { controller = "Product", action = "List", category = item, page = 1 }); }
首先我们添加一个Home(首页)的连接使用的是ActionLink,这个连接会展示出我们所有商品信息。这种的URL请求机制我们在Global.asax文件中已经配置过来。然后我们使用循环列举了所有的列表使用的RouteLink(这个和ActionLinke类似),但是这个需要我们供给一组类似"名称/值(Name/Value)"对,请求时来从路由配置(具体到路由的东西后续分享)。到这里我们在跑下我们的Web程序看看!如下图4.
图4.哦,我们应该还需要一个"高亮"的效果,就是当我们/用户点击到某个分裂的时候,当前分类应该处于选择状态的时候,有"高亮"效果,我们修改一下我们修改一下我们的Menu Action(方法)及对应的前台代码。
修改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 { private IProductRepository repository; public NavController(IProductRepository repo) { this.repository = repo; } public PartialViewResult Menu(string category = null) { this.ViewBag.SelectedCategory = category; IEnumerable<string> categories = this.repository.Products .Select(h => h.Category) .Distinct() .OrderBy(h => h); return this.PartialView(categories); } } }
修改页面Menu.cshtml如下(选择类别的高亮实现):
@model IEnumerable<string> @{ this.Layout = null; } @Html.ActionLink("Home","List","Product") @foreach (var item in Model) { @Html.RouteLink(item, new { controller = "Product", action = "List", category = item, page = 1 }, new { @class = item == this.ViewBag.SelectedCategory ? "selected" : null }); }
注:具体实现高亮的地方,代码都表明使用了红色粗体。
接下来运行我们的web程序如下图5.
图5.这里我们的高亮效果已定是OK的了。接下来我们似乎有出现一个新的问题,就是在我们某些类别页面存在分页没有商品的信息,如下图6.
图6.为什么呢!还记得前面我们搞分页的时候,依据的商品的总数量,与分类好无瓜葛,所以导致现在这种局面也是必然。所以当用户点击某些类别的具体某些页面时,没有足够的商品信息展示出来就出现空白页面,接下来就搞这个问题吧!问题的根源弄出来,那么我们是不是应该在商品展示List Action(方法)上动些手脚,让分页的时候把类别也考虑进来,所以修改ListAction(方法)如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SportsStore.Domain.Abstract; using SportsStore.Domain.Concrete; using SportsStore.WebUI.Models; namespace SportsStore.WebUI.Controllers { public class ProductController : Controller { public int PageSize = 4; //设置一页显示多少商品 private IProductRepository repository; public ProductController(IProductRepository productReposittory) { this.repository = productReposittory; } //返回一个视图 public ViewResult List(string category, int page = 1) { ProductsListViewModel viewModel = new ProductsListViewModel { Products = this.repository.Products .Where(h => category == null || h.Category == category) .OrderBy(h => h.ProductID) .Skip((page - 1) * PageSize) .Take(PageSize), PagingInfo = new PagingInfo { CurrentPage = page, ItemsPerPage = PageSize, TotalItems = category == null ? this.repository.Products.Count() : this.repository.Products.Where(h => h.Category == category).Count() }, CurrentCategory = category }; return this.View(viewModel); } } }
这样修改后,当我们选择某些特定的类别的时候,就返回特定类别的商品的总数,就会正确的分页,避免之前的空白页面了!到时是不是呢,我们还是刚才选择的那个分类运行下我们的web项目,如下图7.
图7.这里已经没有什么在说的了,证明我们的修改是OK的。
接下是相对于之前比较复杂的购物车,说是复杂就复杂,要说不复杂也就不复杂了,直白点"购物车不就那几点事,无非就是计算我们的商品价格,无非就是支付,无非就是跟商品页面的交互,...",我们项目的购物车的流程如下图8.
图8.具体实现购物车的东东后续在继续,这里就引下!我们本次也就搞了一个导航菜单,就先实战到这里,文章要是存在什么地方的错误或者描述不清楚的地方还请路过的前辈们多多批评指教,大家共同学习哈!