- Azure
- Azure Cosmos DB
- Azure Functions
Lors du développement de votre API, vous devez souvent retourner une liste d’éléments. Dans la plupart des cas, vous ne pouvez pas retourner tous ces éléments à la fois. C’est là qu’intervient le mécanisme de pagination !
Dans ce tutoriel, je vais vous montrer les parties importantes de ce mécanisme à l’aide du nouveau nuget Azure Cosmos DB dans une Azure Functions. Veuillez noter que ce package NuGet est en preview au moment de la rédaction de cet article.
Cette nouvelle version implémente les meilleures pratiques et les patterns courants pour utiliser Cosmos DB. L’exemple de code source complet est bien entendu disponible dans le répeertoire Github associé.
Imaginons ce scénario : vous devez retourner une liste de légumes depuis votre API. Vous avez un très grand nombre de légumes dans votre base de données, vous devez donc paginer les résultats.
Supposons que vous ayez 1000 éléments dans votre liste et que vous vouliez simplement envoyer 25 éléments par page. Votre API doit également envoyer quelque chose pour aider le client à savoir sur quelle page reprendre le reste des éléments. C’est là qu’intervient le continuation token.
Ce token est renvoyé par Azure Cosmos DB pour vous permettre d’obtenir les éléments suivants. Lorsque le client demande les éléments suivants, il doit envoyer ce continuation token à l’API. C’est comme un marque page dans votre API.
Voici un petit schéma pour vous aider à comprendre le principe :
Lorsqu’il n’y a plus d’éléments à récupérer, Azure Cosmos DB renvoie une valeur null
pour le token de continuation.
Le package nuget Azure.Cosmos
renvoie une propriété string
appelée ContinuationToken
qui est un json à plat. Voici le format :
{
"Version":"1.1",
"QueryPlan":"{\"partitionedQueryExecutionInfoVersion\":2,\"queryInfo\":{\"distinctType\":\"None\",\"top\":null,\"offset\":null,\"limit\":null,\"orderBy\":[],\"orderByExpressions\":[],\"groupByExpressions\":[],\"groupByAliases\":[],\"aggregates\":[],\"groupByAliasToAggregateType\":{},\"rewrittenQuery\":\"\",\"hasSelectValue\":false},\"queryRanges\":[{\"min\":\"\",\"max\":\"FF\",\"isMinInclusive\":true,\"isMaxInclusive\":false}]}",
"SourceContinuationToken":"[{\"token\":\"-RID:~OuEWAPVCbA0UAAERTTAAAAA==#RT:1#TRC:20#ISV:2#IEO:65567#QCF:4\",\"range\":{\"min\":\"\",\"max\":\"FF\"}}]"
}
La valeur la plus importante ici est contenue dans la propriété SourceContinuationToken
. Il s’agit du continuation token lui-même, le client devra le renvoyer à l’API pour obtenir la prochaine donnée.
Pour extraire cette valeur, on peut créer un objet basique pour désérialiser ce json :
public class ContinuationToken
{
[JsonProperty("Version")]
public string Version { get; set; }
[JsonProperty("QueryPlan")]
public string QueryPlan { get; set; }
[JsonProperty("SourceContinuationToken")]
public string SourceContinuationToken { get; set; }
}
Il est maintenant temps de regarder le code de pagination lui-même :
public async Task<PagedListResponse<Vegetable>> GetPaginatedVegetablesAsync(int pageSize, string continuationToken)
{
try
{
// Get the container where the elements are stored
var container = GetContainer();
// Create your query
var query = new QueryDefinition("SELECT * FROM v");
var queryResultSetIterator = container.GetItemQueryIterator<Vegetable>(query, requestOptions: new QueryRequestOptions()
{
MaxItemCount = pageSize,
}, continuationToken: continuationToken).AsPages();
var result = await queryResultSetIterator.FirstOrDefaultAsync();
// Deserialize the ContinuationToken propety to access the SourceContinuationToken
var sourceContinuationToken = result.ContinuationToken != null ?
JsonConvert.DeserializeObject<ContinuationToken>(result.ContinuationToken).SourceContinuationToken : null;
// Send the data with the continuation token
return new PagedListResponse<Vegetable>()
{
ContinuationToken = sourceContinuationToken,
Data = result.Values.ToList(),
};
}
catch (CosmosException ex)
{
_logger.LogError($"Entities was not retrieved successfully - error details: {ex.Message}");
if (ex.Status != (int)HttpStatusCode.NotFound)
{
throw;
}
return null;
}
}
Comme vous pouvez le voir ci-dessus, nous commençons par obtenir le conteneur où nous voulons récupérer les données. Ensuite, nous définissons notre query
, il s’agit d’un select
basique mais vous pouvez injecter des paramètres avec la méthode WithParameter()
.
Les QueryRequestOptions
définissent le nombre maximum d’éléments par page. Bien sûr, si vous en demandez 10 et que vous n’en avez que 3, vous en recevrez 3.
La méthode FirstOrDefaultAsync
provient du package nuget System.Linq.Async qui est vraiment utile pour manipuler les objets IAsyncEnumerable
. Cette méthode est accessible dans le namespace using System.Linq;
.
Ensuite, nous désérialisons la propriété ContinuationToken
afin de pouvoir accéder au SourceContinuationToken
qui sera utilisé pour le prochain appel.
Enfin, nous retournons un objet PagedListResponse
qui contient la liste des données et le continuation token.
Avec ce mécanisme prêt, comment l’utiliser en pratique ?
Lorsque vous appelez le end point la première fois, vous le faites directement comme ceci : https://YOUR_HOST/api/demo-az-cosmos-pagination?page_size=25
sans spécifier la propriété continuation
car vous ne l’avez pas au début.
Ensuite, pour les prochains appels, l’url sera renseignée avec la propriété continuation
comme ci-dessous :
https://YOUR_HOST/api/demo-az-cosmos-pagination?page_size=25&continuation=%5B%7B%22token%22%3A%22-RID%3A~OuEWA...
La valeur du continuation token est convertie en sa représentation échappée pour la transmettre à l’url. Pour aider le client, votre API peut l’envoyer directement comme ceci :
public string UrlEncodedContinuationToken
{
get => Uri.EscapeDataString(this.ContinuationToken ?? string.Empty);
}
Voici le genre de résultats que vous aurez :
{
"continuationToken": "[{\"token\":\"-RID:~OuEWAPVCbA0PAAAAAAAAAA==#RT:3#TRC:15#ISV:2#IEO:65567#QCF:4\",\"range\":{\"min\":\"\",\"max\":\"FF\"}}]",
"data": [
{
"id": "ef99e80e-b028-4d0c-ac2f-f277800f6886",
"name": "squash"
},
{
"id": "cf8fc35d-adae-44ae-9a47-010d790de966",
"name": "spinach"
},
{
"id": "c6986662-78ab-422f-8887-8530621d9da9",
"name": "bean"
},
{
"id": "3bc0c55d-9f47-4deb-a31b-c0b877605e20",
"name": "lamb's lettuce"
},
{
"id": "8ecd6b1b-1d61-409f-beb5-91e9f2a7fa21",
"name": "melon"
}
],
"urlEncodedContinuationToken": "%5B%7B%22token%22%3A%22-RID%3A~OuEWAPVCbA0PAAAAAAAAAA%3D%3D%23RT%3A3%23TRC%3A15%23ISV%3A2%23IEO%3A65567%23QCF%3A4%22%2C%22range%22%3A%7B%22min%22%3A%22%22%2C%22max%22%3A%22FF%22%7D%7D%5D"
}
Comme vous l’avez vu, il est très facile d’ajouter une pagination avec le nouveau nuget Azure.Cosmos. Vous trouverez un exemple complet utilisant une Azure Functions dans ce répertoire Github. Toute la configuration se trouve dans le fichier README.md de ce projet.
Happy coding!
Vous avez aimé ce tutoriel ? Laissez une étoile sur le répertoire Github associé !
Sources: