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

C#中如何封装请求重试逻辑?避免代码冗余

解决C#中请求401/403重试逻辑冗余的方案

方案一:提取通用重试逻辑方法

把重复的异常捕获、Token刷新、重试逻辑抽成私有通用方法,让Get/Put/Delete等业务方法只需要传入实际的请求逻辑即可,是最轻量化的原生实现方式。

先修正原代码里的异步问题(Auth方法需改为异步,因为Token获取是IO操作),最终实现示例:

class Loader 
{
    private string _accessToken { get; set; }
    private string _refreshToken { get; set; }

    // 改为异步方法,适配Token获取的IO操作
    private async Task AuthAsync()
    {
        // ... 实际获取/刷新access token的逻辑
    }

    // 带返回值的通用重试逻辑封装
    private async Task<T> ExecuteWithAuthRetryAsync<T>(Func<Task<T>> requestFunc)
    {
        try
        {
            return await requestFunc();
        }
        catch (HttpRequestException exception) 
            when (exception.StatusCode == HttpStatusCode.Unauthorized) // 若需处理403则改为HttpStatusCode.Forbidden
        {
            await AuthAsync();
            return await requestFunc();
        }
    }

    // 无返回值的重载方法,适配Delete这类无返回的请求
    private async Task ExecuteWithAuthRetryAsync(Func<Task> requestFunc)
    {
        try
        {
            await requestFunc();
        }
        catch (HttpRequestException exception) 
            when (exception.StatusCode == HttpStatusCode.Unauthorized)
        {
            await AuthAsync();
            await requestFunc();
        }
    }

    // Get方法只需关注自身请求逻辑
    public Task<T> GetAsync(string id) 
    {
        return ExecuteWithAuthRetryAsync(() => 
        {
            // ... 实际发送Get请求的逻辑,返回Task<T>
            return Task.FromResult(default(T)); // 示例占位
        });
    }

    // Put方法同理
    public Task<T> PutAsync(string id, T data) 
    {
        return ExecuteWithAuthRetryAsync(() => 
        {
            // ... 实际发送Put请求的逻辑,返回Task<T>
            return Task.FromResult(default(T)); // 示例占位
        });
    }

    // Delete方法调用无返回值的重载
    public Task DeleteAsync(string id) 
    {
        return ExecuteWithAuthRetryAsync(async () => 
        {
            // ... 实际发送Delete请求的逻辑
        });
    }
}

方案二:装饰器模式

如果后续需要扩展更多横切逻辑(比如日志、超时处理),可以用装饰器模式将重试逻辑与核心请求逻辑分离。

  1. 先定义请求操作的接口:
public interface IDataLoader
{
    Task<T> GetAsync<T>(string id);
    Task<T> PutAsync<T>(string id, T data);
    Task DeleteAsync(string id);
}
  1. 实现核心业务Loader(仅包含请求逻辑,不带重试):
public class DataLoader : IDataLoader
{
    private string _accessToken { get; set; }
    private string _refreshToken { get; set; }

    public async Task AuthAsync()
    {
        // ... 刷新Token逻辑
    }

    public async Task<T> GetAsync<T>(string id)
    {
        // ... 实际Get请求逻辑
        return default;
    }

    public async Task<T> PutAsync<T>(string id, T data)
    {
        // ... 实际Put请求逻辑
        return default;
    }

    public async Task DeleteAsync(string id)
    {
        // ... 实际Delete请求逻辑
    }
}
  1. 编写重试装饰器类,封装重试逻辑:
public class AuthRetryLoaderDecorator : IDataLoader
{
    private readonly IDataLoader _innerLoader;

    public AuthRetryLoaderDecorator(IDataLoader innerLoader)
    {
        _innerLoader = innerLoader;
    }

    public async Task<T> GetAsync<T>(string id)
    {
        try
        {
            return await _innerLoader.GetAsync<T>(id);
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            await ((DataLoader)_innerLoader).AuthAsync();
            return await _innerLoader.GetAsync<T>(id);
        }
    }

    public async Task<T> PutAsync<T>(string id, T data)
    {
        try
        {
            return await _innerLoader.PutAsync<T>(id, data);
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            await ((DataLoader)_innerLoader).AuthAsync();
            return await _innerLoader.PutAsync<T>(id, data);
        }
    }

    public async Task DeleteAsync(string id)
    {
        try
        {
            await _innerLoader.DeleteAsync(id);
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            await ((DataLoader)_innerLoader).AuthAsync();
            await _innerLoader.DeleteAsync(id);
        }
    }
}

使用时通过装饰器包装原始Loader:

var loader = new AuthRetryLoaderDecorator(new DataLoader());
await loader.GetAsync<MyModel>("123");

方案三:AOP框架(可选)

如果项目已使用AOP框架(如PostSharp、Castle DynamicProxy),可以用自定义特性标记需要重试的方法,实现最简洁的代码编写。

以PostSharp为例,自定义重试特性:

public class RetryOnUnauthorizedAttribute : MethodInterceptionAspect
{
    public override async Task OnInvokeAsync(MethodInterceptionArgs args)
    {
        try
        {
            await args.ProceedAsync();
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            var loader = (Loader)args.Instance;
            await loader.AuthAsync();
            await args.ProceedAsync();
        }
    }
}

然后在业务方法上标记特性即可:

[RetryOnUnauthorized]
public async Task<T> GetAsync<T>(string id)
{
    // ... 实际Get请求逻辑
}

内容的提问来源于stack exchange,提问作者Alexander Petrushyn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 18:27:53