Contoso 大学 - 3 - 排序、过滤及分页

  1. 云栖社区>
  2. 博客>
  3. 正文

Contoso 大学 - 3 - 排序、过滤及分页

杰克.陈 2013-11-23 23:39:00 浏览551
展开阅读全文

原文 Contoso 大学 - 3 - 排序、过滤及分页

目录

Contoso 大学 - 使用 EF Code First 创建 MVC 应用

 原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application

在上一个课程中,我们已经学习了如何使用 EF 对 Student 实体进行增、删、改、查处理。这次的课程我们将对学生的 Index 页面加入排序、过滤以及分页的功能。还要创建一个页面完成简单的分组。

下面的截图展示了完成之后的页面,列的标题作为链接支持用户通过点击完成排序,点击标题可以在升序和降序之间进行切换。

3-1  在 Students 的 Index 页面增加列标题链接

为 Index 页面增加排序的功能,我们需要修改 Student 控制器的 Index 方法,还需要为 Student 视图增加代码。

3-1-1  为 Index 方法增加排序功能

打开 Controllers\StudentController.cs,将 Index 方法替换为如下的代码。

复制代码
public ViewResult Index(string sortOrder)
{
ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "Name desc" : "";
ViewBag.DateSortParm = 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 的参数,这个参数由 ASP.NET MVC 作为参数传递给 Action 方法。这个参数可以是  “Name” 或者 “Date”, 可能还有一个空格隔开的 desc 来指定降序。

当第一次请求 Index 的时候,没有参数,学生使用 LastName 的升序顺序显示。这是通过 switch  default 代码段指定的,当用户点击一个列的标题链接的时候,合适的 sortOrder 值需要通过查询字符串传递进来。

两个 ViewBag 变量用来为视图提供合适的查询字符串链接值。

ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "Name desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "Date desc" : "Date";

这里使用了条件语句,第一个用来指定当 sortOrder 参数为 null 或者空串的时候, ViewBag.NameSortParm 应用被设置为 Name desc,其他情况下,应该被设置为空串。

这里有四种可能,依赖于当前的排序情况:

  • 如果当前的排序规则为 LastName 升序,那么,LastName 链接应该设置为降序,Enrollment Date 链接必须被设置为按日期升序。
  • 如果当前的排序规则为 LastName 降序,那么,LastName 链接应该设置为升序,排序串应该为空串,日期为升序。
  • 如果当前排序的规则为 Date 升序,那么,链接应该为 LastName 升序和日期升序。
  • 如果当前的排序规则为 Date 降序,那么,链接应该为 LastName 升序和日期降序。

方法中使用 LINQ to Entities 来指定排序,在 switch 之前,代码首先创建一个 IQueryable 变量,在 switch 语句中修改这个查询表达式,最后调用 ToList 方法。在创建和修改查询表达式 IQueryable 的时候,并没有将查询发送到数据库中执行,查询直到将 IQueryable 对象驼工调用类似 ToList 方法转换到集合对象的时候才会执行,因此,代码中查询直到最后的 return View 才会被执行。

3-2-2  为 Index 视图增加列标题链接

 Views\Student\Index.cshtml,使用如下的代码替换标题行中的 <tr> 和 <th> 元素。

复制代码
<tr>
<th></th>
<th>
@Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm })
</th>
<th>
First Name
</th>
<th>
@Html.ActionLink("Enrollment Date", "Index", new { sortOrder=ViewBag.DateSortParm })
</th>
</tr>
复制代码

这段代码使用 ViewBag 属性来设置超级链接中包含适当的查询字符串。

运行页面,点击列标题,来验证排序是否正常。

3-2  为 Index 页面增加搜索框

为 Index 页面增加过滤功能,需要增加一个文本框和一个提交按钮,然后,对 Index 方法进行一些修改,文本框允许你输入一个搜索字符串,用来在 FirstName 和 LastName 中进行搜索。

3-2-1  为 Index 方法增加过滤功能

打开 Controllers\StudentController.cs 文件,使用下面的代码替换 Index 方法。

复制代码
public ViewResult Index(string sortOrder, string searchString)
{
ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "Name desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "Date desc" : "Date";
var students = from s in db.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
|| s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}
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 方法增加了一个参数 searchString ,LINQ 语句中也增加了一个 where 子句,用来选择在 FirstName 或者 LastName 中包含过滤字符串的学生。搜索串来自文本框的输入,后面需要你在视图中加入它。增加的 where 条件子句仅仅在提供了搜索串的情况下才会被处理。

