Search This Blog

2021-03-05

Call Web API from .NET Core using Polly

There is a lot of HttpClient Errors is caused by server overload, temporary network timeouts and generic gliches in the the downstream systems. Those error are temporary and can be dealt with using a retry pattern. In .NET Core, the most common retry library is the Polly library.

Step 1. Download the nuget packages

Microsoft.Extensions.Http.Polly

Polly.Extensions.Http


Step 2.  CONFIGURE SERVICES IN STARTUP.CS

var serviceProvider = services.BuildServiceProvider();
var httpSettings = serviceProvider.GetService<IOptions<HttpSettingsOptions>>()?.Value;            services.AddHttpClient(HttpClientConstants.HttpClientWithBackOff).AddPolicyHandler(GetRetryPolicy(httpSettings));


IOption is used for strongly typed configuration. We need to download the nuget package Microsoft.Extensions.Options for IOption. We need a class structure that matches with the configuration.

So create a class HttpSettingsOptions as below

public class HttpSettingsOptions
    {
        public const string HttpSettings = "HttpSettings";
        public int RetryCount { get; set; }
        public int InitialRetryDelay { get; set; }
    }
Create the similar structure in appSetting.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "HttpSettings": {
    "RetryCount": 3,
    "InitialRetryDelay": 2
  },
  "ServiceURL": {
    "Questionnaire": "https://localhost:44368/api"
  }
}

Create a method GetRetryPolicy in startup.cs and which accept the above httpsetting

 private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(HttpSettingsOptions options)
        {
            return HttpPolicyExtensions
                .HandleTransientHttpError()
                .WaitAndRetryAsync(options.RetryCount, retryAttempt =>
                {
                    return TimeSpan.FromSeconds(Math.Pow(options.InitialRetryDelay, retryAttempt));
                });
        }
    }
Now for the parameter of AddHttpClient methods (i.eHttpClientWithBackOff) create a below class
public class HttpClientConstants
    {
        public const string HttpClientWithBackOff = "HttpClientBackOff";
    }

Step 3: USE THE IHttpClientFactory IN THE CALLING CLASS

The IHttpClientFactory can be injected using constructor injection. The cool part of the Polly implementation is that your HttpClient code does not contain any special retry-code, just the usual Get or Post calls:

public class QuestionnaireQueryService : IQuestionnaireQueryService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ServiceURLOptions _serviceUrlOptions;
        public QuestionnaireQueryService(IOptions<ServiceURLOptions>
            serviceUrlOptions,
            IHttpClientFactory httpClientFactory)
        {
            _serviceUrlOptions = serviceUrlOptions != null
                ? serviceUrlOptions.Value
                : throw new ArgumentNullException($"Options/Configuration not found for {nameof(serviceUrlOptions)}");
            _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
        }
        public async Task<QuestionnaireServiceModel> GetAllQuestionnaire()
        {
            QuestionnaireServiceModel questionnaireServiceModel = null;
            using var httpClient = _httpClientFactory.CreateClient(HttpClientConstants.HttpClientWithBackOff);
            //httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + Token);
            var questionnaireResponse = await httpClient.GetAsync($"{_serviceUrlOptions.Questionnaire}/Questions");
            var qResponse = await questionnaireResponse.Content.ReadAsStringAsync();
            if (questionnaireResponse.IsSuccessStatusCode)
            {
                questionnaireServiceModel = JsonConvert.DeserializeObject<QuestionnaireServiceModel>(qResponse);
            }
            return questionnaireServiceModel;
        }
    }

Step 4. Add ServiceURLOptions in ConfigureServices of Startup.cs
services.Configure<ServiceURLOptions>(Configuration.GetSection(ServiceURLOptions.ServiceURLs));

Add below class and make sure ServiceURLOptions is present in appsetting.json as per Step 2 Above

 public class ServiceURLOptions
    {
        public const string ServiceURLs = "ServiceURL";
        public string Questionnaire { get; set; }        
    }

No comments: