前几天看见博客园上有人写ASP.NET MVC的分页思想,这让我不禁想起了PagedList。PagedList是NuGet上提供的一个分页的类库,能对任何IEnumerable<T>进行分页,而且非常简单好用。从NuGet上,可以获取两个DLL:PagedList.dll和PagedList.Mvc.dll。PagedList.dll提供分页的核心操作,PagedList.Mvc.dll是一个辅助类库,在创建分页的UI时候提供简单、可扩展的创建方法。不过PagedList.dll可以用于MVC2及其以上,但是PagedList .Mvc.dll只能用于MVC3(及其以上)。
使用PagedList:
(一)、安装PagedList:引用-->Add Library Package Reference--->OnLine All--->搜索PagedList,点击Install安装。(如果没有安装Nuget,可以到下面地址下载:http://www.nuget.org/)
(二)、NuGet的好处就是我们不用再进行web.config等各种复杂的配置,所以下面直接编码:
using PagedList; ..... //Controller:PersonController public ViewResult Index(int? page) { int pageNumber = page ?? 1; int pageSize = 2; var persons = db.Persons.ToList(); return View(persons.ToPagedList(pageNumber, pageSize)); } ...... //View:Views/Person/Index
@model PagedList.PagedList<XXX.Person>
...... <div> Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount @if (Model.HasPreviousPage) { @Html.ActionLink("<<", "Index", new { Page = 1 }) @Html.Raw(" ") @Html.ActionLink("< Prve", "Index", new { Page = Model.PageNumber - 1 }) } else { @:<< @Html.Raw(" ") @:< Prev } @Html.Raw(" ") @if (Model.HasNextPage) { @Html.ActionLink("Next >", "Index", new { Page = Model.PageNumber + 1 }) @Html.Raw(" ") @Html.ActionLink(">>", "Index", new { Page = Model.PageCount }) } else { @:Next> @Html.Raw(" ") @:>> } </div>
是不是很简单一句简单的ToPageList就会返回一个强类型的PagedList.PagedList<T>对象,而且PagedList.PagedList<T>实现了IPagedList接口,通过对象浏览器我们可以看到IPagedList提供了很多方法和属性供我们在View绑定时候使用(例如HasPreviousPage、HasNextPage、PageCount、PageNumer等等),如下图:
不过还有两个问题:
第一、看了Controller中的代码,第一感觉是糟了。db.Persons.ToList() 太危险了,ToList时候数据已经执行,幻想一下,如果是百万甚至千万级的数据。。。所以这样不行。因为这里没有起到分页的效果。解决思路,赶紧组织其执行。
第二、View中绑定分页还有没有更好的方法,每次这样写是不是太费劲了呢?答案是肯定的,PagedList.Mvc.dll提供了分页导航功能。
利用PagedList优化分页:
//Controller: PersonController public ViewResult Index(int? page) { int pageNumber = page ?? 1; int pageSize = 2; //Skip之前必须orderby var persons = from p in db.Persons orderby p.PersonID descending select p; return View(persons.ToPagedList(pageNumber, pageSize)); }
ok,这样就可以在分页前阻止查询的执行了。但是这里看到还是很虚的。PagedList为我们提供了StaticPagedList<T>类,而且我个人也比较推崇用它来进行分页查询:
public StaticPagedList(System.Collections.Generic.IEnumerable<T> subset, int pageNumber, int pageSize, int totalItemCount)
可以看到,StaticPagedList需要将:某一页的数据、页码、每页数据的容量、和数据总条目传入。也就是说这时候StaticPagedList不再像PagedList一样承担数据的划分工作,而仅仅承担数据的绑定操作。good。看个例子:
public ViewResult IndexTwo(int? page) { int pageIndex = page ?? 1; int pageSize = 2; int totalCount = 0; var persons = GetPerson(pageIndex, pageSize, ref totalCount); var personsAsIPagedList = new StaticPagedList<Person>(persons, pageIndex, pageSize, totalCount); return View(personsAsIPagedList); } public List<Person> GetPerson(int pageIndex, int pageSize, ref int totalCount) { var persons = (from p in db.Persons orderby p.PersonID descending select p).Skip((pageIndex - 1) * pageSize).Take(pageSize); totalCount = db.Persons.Count(); return persons.ToList(); }
不过这里注意View中的@model PagedList.PagedList<XXX.Person>得换成@model PagedList.StaticPagedList<XXX.Person>额,因为返回的强类型对象不一样了。
PagedList.Mvc设置分页导航:
和上面一样安装Package.Mvc.dll,接下来看我们如何秒杀分页导航:
@model PagedList.StaticPagedList<XXX.Person> @using PagedList @using PagedList.Mvc ...... <link href="/Content/PagedList.css" rel="stylesheet" type="text/css" /> ...... <div> @Html.PagedListPager((IPagedList)Model, page => Url.Action("IndexPagedListMvc", new { page = page })) </div>
将上面代码替换掉刚才View:Views/Person/Index中的代码,完了,你可以看到分页导航几乎和上面的差不多。PagedListed.MVC封装了分页导航的代码,如下图:
可以看到上面的HtmlHelper主要提供了两个扩展方法:PagedListPager和PagedListGoToPageForm。其中PagedListPager主要提供“上一页、下一页......”这类的导航方式(这里不知道怎么描述了),而PagedListGoToPageForm提供了input输入页面点击条状的导航方式,而PagedListRenderOptions和GoToFormRenderOptions分别问它们提供了配置选项。
PagedListPager配置选项
上面的例子使用了默认的配置选项,PagedListRenderOptions还提供了一下其他的配置选项,格式如下:
@Html.PagedListPager((IPagedList)Model, page => Url.Action("IndexPagedListMvc", new { page = page }),PagedListRenderOptions.XXX);
PagedListRenderOptions.XXX提供了更多配置属性,上面所说的配置选项也仅仅是这些配置属性的组合,我们还可以自己配置属性组合如下:
@Html.PagedListPager((IPagedList)Model, page => Url.Action("IndexPagedListMvc", new { page = page }),new PagedListRenderOptions{ LinkToPreviousPageFormat = "上一页", LinkToNextPageFormat = "下一页",MaximumPageNumbersToDisplay=5 });
越看越简单哇?哈哈哈除此之外,PagedList还为我们提供了分页导航的样式。上面的<link href="/Content/PagedList.css" rel="stylesheet" type="text/css" />就是引入分页导航的样式。你安装了PagedList.Mvc会自动的放在你的Content中,这既是NuGet的好处啊。。。。当然你也可以自定义Css和引用其他的Css,这里推荐一个最经一直比较火的Twiter BootStrap(官网地址为:http://twitter.github.com/bootstrap/,表示看不懂....什么12分格系统等看的我是一头雾水)的样式:
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
PagedListGoToPageFrom及其配置
@Html.PagedListGoToPageForm((IPagedList)Model, "IndexPagedListMvc");
@Html.PagedListGoToPageForm((IPagedList)Model, "IndexPagedListMvc",new GoToFormRenderOptions { XXX=xxx });
和PagedListRenderOptions不同,的GoToFromRenderOptions没有配置选项,其他的都差不多。
到目前为止,貌似两个问题都已经完全解决了,不过好的用户体验的分页肯定不希望点击下一页后有页面刷新的操作-------Ajax。由于涉及到jquery.templ.js,而我以前没有见过这个东东,所以先放下一下,后头再来说说PagedList+Ajax的分页和PagedList的实现原理。
ASP.NET MVC架构与实战系列之三:MVC控件解析
俗话说"工欲善其事,必先利其器",要真正的开发MVC网站,不光要掌握我在前两节提到的理论知识,而且还要拥有强大的武器装备。MVC视图开发是通过HtmlHelper的各种扩展方法来实现的(位于System.Web.Mvc.Html下)。主要包含以下7大类:FormExtensions、InputExtensions、LinkExtensions、SelectExtensions、TextAreaExtensions、ValidationExtensions及RenderPartialExtensions类。不仅如此,通过HtmlHelper的扩展方法还能开发更多的自定义控件(如我以后讲到的GridView等)。下面我一一讲解这些控件。
FormExtensions:在视图中添加表单和表单路由,分别是BeginForm、BeginRouteForm和EndForm。BeginForm用于定义表单的开始部分,重载方法如下:
BeginForm(); BeginForm(object routeValues); BeginForm(RouteValueDictionary routeValues); BeginForm(string actionName,string controllerName); BeginForm(string actionName,string controllerName,object routeValues); BeginForm(string actionName,string controllerName,RouteValueDictionary routeValues); BeginForm(string actionName,string controllerName,FormMethod method); BeginForm(string actionName,string controllerName,object routeValues,FormMethod method); BeginForm(string actionName,string controllerName,RouteValueDictionary routeVaues,FormMethod method); BeginForm(string actionName,string controllerName,FormMethod method,object htmlAttributes); BeginForm(string actionName,string controllerName,FormMethod method,IDictionary<string,object> htmlAttributes); BeginForm(string actionName,string controllerName,object routeValues,FormMethod method,object htmlAttributes); BeginForm(string actionName,string controllerName,RouteValueDictionary routeValues,FormMethod method,IDictionary<string,object> htmlAttributes);
可以通过以下代码设置路由对象:
Html.BeginForm(new { controller = "blog", action = "show", author = "miracle" })
对应生成的HTML代码(默认提交方法为post):
<form action="/blog/show/miracle" method="post"/>
也可以通过BeginForm设置提交方法,以FormMethod为最高优先级,然后才是属性设置。
Html.BeginForm("show", "blog", FormMethod.Post, new { method = "get" })
对应生成的HTML代码(尽管后面又对方法进行了更改,但是以FormMethod优先级最高,提交方法仍然为post):
<form action="/blog/show" method="post"/>
还可以设置属性(例如id,class等),如同时在属性中设置了action,则以属性设置为最高优先级。
Html.BeginForm("show", "blog", new RouteValueDictionary { { "author", "miracle" } }, FormMethod.Post, new RouteValueDictionary { { "action", "compose" }, { "class", "begin-form" } })
对应生成的HTML代码(添加了class,同时更改了action):
<form action="/blog/show/miracle" class="begin-form" method="post"/>
同时,也可以利用BeginRouteForm来定义form开头部分,不过不同的是,此时不用强制指定controller(当前也可以指定),默认当前页面所在的目录对应的控制器。重载方法如下:
BeginRouteForm(object routeValues); BeginRouteForm(RouteValueDictionary routeValues); BeginRouteForm(string routeName); BeginRouteForm(string routeName,object routeValues); BeginRouteForm(string routeName,RouteValueDictionary routeValues); BeginRouteForm(string routeName,FormMethod method); BeginRouteForm(string routeName,object routeValues,FormMethod method); BeginRouteForm(string routeName,RouteValueDictionary routeValues,FormMethod method); BeginRouteForm(string routeName,FormMethod method,object htmlAttributes); BeginRouteForm(string routeName,FormMethod method,IDictionary<string,object> htmlAttributes); BeginRouteForm(string routeName,object routeValues,FormMethod method,object htmlAttributes); BeginRouteForm(string routeName,RouteValueDictionary routeValues,FormMethod method,IDictionary<string,object> htmlAttributes);
可以通过以下代码设置路由对象:
Html.BeginRouteForm(new { action="show"})
对应生成的HTML代码(尽管没有指定controller):
<form action="/blog/show" method="post"/>
其他的设置与BeginForm类似。可通过EndForm定义结尾部分,不过一般在实际项目中,使用using来定义form而不是调用Html.EndForm。
<% using (Html.BeginForm(new { controller = "blog", action = "show", author = "miracle" })) {%> 表单内容 <%} %>
InputExtensions:包含设置CheckBox、RadioButton、Hidden、TextBox及Password控件。
首先来看CheckBox控件,重载方法列表:
CheckBox(string name); CheckBox(string name,bool isChecked); CheckBox(string name,bool isChecked,object htmlAttributes); CheckBox(string name,object htmlAttributes); CheckBox(string name,IDictionary<string,object> htmlAttributes); CheckBox(string name,bool isChecked,IDictionary<string,object> htmlAttributes);
我们来看看对应的页面代码:
<%using (Html.BeginForm("CheckBox", "Control")) {%> <fieldset> <legend>设置字体</legend> <%=Html.CheckBox("chkBlack", true, new { id="chkBlack"})%> <label for="chkBlack"> 黑色</label> <%=Html.CheckBox("chkBlue", false, new { id = "chkBlue" })%> <label for="chkBlue"> 蓝色</label> </fieldset> <%} %>
对应生成的HTML代码:
<form action="/Control/CheckBox" method="post"> <fieldset> <legend>设置字体</legend> <input checked="checked" id="chkBlack" name="chkBlack" type="checkbox" value="true" /> <input name="chkBlack" type="hidden" value="false" /> <label for="chkBlack"> 黑色</label> <input id="chkBlue" name="chkBlue" type="checkbox" value="true" /> <input name="chkBlue" type="hidden" value="false" /> <label for="chkBlue"> 蓝色</label> </fieldset> </form>
我们可以看出,每一个CheckBox都会对应另外生成一个隐藏控件,可以利用它来检测复选框的选中状态。
public ActionResult ShowCheckBox(FormCollection form) { bool isCheckedBlack = form["chkBlack"].Contains("true"); bool isCheckedBlue = form["chkBlue"].Contains("true"); ViewData["Black"] = isCheckedBlack; ViewData["Blue"] = isCheckedBlue; return View(); }
接下来看看单选控件RadioButton,重载方法列表:
RadioButton(string name,object value); RadioButton(string name,object value,object htmlAttributes); RadioButton(string name,object value,IDictionary<string,object> htmlAttributes); RadioButton(string name,object value,bool isChecked); RadioButton(string name,object value,bool isChecked,object htmlAttributes); RadioButton(string name,object value,bool isChecked,IDictionary<string,object> htmlAttributes);
<%using (Html.BeginForm("RadioButton", "Control")) {%> <fieldset> <legend>设置字体</legend> <%=Html.RadioButton("color", "black", true, new { id = "rbBlack" })%> <label for="rbBlack"> 黑色</label> <%=Html.RadioButton("color", "blue", false, new { id = "rbBlue" })%> <label for="rbBlue"> 蓝色</label> </fieldset> <%} %>
<form action="/Control/RadioButton" method="post"> <fieldset> <legend>设置字体</legend> <input checked="checked" id="rbBlack" name="color" type="radio" value="black" /> <label for="rbBlack"> 黑色</label> <input id="rbBlue" name="color" type="radio" value="blue" /> <label for="rbBlue"> 蓝色</label> </fieldset> </form>
我们可以发现RadioButton的name值是一样的,保证单选的唯一性。同时不需要额外的隐藏控件来保存是否选中。
接下来看看隐藏控件是如何实现的:
Hidden(string name); Hidden(string name,object value); Hidden(string name,object value,object htmlAttributes); Hidden(string name,object value,IDictionary<string,object> htmlAttributes);
生成的隐藏控件及HTML代码如下:
Html.Hidden("name", "miracle"); <input id="name" name="name" type="hidden" value="miracle" />
由于文本框及密码的生成方式与Hidden类似,这里就不再介绍了。
LinkExtensions:在视图中设置链接,包含ActionLink和RouteLink。两者基本功能一致,只是后者可以指定路由名称而已。
我们以ActionLink为例来讲解,重载方法列表:
ActionLink(string linkText,string actionName); ActionLink(string linkText,string actionName,object routeValues); ActionLink(string linkText,string actionName,object routeValues,object htmlAttributes); ActionLink(string linkText,string actionName,RouteValueDictionary routeValues); ActionLink(string linkText,string actionName,RouteValueDictionary routeValues,IDictionary<string,object> htmlAttributes); ActionLink(string linkText,string actionName,string controller); ActionLink(string linkText,string actionName,string controller,object routeValues,object htmlAttributes); ActionLink(string linkText,string actionName,string controller,RouteValueDictionary routeValues,IDictionary<string,object> htmlAttributes); ActionLink(string linkText,string actionName,string controller,string protocol,string hostName,string fragment,object routeValues,object htmlAttributes); ActionLink(string linkText,string actionName,string controller,string protocol,string hostName,string fragment,RouteValueDictionary routeValues,IDictionary<string,object> htmlAttributes);
Html.ActionLink("Miracle's Blog", "Show", "Blog") <a href="/Blog/Show">Miracle's Blog</a>
在这里,简单的列举一下RouteLink的相关方法:
RouteLink(string linkText,object routeValues); RouteLink(string linkText,RouteValueDictionary routeValues); RouteLink(string linkText,string routeName,object routeValues); RouteLink(string linkText,string routeName,RouteValueDictionary routeValues); RouteLink(string linkText,object routeValues,object htmlAttributes); RouteLink(string linkText,RouteValueDictionary routeValues,IDictionary<string,object> htmlAttributes); RouteLink(string linkText,string routeName,object routeValues,object htmlAttributes); RouteLink(string linkText,string routeName,RouteValueDictionary routeValues,IDictionary<string,object> htmlAttributes); RouteLink(string linkText,string routeName,string protocol,string hostName,string fragment,object routeValues,object htmlAttributes); RouteLink(string linkText,string routeName,string protocol,string hostName,string fragment,RouteValueDictionary routeValues,IDictionary<string,object> htmlAttributes);
Html.RouteLink("Miracle's Blog", "default", new { author="miracle"}) <a href="/Blog/Show/miracle">Miracle's Blog</a>
SelectExtensions:包含DropDownList和ListBox控件。前者为下拉列表,后者为列表框。
DropDownList下拉列表的重载方法列表:
DropDownList(string name); DropDownList(string name,string optionLabel); DropDownList(string name,IEnumerable<SelectListItem> selectList,string optionLabel); DropDownList(string name,IEnumerable<SelectListItem> selectList,string optionLabel,object htmlAttributes); DropDownList(string name,IEnumerable<SelectListItem> selectList); DropDownList(string name,IEnumerable<SelectListItem> selectList,object htmlAttributes); DropDownList(string name,IEnumerable<SelectListItem> selectList,IDictionary<string,object> htmlAttributes); DropDownList(string name,IEnumerable<SelectListItem> selectList,string optionLabel,IDictionary<string,object> htmlAttributes);
查看一下DropDownList应用页面及HTML:
public ActionResult ShowDropDownList() { ViewData["Category"] = new SelectList(db.Categories, "CategoryID", "CategoryName"); return View(); }
<%using (Html.BeginForm("SelectDropDownList", "Control")) {%> <fieldset> <legend>选择类别</legend> <%=Html.DropDownList("Category")%> <input type="submit" value="选择"/> </fieldset> <%} %> <form action="/Control/SelectDropDownList" method="post"> <fieldset> <legend>选择产品类别</legend> <select id="Category" name="Category"> <option value="1">Beverages</option> <option value="2">Condiments</option> <option value="3">Confections</option> <option value="4">Dairy Products</option> <option value="5">Grains/Cereals</option> <option value="6">Meat/Poultry</option> <option value="7">Produce</option> <option value="8">Seafood</option> </select> <input type="submit" value="选择"/></fieldset> </form>
要获取当前选中的项,代码如下:
public ActionResult SelectDropDownList(FormCollection form) { var selectedCategories = form["Category"]; ViewData["SelectCategory"] = new SelectList(db.Categories, "CategoryID", "CategoryName", selectedCategories); return View(); }
<%using (Html.BeginForm("ShowDropDownList", "Control")) {%> <fieldset> <legend>当前选中类别</legend> <%=Html.DropDownList("SelectCategory")%> <input type="submit" value="返回"/> </fieldset> <%} %> <form action="/" method="post"> <fieldset> <legend>当前选中类别</legend> <select id="SelectCategory" name="SelectCategory"> <option value="1">Beverages</option> <option value="2">Condiments</option> <option value="3">Confections</option> <option value="4">Dairy Products</option> <option value="5">Grains/Cereals</option> <option value="6">Meat/Poultry</option> <option value="7">Produce</option> <option selected="selected" value="8">Seafood</option> </select> <input type="submit" value="返回"/> </fieldset> </form>
ListBox列表框可选中多个项(设置multiple):
ListBox(string name); ListBox(string name,IEnumerable<SelectListItem> selectList); ListBox(string name,IEnumerable<SelectListItem> selectList,object htmlAttributes); ListBox(string name,IEnumerable<SelectListItem> selectList,IDictionary<string,object> htmlAttributes);
查看一下ListBox应用页面及HTML:
public ActionResult ShowListBox() { ViewData["Category"] = new SelectList(db.Categories, "CategoryID", "CategoryName"); return View(); }
<%using (Html.BeginForm("SelectListBox", "Control")) {%> <fieldset> <legend>选择类别</legend> <%=Html.ListBox("Category")%> <input type="submit" value="选择"/> </fieldset> <%} %> <form action="/Control/SelectListBox" method="post"> <fieldset> <legend>选择类别</legend> <select id="Category" multiple="multiple" name="Category"> <option value="1">Beverages</option> <option value="2">Condiments</option> <option value="3">Confections</option> <option value="4">Dairy Products</option> <option value="5">Grains/Cereals</option> <option value="6">Meat/Poultry</option> <option value="7">Produce</option> <option value="8">Seafood</option> </select> <input type="submit" value="选择"/> </fieldset> </form>
当选中多项时,代码如下:
public ActionResult SelectListBox(FormCollection form) { var selectedCategories = form["Category"].Split(',').AsEnumerable(); ViewData["SelectCategory"] = new MultiSelectList(db.Categories, "CategoryID", "CategoryName", selectedCategories); return View(); }
<%using (Html.BeginForm("ShowListBox", "Control")) {%> <fieldset> <legend>当前选中类别</legend> <%=Html.ListBox("SelectCategory")%> <input type="submit" value="返回"/> </fieldset> <%} %> <form action="/" method="post"> <fieldset> <legend>当前选中类别</legend> <select id="SelectCategory" multiple="multiple" name="SelectCategory"> <option value="1">Beverages</option> <option selected="selected" value="2">Condiments</option> <option selected="selected" value="3">Confections</option> <option value="4">Dairy Products</option> <option value="5">Grains/Cereals</option> <option value="6">Meat/Poultry</option> <option value="7">Produce</option> <option value="8">Seafood</option> </select> <input type="submit" value="返回"/> </fieldset> </form>