概述
通过前几节的学习,我们知道如何在MVC 3应用程序中实现一个简单表格并在表格之中加上了排序的表头及给分页,这些功能都是单一的,很多时候我们肯定不会只单单做一个排序或者分页功能吧,这样的表格往往不能满足我们的需求,为了更好的展示数据我们需要把这些功能合并在一个表格之中。
问题分析
我们知道
在排序功能里,我们有两个参数:
①SortBy列名称:显示用户是通过那个列来排序的。
②ascending排序标识:显示要排序的列名是升序还降序排列。
在分页功能里,也有两个参数:
①page当前页:当前表格索引页。
②pageSize每页显示条数:当前表格中显示几条数据。
而在数据筛选里,有三个参数:
①EmployeeNO查询编号:可以通过输入编号来筛选数据。
②DepartmentID家族编号:可以通过选择家族的列表来筛选数据。
③IsMarital婚姻状态:可以通过选择是否结婚来筛选数据。
这样一来,我们是不是猜想把这几个功能合并无非就是把这些参数合并在一起嘛。好那我们先来试试看。
创建Action
我们直接在EmployeeController添加名为SortPageAndFilter的方法
把前面几个方法的参数先复制下来,参数就变为ascending、employeeNO、departmentID、isMarital、sortBy、 page 、pageSize
对于这几个功能来说必须有先后顺序,我们就以 筛选--排序--分页的顺序来写代码吧。
代码如下
public ActionResult SortPageAndFilter(bool? ascending, int? employeeNO, int? departmentID, bool? isMarital = null, string sortBy = "EmployeeNO", int page = 1, int pageSize = 5) { var model = new EmployeeGridModel() { //排序默认 SortBy = sortBy, SortAscending = ascending.GetValueOrDefault(), // 分页默认 CurrentPageIndex = page, PageSize = pageSize, // 筛选默认 DepartmentID = departmentID, EmployeeNO = employeeNO.ToString(), IsMarital = isMarital.HasValue ? isMarital.Value : false, DepartmentList = this.DataContext.Department .OrderBy(c => c.DepartName) .Select(c => new SelectListItem { Text = c.DepartName, Value = c.DepartmentID.ToString() } ) }; // 筛选结果 var filteredResults = this.DataContext.Employee.AsQueryable(); if (employeeNO != null) filteredResults = filteredResults.Where(o => o.EmployeeNO == employeeNO.ToString()); if (departmentID > 0) filteredResults = filteredResults.Where(o => o.DepartmentID == departmentID.ToString()); if (isMarital != null && isMarital.Value == true) filteredResults = filteredResults.Where(o => o.Marital == "1"); //获取筛选结果的总条数 model.TotalRecordCount = filteredResults.Count(); // 为筛选结果分页 model.Employees = filteredResults .OrderBy(model.SortExpression) .Skip((model.CurrentPageIndex - 1) * model.PageSize) .Take(model.PageSize); return View(model); }
首先我们按照以前的思路,初始化一个辅助类EmployeeGridModel,读取了数据库中表的记录条数,为相关属性设置默认值,返回一个辅助类的Employeelist
注意:此处我们的OrderBy可以参照上篇中的具体操作,这里就不多赘述。
创建视图View
为SortPageAndFilter的Action添加一个视图View,选择强类型为EmployeeGridModel
按照以前的代码我们复制过来,该加的加该减的减
最后代码变为
@model MVC3.Grid.Models.EmployeeGridModel @{ ViewBag.Title = "SortPageAndFilter"; Layout = "~/Views/Shared/_Layout.cshtml"; int i = 0; } <h2>排序、分页和筛选</h2> @using (Html.BeginForm("SortPageAndFilter", "Employee", FormMethod.Get)) { <fieldset class="filtering"> <legend>内容筛选</legend> <div> <b>编号:</b> @Html.TextBoxFor(o => o.EmployeeNO, new { size = 8 }) <b>种族:</b> @Html.DropDownListFor(o => o.DepartmentID, Model.DepartmentList, "-- 所有分类 --") <b>是否婚配:</b> @Html.CheckBoxFor(o => o.IsMarital) <input type="submit" value="查询" /> </div> <table border="1" width="100%" style="text-align: center; border-collapse: collapse"> <tr> <th> @Html.Partial("_SmartLink", Model, new ViewDataDictionary { { "ColumnName", "EmployeeNO" }, { "DisplayName", "编号" } }) </th> <th> @Html.Partial("_SmartLink", Model, new ViewDataDictionary { { "ColumnName", "EmployName" }, { "DisplayName", "姓名" } }) </th> <th>性别</th> <th>生日</th> <th>是否婚配</th> </tr> @foreach (var item in Model.Employees) { <tr> <td>@item.EmployeeNO</td> <td>@item.EmployName</td> <td>@item.Sex</td> <td>@string.Format("{0:yyyy年MM月dd日}", item.Birthday)</td> <td> @if (item.Marital == "1") { @:@("是") } @if (item.Marital != "1") { @:@("否") } </td> </tr> i++; } @if (i < 5) { while (i < Model.PageSize) { i++; <tr> <td colspan="5">   </td> </tr> } } <tr> <td colspan="5"> @Html.Partial("_Pager", Model) </td> </tr> </table> </fieldset> }
运行起来看看
呵呵,效果已经出来了,功能怎么样啊我们试试。
我们叫三选手来试试看:
①号选手:点击编号 倒序排序,然后点击下一页。
我晕,神马情况??倒叙居然该为了顺序。完全与上下页无关。。呜呜杯具了。。。
②号选手:选择了筛选条件为 羊族。点击查询后,发现上下也能用。窃喜中。。。点击下一页
奇迹发生了
筛选的数据部是六条的嘛,分页怎么还是8条??而且刚刚选择的 羊族也不知去哪里了,筛选和分页没有连接起来。。。晕死啊。
③号选手:采用了迂回策略,点击筛选了 羊族 作为筛选条件 点击查询
点击按编号排序
啊啊~~~不会吧 我筛选的羊族唉 怎么灰太狼都排在里面了呀?太离谱了吧。筛选和排序都没有连接在一起。
经过这三位选手的测试我们不难看出,即便是单纯的合并还不能满足我们的需求。
下面我们来一步步把这些错误处理掉。。
合并分页和排序
原因分析:先看看当我们点击排序的时候路由是怎么样通知的
/?sortBy=EmployeeNO&ascending=False
然而当我们点击下一页的时候路由就会变成
/?page=2&pageSize=5
要是我们把这两个路由合并在一起,那问题不就解决了吗。
所以我们在分页链接里面要读取早先存储的排序标识,然后将排序标识添加到路由数据里面,在指向Action的时候,路由不是就完整了吗。
分析到这里我们就开始做吧
为_PagerLink.cshtml中添加分割线内的内容。
//路由参数 var routeData = new RouteValueDictionary { { "page", ViewData["PageIndex"].ToString() }, { "pageSize", Model.PageSize } }; ............................................................................................ //记录排序历史 //获取路由参数的sortBy排序列和ascending排序方法 //如果没有排序则给路由参数加如该参数 if (!string.IsNullOrEmpty(Request.QueryString["sortBy"])) { routeData.Add("sortBy", Request.QueryString["sortBy"]); } if (!string.IsNullOrEmpty(Request.QueryString["ascending"])) { routeData.Add("ascending", Request.QueryString["ascending"]); } ........................................................................................... var htmlAttributes = new Dictionary<string, object>();
看看效果
选择倒叙排列 点击下一页
上面的代码看起来不错但是复用性太差,我们进行小小的封装
在前面我们已经建好了一个名为HelperClasses的文件夹
我们在此文件夹中添加一个名为RouteValueDictionaryExtensions的类
代码如下
public static class RouteValueDictionaryExtensions { /// <summary> /// 路由扩展 /// 添加路由历史记录,确保路由字典键与Action参数一致 /// </summary> /// <param name="dict"></param> /// <returns></returns> public static RouteValueDictionary AddQueryStringParameters(this RouteValueDictionary dict) { var querystring = HttpContext.Current.Request.QueryString; foreach (var key in querystring.AllKeys) if (!dict.ContainsKey(key)) dict.Add(key, querystring.GetValues(key)[0]); return dict; } /// <summary> /// 路由扩展 /// 从路由字典中移除指定的路由参数 /// </summary> /// <param name="dict"></param> /// <param name="keysToRemove"></param> /// <returns></returns> public static RouteValueDictionary ExceptFor(this RouteValueDictionary dict, params string[] keysToRemove) { foreach (var key in keysToRemove) if (dict.ContainsKey(key)) dict.Remove(key); return dict; } }
上面的代码可以看出,我们之间为路由创建了扩展方法,来实现对历史路由参数的添加。
既然有了扩展方法,那我们可以把PagerLink.cshtml添加的内容替换为
完整代码
@model MVC3.Grid.Models.EmployeeGridModel @using MVC3.Grid.HelperClasses; @{ //文本编写器 var razorWriter = ViewContext.Writer; //判断当前链接是否选中 if ((bool)ViewData["Inactive"]) { //将当前的Text输出 加入了css样式 该样式可以写在样式表、母版页、当前页中 razorWriter.Write(string.Format("<span class=\"{0}\">{1}</span>", "pagerButtonDisabled", ViewData["Text"])); } else { //路由参数 var routeData = new RouteValueDictionary { { "page", ViewData["PageIndex"].ToString() }, { "pageSize", Model.PageSize } }; ////记录排序历史 ////获取路由参数的sortBy排序列和ascending排序方法 ////如果没有排序则给路由参数加如该参数 //if (!string.IsNullOrEmpty(Request.QueryString["sortBy"])) //{ // routeData.Add("sortBy", Request.QueryString["sortBy"]); //} //if (!string.IsNullOrEmpty(Request.QueryString["ascending"])) //{ // routeData.Add("ascending", Request.QueryString["ascending"]); //} // 添加排序历史参数 routeData.AddQueryStringParameters(); var htmlAttributes = new Dictionary<string, object>(); //判断是否为选中状态 添加CSS样式 if ((bool)ViewData["Selected"]) { htmlAttributes.Add("class", "pagerButtonCurrentPage"); } else { htmlAttributes.Add("class", "pagerButton"); } var linkMarkup = Html.ActionLink( ViewData["Text"].ToString(), // 超链接文本 Html.ViewContext.RouteData.Values["action"].ToString(), // Action Html.ViewContext.RouteData.Values["controller"].ToString(), // Controller routeData, // 路由参数 htmlAttributes // HTML属性适用于超链接 ).ToHtmlString(); razorWriter.Write(linkMarkup); } }
运行效果是一样的。。
合并筛选和排序
我们继续看看排序的路由
/?sortBy=EmployeeNO&ascending=True
而筛选的路由则是
/?EmployeeNO=&DepartmentID=1&IsMarital=false
可以看到在筛选的时候 完全没有在排序的条件下筛选。
因此我们在筛选的时候,将排序标识隐藏在界面中,只用于读取和传递,这样两个参数就有了。
看SortPageAndFilter.cshtml代码:
........................................... @using (Html.BeginForm("SortPageAndFilter", "Employee", FormMethod.Get)) { @Html.Hidden("sortBy", Model.SortBy) @Html.Hidden("ascending", Model.SortAscending) <fieldset class="filtering"> <legend>内容筛选</legend> ............................................
效果
选择排序 然后使用 羊族为关键字 筛选
但是这里还是不完美,如果重新排序的话,就又乱了。。
仔细思考思考,原来我们在分页控件里面没有添加 分页的历史记录导致分页失效
所以_SmartLink.cshtml完整代码
@model MVC3.Grid.Models.EmployeeGridModel @using MVC3.Grid.HelperClasses; @{ //判断传过来的列名是否一致 是降序还是升序排列 var isDescending = string.CompareOrdinal(Model.SortBy, ViewData["ColumnName"].ToString()) == 0 && Model.SortAscending; //路由数据 如:Employee/Sortable?sortBy=EmployeeNO&ascending=False var routeData = new RouteValueDictionary { { "sortBy", ViewData["ColumnName"].ToString() }, { "ascending", !isDescending } }; //添加分页历史记录 routeData.AddQueryStringParameters().ExceptFor("page", "pageSize"); var htmlAttributes = new Dictionary<string, object>(); if (string.CompareOrdinal(Model.SortBy, ViewData["ColumnName"].ToString()) == 0) { if (Model.SortAscending) { htmlAttributes.Add("class", "sortAsc");//添加css样式 } else { htmlAttributes.Add("class", "sortDesc"); } } @Html.ActionLink( ViewData["DisplayName"].ToString(), // 链接文本 Html.ViewContext.RouteData.Values["action"].ToString(), // Action Html.ViewContext.RouteData.Values["controller"].ToString(), // Controller routeData, // 路由数据 htmlAttributes //HTML属性适用于超链接 ) }
继续运行,问题全部得到了解决。
点击编号排序后
大功告成!!!
总结
对于合并功能,其目的还是为了控制路由数据的传递,来应对不断的变化。