You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 21:55:24