接着我们添加一个分页功能。修改ProductController,如下所示:
public class ProductController : Controller {public int PageSize = 4;//后面会更改 private IProductsRepository repository;public ProductController(IProductsRepository productRepository) { repository = productRepository; } public ViewResult List(int page = 1) {//return View(repository.Products); return View(repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize));//分页方法,LINQ使得分页变得简单 } }
这里给List()方法添加了一个可选的参数。如果我们没有给List()传参,则默认是page=1。这里我们可以体会下使用LINQ分页的方便,首先是按照ProductID升序排列,然后Skip跳过已经显示在本页的数据和本月之前的数据,并且在没有显示的数据里面Take取出PageSize个数据。
如果运行程序,会显示4条数据。如果你想浏览第2页的数据,可以这样做:http://localhost:4162/?page=2。当然分页到这里只是进行了一半,下面会有一个HTML辅助的方法来生成分页的链接。在此之前,我们先添加一个View Model,用来传递分页索引。代码如下所示:
namespace SportsStore.WebUI.Models{public class PagingInfo {public int TotalItems { get; set; }//总条数public int ItemsPerPage { get; set; }//每页条数public int CurrentPage { get; set; }//当前页public int TotalPages//总页数 {get { return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage); } } }}
因为该View Model并不是我们Domain Model的一部分,仅仅是为了方便在Controller和View之间传递数据。为了更加突出这点,我们在WebUI里面新建了一个Models文件夹,在这里面添加了PagingInfo类
添加HTML辅助方法PageLinks().在WebUI里面创建一个HtmlHelpers文件夹,然后添加类PagingHelpers.cs,如下所示:
public static class PagingHelpers {public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl) { StringBuilder result = new StringBuilder();for (int i = 1; i <= pagingInfo.TotalPages; i++) { TagBuilder tag = new TagBuilder("a");//创建<a>标签 tag.MergeAttribute("href", pageUrl(i));//<a>添加href属性 tag.InnerHtml = i.ToString();if (i == pagingInfo.CurrentPage) { tag.AddCssClass("selected"); } result.Append(tag.ToString()); }return MvcHtmlString.Create(result.ToString()); } }
下面是SportsStore.WebUI的结构截图,如下所示:
我们要使用这个方法必须添加命名空间。在WebForm里面,我们可以直接在.cs里面使用using来引用。对于Razor View,需要添加配置到Web.config里面,或者是使用@Using在View里面。我们采用前面一种方法,在Views/Web.config里面添加如下:
<system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /><add namespace="SportsStore.WebUI.HtmlHelpers"/><!--这就是我们要添加的--> </namespaces> </pages> </system.web.webPages.razor>
我们可以将PagingInfo的实例传递给View,通过ViewBag或者ViewData。但是我们需要进行一些强制转换。为了避免强转,我们对其进行封装到一个实体类里面。在Models里面创建一个实体类ProductsListViewModel,如下所示:
public class ProductsListViewModel {public IEnumerable<Product> Products { get; set; }public PagingInfo PagingInfo { get; set; } }
接着更新我们的ProductController,如下所示:
public ViewResult List(int page = 1) {//return View(repository.Products);//return View(repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize)); ProductsListViewModel viewModel = new ProductsListViewModel { Products = repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize), PagingInfo = new PagingInfo { CurrentPage = page, ItemsPerPage = PageSize, TotalItems = repository.Products.Count() } };return View(viewModel); }
继续更新我们的View List.cshtml,如下所示:
@model SportsStore.WebUI.Models.ProductsListViewModel@{ ViewBag.Title = "Products";}<h2> Product List</h2>@foreach (var p in Model.Products){ <div class="item"> <h3>@p.Name</h3> @p.Description <h4>@p.Price.ToString("c")</h4> </div> //Html.RenderPartial("ProductSummary", p);}<div class="pager"> @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))</div>
这个时候你可以运行下程序,测试下分页功能。
为什么这里不用GridView?
在WebFrom里用GridView控件可以实现我们这里的效果,那MVC这里的做法相对于拖控件有什么不同呢?
首先,我们构建了一个稳固并且可维护的架构,这里面包含了分解关注点的思想。不像简单的使用GridView控件,将UI跟数据访问耦合在了一起,这样做非常快而且方便,但从长远看,这只是图一时之快。其次,我们创建了单元测试,有利于我们在一个非常自然的状态下验证应用程序的行为,而这在GridView控件里面几乎是不可能的。比如我们可以测试分页的方法如下所示:
View Code
[TestMethod]public void Can_Send_Pagination_View_Model() {//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"}, new Product {ProductID = 2, Name = "P2"}, new Product {ProductID = 3, Name = "P3"}, new Product {ProductID = 4, Name = "P4"}, new Product {ProductID = 5, Name = "P5"}}.ToList());//Arrange -create a controller and make the page size 3 items ProductController controller = new ProductController(mock.Object); controller.PageSize = 3;//Action ProductsListViewModel result = (ProductsListViewModel)controller.List(2).Model;//Assert PagingInfo pageInfo = result.PagingInfo; Assert.AreEqual(pageInfo.CurrentPage, 2); Assert.AreEqual(pageInfo.ItemsPerPage, 3); Assert.AreEqual(pageInfo.TotalItems, 5); Assert.AreEqual(pageInfo.TotalPages, 2); }
从这里我们可以体会MVC的可测试性。
接下来我们完善下URL。在上面运行程序时,你可能发现,分页时,地址栏里显示是如http://localhost/?page=2 这样的链接。这里仍然使用的是query string的方式来传递数据的。我们能做得更加人性化点,如将URL组合成http://localhost/Page2 这种,表达的意思跟上面一样。MVC里面使用的ASP.NET routing功能可以很容易的做到。在Global.asax.cs里面添加一个路由映射,如下所示:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 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 } // 参数默认值 ); }
添加的顺序非常重要,这里必须放在Default路由的上面。MVC就是按这种顺序来处理路由的。
接下来添加样式和创建部分视图
我们对_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"> Sports Store</div> </div> <div id="categories"> Will put something useful here later </div> <div id="content"> @RenderBody() </div></body></html>
在Content/Site.css添加如下样式:
View Code
BODY{ font-family: Cambria, Georgia, "Times New Roman"; margin: 0;}DIV#header DIV.title, DIV.item H3, DIV.item H4, DIV.pager A{ font: bold 1em "Arial Narrow" , "Franklin Gothic Medium" , Arial;}DIV#header{ background-color: #444; border-bottom: 2px solid #111; color: White;}DIV#header DIV.title{ font-size: 2em; padding: .6em;}DIV#content{ border-left: 2px solid gray; margin-left: 9em; padding: 1em;}DIV#categories{float: left; width: 8em; padding: .3em;} DIV.item{ border-top: 1px dotted gray; padding-top: .7em; margin-bottom: .7em;}DIV.item:first-child{ border-top: none; padding-top: 0;}DIV.item H3{ font-size: 1.3em; margin: 0 0 .25em 0;}DIV.item H4{ font-size: 1.1em; margin: .4em 0 0 0;} DIV.pager{ text-align: right; border-top: 2px solid silver; padding: .5em 0 0 0; margin-top: 1em;}DIV.pager A{ font-size: 1.1em; color: #666; text-decoration: none; padding: 0 .4em 0 .4em;}DIV.pager A:hover{ background-color: Silver;}DIV.pager A.selected{ background-color: #353535; color: White;}
下面创建一个部分视图,其实这个东东有点类似于WebFrom里面用户控件,主要是为了重用。
我们在Shared文件夹右键添加视图,做如下选择:
然后对List.cshtml进行修改,如下所示:
@model SportsStore.WebUI.Models.ProductsListViewModel@{ ViewBag.Title = "Products";}<h2> Product List</h2>@foreach (var p in Model.Products){Html.RenderPartial("ProductSummary", p);}<div class="pager"> @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))</div>
Tips:RenderPartial()方法不是像很多的辅助方法那样返回HTML标签,而是直接写入Response Stream,这样会对效率有所提高。如果你习惯使用Html.Partial()方法也没问题,两个实现的功能是一样的.
好了,今天的笔记就到这里,后面两章依然是关于这个项目的。这个项目完了以后,本书的第一部分也就结束。接下来,也是最核心的内容,ASP.NET MVC3详解。会对MVC3的本质进行讲解。关于SportsStore项目的笔记有点枯燥,而且可能你跟我一样,有很多地方不明白,不要紧。我相信第二部分的学习会让我们所有的问题迎刃而解的,o(∩_∩)o 。
笔记里面肯定有不准确或错误的地方,希望路过的大牛们多指导,帮助,谢谢!
晚安!