- Changed IsThrottleException to internal static in ExecuteQueryRetryHelper - Changed BuildPagedViewXml to internal static in SharePointPaginationHelper - Created ExecuteQueryRetryHelperTests: 5 tests (throttle true x3, non-throttle false, nested false) - Created SharePointPaginationHelperTests: 5 tests (null, empty, whitespace, replace, append)
46 lines
1.5 KiB
C#
46 lines
1.5 KiB
C#
using Microsoft.SharePoint.Client;
|
|
using SharepointToolbox.Core.Models;
|
|
|
|
namespace SharepointToolbox.Core.Helpers;
|
|
|
|
public static class ExecuteQueryRetryHelper
|
|
{
|
|
private const int MaxRetries = 5;
|
|
|
|
/// <summary>
|
|
/// Executes a SharePoint query with automatic retry on throttle (429/503).
|
|
/// Surfaces retry events via progress for user visibility ("Throttled — retrying in 30s…").
|
|
/// </summary>
|
|
public static async Task ExecuteQueryRetryAsync(
|
|
ClientContext ctx,
|
|
IProgress<OperationProgress>? progress = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
int attempt = 0;
|
|
while (true)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
try
|
|
{
|
|
await ctx.ExecuteQueryAsync();
|
|
return;
|
|
}
|
|
catch (Exception ex) when (IsThrottleException(ex) && attempt < MaxRetries)
|
|
{
|
|
attempt++;
|
|
int delaySeconds = (int)Math.Pow(2, attempt) * 5; // 10, 20, 40, 80, 160s
|
|
progress?.Report(OperationProgress.Indeterminate(
|
|
$"Throttled by SharePoint — retrying in {delaySeconds}s (attempt {attempt}/{MaxRetries})…"));
|
|
await Task.Delay(TimeSpan.FromSeconds(delaySeconds), ct);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static bool IsThrottleException(Exception ex)
|
|
{
|
|
var msg = ex.Message;
|
|
return msg.Contains("429") || msg.Contains("503") ||
|
|
msg.Contains("throttl", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|