如何在ASP.NET Core中建立有效的分页

目录

介绍

背景

创建项目

在后端处理分页

创建分页UI控件

添加搜索过滤器

自定义分页UI控件

改善性能

改善搜寻逻辑


介绍

本文将解释如何使用分页来仅检索所需数量的记录,并显示参考总记录的分页控件。

背景

最有可能遇到了这个问题,您需要从包含数千个或更多记录的数据源中列出几行记录,但随后注意到分页是提高站点性能的重要因素。从过滤数据到从数据库中选择相关记录到显示页面调度控件,要构建一个可靠的页面调度系统,需要考虑一些很重要的步骤。

创建项目

我将使用VS2019附带的默认ASP.NET Core 2.2项目模板,因此只需创建基本的ASP.NET Core 2.2项目并在此处继续阅读。

在深入探讨分页之前,我们需要为记录创建一个数据源。我们的数据源必须包含很多记录,以便我们可以看到分页控件的真正好处。为了使注意力集中在分页主题上,我将使用框架中已包含的项目列表(CultureInfo)。

打开Pages/Index.cshtml.cs并添加数据源,如下所示:

public class IndexModel : PageModel
{      
    public CultureInfo[] CulturesList { get; set; }
    private CultureInfo[] Cultures { get; set; }

    public IndexModel()
    {
        //this will act as the main data source for our project
        Cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
    }

    public void OnGet()
    {
        CulturesList = Cultures;
    }
}

打开Pages/Index.cshtml 并添加以下代码以显示区域性列表:

<table class="table table-striped">
    <thead>
        <tr>
            <th>LCID</th>
            <th>English Name</th>
            <th>Native Name</th>
            <th>Culture types</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var c in Model.CulturesList)
        {
            <tr>
                <td>@c.LCID</td>
                <td>@c.EnglishName</td>
                <td>@c.NativeName</td>
                <td>@c.CultureTypes</td>
            </tr>
        }
    </tbody>
</table>

运行应用程序以查看第一个结果:

在后端处理分页

如您所见,我们正在将所有记录发送到视图,但这不是一种有效的编程方式,因此我们将限制所选记录的数量,以将少量数据发送到视图。基本上,我们需要两个用于分页的变量;

  • 页码:用于指示请求的页码的变量
  • 页面大小:一个变量,指示应该一次选择的记录总数

稍后,我们还将添加更多变量以进行过滤。

返回Pages/Index.cshtml.cs文件,定义变量并修改OnGet,所以我们的新IndexModel看起来如下所示:

public class IndexModel : PageModel
{      
    public IList<CultureInfo> CulturesList { get; set; }
    private CultureInfo[] Cultures { get; set; }

    //page number variable
    [BindProperty(SupportsGet = true)]
    public int P { get; set; } = 1;

    //page size variable
    [BindProperty(SupportsGet = true)]
    public int S { get; set; } = 10;

    public IndexModel()
    {
        //this will act as the main data source for our project
        Cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
    }

    public void OnGet()
    {
        CulturesList = Cultures
            //make sure to order items before paging
            .OrderBy(x=>x.EnglishName)

            //skip items before current page
            .Skip((P-1)*S)

            //take only 10 (page size) items
            .Take(S)
            
            //call ToList() at the end to execute the query and return the result set
            .ToList();
    }
}

运行该应用程序,您将仅看到前10条记录。

创建分页UI控件

Bootstrap提供了一个非常不错的分页UI控件,但它仅呈现HTML元素,并且仍然需要进行大量工作才能知道在控件内呈现什么内容以及如何呈现,例如,记录总数,最大显示页数,搜索过滤器,根据当前页面索引等启用/禁用上一个——下一个按钮。

我将使用LazZiya.TagHelpers nuget包中的页面标记助手,它将为我们完成所有艰苦的工作。:)

