StarBlog Development Notes (23): Pagination, Filtering, Search, and Sorting for the Article List API
Preface Quickly filling in the gap I left in the previous post. In part 6, I already gave a preliminary introduction. This article adds some extra details. If you've already got this part working, you can skip ahead: Developing a Blog Project with .NetCore — StarBlog (6): Blog Post List Page Development For a standard Web API, pagination, filtering, search, and sorting are very common features. They make it easier for users to view data and can also improve application performance. Common Request Parameters Define a class to serve as the common request parameters The common parameters for list APIs are these: PageSi
目录8 个章节
Preface
Quickly filling in the gap I left in the previous post.
In part 6, I already gave a preliminary introduction. This article adds some extra details. If you've already got this part working, you can skip ahead: Developing a Blog Project with .NetCore — StarBlog (6): Blog Post List Page Development
For a standard Web API, pagination, filtering, search, and sorting are very common features. They make it easier for users to view data and can also improve application performance.
Common Request Parameters
Define a class to serve as the common request parameters.
The common parameters for list APIs are these: PageSize, Page, Search, SortBy
Mapped onto the URL, they look like Blog/?pageSize=10&page=1&search=关键词
public class QueryParameters {
/// <summary>
/// 最大页面条目
/// </summary>
public const int MaxPageSize = 50;
private int _pageSize = 10;
/// <summary>
/// 页面大小
/// </summary>
public int PageSize {
get => _pageSize;
set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value;
}
/// <summary>
/// 当前页码
/// </summary>
public int Page { get; set; } = 1;
/// <summary>
/// 搜索关键词
/// </summary>
public string? Search { get; set; }
/// <summary>
/// 排序字段
/// </summary>
public string? SortBy { get; set; }
}
Post List Request Parameters
Based on the common request parameters QueryParameters, derive the post list request parameter class PostQueryParameters.
public class PostQueryParameters : QueryParameters {
/// <summary>
/// 仅请求已发布文章
/// </summary>
public bool OnlyPublished { get; set; } = false;
/// <summary>
/// 文章状态
/// </summary>
public string? Status { get; set; }
/// <summary>
/// 分类ID
/// </summary>
public int CategoryId { get; set; } = 0;
/// <summary>
/// 排序字段
/// </summary>
public new string? SortBy { get; set; } = "-LastUpdateTime";
}
On top of the common request parameters, add post-related filter fields.
The SortBy field uses the new keyword to hide the base class property, with the default sort set to last update time. A leading minus sign indicates descending order.
Service
Wrap the method for getting a paged list in StarBlog.Web/Services/PostService.cs.
The code has comments and is fairly easy to follow: it filters according to the various parameters in PostQueryParameters.
public IPagedList<Post> GetPagedList(PostQueryParameters param) {
var querySet = _postRepo.Select;
// 是否发布
if (param.OnlyPublished) {
querySet = _postRepo.Select.Where(a => a.IsPublish);
}
// 状态过滤
if (!string.IsNullOrEmpty(param.Status)) {
querySet = querySet.Where(a => a.Status == param.Status);
}
// 分类过滤
if (param.CategoryId != 0) {
querySet = querySet.Where(a => a.CategoryId == param.CategoryId);
}
// 关键词过滤
if (!string.IsNullOrEmpty(param.Search)) {
querySet = querySet.Where(a => a.Title.Contains(param.Search));
}
// 排序
if (!string.IsNullOrEmpty(param.SortBy)) {
// 是否升序
var isAscending = !param.SortBy.StartsWith("-");
var orderByProperty = param.SortBy.Trim('-');
querySet = querySet.OrderByPropertyName(orderByProperty, isAscending);
}
return querySet.Include(a => a.Category).ToList()
.ToPagedList(param.Page, param.PageSize);
}
Search Implementation
In the service code above
you can see that search is just simple “keyword filtering”.
It uses Title.Contains(param.Search), which converts to SQL as
select * from post where title like '%关键词%'
It simply checks whether the title string contains the keyword as a substring.
This is enough for a simple article search. If you want to search article content the way search engines like Google or Baidu do, you need full-text search.
The mainstream options today are ElasticSearch and Solr. We can consider adding this feature to the project later~
P.S. On full-text search, I previously wrote an article: Principles of Full-Text Search Engines and a Brief Introduction to Lucene
I also open-sourced a toy-level full-text search engine: https://github.com/Deali-Axy/CloverSearch
Pagination Implementation
This project uses X.PagedList to implement pagination.
This component is convenient when used with MVC. For a pure Web API, using the database’s built-in pagination is a better choice and performs better.
This pagination component adds an extension method
ToPagedListonIEnumerable<T>, so when using it you have to read all the data first and then paginate. Performance is not as good as doing pagination in the database and then reading the results. Many ORMs support this, and FreeSQL is no exception.Usage example:
var list = fsql.Select<Topic>() .Where(a => a.Id > 10) .Count(out var total) //总记录数量 .Page(1, 20) .Tolist();For details, see the FreeSQL official documentation: https://freesql.net/guide/paging.html
Once you use the X.PagedList component, calling the ToPagedList method on any IEnumerable<T> object yields an IPagedList<T> object.
This object holds the list data for the current page, as well as pagination information.
To make this information convenient for the frontend to use, I also wrote an extension method.
StarBlog.Web/Extensions/PagedListExt.cs
public static class PagedListExt {
public static PaginationMetadata ToPaginationMetadata(this IPagedList page) {
return new PaginationMetadata {
PageCount = page.PageCount,
TotalItemCount = page.TotalItemCount,
PageNumber = page.PageNumber,
PageSize = page.PageSize,
HasNextPage = page.HasNextPage,
HasPreviousPage = page.HasPreviousPage,
IsFirstPage = page.IsFirstPage,
IsLastPage = page.IsLastPage,
FirstItemOnPage = page.FirstItemOnPage,
LastItemOnPage = page.LastItemOnPage
};
}
public static string ToPaginationMetadataJson(this IPagedList page) {
return JsonSerializer.Serialize(ToPaginationMetadata(page));
}
}
That way you can call ToPaginationMetadata on the IPagedList object you get after pagination to obtain the pagination metadata.
This PaginationMetadata is also a ViewModel defined in this project, StarBlog.Web/ViewModels/PaginationMetadata.cs
The code is as follows
public class PaginationMetadata {
public int PageCount { get; set; }
public int TotalItemCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasPreviousPage { get; set; }
public bool HasNextPage { get; set; }
public bool IsFirstPage { get; set; }
public bool IsLastPage { get; set; }
public int FirstItemOnPage { get; set; }
public int LastItemOnPage { get; set; }
}
Done.
Controller and Final Result
The code is as follows
[AllowAnonymous]
[HttpGet]
public ApiResponsePaged<Post> GetList([FromQuery] PostQueryParameters param) {
var pagedList = _postService.GetPagedList(param);
return new ApiResponsePaged<Post> {
Message = "Get posts list",
Data = pagedList.ToList(),
Pagination = pagedList.ToPaginationMetadata()
};
}
After obtaining the paged data, return a value of type ApiResponsePaged<T>.
This is also an API response type I encapsulated; the next article will cover it in detail.
The Data property is the list data, and the Pagination property is the pagination information.
A request to this API returns something like the following
{
"pagination": {
"pageCount": 40,
"totalItemCount": 394,
"pageNumber": 1,
"pageSize": 10,
"hasPreviousPage": false,
"hasNextPage": true,
"isFirstPage": true,
"isLastPage": false,
"firstItemOnPage": 1,
"lastItemOnPage": 10
},
"statusCode": 200,
"successful": true,
"message": "Get posts list",
"data": [{...},{...},{...},{...},{...}]
}
Series Articles
- Developing a Blog Project with .NET Core: StarBlog - (1) Why Write Your Own Blog?
- Developing a Blog Project with .NET Core: StarBlog - (2) Environment Setup and Creating the Project
- Developing a Blog Project with .NET Core: StarBlog - (3) Model Design
- Developing a Blog Project with .NET Core: StarBlog - (4) Batch Import of Markdown Blog Posts
- Developing a Blog Project with .NET Core: StarBlog - (5) Starting to Build the Web Project
- Developing a Blog Project with .NET Core: StarBlog - (6) Page Development: Blog Article List
- Developing a Blog Project with .NET Core: StarBlog - (7) Page Development: Article Detail Page
- Developing a Blog Project with .NET Core: StarBlog - (8) Displaying Category Hierarchy
- Developing a Blog Project with .NET Core: StarBlog - (9) Batch Image Import
- Developing a Blog Project with .NET Core: StarBlog - (10) Image Waterfall Layout
- Developing a Blog Project with .NET Core: StarBlog - (11) Implementing Visit Statistics
- Developing a Blog Project with .NET Core: StarBlog - (12) Dynamic Compilation of Razor Pages
- Developing a Blog Project with .NET Core: StarBlog - (13) Adding Friend Links
- Developing a Blog Project with .NET Core: StarBlog - (14) Implementing Theme Switching
- Developing a Blog Project with .NET Core: StarBlog - (15) Generating Random-Size Images
- Developing a Blog Project with .NET Core: StarBlog - (16) Some New Features (Monitoring/Statistics/Configuration/Initialization)
- Developing a Blog Project with .NET Core: StarBlog - (17) Automatically Downloading External Images in Articles
- Developing a Blog Project with .NET Core: StarBlog - (18) Packaging and Uploading Local Typora Articles
- Developing a Blog Project with .NET Core: StarBlog - (19) Exploring Markdown Rendering Solutions
- Developing a Blog Project with .NET Core: StarBlog - (20) Image Display Optimization
- Developing a Blog Project with .NET Core: StarBlog - (21) Starting RESTFul API Development
- Developing a Blog Project with .NET Core: StarBlog - (22) Developing Blog Article Related APIs
- Developing a Blog Project with .NET Core: StarBlog - (23) Article List API Pagination, Filtering, Search, and Sorting
_EOF_
StarBlog Development Notes (23): Pagination, Filtering, Search, and Sorting for the Article List API
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
StarBlog博客开发笔记(15):生成随机尺寸图片
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetCore开发博客项目 StarBlog - (3) 模型设计 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入 基于.NetCore开发博客项目 StarBl
2022-07-18 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(21):开始开发RESTFul接口
前言 最近电脑坏了,开源项目的进度也受到一些影响 这篇酝酿很久了,作为本系列第二部分(API接口开发)的第一篇,得想一个好的开头,想着想着就鸽了好久,索性不扯那么多了,直接开写吧~ 关于RESTFul 网上很多相关的文章都要把RESTFul历史来龙去脉给复制一遍,所以我这就不重复了,现在主要的HTTP接口风格就俩:RPC和RESTFul。 举个例子就可以看出这俩的区别 RPC风格 分别是增删改查的
2023-01-02 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(8):分类层级结构展示
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetCore开发博客项目 StarBlog - (3) 模型设计 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入 基于.NetCore开发博客项目 StarBl
2022-05-24 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记:部署
Nginx 反向代理 后端配置中间件 app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); Nginx配置 server { listen
2022-12-05 · StarBlog博客开发笔记 · 约 1 分钟
讨论
共 0 条加载评论中…