feat(01-02): add SharePointPaginationHelper, ExecuteQueryRetryHelper, LogPanelSink
- SharePointPaginationHelper: async iterator with ListItemCollectionPosition loop (bypasses 5k limit); RowLimit=2000; [EnumeratorCancellation] for correct WithCancellation support - ExecuteQueryRetryHelper: exponential backoff on 429/503/throttle; surfaces retry events via IProgress<OperationProgress>; max 5 retries - LogPanelSink: custom Serilog ILogEventSink writing color-coded entries to RichTextBox via Dispatcher.InvokeAsync for thread safety
This commit is contained in:
45
SharepointToolbox/Core/Helpers/ExecuteQueryRetryHelper.cs
Normal file
45
SharepointToolbox/Core/Helpers/ExecuteQueryRetryHelper.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsThrottleException(Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
return msg.Contains("429") || msg.Contains("503") ||
|
||||
msg.Contains("throttl", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user