《Pro ASP.NET MVC 3 Framework》学习笔记之十三【示例项目SportsStore】

接着我们添加一个分页功能。修改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的结构截图,如下所示:

《Pro ASP.NET MVC 3 Framework》学习笔记之十三【示例项目SportsStore】-attach

我们要使用这个方法必须添加命名空间。在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添加如下样式:

View Code
下面创建一个部分视图,其实这个东东有点类似于WebFrom里面用户控件,主要是为了重用。

我们在Shared文件夹右键添加视图,做如下选择:

《Pro ASP.NET MVC 3 Framework》学习笔记之十三【示例项目SportsStore】-attach

然后对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 。
笔记里面肯定有不准确或错误的地方,希望路过的大牛们多指导,帮助,谢谢!

晚安!

作者:张雪飞
出处:https://zhangxuefei.site/p/260
版权说明:欢迎转载,但必须注明出处,并在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

发表评论

电子邮件地址不会被公开。 必填项已用*标注