Lorsque l’on propose une API, c’est avant tout pour exposer des données à ses « clients » ; c’est à dire que l’on va leur permettre de lire, de modifier, de supprimer des informations.
Cependant, lorsque le volume de données devient important et que vous devez exposer une route retournant une collection, il peut être pratique de paginer ces informations : si le client utilise une connexion limitée, cas d’une application mobile, la volumétrie sera un facteur de performance important.
Je vais donc vous proposer une manière simple et rapide pour mettre en place ce découpage des données.
Une fois n’est pas coutume, cet article n’est pas axé le code, mais sur la données, vous n’y trouverez donc pas de C# 😉
1ère approche
Il existe plusieurs approches pour exposer ses données paginées. En voici une qui est souvent utilisée mais qui ne me satisfait pas.
HTTP/1.1 200 OK ... Location: /clients?pageIndex=4&pageSize=20 Content-Type: application/json; charset=UTF-8 { pageIndex : 4, pageCount : 10, pageSize : 20, data : [ {id:1, firstName:"Michel", lastName:"Dupont"}, {id:2, firstName:"Gui", lastName:"Liguili"}, /*...*/ ] }
En effet, pour moi, une API ne doit retourner que des données, donc le contenu de data
. Les informations de pagination viennent « polluer » l’information, cette approche n’est donc pas RESTful.
2nde approche
Pour cette seconde solution, on ne va retourner que les données, c’est à dire que si on reprend l’exemple précédent le résultat sera :
HTTP/1.1 200 OK ... Location: /clients?pageIndex=4&pageSize=20 Content-Type: application/json; charset=UTF-8 { [ {id:1, firstName:"Michel", lastName:"Dupont"}, {id:2, firstName:"Gui", lastName:"Liguili"}, /*...*/ ] }
Vous voyez bien ici que mes données sont propres, et c’est ça qui prime : la donnée avant tout !
Donc, pour informer le client qu’il y a plus de données qu’il en voit, il faut que j’ajoute un complément d’information. Pour cela, je vais enrichir les headers
de ma réponse comme ceci :
HTTP/1.1 200 OK ... Location: /clients?pageIndex=4&pageSize=20 Content-Type: application/json; charset=UTF-8 x-total-count: 432 x-total-pages: 22 x-current-page: 4 { [ {id:1, firstName:"Michel", lastName:"Dupont"}, {id:2, firstName:"Gui", lastName:"Liguili"}, /*...*/ ] }
Par exemple, ma collection comporte 432 éléments découpés en 22 pages et la page courante est la 5ème… oui, on commence la numérotation à 0 😉
Je préfixe mes informations d’un x
afin de préciser qu’elles ne sont pas standards.
Finalement, on peut même pousser plus loin et ajouter des informations sur les enregistrements précédent, suivant, etc. en utilisant l’entête standard (RFC 5988) Link
HTTP/1.1 200 OK ... Location: /clients?pageIndex=4&pageSize=20 Content-Type: application/json; charset=UTF-8 x-total-count: 432 x-total-pages: 22 x-current-page: 4 Link: </clients?pageIndex=3&pageSize=20>; rel="previous", </clients?pageIndex=4&pageSize=20>; rel="next", </clients?pageIndex=0&pageSize=20>; rel="first", </clients?pageIndex=21&pageSize=20>; rel="last" { [ {id:1, firstName:"Michel", lastName:"Dupont"}, {id:2, firstName:"Gui", lastName:"Liguili"}, /*...*/ ] }
Ainsi lorsque le client utilisera vos données, il lui suffira de récupérer les liens vers les resources précédentes, suivantes, etc. pour les interroger. C’est simple, propre et standard.
Idéalement, vu que l’on cherche a être RESTful, le code HTTP de retour ne doit pas être 200
mais 206
qui précise au client que le contenu qu’il a reçu est partiel.
Finalement, on obtient le résultat suivant :
HTTP/1.1 206 PARTIAL CONTENT ... Location: /clients?pageIndex=4&pageSize=20 Content-Type: application/json; charset=UTF-8 x-total-count: 432 x-total-pages: 22 x-current-page: 4 Link: </clients?pageIndex=3&pageSize=20>; rel="previous", </clients?pageIndex=4&pageSize=20>; rel="next", </clients?pageIndex=0&pageSize=20>; rel="first", </clients?pageIndex=21&pageSize=20>; rel="last" { [ {id:1, firstName:"Michel", lastName:"Dupont"}, {id:2, firstName:"Gui", lastName:"Liguili"}, /*...*/ ] }
J’espère que ces informations vous aideront à gérer proprement la pagination des collections de vos APIs.