接着我們添加一個分頁功能。修改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控件里面幾乎是不可能的。比如我們可以測試分頁的方法如下所示:

[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添加如下樣式:

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 。
筆記里面肯定有不准確或錯誤的地方,希望路過的大牛們多指導,幫助,謝謝!
晚安!