上节中,我们实现了基本增删查改功能,本节中要在Student的Index页面添加排序,分页和过滤功能,同时创建一个简单的分组页面。
在Student的Index页面添加列排序链接
为了在Index页面中实现排序。修改Index方法中的代码。
在Index方法中添加排序功能
修改Student控制器中的Index方法,在Index视图中添加代码。
// GET: /Student/ public ActionResult Index(string sortOrder) { ViewBag.NameSort = string.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewBag.DateSort=sortOrder=="Date"?"date_desc":"Date"; var students=from s in db.Students select s; switch(sortOrder) { case "name_desc": students=students.OrderByDescending(s=>s.LastName); break; case "Date": students=students.OrderBy(s=>s.EnrollmentDate); break; case "date_desc": students=students.OrderByDescending(s=>s.EnrollmentDate); break; default: students=students.OrderBy(s=>s.LastName); break; } return View(students.ToList()); }
上述方法中,接收到一个来自URL的sortOrder查询字符串参数,查询字符串的值是由MVC提供,作为一个参数传递给action。参数值是“Name”或者“Date”,然后后面随意跟随_desc用于说明降序排列,默认是正序。
第一次请求Index页面,是没有查询字符串的,页面会呈现出按照Last字段正序查询出的数据。当用户点击列标题超链接的时候,会传递sortOrder的值进行查询。
Index方法使用了 指定要排序的列。在switch语句之前创建 变量,并在switch语句中修改其值,最后调用ToList()方法。当你创建并修改IQueryable
变量的时候,不向数据库中发送查询请求。当你将IQueryable
对象通过像ToList这样的方法,转换为集合的时候才会执行查询。有关更多动态LINQ,请查看:.
在Index视图页添加列标题超链接
对Index视图代码做如下修改:
@Html.ActionLink("LastName", "Index", new { sortOrder=ViewBag.NameSort}) | @*@Html.DisplayNameFor(model => model.FirstMidName)*@ FirstMidName | @Html.ActionLink("Enrollment Date", "Index", new { sortOrder=ViewBag.DateSort}) |
---|
在Index页面添加搜索框
接下来实现页面的搜索功能
在Index方法中添加过滤功能
修改Index方法如下:
// GET: /Student/ public ActionResult Index(string sortOrder,string searchStr) { ViewBag.NameSort = string.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewBag.DateSort=sortOrder=="Date"?"date_desc":"Date"; var students = from s in db.Students select s; if (!string.IsNullOrEmpty(searchStr)) { students = students.Where(s=>s.LastName.Contains(searchStr)||s.FirstMidName.Contains(searchStr)); } switch(sortOrder) { case "name_desc": students=students.OrderByDescending(s=>s.LastName); break; case "Date": students=students.OrderBy(s=>s.EnrollmentDate); break; case "date_desc": students=students.OrderByDescending(s=>s.EnrollmentDate); break; default: students=students.OrderBy(s=>s.LastName); break; } return View(students.ToList()); }
在Index方法中添加searchStr参数,此参数来自页面的输入框中写入的值,在where字句中添加LINQ筛选出符合条件的实体。
注意:在很多情况下,可以对EF实体集,或者作为内存中集合的扩展方法调用相同的方法,其作用结果相同。但是有些情况下是不同的。
例如:.NET Framework实现Contains方法的时候,如果传入空字符串会返回数据库中所有行。但是对于SQL Server Compact 4实体框架提供程序,如果输入空字符串不会返回任何行。因此才会将上述中的where字句放在if中以确保不同情况下都能显示数据。同时,.NET Framework实现Contains方法的时候默认是区分大小写的。但是SQL Server Compact 4实体框架提供程序默认不区分大小写。为了避免这种情况,我们可以使用ToUpper方法避免这种情况。稍后的代码中,我们的结果集会返回一个IEnumerable
集合,而不是一个IQueryable
对象。对IEnumerable
集合调用Contains方法的时候,是由.NET Framework实现。而对IQueryable
调用Contains方法的时候,是由数据库提供程序实现的。
不同的数据库应用程序对NULL处理也不同。例如,有时候一个where中包含table.Column != 0
时,不会返回带有NULL的数据行。更多信息请查看:.
Index视图中添加搜索框
如下:
@Html.ActionLink("Create New", "Create")
@using(Html.BeginForm()){Find by Name:@Html.TextBox("searchStr")
}
注意:.NET Framework实现了Contains方法,当你传入的字符串为空时,返回所有行,但是SQL Server契约4.0的EF提供程序对于这种情况会返回0行数据。就是考虑到这种情况所以将where声明放在If中。.NET Framework对于Contains方法是区分大小写的,但是EF sql server提供程序对于Contains方法是不区分大小写的。对于IEnumerable
集合调用Contains方法会由.NET Framework实现。当对IQueryable对象调用Contains方法,会由数据库提供程序实现。
空处理对于不同的数据库提供程序或者 IQueryable
对象和IEnumerable
集合也是不同的。详情查看: .
添加分页
接下来在Index页面添加分页,我们使用PagedList.Mvc进行分页。我们仅仅在这个例子中使用它,不推荐在其他地方使用其进行分页。
安装PagedList.MVC NuGet包。
工具->库程序包管理器(NuGet程序包管理器)—>程序包管理器控制台->Install-Package PagedList.Mvc
生成项目
在Index方法中添加分页
引入命名空间
using PagedList;
public ActionResult Index(string sortOrder,string searchStr,int? page,string currentFilter) { ViewBag.CurrentSort = sortOrder; //如果是sortOrder为空,返回name_desc,否则返回空字符串。 ViewBag.NameSort = string.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewBag.DateSort=sortOrder=="Date"?"date_desc":"Date"; if (searchStr!=null) { page = 1; } else { searchStr = currentFilter; } ViewBag.CurrentFilter = searchStr; var students = from s in db.Students select s; if (!string.IsNullOrEmpty(searchStr)) { students = students.Where(s=>s.LastName.Contains(searchStr)||s.FirstMidName.Contains(searchStr)); } switch(sortOrder) { case "name_desc": students=students.OrderByDescending(s=>s.LastName); break; case "Date": students=students.OrderBy(s=>s.EnrollmentDate); break; case "date_desc": students=students.OrderByDescending(s=>s.EnrollmentDate); break; default: students=students.OrderBy(s=>s.LastName); break; } int pageSize = 2; int pageNum = (page ?? 1); return View(students.ToPagedList(pageNum,pageSize)); }
1 @*@model IEnumerable*@ 2 @model PagedList.IPagedList 3 @using PagedList.Mvc 4 5 6 @{ 7 ViewBag.Title = "Student"; 8 } 9 10 Student
11 1213 @Html.ActionLink("Create New", "Create")14
15 @using(Html.BeginForm("Index","Student",FormMethod.Get))16 {1718 Find by Name:@Html.TextBox("searchStr", ViewBag.CurrentFilter as string)19 20
21 }2223
57 58 第 @(Model.PageCount24 36 37 @foreach (var item in Model) {3825 @Html.ActionLink("LastName", "Index", new { sortOrder = ViewBag.NameSort, currentFilter = ViewBag.CurrentFilter })26 2728 @*@Html.DisplayNameFor(model => model.FirstMidName)*@29 FirstMidName30 3132 @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSort, currentFilter = ViewBag.CurrentFilter })33 3435 39 54 }55 5640 @Html.DisplayFor(modelItem => item.LastName)41 4243 @Html.DisplayFor(modelItem => item.FirstMidName)44 4546 @Html.DisplayFor(modelItem => item.EnrollmentDate)47 4849 @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |50 @Html.ActionLink("Details", "Details", new { id=item.ID }) |51 @Html.ActionLink("Delete", "Delete", new { id=item.ID })52 53Url.Action("Index", new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
页面首次加载或者用户未点击分页或者分类链接,所有的参数为null,如果点击分页链接,将看到page变量中包含了页码。
ViewBag
属性提供给页面一个当前排序,因为分页时也要保持现有的排序,
ViewBag.CurrentSort = sortOrder;
另一个属性ViewBag.CurrentFilter,向页面提供当前过滤字符串,这样确保分页的时候也保持当前过滤条件。如果分页时查找条件改变,页面就重置为1,因为新的过滤条件查询结果与之前不同。
if (searchStr!=null) { page = 1; } else { searchStr = currentFilter; }
在方法的最后,students的IQueryable
扩展方法:ToPagedList
,将查询结果转为一个分页的学生集合,并传递到视图页。
int pageSize = 3;int pageNumber = (page ?? 1);return View(students.ToPagedList(pageNumber, pageSize));
修改页面的@model,将List对象改为PageList对象。
using声明能够访问MVC的辅助方法。
默认提交数据方式为POST,这表示在http消息体中传入的查询字符串参数而不是在URL中传入。当指定HTTP GET,表单数据会以URL作为查询字符串传入这样会使用户看到URL参数,在中提到,当Action不是update时推荐使用GET。
使用当前查询字符串初始化文本框。当点击新页面时可以看到当前查询字符串。
Find by Name:@Html.TextBox("searchStr", ViewBag.CurrentFilter as string)
列标题连接使用查询字符串把关键字传递给控制器,这样用户就可以对查询出的结果进行排序。
@Html.ActionLink("LastName", "Index", new { sortOrder = ViewBag.NameSort, currentFilter = ViewBag.CurrentFilter })
显示当前页和总页数
Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
使用PagedListPager
的辅助方法显示分页按钮。
@Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
创建About页面显示学生统计信息
在这个页面中,将要显示每学期有多少学生入学,这用到了分组和简单的分组计算。接下来将要做:
- 创建需要传递给视图用的模型类
- 修改HomeController中的About方法
- 修改About视图页面
创建视图模型
在项目中添加ViewModels文件夹,在此文件夹中添加类EnrollmentDateGroup.cs。代码如下:
1 using System; 2 using System.ComponentModel.DataAnnotations; 3 4 namespace ContosoUniversity.ViewModels 5 { 6 public class EnrollmentDateGroup 7 { 8 [DataType(DataType.Date)] 9 public DateTime? EnrollmentDate { get; set; }10 public int StudentCount { get; set; }11 }12 }
修改HomeController
在Home控制器中添加引用:
using ContosoUniversity.DAL;using ContosoUniversity.ViewModels;
添加数据库上下文:
public class HomeController : Controller { private SchoolContext db = new SchoolContext();
修改About方法:
public ActionResult About() { IQueryabledata = from student in db.Students group student by student.EnrollmentDate into dateGroup select new EnrollmentDateGroup() { EnrollmentDate=dateGroup.Key, StudentCount=dateGroup.Count() }; return View(data.ToList()); }
LINQ查询:根据入学登记日期对学生信息分组,同时计算每组的学生人数,并将结果存储在EnrollmentDateGroup视图模型对象
中。
添加Dispose方法:
protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); }
修改About视图
@model IEnumerable< ContosoUniversity.ViewModels.EnrollmentDateGroup>@{ ViewBag.Title = "学生统计";}学生统计
Enrollment Date | Students |
---|---|
@Html.DisplayFor(modelItem => item.EnrollmentDate) | @item.StudentCount |
Use this area to provide additional information.
运行页面,ok。本节完!