《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的结构截图,如下所示:

我们要使用这个方法必须添加命名空间。在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 。
笔记里面肯定有不准确或错误的地方,希望路过的大牛们多指导,帮助,谢谢!

晚安!

时间: 2024-10-24 19:44:19

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

ASP.NET MVC 3 Framework学习笔记之Model Templates

.使用模板化的视图Helpers(Using Templated View Helpers) 模版化视图helpers的创意就是它们更加灵活.我们不用自己去指定应该用什么HTML元素来呈现一个模型的属性,MVC自己会搞定,在我们更新了视图模型时,也不用手动的更新视图.下面是一个例子:  代码如下 复制代码 //在Models里面添加Persons.cs using System; using System.Collections.Generic; using System.Linq; using

《Pro ASP.NET MVC 3 Framework》学习笔记目录

<Pro ASP.NET MVC 3 Framework>简介: 作者: Adam Freeman 和 Steven Sanderson 出版社: Apress; New 平装: 820页 语种: 英语 ISBN: 1430234040 声明:笔记里面按我自己的理解翻译了大部分内容,写这个笔记的目的:为了方便自己查阅,也为园友提供学习的方便. 我无意侵犯作者的任何权利,仅仅为了自己学习.也希望路过的朋友不要用于任何商业目的. 第一部分 ASP.NET MVC3介绍   <Pro ASP.

《Pro ASP.NET MVC 3 Framework》学习笔记之一【MVC的历程,优点,HelloWorld】

序论:asp.net mvc出现已经有两三年的时间了(2009开始1.0版本),但是这么方面的中文学习资料仍然非常少,特别是asp.net mvc3,几乎就没有中文的学习书籍.在英文的书籍中有两本是非常经典的mvc3教程:<Professional ASP.NET MVC 3>--作者:Jon Galloway , Phil Haack, Brad Wilson , K. Scott Allen和<Pro ASP.NET MVC 3 Framework>--作者:Steven Sa

ASP.NET MVC Web API 学习笔记----HttpClient简介

  1. HttpClient简单介绍  依稀还记得那个时候用WebClient,HttpWebRequest来发送一个请求,现在ASP.NET MVC4中自带了一个类HttpClient,用于接收HttpResponseMessage和发送HttpRequestMesssage. 问题在于既然WebClient,HttpWebRequest可以完成相应的功能,为什么还要使用HttpClient类,.NET Framework中既然提出了这样一个类肯定是有其特别之处的,这里罗列几个不同之处: (

ASP.NET MVC Web API 学习笔记---联系人增删改查

本章节简单介绍一下使用ASP.NET MVC Web API 做增删改查.目前很多Http服务还是通过REST或者类似RESP的模型来进行数据操作的.下面我们通过创建一个简单的Web API来管理联系人          说明:为了方便数据不使用真正的数据库,而是通过内存数据模拟    1.       Web API中包含的方法 Action HTTP method Relative URI GetAllContact GET /api/contact GetContact GET /api/

《Pro ASP.NET MVC 3 Framework》学习笔记之九【Ninject的使用-下】

接着上次的Ninject的笔记,如果你是初次路过,可以先看看我前面的笔记. 一,创建依赖链(Chains of Dependency) 当我们向Ninject请求创建一个类型时,Ninject会去检查该类型和其他类型之间的耦合关系.如果有额外的依赖,Ninject也会解析它们并创建我们需要的所有类的实例.为了进一步说明,我们创建一个新的接口和一个实现该接口的类.请注意我们的例子是跟前面的笔记衔接的,所以如果你打算跟着一起操作的话,最好能够去看看前面的笔记. 创建一个IDiscountHelper

《Pro ASP.NET MVC 3 Framework》学习笔记之四【领域模型介绍】

主题:应用领域驱动开发(Applying Domain-Driven Development) Domain Model是MVC程序的"心脏",其他的一切,包括Controllers和Views仅仅是用来跟Domain Model交互的一种方式,ASP.NET MVC并没有限制使用在Domain Model上面的技术,我们可以自由的选择跟.net framework交互的技术,并且这样的选择是非常多的.不仅如此,ASP.NET MVC为我们提供了基础的架构和约定来帮助Domain Mo

《Pro ASP.NET MVC 3 Framework》学习笔记之五【依赖注入及ninject工具使用】

一,创建松耦合的组件 1."分解关注点"是MVC模式里面一个非常重要的特性.我们想要在应用程序里面创建的组件尽可能的独立,这样我们就能管理比较少的依赖关系.理想情况下,每个组件都是孤立的,不知道其他组件的存在,处理应用程序的其他领域仅仅通过抽象接口,这就是所谓的松耦合,它让我们的应用程序更加容易测试和修改.通过一个简单的例子可以帮助我们理解,假如我们想写一个发邮件的组件,暂且就把这个组件命名为MyEmailSender,接着我们实现一个接口,这个接口定义了所有需要发送邮件的功能,也暂且

《Pro ASP.NET MVC 3 Framework》学习笔记之三十五 【部署】

准备要部署的应用程序 在正式进入部署MVC程序到IIS之前,会介绍一些关于应用程序迁移到生产环境之前探测错误以及一旦进入生产环境最大化性能的技术.同时也会展示关于流线型部署过程的有用的功能.   检测视图错误 Razor视图会在服务器需要的时候编译而不是在VS里面生成项目时编译,正常情况下,探测视图编译错误的方式是系统的访问每一个action,从而让每一个view都能够呈现.这显然是非常乏味而且不会一直成功的技术,特别是在基于不同的model状态呈现不同的view的时候.我们可以启用一个特别的项

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

绑定Shopping Cart 定义购物车Cart的实体,购物车是我们程序业务领域的一个部分,所以在我们领域模型(Domain Model)里面添加一个cart的实体是合理的.在SportsStore.Domain的Entities文件夹下添加一个Cart的实体类,如下所示: View Code public class Cart {private List<CartLine> lineCollection = new List<CartLine>();//添加 public vo