if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
|| s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}

注意:在传递一个空串的时候,.NET 实现的 Contains 方法将会返回所有的数据行,但是 EF Provider for SQL Server Compact 4.0 对于空串不返回任何行。因此,代码中增加了一个 if 判断语句,以确保对于所有的 SQL Server 都有一致的处理结果。另外,.NET 实现的 Contains 默认进行区分大小写的字符串比较,因此,通过调用 ToUpper 方法显式转换字符串为大写,

以确保在转换到使用资源库模式的时候不需要修改代码。那个时候将会返回一个 IEnumerable 集合而不是 IQueryable 对象 ( 在调用 IEnumerable 集合上的 Contains 方法的时候,使用 .NET 实现的方法,在调用 IQueryable 对象上的 Contains 方法的时候,使用数据库 Provider 提供的实现 )。

3-2-2  在 Student 视图上加入搜索框

在视图 Views\Student\Index.cshtml 上,table 开始标记之前,增加一个标题,一个文本框,以及一个 Search 按钮。

@using (Html.BeginForm())
{
<p>
Find by name: @Html.TextBox("SearchString")
<input type="submit" value="Search" /></p>
}

运行程序,输入一个搜索串,然后点击 Search 按钮来查看过滤的效果。



3-3  在 Student 的 Index 视图上增加分页

为了支持分页,你需要通过 NuGet 包管理器安装 PagedList ,然后,需要在 Index 方法中增加一些代码,在视图中增加分页的链接,下面的截图展示了分页的链接。

3-3-1  安装 PagedList 包

NuGet 中的 PagedList 包将会增加一种类型:PagedList,当将查询结果传入到 PagedList 中后,它提供的一系列属性和方法使得排序更加简单。

在 Visual Studio 中,确信选中了当前的项目,而不是解决方案。在 Tools 菜单中,选择 Library Package Manager,然后选择 Add Library Package Reference。

在 Add Library Package Reference 对话框中,点击左边的 Online 窗格,然后在搜索框中输入 pagedlist ,在看到 PagedList 包之后,点击 Install。

3-3-2  为 Index 方法增加分页功能

打开 Controllers\StudentController.cs,在代码的前面为 PagedList 命名空间增加 using 语句.

using PagedList;

将 Index 方法替换成如下的代码。

复制代码
 public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
ViewBag.CurrentSort = sortOrder;
ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "Name desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "Date desc" : "Date";

if (Request.HttpMethod == "GET")
{
searchString = currentFilter;
}
else
{
page = 1;
}
ViewBag.CurrentFilter = searchString;

var students = from s in db.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
|| s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}
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 = 3;
int pageNumber = (page ?? 1);
return View(students.ToPagedList(pageNumber, pageSize));
}
复制代码

方法又增加了一个 page 参数,方法的签名如下所示。

public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)

当第一次显式这个页面的时候,或者用户没有点击分页链接的时候,page 参数将会是 null。如果分页链接被点击了,page 参数将会包含需要显示的页码。

ViewBag 中的 CurrentSort 属性用来提供当前的排序顺序,它必须被包含到当前的分页链接中,以便在分页处理过程中保持当前的排序规则。

ViewBag.CurrentSort = sortOrder;

其它的 ViewBag 属性为视图提供当前的过滤串,因为这个过滤串在页面被重新显示的时候,必须重新回到文本框中,另外,这个串也必须包含在分页链接中,以便在分页过程中,保持过滤效果。最后,如果在分页的过程中修改了过滤串,那么页码将会回到第一页,因为新的过滤规则返回了不同的数据,很可能原来的页码在这时候已经不再存在了。

复制代码
if (Request.HttpMethod == "GET")
{
searchString = currentFilter;
}
else
{
page = 1;
}
ViewBag.CurrentFilter = searchString;
复制代码

在方法的最后,查询学生的表达式被转换为 PagedList ,而不再是通常的 List,这样传递到视图中的就是支持分页的集合,代码如下:

int pageSize = 3;
int pageNumber = (page ?? 1);
return View(students.ToPagedList(pageNumber, pageSize));

ToPagedList 方法需要一个页码值,两个问号用来为可空的页码提供一个默认值,表达式 ( page ?? 1 ) 意味着如果 page 有值得话返回这个值,如果是 null 的话,返回 1。

3-3-3  为视图增加分页链接

 Views\Student\Index.cshtml中,使用下面的代码替换原有代码。

