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

接着我们添加一个分页功能。修改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");//创建标签
tag.MergeAttribute("href", pageUrl(i));//
添加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.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
"System.Web.Mvc.WebViewPage">

namespace="System.Web.Mvc" />
namespace="System.Web.Mvc.Ajax" />
namespace="System.Web.Mvc.Html" />
namespace="System.Web.Routing" />
namespace="SportsStore.WebUI.HtmlHelpers"/>


我们可以将PagingInfo的实例传递给View,通过ViewBag或者ViewData。但是我们需要进行一些强制转换。为了避免强转,我们对其进行封装到一个实体类里面。在Models里面创建一个实体类ProductsListViewModel,如下所示:

    public class ProductsListViewModel
{
public IEnumerable 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";
}


Product List


@foreach (var p in Model.Products)
{
class="item">

@p.Name


@p.Description

@p.Price.ToString("c")



//Html.RenderPartial("ProductSummary", p);
}
class="pager">
@Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))

这个时候你可以运行下程序,测试下分页功能。

为什么这里不用GridView?

在WebFrom里用GridView控件可以实现我们这里的效果,那MVC这里的做法相对于拖控件有什么不同呢?

首先,我们构建了一个稳固并且可维护的架构,这里面包含了分解关注点的思想。不像简单的使用GridView控件,将UI跟数据访问耦合在了一起,这样做非常快而且方便,但从长远看,这只是图一时之快。其次,我们创建了单元测试,有利于我们在一个非常自然的状态下验证应用程序的行为,而这在GridView控件里面几乎是不可能的。比如我们可以测试分页的方法如下所示:

View Code

        [TestMethod]
public void Can_Send_Pagination_View_Model()
{
//Arrange
//-Create the mock repository
Mock mock = new Mock();
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做如下更改:




@ViewBag.Title
"@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />



"header">
class="title">
Sports Store


"categories">
Will put something useful here later

"content">
@RenderBody()


在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";
}


Product List


@foreach (var p in Model.Products)
{
Html.RenderPartial("ProductSummary", p);
}
class="pager">
@Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))

Tips:RenderPartial()方法不是像很多的辅助方法那样返回HTML标签,而是直接写入Response Stream,这样会对效率有所提高。如果你习惯使用Html.Partial()方法也没问题,两个实现的功能是一样的.

好了,今天的笔记就到这里,后面两章依然是关于这个项目的。这个项目完了以后,本书的第一部分也就结束。接下来,也是最核心的内容,ASP.NET MVC3详解。会对MVC3的本质进行讲解。关于SportsStore项目的笔记有点枯燥,而且可能你跟我一样,有很多地方不明白,不要紧。我相信第二部分的学习会让我们所有的问题迎刃而解的,o(∩_∩)o 。
笔记里面肯定有不准确或错误的地方,希望路过的大牛们多指导,帮助,谢谢!

晚安!

发表评论

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