using System.Runtime.CompilerServices; using Microsoft.SharePoint.Client; using SharepointToolbox.Core.Models; namespace SharepointToolbox.Core.Helpers; public static class SharePointPaginationHelper { /// /// Enumerates all items in a SharePoint list, bypassing the 5,000-item threshold. /// Uses CamlQuery with RowLimit=2000 and ListItemCollectionPosition for pagination. /// Never call ExecuteQuery directly on a list — always use this helper. /// public static async IAsyncEnumerable GetAllItemsAsync( ClientContext ctx, List list, CamlQuery? baseQuery = null, [EnumeratorCancellation] CancellationToken ct = default) { var query = baseQuery ?? CamlQuery.CreateAllItemsQuery(); query.ViewXml = BuildPagedViewXml(query.ViewXml, rowLimit: 2000); query.ListItemCollectionPosition = null; do { ct.ThrowIfCancellationRequested(); var items = list.GetItems(query); ctx.Load(items); await ctx.ExecuteQueryAsync(); foreach (var item in items) yield return item; query.ListItemCollectionPosition = items.ListItemCollectionPosition; } while (query.ListItemCollectionPosition != null); } internal static string BuildPagedViewXml(string? existingXml, int rowLimit) { // Inject or replace RowLimit in existing CAML, or create minimal view if (string.IsNullOrWhiteSpace(existingXml)) return $"{rowLimit}"; // Simple replacement approach — adequate for Phase 1 if (existingXml.Contains("", StringComparison.OrdinalIgnoreCase)) { return System.Text.RegularExpressions.Regex.Replace( existingXml, @"]*>\d+", $"{rowLimit}", System.Text.RegularExpressions.RegexOptions.IgnoreCase); } return existingXml.Replace("", $"{rowLimit}", StringComparison.OrdinalIgnoreCase); } }