Generic API Caller in .Net 6

Parag Patil
4 min readFeb 2, 2023

This article covers the implementation of a generic API caller to quickly onboard the APIs in any application. It accepts the following API constructs to make a call to the API

  1. URL: The URL of the target API endpoint to be onboarded in the application
  2. Method: The HTTP method to be used to call an API i.e. GET. POST etc.
  3. RequestBody: The request body is to be sent with the API call.
  4. RequestHeaders: Any Request headers to be included in the API Call
  5. authURL: The URL of the authentication endpoint
  6. AuthRequestBody: The request body to be sent with the Authentication API call
  7. AuthToken: The token returned in response to the Authentication API call, which will be passed as a request header to make the target API call.

Following code snippet is an implementation to call any secured API

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
namespace MyApp
{
public class ApiCaller
{
private readonly HttpClient _client;
private string _authToken;
public ApiCaller()
{
_client = new HttpClient();
}
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 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 (_authToken != null)
{
request.Headers.Add("Authorization", $"Bearer {_authToken}");
}
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 class AuthResponse
{
public string Token { get; set; }
}
}

In this example, the MakeApiCall method takes in the following parameters:

  • url: the URL of the API endpoint
  • method: the HTTP method to use (e.g. GET, POST, etc.)
  • requestBody: the request body to send with the API call, if any
  • headers: any headers to include in the API call, if any

It returns a Task that when awaited, returns the response from the API as the specified type T. If the API call fails, an exception is thrown with a message indicating the status code returned by the API.

the Authenticate method is added to authenticate and retrieve the authentication token. It takes in the following parameters:

  • authUrl: the URL of the authentication endpoint
  • authRequestBody: the request body to send with the authentication call

The response from the authentication API is expected to contain a token, which is stored in the _authToken field.

The MakeApiCall method now checks if the _authToken is not null, and if it is, adds an "Authorization" header to the request with the format Bearer <token>.

The AuthResponse class is added to represent the response from the authentication API, which is expected to contain a single property Token.

System.Text.Json serializer is used to serialize and deserialize the request and response bodies. The JsonSerializer.Serialize method is used to serialize the request body, and the JsonSerializer.Deserialize method is used to de-serialize the response content into an instance of the desired type T.

Test the code by executing the following unit test code

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

namespace MyApp.Tests
{
public class ApiCallerTests
{
[Fact]
public async Task MakeApiCall_WithAuthToken_ShouldIncludeTokenInRequestHeader()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://myapi.com/auth")
.Respond("application/json", JsonSerializer.Serialize(new AuthResponse
{
Token = "test-token"
}));

mockHttp.When("http://myapi.com/data")
.WithHeader("Authorization", "Bearer test-token")
.Respond("application/json", JsonSerializer.Serialize(new DataResponse
{
Data = "test-data"
}));

var client = mockHttp.ToHttpClient();
var apiCaller = new ApiCaller();
apiCaller._client = client;

await apiCaller.Authenticate("http://myapi.com/auth", new AuthRequest
{
UserName = "test-user",
Password = "test-password"
});

var response = await apiCaller.MakeApiCall<DataResponse>("http://myapi.com/data", HttpMethod.Get);

Assert.Equal("test-data", response.Data);
mockHttp.VerifyNoOutstandingExpectation();
}

private class AuthRequest
{
public string UserName { get; set; }
public string Password { get; set; }
}

private class AuthResponse
{
public string Token { get; set; }
}

private class DataResponse
{
public string Data { get; set; }
}
}
}

The unit test code using xUnit testing framework and a mock HTTP message handler (MockHttpMessageHandler) to test the behavior of the ApiCaller class. The test MakeApiCall_WithAuthToken_ShouldIncludeTokenInRequestHeader creates an instance of the ApiCaller class, authenticates with a mock authentication API to get an authentication token, and then makes a request to a mock data API using the MakeApiCall method. The test asserts that the response from the data API is correct and that the correct authorization header was sent with the request.

The next Part of the article is to make the generic API caller resilient using the Polly resilience framework. We will cover three important patterns to achieve resiliency that is Bulkhead Pattern, Circuit Breaker & Retry Pattern.

But before signing off, a big shout-out to ChatGPT which beautifully makes developer life easy.

--

--