Polly 策略句柄 Refit ApiException with DI AddPolicyHandler

问题描述 投票:0回答:2

我在我的 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>'

我做错了什么吗?

c# uwp polly retry-logic refit
2个回答
1
投票

如果你想将 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>
的政策。
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();

备选方案比较

使用它 使用它
选项 优点 缺点
Policy<string>
您可以使用高级强类型响应和
ApiException
你不能通过
AddPolicyHandler
Policy<IApiResponse>
您可以使用低级强类型响应和
ApiException
你不能通过
AddPolicyHandler
Policy<HttpMessageResponse>
你可以使用
AddPolicyHandler
改装的大部分优势都用不上

0
投票

正如彼得在他的回答中指出的那样,我设法使用 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>
© www.soinside.com 2019 - 2024. All rights reserved.