复制代码
@model PagedList.IPagedList<ContosoUniversity.Models.Student>

@{
ViewBag.Title = "Students";
}

<h2>Students</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
@using (Html.BeginForm())
{
<p>
Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
<input type="submit" value="Search" /></p>
}
<table>
<tr>
<th></th>
<th>
@Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
</th>
<th>
First Name
</th>
<th>
@Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter = ViewBag.CurrentFilter })
</th>
</tr>

@foreach (var item in Model) {
<tr>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.StudentID }) |
@Html.ActionLink("Details", "Details", new { id=item.StudentID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.StudentID })
</td>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
</tr>
}

</table>

<div>
Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber)
of @Model.PageCount

@if (Model.HasPreviousPage)
{
@Html.ActionLink("<<", "Index", new { page = 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
@Html.Raw(" ");
@Html.ActionLink("< Prev", "Index", new { page = Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
}
else
{
@:<<
@Html.Raw(" ");
@:< Prev
}

@if (Model.HasNextPage)
{
@Html.ActionLink("Next >", "Index", new { page = Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
@Html.Raw(" ");
@Html.ActionLink(">>", "Index", new { page = Model.PageCount, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
}
else
{
@:Next >
@Html.Raw(" ")
@:>>
}
</div>
复制代码

视图最前面的 @model 语句指定现在传递到视图的不再是 List 而是 PagedList 。

文本框使用当前的搜索串进行初始化,以便在分页的时候不会丢失搜索串。

 Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)  

列的标题链接使用查询串来传递当前的搜索串,以便传递给控制器当前的搜索和排序。

@Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })

在当前页面的最后,通过一行来显示分页的导航 UI。

Page [current page number] of [total number of pages] << < Prev Next > >>

<< 符号连接到第一页, < Prev 链接到上一页,等等。如果用户当前就在第一页,那么,链接到第一页的链接就会被禁用,类似地,如果用户当前在最后一页,导航到最后一页就会被禁用,每一个分页链接传递页码以及当前的排序串和搜索串到控制器,这使得可以在分页的同时维护排序和过滤规则。

如果没有页可以显示,将会显示 “Page 0 of 0 “,在这种情况下,页面数字就会大于页数,因为 Model.PageNumber 是 1,但是 Model.PageCount 为 0。

运行页面。

在不同的排序规则下,点击分页链接,确认分页在正常工作。然后输入一个过滤串,再次点击分页的链接,确认在排序和过滤的同时,分页可以正常工作。

3-4  创建 About 页面显示学生的统计情况

在 Contoso 大学网站的 About 页面,我们希望能够显示每个注册日有多少学生注册。这需要进行分组,然后在每个组上进行简单地计算,需要完成下列工作:

  • 创建用于传递数据到视图的 ViewModel
  • 修改 Home 控制器中的 About 方法
  • 修改 About 视图

3-4-1  创建 ViewModel

创建 ViewModels 文件夹,在文件夹中,创建 EnrollmentDateGroup.cs 类文件,将代码替换为如下代码:

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.ViewModels
{
public class EnrollmentDateGroup
{
[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime? EnrollmentDate { get; set; }

public int StudentCount { get; set; }
}
}
复制代码

3-4-2  修改 Home 控制器

增加如下的 using 语句。

using ContosoUniversity.DAL;
using ContosoUniversity.Models;
using ContosoUniversity.ViewModels;

增加一个数据库上下文变量。

private SchoolContext db = new SchoolContext();

使用如下的代码替换 About 方法。

复制代码
public ActionResult About()
{
var data = from student in db.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
return View(data);
}
复制代码

LINQ 语句通过注册日期对学生进行分组,计算每一组中的实体数量,最后将查询结果保存为 EnrollmentDateGroup 对象。

3-4-3  增加 Dispose 方法

protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}

3-4-4  修改 About 视图

打开 Views\Home\About.cshtml ,替换为如下代码。

复制代码
@model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup>

@{
ViewBag.Title = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>

@foreach (var item in Model) {
<tr>
<td>
@String.Format("{0:d}", item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
复制代码

运行页面,每个注册日注册学生的数量显示在表格中。

现在,你已经看到了如何创建数据模型,以及实现基本的增、删、改、查处理,排序、过滤、分页和分组功能。下一次,我们将会扩展数据模型开始更加高级的内容。

网友评论

登录后评论
0/500
评论
杰克.陈
+ 关注