5

I am trying to create a solution with polly where I request an other api.
I have a list of URLs to multiple instances of the same service.
I want that when the first request failes, an other should automaticly start with the next url from my list.

Here is an example where i try this behaviour with two static addresses
The Problem with this solution is that the url does not change until i start the next request. I want that the urls changes on every retry

 public static void ConfigureUserServiceClient(this IServiceCollection services) { _userServiceUri = new Uri("https://localhost:5001"); services.AddHttpClient("someService", client => { client.BaseAddress = _userServiceUri; client.DefaultRequestHeaders.Add("Accept", "application/json"); }).AddPolicyHandler(retryPolicy()); } private static IAsyncPolicy<HttpResponseMessage> retryPolicy() { return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.RequestTimeout) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt), onRetry: (result, span, ctx) => { _userServiceUri = new Uri("https://localhost:5002"); }); } 

1 Answer 1

6

You should consider to use the Fallback policy instead.

Like this:

private static HttpClient client = new HttpClient(); static async Task Main(string[] args) { var addressIterator = GetUrls().GetEnumerator(); var retryLikePolicy = Policy<string> .Handle<HttpRequestException>() .FallbackAsync(fallbackAction: async (ct) => { if (addressIterator.MoveNext()) return await GetData(addressIterator.Current); return null; }); addressIterator.MoveNext(); var data = await retryLikePolicy.ExecuteAsync( async () => await GetData(addressIterator.Current)); Console.WriteLine("End"); } static async Task<string> GetData(string uri) { Console.WriteLine(uri); var response = await client.GetAsync(uri); return await response.Content.ReadAsStringAsync(); } static IEnumerable<string> GetUrls() { yield return "http://localhost:5500/index.html"; yield return "http://localhost:5600/index.html"; yield return "http://localhost:5700/index.html"; } 

Please note that this code is just for demonstration.


UPDATE #1: Multiple fallback

If you have more than one fallback urls then you can alter the above code like this:

private static HttpClient client = new HttpClient(); static async Task Main(string[] args) { var retryInCaseOfHRE = Policy .Handle<HttpRequestException>() .WaitAndRetryForeverAsync(_ => TimeSpan.FromSeconds(1)); var response = await retryInCaseOfHRE.ExecuteAsync( async () => await GetNewAddressAndPerformRequest()); if (response == null) { Console.WriteLine("All requests failed"); Environment.Exit(1); } Console.WriteLine("End"); } static IEnumerable<string> GetAddresses() { yield return "http://localhost:5500/index.html"; yield return "http://localhost:5600/index.html"; yield return "http://localhost:5700/index.html"; yield return "http://localhost:5800/index.html"; } static readonly IEnumerator<string> AddressIterator = GetAddresses().GetEnumerator(); static async Task<string> GetNewAddressAndPerformRequest() => AddressIterator.MoveNext() ? await GetData(AddressIterator.Current) : null; static async Task<string> GetData(string uri) { Console.WriteLine(uri); var response = await client.GetAsync(uri); return await response.Content.ReadAsStringAsync(); } 
  • The trick: the retry policy wraps a method which is responsible to retrieve the next url and then call the GetData
    • In other word we need to move the iteration process into the to be wrapped method (GetNewAddressAndPerformRequest)
  • I've replaced the Fallback policy to Retry since we need to perform (potentially) more than 1 fallback actions
  • I've used null to indicate we have run out of fallback urls but it might be a better solution to use a custom exception for that
Sign up to request clarification or add additional context in comments.

4 Comments

Could this be done with IHttpClientFactory with 2 URLs instead of many, like primary and failover?
@VergilC. Yes, absolutely the same approach could be used in that case as well. .FallbackAsync(fallbackAction: async (ct) => await GetData(secondaryAddress)); and retryLikePolicy.ExecuteAsync(async () => await GetData(primaryAddress))
@Peter Csala what will be the best way to retry for the entire list (at fallback change the url) until one url succeeds. Seems this one only try one fallback.
@Nlr I've extended my post to show you how to do that. Please check it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.