PagingTagHelper 基本上需要这些参数:

  • page-no:当前页码的必需int变量
  • total-records:数据源中的总记录数所必需的int
  • query-string-valuestring值,如果URL中包含搜索过滤器,则为必填
  • page-size:可选int(默认为10
  • query-string-key-page-no:可选,string指示页码的查询字符串键名称。默认值为"p",我们将不使用它,因为我们在后端定义了相同的键名。
  • query-string-key-page-size:可选,string指示页面大小的查询字符串键名称。默认值为"s",所以我们不会使用它,因为我们也在后端定义了相同的键名。

在此处阅读有关PagingTagHelper更多信息。

因此,在添加页面标记帮助器之前,我们需要再添加一个变量来处理后端的总记录数:

//total number of records
public int TotalRecords { get; set; } = 0;

public void OnGet()
{
    TotalRecords = Cultures.Count();

    CulturesList = Cultures
        //make sure to order items before paging
        .OrderBy(x=>x.EnglishName)

        //skip items before current page
        .Skip((P-1)*S)

        //take only 10 (page size) items
        .Take(S)
        
        //call ToList() at the end to execute the query and return the result set
        .ToList();
}

现在,我们准备使用分页标记帮助器。

使用软件包管理器控制台安装LazZiya.TagHelpers nuget软件包(请确保下载最新版本):

Install-Package LazZiya.TagHelpers -Version 2.2.1

或使用nuget包管理器UI

添加LazZiya.TagHelpers_ViewImports.cshtml页面:

@addTagHelper *, LazZiya.TagHelpers

将页面标记帮助程序代码添加到表下方的Index.cshtml视图中:

<paging page-no="Model.P"

        page-size="Model.S"

        total-records="Model.TotalRecords">
</paging>

稍后,我们将在添加一些搜索过滤器之后添加query-string-value,现在,运行该应用并在其基本设置中测试分页控件:

添加搜索过滤器

记录的基本列表已完成,现在我们将添加一些搜索过滤器以具有更多功能列表。

首先,让我们将基本的文本搜索逻辑添加到后端:

//variable for text search
[BindProperty(SupportsGet = true)]
public string Q { get; set; } = string.Empty;

public void OnGet()
{
    var query = Cultures
        //search in EnglishName and NativeName
        .Where(x =>
            x.EnglishName.Contains(Q, StringComparison.OrdinalIgnoreCase) ||
            x.NativeName.Contains(Q, StringComparison.OrdinalIgnoreCase));

    //count records that returns after the search
    TotalRecords = query.Count();

    CulturesList = query

        //make sure to order items before paging
        .OrderBy(x => x.EnglishName)

        //skip items before current page
        .Skip((P - 1) * S)

        //take only 10 (page size) items
        .Take(S)
        
        //call ToList() at the end to execute the query and return the result set
        .ToList();
}

我们定义了一个名为"Q"string变量,该变量将分配给搜索文本框。此外,我们修改了逻辑,以便TotalRecords值返回搜索后的记录数。

现在我们可以将搜索表单添加到前端:

<form method="get" class="form-inline">
    <input asp-for="Q" class="form-control" />
    <button type="submit" class="btn btn-primary">Search</button>
</form>

确保form方法是"get"因为我们在后端定位目标OnGet()方法,这将使我们能够共享任何编号页面的URL

运行应用程序并测试搜索:

搜索效果很好,但是如果单击另一个页码,则会丢失搜索关键字!为了使所有查询字符串参数都包含在带编号的页面URL中,我们需要添加query-string-value到标签帮助器中:

<paging page-no="Model.P"

        page-size="Model.S"

        total-records="Model.TotalRecords"

        query-string-value="@(Request.QueryString.Value)">
</paging>

现在,搜索和分页可以很好地协同工作。

自定义分页UI控件

可以通过添加更多控件(如页面总数标签,总记录标签和页面大小控件)来自定义我们的页面标记助手,并修改如下的页面标记助手代码以获取更多详细信息:

<paging page-no="Model.P"

        page-size="Model.S"

        total-records="Model.TotalRecords"

        query-string-value="@(Request.QueryString.Value)"

        show-prev-next="true"

        show-total-pages="true"

        show-total-records="true"

        show-page-size-nav="true"

        show-first-numbered-page="true"

        show-last-numbered-page="true">
</paging>

现在我们有了更多功能的分页控件:

改善性能

到现在为止,我们将返回CultureInfo项列表,但是在表中仅显示了几个字段!因此,我们可以通过返回仅包含显示字段的对象列表来改善内存/带宽的使用。

创建一个名为CultureItem的新类,并修改搜索逻辑以返回CultureItem的列表,而不是CultureInfo

//object that contains only displayed fields
public class CultureItem
{
    public int LCID { get; set; }
    public string EnglishName { get; set; }
    public string NativeName { get; set; }
    public CultureTypes CultureTypes { get; set; }
}

//return list of CultureItem
public IList<CultureItem> CulturesList { get; set; }

public void OnGet()
{
    var query = Cultures
        //search in EnglishName and NativeName
        .Where(x =>
            x.EnglishName.Contains(Q, StringComparison.OrdinalIgnoreCase) ||
            x.NativeName.Contains(Q, StringComparison.OrdinalIgnoreCase))
            
            //map the selected fields to our new object
            .Select(x => new CultureItem
            {
                LCID = x.LCID,
                EnglishName = x.EnglishName,
                NativeName = x.NativeName,
                CultureTypes = x.CultureTypes
            });

    //count records that returns after the search
    TotalRecords = query.Count();

    CulturesList = query

        //make sure to order items before paging
        .OrderBy(x => x.EnglishName)

        //skip items before current page
        .Skip((P - 1) * S)

        //take only 10 (page size) items
        .Take(S)
        
        //call ToList() at the end to execute the query and return the result set</span>
        .ToList();
}

改善搜寻逻辑

我们将关键字用作搜索中的一个文本字符串,我们可以通过拆分搜索关键字并删除空白和重复项来改善查询效果:

var _keyWords = Q.Split(new[] { ' ', ',', ':' }, 
StringSplitOptions.RemoveEmptyEntries).Distinct();

当使用MSSqlDb之类的数据库并在可为空的字段中进行搜索时,如果搜索到的字段为null,则可能会出现异常,为了避免在null字段中进行搜索,我们可以在搜索逻辑中添加null检查条件。

var query = Cultures
    //search in EnglishName and NativeName
    .Where(x => _keyWords.Any(kw =>
            (x.EnglishName!=null && x.EnglishName.Contains
            (kw, StringComparison.OrdinalIgnoreCase)) ||
            (x.NativeName != null && x.NativeName.Contains
            (kw, StringComparison.OrdinalIgnoreCase))))

通过在搜索数据库时使用AsNoTracking(),甚至可以进一步提高性能,因此框架不会继续跟踪所选实体,这将有助于释放一些内存。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值