Calling Multiple APIs Using HttpClient And Polly

Introduction

Today, we will look at how we can call multiple Rest APIs using HttpClient in a parallel model. We will also implement retry and timeout features using a NuGet package called Polly. So, let us begin.

Creating an ASP.NET Razor application

We will start by creating an ASP.NET Razor pages application using Visual Studio 2022 Community Edition. The steps are below,

Calling multiple APIs using HttpClient and Polly

Calling multiple APIs using HttpClient and Polly

Calling multiple APIs using HttpClient and Polly

Once created, add the “Microsoft.Extensions.Http.Polly” NuGet package.

Calling multiple APIs using HttpClient and Polly

Now, add a new class called “WeatherService.cs” in the root of the project with the below code:

namespace WebAppRazorPages {
    public interface IWeatherService {
        Task < string > GetWeatherInfo();
        Task < string > GetWeatherInfoSecondCity();
    }
    public class WeatherService: IWeatherService {
        private readonly HttpClient _httpClient;
        public WeatherService(HttpClient httpClient) {
            _httpClient = httpClient;
        }
        public async Task < string > GetWeatherInfo() {
            var request = new HttpRequestMessage {
                Method = HttpMethod.Get,
                    RequestUri = new Uri("https://weatherbit-v1-mashape.p.rapidapi.com/forecast/3hourly?lat=35.5&lon=-78.5"),
                    Headers = {
                        {
                            "X-RapidAPI-Key",
                            "<Add key here>"
                        },
                        {
                            "X-RapidAPI-Host",
                            "weatherbit-v1-mashape.p.rapidapi.com"
                        },
                    },
            };
            using(var response = await _httpClient.SendAsync(request)) {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
        public async Task < string > GetWeatherInfoSecondCity() {
            var request = new HttpRequestMessage {
                Method = HttpMethod.Get,
                    RequestUri = new Uri("https://weatherbit-v1-mashape.p.rapidapi.com/forecast/3hourly?lat=33.6&lon=-84.4"),
                    Headers = {
                        {
                            "X-RapidAPI-Key",
                            "<Add key here>"
                        },
                        {
                            "X-RapidAPI-Host",
                            "weatherbit-v1-mashape.p.rapidapi.com"
                        },
                    },
            };
            using(var response = await _httpClient.SendAsync(request)) {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
    }
}

Here you see that we have created two functions to get the weather from an API using the latitude and longitude values. This is making a call to an API available at RapidAPI. You can sign-up to use these available API services for testing your API clients. In this way, we did not need to create an API service. We can just concentrate on the client.

Here, we also see that we are dependency injecting the HttpClient. This allows us to pool the connections and pick one when needed and not have to create a new one each time. For this and to setup the Polly retry and timeout features, we need to update our "Program.cs" file as below,

using Polly.Extensions.Http;
using Polly;
using WebAppRazorPages;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddHttpClient < IWeatherService, WeatherService > ().AddPolicyHandler(GetRetryPolicy()).AddPolicyHandler(Policy.TimeoutAsync < HttpResponseMessage > (15)).SetHandlerLifetime(TimeSpan.FromMinutes(5));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment()) {
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
static IAsyncPolicy < HttpResponseMessage > GetRetryPolicy() {
    return HttpPolicyExtensions.HandleTransientHttpError().OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound).WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

In the above code, we are injecting the HttpClient into our service class instance along with a policy for retries using Polly. This is done via the "GetRetryPolicy" function. Here, we specify that we want 3 retries each after 2 seconds. We also specify the timeout to a maximum run time of 15 seconds. Finally, we set the handler runtime to 5 minutes which means that after 5 minutes the connections pool is refreshed.

Finally, we modify the “Privacy.cshtml.cs” file as below:

using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebAppRazorPages.Pages {
    public class PrivacyModel: PageModel {
        private readonly ILogger < PrivacyModel > _logger;
        private readonly IWeatherService _weatherService;
        public PrivacyModel(ILogger < PrivacyModel > logger, IWeatherService weatherService) {
            _logger = logger;
            _weatherService = weatherService;
        }
        public async Task OnGet() {
            Task[] tasks = new Task[2];
            tasks[0] = _weatherService.GetWeatherInfo();
            tasks[1] = _weatherService.GetWeatherInfoSecondCity();
            await Task.WhenAll(tasks);
            List < string > results = new List < string > ();
            foreach(var task in tasks) {
                var result = ((Task < string > ) task).Result;
                results.Add(result);
            }
            var data = string.Join(" ", results.ToArray());
            ViewData["MyData"] = data;
        }
    }
}
@page
@model PrivacyModel
@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
<p>
    @ViewData["MyData"]
</p>

Here, we simply DI the “WeatherService” class and add the two functions as tasks and run them in parallel using Task.WhenAll. Finally, we extract the results and display them on the Privacy screen.

Calling multiple APIs using HttpClient and Polly

Summary

In this article, we looked at how we can call multiple Rest API endpoints in a parrel manner. We then also saw how we can pool the connections and add retry and timeout features using Polly. As seen here the process is simple and it gives us a high performance and resilient service client.


Similar Articles