Polly策略句柄用DI AddPolicyHandler重新定义ApiException



我用一个Refit客户端和一个Polly策略在我的UWP应用程序中设置了以下服务集合:

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)
);           

但是我意识到,当http响应状态不OK时,Refit抛出一个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>

  1. 让我们假设你的IClassevivaAPI看起来像这样:
public interface IClassevivaAPI
{
[Get("/500")]
Task<string> Get();
}
  1. 和改装客户端注册如下:
builder.Services
.AddRefitClient(typeof(IClassevivaAPI))
.ConfigureHttpClient((_, client) => client.BaseAddress = new Uri("https://httpstat.us/"));
  1. 使用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);
  1. 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>的策略。AB都没有实现它,这就是为什么你不能使用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();

备选方案比较

<表类>选择优点缺点Policy<string>您可以使用高级强类型响应和ApiException您不能通过AddPolicyHandler使用它Policy<IApiResponse>您可以使用低级别强类型响应和ApiException您不能通过AddPolicyHandler使用它Policy<HttpMessageResponse>你可以使用AddPolicyHandler你不能使用refit的大部分优点

我设法用DispatchProxy类解决了这个问题,Peter在他的回答中指出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次,如果在第三次之后抛出另一个异常,则回退策略返回调用结果。

我是这样使用它的:

我得到Refit客户端使用DI:

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(发布模式)编译时,使用反射会导致应用程序崩溃,因此在这种情况下,您需要在Default.rd.xml文件中添加以下Microsoft扩展的汇编标签:

<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>

相关内容

  • 没有找到相关文章

最新更新