Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pagination in ms.Graph 'groups' in SDK v5

SUMMARY:


the basis of what I'm trying to do is paginate from groups in the MS-Graph API (https://learn.microsoft.com/en-us/graph/paging) although I can achieve it fine using HTTP requests - the SDK seems significantly more complicated for some reason, so the question explicitly revolves around the usage of the latest version of C# SDK (v5 - latest available nuget package for .netCore 6) (https://github.com/microsoftgraph/msgraph-sdk-dotnet)

GroupCollectionResponse PageofGroups = await graphClient.Groups.GetAsync();

Attempts and Background of Issue:


Ideally I could grab the members from each group on each page using a function like the following - thanks to poster @Tiny Wang for posting the proper iteration method - fixing code below to show existing progress with issue

requestConfiguration.QueryParameters.Select = new string[] { "displayName" };
requestConfiguration.QueryParameters.Expand = new string[] { "members($select=id,displayName)" };

var pageIterator = Microsoft.Graph.PageIterator<Group, UserCollectionResponse>
        .CreatePageIterator(graphClient, groups, (m) =>
        {
            count++;
            if (count < MaxRecordWanted)
            {
                return false;// stop iterating
            }
            else
            {
                Console.WriteLine(m.DisplayName);
                Console.WriteLine(m.Id);
                foreach(User member in m.Members)
                {
                    Console.WriteLine(member.DisplayName);
                    Console.WriteLine(member.Id);
                }
                return true;// true means keep paging
            }
        });
pageIterator.IterateAsync();

Alternatively using the regular upgrade method: Line "return true" throws a conversion error - Cannot implicitly convert type bool to System.Threading.Tasks.Task<bool>

they seemed to remove 'nextLink' from 'queryOptions' when they deprecated it to query parameters

string url = PageofGroups.OdataNextLink;// this URL works by the way - I just cannot seem to find a way to send this link back to graph via SDK

await graphClient.Groups
   .GetAsync((requestConfiguration) =>
   {
     //requestConfiguration.QueryParameters.xxxx = url;
   }

although the API reference here works fine - the SDK is not as well documented https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0

any help figuring out the upgrade pattern from v4-v5 would be appreciated the following doc:https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/upgrade-to-v5.md completely ignores groups specifically and uses query parameters which don't seem to work (also the hacky work around of using skip doesn't even work on group's since it cannot be applied to groups only certain cases like users)

in v4 I can use optionParameters to leverage the NextLink Obj after grabbing it from the Json response - I can no longer do this - so what is expected is another method of sending the URL to acquire the next page also being able to grab a specific page from the group iterator without having to loop 'up to' that page - ie pages of 100, not having to check the record of each record in pages 1-7, and begin reading records from 701-800 (i.e. only grabbing page 8) and assigning it directly to a group object

Code to be fixed:


therefore instead of the documentation as follows: https://microsoftgraph.github.io/msgraph-sdk-design/tasks/PageIteratorTask.html#example-usage

Something more along the lines of:

var output = new List<GroupCollectionResponse> { };
var pageIterator2 = PageIterator<Group, GroupCollectionResponse>
    .CreatePageIterator(graphClient, groups, m =>
        {
            count++;
            if (count >= output.Count())
                output.Add(groups);
            return true;
        }
    );

But instead of using a lambda on m => {} either being able to use a lambda expression on "groups" or being able to pass an iteration into CreatePageIterator in order to generate the groups via .AddRange

like image 581
Jeff Avatar asked Feb 02 '26 11:02

Jeff


1 Answers

I had the same issue with Users, after a long time digging into this problem and reading the source code found this solution that works for me

// Main 
    var users = await graphClient.Users.GetAsync(o =>
    {
        o.QueryParameters.Top = 2;
        o.QueryParameters.Select = new string[] { "id", "displayName" };
    });
    
    var nextPageLink = users.OdataNextLink;
    
    Console.WriteLine("Page 1");
    users.Value.ForEach(x => Console.WriteLine(x.DisplayName));
    
    var nextPageRequestInfo = graphServiceClient.Users.ToGetRequestInformation(o => o.QueryParameters.Select = UserModelMapping.MappedProperties);
                
    nextPageRequestInfo.UseOdataNextLink(odataPageLink);
    
    var nextPageResult = await graphClient.RequestAdapter.SendAsync(nextPageRequestInfo, (parseNode) => new UserCollectionResponse());
    
    Console.WriteLine("Page 2");
    nextPageResult.Value.ForEach(x => Console.WriteLine(x.DisplayName));

// Helpers
    public static class GraphRequestExtensions
    {
        private static string DeltaTokenKey = "%24deltatoken";
        private static string SkipTokenKey = "%24skiptoken";
        
        public static void UseOdataNextLink(this RequestInformation request, string oDataNextLink)
        {
            if (string.IsNullOrEmpty(oDataNextLink))
                throw new ArgumentNullException(nameof(oDataNextLink), @"OdataNextLink cannot be null");
    
            // Add the skip token parameter to the url template, for some reason it's not added by default.
            if (!request.UrlTemplate.Contains(SkipTokenKey))
                request.UrlTemplate = request.UrlTemplate?.Insert(request.UrlTemplate.Length - 1, $",{SkipTokenKey}");
    
            request.QueryParameters[SkipTokenKey] = DeltaTokenHelper.ParseSkipToken(oDataNextLink);
           
        }
        
        public static void UseDeltaLink(this RequestInformation request, string deltaToken)
        {
            if (string.IsNullOrEmpty(deltaToken))
                throw new ArgumentNullException(nameof(deltaToken), @"DeltaToken cannot be null");
    
            // Add the delta token parameter to the url template, for some reason it's not added by default.
            if(!request.UrlTemplate.Contains(DeltaTokenKey)) 
                request.UrlTemplate = request.UrlTemplate?.Insert(request.UrlTemplate.Length - 1, $",{DeltaTokenKey}");
            
            request.QueryParameters[DeltaTokenKey] = DeltaTokenHelper.ParseDeltaToken(deltaToken);
        }
    }

    internal static class DeltaTokenHelper
    {
        internal static string ParseDeltaToken(string url)
        {
            var uri = new Uri(url);
            var query = HttpUtility.ParseQueryString(uri.Query);
            return query["$deltatoken"];
        }
        
        internal static string ParseSkipToken(string url)
        {
            var uri = new Uri(url);
            var query = HttpUtility.ParseQueryString(uri.Query);
            return query["$skiptoken"];
        }
    }

This answers the question about "A way to send this link back to graph via SDK"

like image 140
Bohdan Maryniak Avatar answered Feb 05 '26 01:02

Bohdan Maryniak