本文内容来自书籍: Marinko Spasojevic - Ultimate ASP.NET Core Web API - From Zero To Six-Figure Backend Developer (2nd edition)
分页的意思是,只是返回部分结果的API,返回所有结果不仅仅是非常无效的,而且对应用和硬件有着毁灭性的影响。还有客户端的资源一般都是有限的,必须限制展示的数据
我们不希望改变base repository
的逻辑或者实现任何业务逻辑在控制器当中
我们希望URI是这样的
https://localhost:5001/api/companies/companyId/employees?pageNumber=2&pageSize=2
而且,即使客户端的请求是:
https://localhost:5001/api/companies/companyId/employees
我们也要约束我们的API,而不是返回所有的数据
所以现在我们开始修改controller
我们需要一个包装着查询参数的DTO
// 首先创建一个分页参数的基类 public abstract class RequestParameters { private const int MaxPageSize = 50; public int PageNumber { get; set; } = 1; private int _pageSize = 10; public int PageSize { get => _pageSize; set => _pageSize = value > MaxPageSize ? MaxPageSize : value; } } public class EmployeeParameters : RequestParameters { }
[HttpGet] public async Task<IActionResult> GetEmployeesForCompany(Guid companyId, [FromQuery] EmployeeParameters employeeParameters) { var employees = await _service.EmployeeService.GetEmployeesAsync(companyId, trackChanges: false); return Ok(employees); }
[FromQuery]
,查询参数可以对分页的参数进一步封装
public class MetaData { public int CurrentPage { get; set; } public int TotalPages { get; set; } public int PageSize { get; set; } public int TotalCount { get; set; } public bool HasPrevious => CurrentPage > 1; public bool HasNext => CurrentPage < TotalPages; } public class PagedList<T> : List<T> { public MetaData MetaData { get; set; } public PagedList(IEnumerable<T> items, int count, int pageNumber, int pageSize) { MetaData = new MetaData { TotalCount = count, PageSize = pageSize, CurrentPage = pageNumber, TotalPages = (int)Math.Ceiling(count / (double)pageSize) }; AddRange(items); } public static PagedList<T> ToPagedList(IEnumerable<T> source, int pageNumber, int pageSize) { var enumerable = source.ToList(); var count = enumerable.Count; var items = enumerable .Skip((pageNumber - 1) * pageSize) .Take(pageSize).ToList(); return new PagedList<T>(items, count, pageNumber, pageSize); } }
然后在响应的时候,需要将这个元数据也一并返回
[HttpGet] public async Task<IActionResult> GetEmployeesForCompany(Guid companyId, [FromQuery] EmployeeParameters employeeParameters) { var pagedResult = await _service.EmployeeService.GetEmployeesAsync(companyId, employeeParameters, trackChanges: false); Response.Headers.Add("X-Pagination", JsonSerializer.Serialize(pagedResult.metaData)); return Ok(pagedResult.employees); }
但是上面的方案,存在一个性能问题,当数据库的数据达到千万级别的时候,就会很慢,因为在分页之前,首先是获取的ID相关的数据,也就是某一个ID的全部数据,然后再创建分页实体,过滤数据;当面对很多数据的时候,我们需要在数据库查询的时候,就已经做出过滤的动作,使得筛选放到数据库,而不是APP中,这样的性能会好很多
还有就是,我们需要让客户端读取我们的新的响应头X-Pagination
,我们需要更改CORS
的配置
public static void ConfigureCors(this IServiceCollection services) => services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .WithExposedHeaders("X-Pagination")); });