Generic API Caller in .Net 6 — Resiliency Part I

Parag Patil
3 min readFeb 6, 2023

In Part I, we’ll see the implementation of the Circuit Breaker Pattern. By definition, the circuit breaker pattern handle faults that might take a variable amount of time to recover from, when connecting to a remote service or resource. This can improve the stability and resiliency of an application.

Click here for more details

To implement the Circuit Breaker pattern in the previous code. Let’s separate the authentication and circuit breaker implementation using dependency injection in APICaller class.

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
using Polly;

namespace MyApp
{
public interface IAuthenticator
{
Task Authenticate(string authUrl, object authRequestBody);
string AuthToken { get; }
}

public class Authenticator : IAuthenticator
{
private readonly HttpClient _client;
private string _authToken;

public Authenticator(HttpClient client)
{
_client = client;
}

public async Task Authenticate(string authUrl, object authRequestBody)
{
var authHeaders = new Dictionary<string, string>
{
{ "Content-Type", "application/json" }
};

var authResponse = await MakeApiCall<AuthResponse>(authUrl, HttpMethod.Post, authRequestBody, authHeaders);
_authToken = authResponse.Token;
}

public string AuthToken => _authToken;

private async Task<T> MakeApiCall<T>(string url, HttpMethod method, object requestBody = null,
Dictionary<string, string> headers = null)
{
using var request = new HttpRequestMessage(method, url);

if (headers != null)
{
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}

if (requestBody != null)
{
request.Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json");
}

var response = await _client.SendAsync(request);

if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(responseContent);
}

throw new Exception($"Failed to make API call. Response status code: {response.StatusCode}");
}
}

public interface ICircuitBreaker
{
Task<HttpResponseMessage> ExecuteAsync(Func<Task<HttpResponseMessage>> action);
}

public class CircuitBreaker : ICircuitBreaker
{
private readonly Policy _policy;

public CircuitBreaker(int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak)
{
_policy = Policy
.Handle<Exception>()
.CircuitBreaker(exceptionsAllowedBeforeBreaking, durationOfBreak);
}

public async Task<HttpResponseMessage> ExecuteAsync(Func<Task<HttpResponseMessage>> action)
{
return await _policy.ExecuteAsync(action);
}
}
}

ApiCaller class can be modified as follows to inject the implementation of the Circuit Breaker pattern & Authentication

public class ApiCaller
{
private readonly HttpClient _client;
private readonly IAuthService _authService;
private readonly ICircuitBreaker _circuitBreaker;

public ApiCaller(IAuthService authService, ICircuitBreaker circuitBreaker)
{
_client = new HttpClient();
_authService = authService;
_circuitBreaker = circuitBreaker;
}

public async Task<T> CallApi<T>(string url, HttpMethod method, object requestBody = null,
Dictionary<string, string> headers = null)
{
_authService.Authenticate("https://auth.com", new {Username = "username", Password = "password"});

var authToken = _authService.GetAuthToken();

headers = headers ?? new Dictionary<string, string>();
headers.Add("Authorization", $"Bearer {authToken}");

Func<Task<HttpResponseMessage>> action = async () =>
{
using var request = new HttpRequestMessage(method, url);

if (headers != null)
{
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}

if (requestBody != null)
{
request.Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json");
}

return await _client.SendAsync(request);
};

var response = await _circuitBreaker.ExecuteAsync(action);

if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(responseContent);
}

throw new Exception($"Failed to make API call. Response status code: {response.StatusCode}");
}
}

In this implementation, the ApiCaller class has dependencies on IAuthService and ICircuitBreaker which are injected through the constructor. The class uses the IAuthService to get the authentication token and the ICircuitBreaker to execute the API call with a circuit breaker pattern.

Now let’s write the unit test code to test the Circuit Breaker pattern implementation

[TestClass]
public class CircuitBreakerTests
{
private ApiCaller _apiCaller;
private Mock<IAuthService> _authServiceMock;
private Mock<ICircuitBreaker> _circuitBreakerMock;

[TestInitialize]
public void TestInitialize()
{
_authServiceMock = new Mock<IAuthService>();
_authServiceMock.Setup(x => x.Authenticate(It.IsAny<string>(), It.IsAny<object>()))
.Returns(Task.CompletedTask);
_authServiceMock.Setup(x => x.GetAuthToken()).Returns("fake_token");

_circuitBreakerMock = new Mock<ICircuitBreaker>();
_circuitBreakerMock.Setup(x => x.ExecuteAsync(It.IsAny<Func<Task<HttpResponseMessage>>>()))
.Returns(Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{}", Encoding.UTF8, "application/json")
}));

_apiCaller = new ApiCaller(_authServiceMock.Object, _circuitBreakerMock.Object);
}

[TestMethod]
public async Task Test_CallApi_Successful()
{
var result = await _apiCaller.CallApi<object>("https://api.com", HttpMethod.Get);
Assert.IsNotNull(result);
}

[TestMethod]
[ExpectedException(typeof(Exception))]
public async Task Test_CallApi_Failed()
{
_circuitBreakerMock.Setup(x => x.ExecuteAsync(It.IsAny<Func<Task<HttpResponseMessage>>>()))
.Throws(new Exception("Failed to make API call."));

await _apiCaller.CallApi<object>("https://api.com", HttpMethod.Get);
}
}

In this test case, _authServiceMock and _circuitBreakerMock are instances of the IAuthService and ICircuitBreaker mocks respectively. The TestInitialize method sets up the mocks and the ApiCaller instance.

The Test_CallApi_Successful method tests the successful scenario when the API call is successful, and the Test_CallApi_Failed method tests the scenario when the API call fails.

The next part of the article is to make the generic API caller resilient using the Polly resilience framework. We will cover Bulkhead Pattern & demonstrate how the elements of an application are isolated into pools so that if one fails, the others will continue to function.

--

--