Microsoft Graph: Working with Paginated result sets

Introduction

The Microsoft Graph API offers a single endpoint, https://graph.microsoft.com, to provide access to rich, people-centric data and insights in the Microsoft cloud, including Microsoft 365. It exposes REST APIs and client libraries to access data on the following Microsoft cloud services:

  • Microsoft 365 core services: Bookings, Calendar, Delve, Excel, Microsoft 365 compliance eDiscovery, Microsoft Search, OneDrive, OneNote, Outlook/Exchange, People (Outlook contacts), Planner, SharePoint, Teams, To Do, Workplace Analytics.
  • Enterprise Mobility and Security services: Advanced Threat Analytics, Advanced Threat Protection, Azure Active Directory, Identity Manager, and Intune.
  • Windows 10 services: activities, devices, notifications, Universal Print.
  • Dynamics 365 Business Central.

While working with Graph API queries, there will be situations where multiple pages of data will be returned due to server-side paging. In this article, we will see how to deal with multiple pages of data so that we can still retrieve the entire data from the source and process it as a single dataset.

Graph Pagination

When the returned result set contains multiple pages of data, Microsoft Graph returns @odata.nextLink property in the response that contains a URL to the next page of results. In this example, we will be querying a SharePoint List with 10K items. By default, the page size for the items returned from a List Item using Graph Query will be 200. But we can use the $top parameter to define the page size and give a max value of 5000. Any number higher than 5000 will still return a max item limit of 5000 due to inherent SharePoint Limitation.

To test this out, let’s head over to Graph Explorer and use the Graph API to fetch the List Items which has the syntax :

https://graph.microsoft.com/v1.0/sites/{site-id}/lists/{list-id}/items

We will replace the site id and list id with respective values and the article mentioned here will give you the insight to fetch these IDs. We will use the final URL with Graph Explorer and issue a Get Request.

https://graph.microsoft.com/v1.0/sites/bf7488ef-59ab-41a0-b3ff-6f6e8ee267f4/lists/25c00cd4-0ebe-4e19-b24e-482ca9aa0fdf/items

Graphical user interface, text, application, email

Description automatically generated

The current page will return 5000 items and as the list has overall 10K items, the next set of 5000 items will be available using the link mentioned in the @odata.nextLink.The URL uses a Skiptoken to skip the already fetched items and fetch the subsequent list of items. In the case of Graph Explorer, we can either copy this URL and paste it in the address bar and Run Query to fetch the next page of 5000 items. Or else if we click the link “Click here to follow the link” as marked with the arrow in the above image, it will automatically paste the URL in the address bar, run the query and fetch the next page of results which will be displayed as below :

Graphical user interface, text, application, email

Description automatically generated

Here we can see that the ID starts from 5001 which indicates that the first batch had 5000 items. Since this page has returned the remaining set of items, there is no @odata.nextLink in the returned result indicating the end of the page. Microsoft Graph will continue to return a reference to the next page of data in the @odata.nextLink property with each response until all pages of the result have been read.

Note: In case you receive an Access Denied Error, it could well be because we have not consented to the permissions. If we navigate to the Modify permissions tab, it will display the permissions to which you have to consent when using a specific API. Currently, I have consented to Sites.ReadWrite.All so that the Graph calls with successfully query the List.

Graphical user interface, text, application, email

Description automatically generated

Using Pagination in C#

We did see how to use Pagination and retrieve the complete result set in Graph Explorer, which we can carry over easily to client-side development by issuing HTTP Requests. Let’s see how we can implement the same in C# based console application.

As we are using the code in a Console Application, it is essential for us to register an Azure App and provide the needed permissions to the resources. In this example, we have already registered an App in Azure and Consented to the Application permission for Sites.FullControl.All.

Note: It’s advisable to give the least permissive permissions required for an operation. To read a List, Sites.Read.All would be good enough in an ideal scenario.

Graphical user interface, text, application, email

Description automatically generated

We have also copied the ClientID, TenantID, and Client Secret of the application which we will use in the C# Code.

We will create an instance of the graph client and query the list by ID and the list items using the code :

var items = await graphClient.Sites[“bf7488ef-59ab-41a0-b3ff-6f6e8ee267f4”]
.Lists[“25c00cd4-0ebe-4e19-b24e-482ca9aa0fdf”].Items
.Request(queryOptions)
.Top(5000)
.GetAsync();

Here we are using queryOptions to fetch the needed fields in the result set. We are also chaining the method Top(5000) to return 5000 items on the page which is the maximum page size we can set for fetching SharePoint List Items. We will get the current page of the result set using items.CurrentPage attribute and store it in a list object.

We will then use a loop to check items.NextPageRequest and run it till it is null which indicates that all the pages have been traversed, upon which the loop will exit.

We will use the items.NextPageRequest.GetAsync().Result to get the next result set and then get the list of items using the items.CurrentPage and append it to the list object and grow the list collection.

This way , we will iterate all the pages and fetch all the items from the SharePoint List.

Sample Code

var appscopes = new[] { “https://graph.microsoft.com/.default” };
var tenantId = “b3629ed1-3361-4ec4-a5066a5c5fa07”;
var clientId = “3a260c3c-a99d-4447-c63e5093b2f3”;
var clientSecret = “hhK7Q~vbf1KHZCh4hUYI3eqBjoJHNcw”;
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, appscopes);
var totalListItems = new List<Microsoft.Graph.ListItem>();
var queryOptions = new List<QueryOption>()
{
new QueryOption(“expand”, “fields(select=FirstName,LastName,EMail)”)
};
try
{
var items = await graphClient.Sites[“bf7488ef-59ab-41a0-b3ff-6f6e8ee267f4”]
.Lists[“25c00cd4-0ebe-4e19-b24e-482ca9aa0fdf”].Items
.Request(queryOptions)
.GetAsync();
totalListItems.AddRange(items.CurrentPage);
Console.WriteLine(“Total Items added to Collection : “+ totalListItems.Count);
while (items.NextPageRequest != null)
{
items = items.NextPageRequest.GetAsync().Result;
totalListItems.AddRange(items.CurrentPage);
Console.WriteLine(“Total Items added to Collection : ” + totalListItems.Count);
Console.WriteLine(“Next URL : ” + items.NextPageRequest.RequestUrl);
Console.WriteLine();
}
Console.WriteLine(totalListItems.Count);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message.ToString());
}

Summary

Thus we saw how to work with pagination in Microsoft Graph to fetch the result set as pages and iterate them to get the complete result from the source.

Related Articles

Author

Author

Priyaranjan KS is a Modern Workplace Architect primarily focused on developing and architecting solutions around Office 365,Power Platform and Azure.He is also a Microsoft Most Valuable Professional(MVP) and a Microsoft Certified Trainer(MCT)

Latest Articles