ASP.NET Core 2.2 WebAPI で Pagination対応 する
1. はじめに
ASP.NET Core WebAPIにおいて、pagination(ページング)でJSONデータを返す実装のメモです。
(ググれば既出だけど、意外に情報少なめだったので、自分メモの意味も込めて)
開発環境
- Visual Studio 2019 Preview
- ASP.NET Core 2.2
※ VS2017でもCore2.xでも同じだと思う。
2. こんな実装をする
pagination対応するときは、主に以下の2つがあります。
- body要素のjsonにページ番号や全体件数を含ませる
- body要素には本来のデータのみを含ませ、HTTP Response Headerにページ情報や全体件数を含ませる
ここでは後者の実装を行います。
社員情報をpaginationで5件ずつ取得することができるAPIを想定します。
GET /api/employee?page=10
↑↑↑とすると
↓↓↓が帰るみたいな
[ { "id": 45, "firstName": "しゃいん", "lastName": "45号" }, { "id": 46, "firstName": "しゃいん", "lastName": "46号" }, { "id": 47, "firstName": "しゃいん", "lastName": "47号" }, { "id": 48, "firstName": "しゃいん", "lastName": "48号" }, { "id": 49, "firstName": "しゃいん", "lastName": "49号" } ]
3. 実装コード
注意)以下のコードは、paginationに関する部分のみに集中したコードです。DBもEFもクラスモデリングも無視でなるべく簡易な実装にしているので、クラス構成・メソッドの抽出等々は無視したコードです。
3.1. モデルクラス Employee。
// Models/Employee.cs namespace PaginationExam.Models { public class Employee { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } }
3.2. Daoクラス
Employeeを取得するDaoクラス。(ダミーリストデータをメモリ内に作ってそれをLINQでpaginationして返すだけの実装)
// Dao/EmployeeDao.cs using System.Linq; using System.Collections.Generic; using PaginationExam.Models; using System; namespace PaginationExam.Dao { public class EmployeeDao { private List<Employee> _dummyEmployeeData = new List<Employee>(); public EmployeeDao() { for (int i = 0; i < 100; i++) { this._dummyEmployeeData.Add( new Employee() { ID = i, FirstName = "しゃいん", LastName = i.ToString() + "号" }); } } public (int totalItemCount, int lastPage, List<Employee>) GetEmployees(int page, int countPerPage) { int totalItemCount = 0; int lastPage = 0; List<Employee> employees = null; employees = this._dummyEmployeeData.Skip(countPerPage * (page - 1)).Take(countPerPage).ToList(); totalItemCount = this._dummyEmployeeData.Count(); lastPage = (int)Math.Floor((decimal)totalItemCount / countPerPage); if (totalItemCount % countPerPage > 0) lastPage++; return (totalItemCount, lastPage, employees); } } }
3.3. コントローラクラス
Web API のコントローラクラス。
「GET api/Employee?page=xx」を受け付けます。
1ページ当たり5件は固定です。
pagination用のHTTP Response Headerは以下の3つを追加しています。
Links
以下の4つのリンク先を示します。
first - 1ページ目のURI
prev - 前のページのURI
next - 次のページのURI
last - 最後のページのURI
X-TotalItemCount
「X-」なのでカスタムヘッダです。
Employeeの全件数を「X-TotalItemCount」に設定しています。
X-CurrentPage
現在のページを設定しています。
※「X-」カスタムヘッダは非推奨とされましたが、個人的にはその経緯から使ってOKじゃね?との認識。
// Controllers/EmployeeController.cs using Microsoft.AspNetCore.Mvc; using PaginationExam.Dao; using PaginationExam.Models; using System.Collections.Generic; namespace PaginationExam.Controllers { [Route("api/[controller]")] [ApiController] public class EmployeeController : Controller { // 1ページに5件 const int CountPerPage = 5; [HttpGet] public IEnumerable<Employee> GetList([FromQuery] int page) { if (page < 1) // 1ページスタートなので、1未満は1に簡単に補正 page = 1; // データ取得 // 全件数、該当ページのEmployeeリスト var dao = new EmployeeDao(); (int totalItemCount, int lastPage, List<Employee> employees) = dao.GetEmployees(page, EmployeeController.CountPerPage); // Response Header追加 this.Response.Headers.Add("Links", this.CreateLinksHeader("Employee", page, lastPage)); this.Response.Headers.Add("X-TotalItemCount", totalItemCount.ToString()); this.Response.Headers.Add("X-CurrentPage", page.ToString()); // Body Jsonは本来のデータのみ return employees; } /// <summary> /// Pagination用のLinkヘッダ値を作成 /// </summary> /// <param name="controller"></param> /// <param name="currentPage"></param> /// <param name="lastPage"></param> /// <returns></returns> protected string CreateLinksHeader(string controller, int currentPage, int lastPage) { List<string> links = new List<string>(); links.Add(string.Format("<{0}>; rel=\"first\"", this.Url.Link("", new { Controller = controller, page = 1 }))); if (currentPage > 1) { links.Add(string.Format("<{0}>; rel=\"prev\"", this.Url.Link("", new { Controller = controller, page = currentPage - 1 }))); } if (currentPage < lastPage) { links.Add(string.Format("<{0}>; rel=\"next\"", this.Url.Link("", new { Controller = controller, page = currentPage + 1 }))); } links.Add(string.Format("<{0}>; rel=\"last\"", this.Url.Link("", new { Controller = controller, page = lastPage }))); return string.Join(", ", links); } } }
4. 実行
実行します。
Postmanを起動して「GET https://localhost:44332/api/employee?page=10」をSendした結果は以下です。
BodyのJSONには本来のデータ(Employeeリスト)のみ。
Response Headerには Link / X-TotalItemCount / X-CurrentPage が返却されている。
5. まとめ
ASP.NET Core 2.2 でのpaginationについての一実装例でしたm( )m
ソースは以下に置いてあります。
あと、paginationに関する説明や検討事項は以下なんかが結構参考になると思います。