如何自定义Microsoft Graph SDK C# 5.2x的429限流错误处理逻辑
如何重写Microsoft Graph SDK for C#的429状态码默认重试行为
问题描述
我在使用Microsoft Graph SDK for C#读取Microsoft Graph数据,同时借助Microsoft Developer Proxy模拟HTTP 429(TooManyRequests)状态响应做测试。SDK默认遇到429会最多重试3次,第三次重试后仍收到429的话,就会抛出System.InvalidOperationException,提示消息为"Too many retries performed.",该行为可从代理输出中观察到。
相关代码
var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider(_token)); var graphServiceClient = new GraphServiceClient(authenticationProvider); var myTeams = await graphServiceClient .Me .JoinedTeams .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = new string[] { "Id" });
代理输出
request GET https://graph.microsoft.com/v1.0/me/joinedTeams?$select=Id chaos ╭ 429 TooManyRequests ╰ GET https://graph.microsoft.com/v1.0/me/joinedTeams?$select=Id ...
需求:重写SDK的默认行为,自行处理429状态码,希望能调整请求速率或实现其他自定义处理逻辑。
解决方案
方法一:自定义重试处理器(实现IRetryHandler)
Microsoft Graph SDK允许通过实现IRetryHandler接口来覆盖默认重试逻辑,你可以自定义重试次数、等待策略等规则。
步骤1:实现自定义重试处理器
using Microsoft.Graph; using Microsoft.Graph.Models.ODataErrors; using System.Net; public class CustomRetryHandler : IRetryHandler { // 自定义最大重试次数 private readonly int _maxRetryCount = 5; public async Task<bool> ShouldRetryAsync(RetryHandlerOptions options) { // 仅针对429状态码处理重试逻辑 if (options.Response?.StatusCode == HttpStatusCode.TooManyRequests) { // 未达到最大重试次数则继续重试 return options.RetryCount < _maxRetryCount; } // 其他状态码不触发重试(或自行扩展逻辑) return false; } public Task WaitAsync(RetryHandlerOptions options) { // 优先遵循响应头中的Retry-After指定的等待时间 if (options.Response?.Headers.TryGetValues("Retry-After", out var retryAfterValues) == true) { if (int.TryParse(retryAfterValues.FirstOrDefault(), out var retryAfterSeconds)) { return Task.Delay(TimeSpan.FromSeconds(retryAfterSeconds)); } } // 无Retry-After头时用指数退避策略等待 var delaySeconds = Math.Pow(2, options.RetryCount); return Task.Delay(TimeSpan.FromSeconds(delaySeconds)); } // 接口要求的属性,可根据需求调整 public TimeSpan MaxRetryDelay { get; set; } = TimeSpan.FromSeconds(30); public IReadOnlyList<HttpStatusCode> RetryStatusCodes { get; set; } = new List<HttpStatusCode> { HttpStatusCode.TooManyRequests }; }
步骤2:将自定义处理器绑定到GraphServiceClient
var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider(_token)); // 初始化自定义重试处理器 var customRetryHandler = new CustomRetryHandler(); // 创建客户端选项并替换默认重试处理器 var clientOptions = new GraphClientOptions(); clientOptions.RetryHandler = customRetryHandler; // 使用自定义配置创建GraphServiceClient var graphServiceClient = new GraphServiceClient(authenticationProvider, clientOptions); // 发起请求,此时会应用自定义重试逻辑 var myTeams = await graphServiceClient .Me .JoinedTeams .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = new string[] { "Id" });
方法二:自定义HTTP中间件完全接管429处理
如果需要更灵活的请求控制(比如完全跳过SDK默认重试,自己实现重试流程),可以通过自定义DelegatingHandler中间件来处理。
步骤1:实现自定义中间件
using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Net; public class Custom429Handler : DelegatingHandler { private readonly int _maxRetries = 5; protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage response = null; for (int retryCount = 0; retryCount <= _maxRetries; retryCount++) { response = await base.SendAsync(request, cancellationToken); // 非429状态直接返回 if (!response.StatusCode.Equals(HttpStatusCode.TooManyRequests)) { return response; } // 根据Retry-After头等待,或用指数退避 if (response.Headers.TryGetValues("Retry-After", out var retryAfterValues) && int.TryParse(retryAfterValues.FirstOrDefault(), out var retryAfterSeconds)) { await Task.Delay(TimeSpan.FromSeconds(retryAfterSeconds), cancellationToken); } else { await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)), cancellationToken); } } // 超过重试次数后返回最终响应(或抛出自定义异常) return response; } }
步骤2:将中间件添加到GraphServiceClient的HttpClient
var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider(_token)); // 创建带有自定义中间件的HttpClient var httpClient = new HttpClient(new Custom429Handler()); // 绑定到GraphServiceClient var graphServiceClient = new GraphServiceClient(httpClient, authenticationProvider); // 发起请求并处理最终可能的429 try { var myTeams = await graphServiceClient .Me .JoinedTeams .GetAsync(requestConfiguration => requestConfiguration.QueryParameters.Select = new string[] { "Id" }); } catch (ODataError odataError) { if (odataError.Error?.Code == "TooManyRequests") { // 自定义最终处理逻辑:比如记录日志、触发告警等 } }
注意事项
- 无论哪种方式,都建议优先遵循响应头中的
Retry-After值,避免因频繁重试导致更严格的限制。 - 自定义重试逻辑时,需根据业务场景合理设置最大重试次数,防止无限重试浪费资源。
内容的提问来源于stack exchange,提问作者Maikeru K
相关产品推荐
相关产品推荐

