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

原文地址: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 的升序顺序显示。这是通过 switchdefault 代码段指定的,当用户点击一个列的标题链接的时候,合适的 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>

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

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

时间: 2024-09-28 20:43:02

Contoso 大学 - 3 - 排序、过滤及分页的相关文章

asp.net core 排序过滤分页组件:sieve(1)

使用asp.net core开发时避免不了要用一个合适的分页组件来让前端获取分页数据.github上面有一个开源的分页组件在这方面很适合我的使用,于是我把他的文档翻译一下,随后会分析它里面的源码.这是第一篇,介绍它如何使用. Sieve Sieves是一个.net core下面的简单.干净并且可扩展的框架,它对外暴露了排序,过滤和分页的功能. ASP.NET Core下的使用方式 在本例中,考虑一个带有Post实体的应用程序.在获取所有可用的帖子时,我们将使用Sieve添加排序.过滤和分页功能.

MVC4中的列表排序、过滤、分页

在上一个课程中,我们已经学习了如何使用 EF 对 Student 实体进行增.删.改.查处理.这次的课程我们将对学生的 Index 页面加入排序.过滤以及分页的功能.还要创建一个页面完成简单的分组. 下面的截图展示了完成之后的页面,列的标题作为链接支持用户通过点击完成排序,点击标题可以在升序和降序之间进行切换. 3-1  在 Students 的 Index 页面增加列标题链接 为 Index 页面增加排序的功能,我们需要修改 Student 控制器的 Index 方法,还需要为 Student

MVC5+EF6--3 排序、过滤和分页

近期学习MVC5+EF6,找到了Microsoft的原文,一个非常棒的系列,Getting Started with Entity Framework 6 Code First using MVC 5,网址:http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-appli

EF6与mvc5系列(3):在MVC应用程序中使用EF进行排序,过滤和分页

上节中,我们实现了基本增删查改功能,本节中要在Student的Index页面添加排序,分页和过滤功能,同时创建一个简单的分组页面. 在Student的Index页面添加列排序链接 为了在Index页面中实现排序.修改Index方法中的代码. 在Index方法中添加排序功能 修改Student控制器中的Index方法,在Index视图中添加代码. // GET: /Student/ public ActionResult Index(string sortOrder) { ViewBag.Name

DRF框架(八)——drf-jwt手动签发与校验、过滤组件、筛选组件、排序组件、分页组件

自定义drf-jwt手动签发和校验 签发token源码入口 前提:给一个局部禁用了所有 认证与权限 的视图类发送用户信息得到token,其实就是登录接口,不然进不了登录页面 获取提交的username和password 1)rest_framework_jwt.views.ObtainJSONWebToken 的 父类 JSONWebTokenAPIView 的 post 方法 接受有username.password的post请求校验数据,并且签发token 2)post方法将请求数据交给 r

签发token、校验token、多方式登录签发token的实现、自定义认证反爬规则的认证类、admin使用自定义User表:新增用户密码密文、群查接口各种筛选组件数据准备、drf搜索过滤组件、drf排序过滤组件、drf基础分页组件

签发token 源码入口 # 前提:给一个局部禁用了所有 认证与权限 的视图类发送用户信息得到token,其实就是登录接口 # 1)rest_framework_jwt.views.ObtainJSONWebToken 的 父类 JSONWebTokenAPIView 的 post 方法 # 接收有username.password的post请求 # 2)post方法将请求得到的数据交给 rest_framework_jwt.serializer.JSONWebTokenSerializer 处

Contoso 大学 - 使用 EF Code First 创建 MVC 应用,实例演练

Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进行演示,下载的代码中同时包含 C# 和 VB 实现.如果你有与这个教程没有直接相关的问题,可以张贴到 ASP.NET Entity Framework forum  或者

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

Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进行演示,下载的代码中同时包含 C# 和 VB 实现.如果你有与这个教程没有直接相关的问题,可以张贴到 ASP.NET Entity Framework forum  或者 

Contoso 大学 - 10 - 高级 EF 应用场景

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用 在上一个教程中,你已经实现了仓储和工作单元模式.这个教程涵盖下列主题: 执行原始的 SQL 查询 执行没有跟踪的查询 检查发送到数据库的查询