我在我的 UWP 应用程序中使用 Refit 客户端和用于依赖注入的 Polly 策略设置了以下服务集合:
var serviceCollection = new ServiceCollection();
serviceCollection
.AddRefitClient(typeof(IClassevivaAPI))
.ConfigureHttpClient(
(sp, client) =>
{
client.BaseAddress = new Uri(Endpoint.CurrentEndpoint);
}
)
.AddPolicyHandler(
Policy<HttpResponseMessage>
.HandleResult(r => r.StatusCode == System.Net.HttpStatusCode.Unauthorized)
.RetryAsync(1)
);
但我意识到 Refit 在 http 响应状态不正常时会抛出 ApiException,所以我查看了 StackOverflow (https://stackoverflow.com/a/74066105/9008381) 并将以下策略添加到服务链:
.AddPolicyHandler(Policy<IApiResponse>
.Handle<ApiException>()
.RetryAsync(2)
问题是在 Visual Studio 2022 上编译应用程序时出现以下错误:
error CS1503: Argument 2: cannot convert from 'Polly.Retry.AsyncRetryPolicy<Refit.IApiResponse>' to 'Polly.IAsyncPolicy<System.Net.Http.HttpResponseMessage>'
我做错了什么吗?
如果你想将 Polly 与 Refit 生成的客户端一起使用,那么你可以执行以下操作之一:
Policy<string>
IClassevivaAPI
看起来像这样:public interface IClassevivaAPI
{
[Get("/500")]
Task<string> Get();
}
builder.Services
.AddRefitClient(typeof(IClassevivaAPI))
.ConfigureHttpClient((_, client) => client.BaseAddress = new Uri("https://httpstat.us/"));
PolicyRegistry
注册您的重试政策var registry = new PolicyRegistry();
registry.Add("A", Policy<string>
.Handle<ApiException>(ex => { return true; }) //Predicate added to be able to add breakpoint here
.RetryAsync(2, onRetry: (_, __) => { })); //onRetry added to be able to add breakpoint here
builder.Services.AddPolicyRegistry(registry);
ExecuteAsync
装饰你的 api 调用private readonly IClassevivaAPI _api;
private readonly IReadOnlyPolicyRegistry<string> _registry;
public WhateverController(IClassevivaAPI api, IReadOnlyPolicyRegistry<string> registry)
{
(_api, _registry) = (api, registry);
}
[HttpGet]
public async Task Get()
{
var retry = _registry.Get<IAsyncPolicy<string>>("A");
var response = await retry.ExecuteAsync(_api.Get);
}
Policy<IApiResponse>
如果你想在策略定义中使用
IApiResponse
那么
registry.Add("B", Policy<IApiResponse>
.Handle<ApiException>(ex => { return true; }) //Predicate added to be able to add breakpoint here
.RetryAsync(2, onRetry: (_, __) => { })); //onRetry added to be able to add breakpoint here
和
var retry = _registry.Get<IAsyncPolicy<IApiResponse>>("B");
var response = await retry.ExecuteAsync(async () =>
{
var result = await _api.Get();
return new ApiResponse<string>(null, result, null);
});
Policy<HttpMessageResponse>
AddPolicyHandler
预期实施IAsyncPolicy<HttpResponseMessage>
的政策。 A
和 B
都没有实现它,这就是为什么你不能为此使用 AddPolicyHandler
的原因。
但是,如果你像这样改变界面:
public interface IClassevivaAPI
{
[Get("/500")]
Task<HttpResponseMessage> Get();
}
政策也需要更新
builder.Services
.AddRefitClient(typeof(IClassevivaAPI))
.ConfigureHttpClient((_, client) => client.BaseAddress = new Uri("https://httpstat.us/"))
.AddPolicyHandler(Policy<HttpResponseMessage>
.HandleResult(res => res.StatusCode == HttpStatusCode.InternalServerError)
.RetryAsync(2, onRetry: (_, __) => { })); //onRetry added to be able to add breakpoint here
和用法
var result = await _api.Get();
选项 | 优点 | 缺点 |
---|---|---|
|
您可以使用高级强类型响应和
|
你不能通过
|
|
您可以使用低级强类型响应和
|
你不能通过
|
|
你可以使用
|
改装的大部分优势都用不上 |
正如彼得在他的回答中指出的那样,我设法使用 DispatchProxy 类解决了问题
Policy<IApiResponse>
不能通过AddPolicyHandler
使用。
我为我的 Refit 客户端界面创建了一个
DispatchProxy
,它通过自定义 Polly 策略以下列方式执行我的 Refit API 调用:
public class PoliciesDispatchProxy<T> : DispatchProxy
where T : class, IClassevivaAPI
{
private T Target { get; set; }
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
var retryPolicy = Policy
.Handle<AggregateException>()
.RetryAsync(
3,
async (exception, retryCount, context) =>
{
//we check whether the exception thrown is actually a Refit's ApiException
if (exception.InnerException is ApiException apiException)
{
if (apiException.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
//custom reauthentication code
}
}
}
);
var fallback = Policy<object>
.Handle<Exception>()
.FallbackAsync(async ct =>
{
//if after the retries another exception occurs, then we let the call flow go ahead
return targetMethod.Invoke(Target, args);
});
AsyncPolicyWrap<object> combinedpolicy = fallback.WrapAsync(retryPolicy);
return combinedpolicy
.ExecuteAsync(async () =>
{
var result = (targetMethod.Invoke(Target, args));
if (result is Task task)
{
task.Wait(); //we wait for the result of the task, so we retry the call if an exception is thrown
}
return result; //if no exception occur then we return the result of the method call
})
.Result;
}
public static T CreateProxy(T target)
{
var proxy = Create<T, PoliciesDispatchProxy<T>>() as PoliciesDispatchProxy<T>;
proxy.Target = target;
return proxy as T;
}
}
在这种情况下,策略会重试 API 调用 3 次,如果在第三次抛出另一个异常后,回退策略会返回调用结果。
我是这样用的:
我使用 DI 获取 Refit 客户端:
private readonly IClassevivaAPI apiClient;
App app = (App)App.Current;
apiClient = app.Container.GetService<IClassevivaAPI>();
然后我将客户端实例传递给代理类:
private readonly IClassevivaAPI apiWrapper;
apiWrapper = PoliciesDispatchProxy<IClassevivaAPI>.CreateProxy(apiClient);
然后我可以从 apiWrapper 进行任何 API 调用,而无需重写任何现有代码。
请注意,在使用反射编译 .NET Native(发布模式)时会导致应用程序崩溃,因此对于这种情况,您需要将以下 Microsoft 扩展程序集标记添加到
Default.rd.xml
文件中:
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<Assembly Name="*Application*" Dynamic="Required All" />
<Assembly Dynamic="Required All" Name="Microsoft.Extensions.Options"/>
<Assembly Dynamic="Required All" Name="Microsoft.Extensions.Logging"/>
<Assembly Dynamic="Required All" Name="Microsoft.Extensions.Http"/>
</Application>
</Directives>