Reputation: 299
I'm trying to come up with a good way to have a service layer that is mainly making REST API calls to an external API. I've currently got a .NET Core 2.0 project where my services are being injected into my controllers, and I'm making calls that way. However, in my services themselves, I'm making calls to external APIs that require an access token. My current architecture has mostly been thrown together pretty quickly just to kind of "get things working", but now I was to decouple things a bit more and make it more testable. Here is an example of one of my service methods to illustrate where I'm at, an implementation of ISomeSearchService:
public async Task<SearchDataResponse> SearchAsync(string query, string accessToken)
{
SearchDataResponsedataResponse = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("www.somesite.com/api");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", accessToken);
IList<KeyValuePair<string, object>> parameters = new List<KeyValuePair<string, object>>()
{
KeyValuePair.Create<string, object>( "searchTerm", query ),
};
var response = await client.GetAsync("/search" + UriFormatter.AsQueryString(parameters));
if(response.IsSuccessStatusCode)
{
string responseBody = await response.Content.ReadAsStringAsync();
dataResponse = JsonConvert.DeserializeObject<SearchDataResponse>(responseBody);
}
}
return dataResponse;
}
I know this is riddled with issues. Namely one of the biggest to me is that is constructing this HTTP client in the service itself. I would like to abstract that out, and maybe pass in a client to the service that is already constructed some way. That way in my tests, I can pass in mock clients to be able to test these methods without having to make actual HTTP calls. I'm not sure if there is a better way to handle that though. I can't find much guidance on service layers that are making HTTP calls. Most documentation I find is related to calling a DB directly.
Another issue is that I don't like passing the access token directly into the service. Since I was limited on time, I just did that to get things working, but I'm not happy with it.
Does anyone have some experience with this or a design that I could look into that would decouple this out more?
Upvotes: 1
Views: 3895
Reputation: 14250
The goal is to inject an HttpClient instance. You can extract an interface from HttpClient to assist with mocking.
public interface IHttpClient : IDisposable
{
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
}
You will need an implementation that constructs instances of HttpClient
but you can also use the interface to mock the request.
Now your service codes against the IHttpClient
public class SearchService : ISomeSearchService
{
private readonly IHttpClient httpClient;
public SearchService(IHttpClient httpClient)
{
this.httpClient = httpClient;
}
}
Alter your request so that you send an HttpRequestMessage
instead of using .GetAsync()
that way you can alter the Authorization header per request.
public async Task<SearchDataResponse> SearchAsync(string query, string accessToken)
{
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri)
{
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
var response = await httpClient.SendAsync(request);
...
}
}
Then register the singleton HttpClient
services.AddSingleton<IHttpClient, HttpClientFactory>();
services.AddScoped<ISomeSearchService, SearchService>();
Upvotes: 1