I set up the following service collection with a Refit client and a Polly policy for dependency injection in my UWP app:
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)
);
But I realized that Refit throws an ApiException when the http response status is not OK, so I looked up on StackOverflow (https://stackoverflow.com/a/74066105/9008381) and added the following policy to the services chain:
.AddPolicyHandler(Policy<IApiResponse>
.Handle<ApiException>()
.RetryAsync(2)
The problem is that when compiling the app on Visual Studio 2022 it gives the following error:
error CS1503: Argument 2: cannot convert from 'Polly.Retry.AsyncRetryPolicy<Refit.IApiResponse>' to 'Polly.IAsyncPolicy<System.Net.Http.HttpResponseMessage>'
Am I doing something wrong?
I managed to solve the problem using a DispatchProxy class as Peter pointed out in his answer that Policy<IApiResponse>
can't be used via AddPolicyHandler
.
I created a DispatchProxy
for my Refit client interface which executes my Refit API call through a custom Polly policy in the following way:
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;
}
}
In this case the policy retries the API call 3 times, if after the third time another exception is thrown the Fallback policy returns the call result.
I used it in the following way:
I get the Refit client using DI:
private readonly IClassevivaAPI apiClient;
App app = (App)App.Current;
apiClient = app.Container.GetService<IClassevivaAPI>();
Then I pass the client instance to the proxy class:
private readonly IClassevivaAPI apiWrapper;
apiWrapper = PoliciesDispatchProxy<IClassevivaAPI>.CreateProxy(apiClient);
Then I can make any API call from the apiWrapper without having to rewrite any existing code.
Be aware that when compiling with .NET Native (Release mode) using reflections it will cause the app to crash, so for this case you will need to add the following assembly tags for Microsoft extensions to the Default.rd.xml
file:
<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>