I am trying to make Refit work with Polly in an ASP.NET Core 6 application. I have a working version, but I feel that there is too much code involved for each new method / consumed API endpoint.
I want to keep things simple for now by defining a retrial policy and using it for multiple endpoints. My code is as follows:
The retrial policy
private static IServiceCollection ConfigureResilience(this IServiceCollection services)
{
var retryPolicy = Policy<IApiResponse>
.Handle<ApiException>()
.OrResult(x => x.StatusCode is >= HttpStatusCode.InternalServerError or HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), RetryPolicyMaxCount));
var register = new PolicyRegistry()
{
{ "DefaultRetrialPolicy", retryPolicy }
};
services.AddPolicyRegistry(register);
return services;
}
The interface to be used with Refit to generate the HTTP calls for the external application:
[Headers("Authorization: Bearer")]
public interface IBarIntegration
{
[Get("/api/ext/Foo/GetFooBriefInfo")]
Task<ApiResponse<GetFooBriefInfoForFooDto>> GetFooBriefInfo(GetFooBriefInfoForFooInputDto inputData);
}
Factory class to configure authentication for Refit. Authentication relies OnBehalfOf token generated based on the currently logged-in user access token.
internal sealed class BarApiClientHelper : IBarApiClientHelper
{
private readonly IOptionsSnapshot<BarApiSettings> _BarSettings;
private readonly IAccessTokenHelperService _accessTokenHelperService;
public BarApiClientHelper(IOptionsSnapshot<BarApiSettings> BarSettings, IAccessTokenHelperService accessTokenHelperService)
{
_BarSettings = BarSettings;
_accessTokenHelperService = accessTokenHelperService;
}
public async Task<TApiClient> CreateApiClient<TApiClient>(CancellationToken token)
{
string baseUrl = _BarSettings.Value.BaseUrl;
string accessToken = await _accessTokenHelperService.GetAccessToken(token);
var refitClient = RestService.For<TApiClient>(baseUrl, new RefitSettings
{
AuthorizationHeaderValueGetter = () => Task.FromResult(accessToken)
});
return refitClient;
}
}
Example (infrastructure) method that will be called by the business layer.
public async Task<GetFooBriefInfoForFooDto> GetFooBriefInfo(string upn, CancellationToken token)
{
var apiClient = await _clientHelper.CreateApiClient<IBarIntegration>(token);
var retrialPolicy = _registry.Get<AsyncRetryPolicy<IApiResponse>>(DefaultRetrialPolicy);
var func = async () => (IApiResponse) await apiClient.GetFooBriefInfo(new GetFooBriefInfoForFooInputDto { FooContactUpn = upn });
var FooInfo = (ApiResponse<GetFooBriefInfoForFooDto>) await retrialPolicy.ExecuteAsync(func);
await FooInfo.EnsureSuccessStatusCodeAsync();
return FooInfo.Content!;
}
This approach seems to work fine, but I am unhappy with the amount of code required in each business-specific method (GetFooBriefInfo function). Is there any way to simplify this, I feel that I am kind of violating DRY by having each method get the retrial policy, executing and ensuring the success code.