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请求的逻辑 }); } }
方案二:装饰器模式
如果后续需要扩展更多横切逻辑(比如日志、超时处理),可以用装饰器模式将重试逻辑与核心请求逻辑分离。
- 先定义请求操作的接口:
public interface IDataLoader { Task<T> GetAsync<T>(string id); Task<T> PutAsync<T>(string id, T data); Task DeleteAsync(string id); }
- 实现核心业务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请求逻辑 } }
- 编写重试装饰器类,封装重试逻辑:
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
相关产品推荐
相关产品推荐

