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?
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>();
Related
With HttpClientFactory we can configure dependency injection to create and manage the lifetime of HttpClients, but this creates a lot of code understanding and transparency problems:
public class GitHubService
{
private readonly HttpClient _client;
private readonly string _repositoryName;
public GitHubService(HttpClient client, string repositoryName)
{
_client = client;
_repositoryName = repositoryName;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await _client.GetAsync(
$"/repos/aspnet/{_repositoryName}/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Then in Startup.cs we configure DI:
services.AddHttpClient<GitHubService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
However, this raises a number of problems:
If I share the GitHubService code with someone, they will not understand that the class is using a non-standard HttpClient. To understand the logic of building requests, headers, authorization, you need to additionally study the Startup.cs class.
You might think that we are setting up an implementation of only the HttpClient for the GitHubService class. However, in fact, we are setting up dependency injection for the entire GitHubService class, without the possibility of managing its lifecycle and creation factory.
If the GitHubService class requires additional parameters in the constructor, we cannot configure them, because we do not have access to control the creation of the object.
Why not just inject a typed HttpClient<GitHubService> (like it does with ILogger<T>) that will not affect the main class and make it clear that you are not using a regular HttpClient? How can this problem be solved?
public class GitHubService
{
private readonly HttpClient<GitHubService> _client;
private readonly string _repositoryName;
public GitHubService(HttpClient<GitHubService> client, string repositoryName)
{
_client = client;
_repositoryName = repositoryName;
}
// Code removed for brevity.
}
I would like to call a third party API which provided us two different authorization token values. So we are getting two different sets of results back via invoking the same endpoint URL.
EndpointUrl: https://mail.yahoo.com/
Authorization: Token User123
//Do something with the response for User123
Authorization: Token User345
//Do something with the response for User345
In my client service, my wrapper function should invoke this API by calling it twice with different token values. Get the result and merge it.
Here is my service.
public class MailService : IMailService
{
private readonly HttpClient _httpClient;
public MailService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<UserResponse> GetUserResponse()
{
var uri = new Uri(_httpClient.BaseAddress.AbsoluteUri + "/user-data/");
var response = await _httpClient.GetAsync(uri);
return response;
}
}
I was using Typed Client:
services.AddHttpClient<IMailService,MailService>(client =>
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", "User123");
client.BaseAddress = new Uri("https://mail.yahoo.com/");
})
Problem Statement:
What is the best way to retrieve the results? I am not sure if I should create two separate instances of HttpClient? I would like to avoid repetitive code if I go with two different classes containing their own HttpClient.
Maybe my solution lies somewhere in Named Client. I just don't know how to implement that gracefully.
Any help would be appreciated.
You can delegate adding the token header later for each message. Remove the auth header from Startup and add Http message handler (Create a new class "AuthHandler").
builder.Services.AddScoped<AuthHandler>();
services.AddHttpClient<IMailService,MailService>(client => {
client.BaseAddress = new Uri("https://mail.yahoo.com/");
})
.AddHttpMessageHandler<AuthHandler>();
In the AuthHandler, you can add the logic to retrieve and set the auth header.
The override SendAsync method will be called every time a http call is made. Below is a sample code, you can modify as per your logic/requirements:
public class AuthHandler : DelegatingHandler
{
private readonly AppSettings _appSettings;
private static string? _accessToken;
public AuthHandler(IOptions<AppSettings> options)
{
_appSettings = options.Value;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = GetToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return base.SendAsync(request, cancellationToken);
}
private string GetToken()
{
if (_accessToken != null)
{
return _accessToken;
}
_accessToken = "123";// Get your token
return _accessToken;
}
}
What is the best way to retrieve the results?
Named clients are one solution, if you are certain you will always have two authentication headers. That seems very odd to me, though.
SendAsync is another solution. By calling SendAsync instead of GetAsync, you can provide an HttpRequestMessage that can have custom headers. GetAsync is essentially a wrapper around SendAsync for convenience, and you can use the lower-level SendAsync since you need more customization.
I am going to answer, and this is more-or-less opinion based. I like my DI services to be decoupled from everything else in my project. Basically you are putting a configuration to another service in your startup.cs. I like to keep all that stuff in the service that consumes the HttpClient.
So when I inject the IHttpClientFactory, I do it by simply calling:
services.AddHttpClient();
And move on.
Now, in your MailService, you would inject it as so:
public class MailService : IMailService
{
// or store this in your applications external configuration
const Uri BaseUri = new Uri("https://mail.yahoo.com/");
const string UserDataPath = "/user-data/";
private readonly IHttpClientFactory _httpClientFactory;
public MailService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<UserResponse> GetUserResponse(string token)
{
var client = _httpClientFactory.CreateClient();
var uri = new UriBuilder(BaseUri){ Path = UserDataPath }.Uri;
using (var msg = new HttpRequestMessage(HttpMethod.Get, uri))
{
msg.Headers.Authorization = new AuthenticationHeaderValue("Token", token);
using (var resp = await _client.SendAsync(msg).ConfigureAwait(false))
{
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsAsync<UserResponse>().ConfigureAwait(false);
}
}
}
}
Keep in mind that you should use the HttpRequestMessage pattern and not modify any Default... properties of an HttpClient. It could be in use some where else in your application at that time.
Using HttpRequestMessage ensures you get a fresh set of headers that no one else can modify. To sum it up: HttpClient.Headers are not thread-safe.
ETA:
Now that I think about it... that really is your question. You want to use two different headers using one HttpClient. It won't work in the scenario you presented. You would have to have two HttpClient with their own distinct headers.
So, if you don't want to do it the old-school way I presented, you should consider "Named" clients... each configuration has a different name.
Well, I know this is a bit of a tricky case. I have an ASP.NET Core site backend (site) that communicates with another system (facade) via http.
But not with HttpClient but with clients that facade provides with HttpClient underneath.
Constructors of these clients look like this
public class OrdersClient : IOrdersClient
{
private HttpClient _client;
public OrdersClient(IEndpoint endpoint, HttpClient client)
{ ... }
}
And registration looks like this
services
.AddHttpClient("facade-client")
.AddHttpMessageHandler<CorrelationIdDelegatingHandler>()
.AddHttpMessageHandler<HttpErrorDelegatingHandler>()
.AddTypedClient<IOrdersClient>((endpoint, client) => new OrdersClient(endpoint, client));
And configuration contains (it's important!)
app.UseCorrelationId(new CorrelationIdOptions());
N.B. What endpoint is for in this case is not important.
Ok. So I want to test my site with brand new cool WebApplicationFactory. I create it and get a client to the site as described in the docs. For some tests I just mock clients to facade like
public TestOrdersClient: IOrdersClient
{
}
and test my logic without facade.
Getting to the point. I want to test handlers that decorates my HttpClient to facade.
How I want to test handlers: I register fake PrimaryHttpMessageHandler so it will imitate answers from facade in my WebApplicationFactory. And I added some ImitationClient that used registered HttpClient.
services
.AddHttpClient("facade-client")
.ConfigurePrimaryHttpMessageHandler(() => new HttpMessageHandlerFake())
.AddTypedClient<ImitationClient>();
And here I am in trouble.
In my test a get ImitationClient from factory and try to call some method that calls HttpClient inside.
var factory = new SiteWebApplicationFactory();
var client = factory.CreateDefaultClient();
var imitationClient =factory.Server.Host.Services.GetService<ImitationClient>();
await imitationClient.GetData();
And here I get System.NullReferenceException because of this handler:
public class CorrelationIdDelegatingHandler : DelegatingHandler
{
private readonly ICorrelationContextAccessor _correlationAccessor;
public CorrelationIdDelegatingHandler(ICorrelationContextAccessor correlationAccessor)
{
_correlationAccessor = correlationAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add(
_correlationAccessor.CorrelationContext.Header,
_correlationAccessor.CorrelationContext.CorrelationId);
return base.SendAsync(request, cancellationToken);
}
}
Correlation Id assigned to incoming request to site and then forwarded to requests from site to facade. But when I test only client to facade I don't have incoming request, don't have CorrelationContext and fail.
Finally my question is how to correctly write test for inner httpClient with WebApplicationFactory. I know my description is rather confusing, I apologize for that. I am ready to answer any questions on the code and architecture of our project. Thank you!
I program ASP.NET Framework MVC and Web API 2
I have to access a REST service for some information. The nature of the security requirements for this service require that I ask from a limited set of known IP addresses. The nature of my client requirements is that there will be an unknown number of them with IPs that are assigned by some DHCP. I think I need to stand up a proxy that will forward requests to the service and return responses to the client that asked. This server can be assigned a single static IP, that I can register with the target service. I don't want to try to duplicate the signatures of the target service and have to maintain my proxy whenever they decide to improve interfaces.
I would have the service that is restricting IPs and accepts a GET for http://S/action as an example. I would have the proxy at http://P/action. The client would send GET http://P/action and P would, in response, send GET http://S/action, collect the response, return it back to the client.
An attempt to implement this strategy, here is a handler I built for P that doesn't work:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
DelegatingHandler handler = new DelegatingHandlerProxy<ProxyHandler>();
config.MessageHandlers.Add(handler);
}
}
DelegatingProxyHandler is a way to get my dependency injection container involved:
public sealed class DelegatingHandlerProxy<THandler> : DelegatingHandler
where THandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
IDependencyScope scope = request.GetDependencyScope();
Task<HttpResponseMessage> task;
if (scope.GetService(typeof(THandler)) is DelegatingHandler handler)
{
if (!ReferenceEquals(handler.InnerHandler, InnerHandler))
{
handler.InnerHandler = InnerHandler;
}
HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
task = invoker.SendAsync(request, cancellationToken);
}
else
{
throw new InvalidOperationException("Handler not registered with DI container");
}
return task;
}
}
The ProxyHandler that I want to do the work is:
public class ProxyHandler: DelegatingHandler
{
public ProxyHandler(
ITransformRequest preProcessor,
ITransformResponse postProcessor,
IForwardRequest forwarder)
{
PreProcessor = preProcessor;
PostProcessor = postProcessor;
Forwarder = forwarder;
}
private ITransformRequest PreProcessor { get; }
private ITransformResponse PostProcessor { get; }
private IForwardRequest Forwarder { get; }
#region Overrides of DelegatingHandler
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (PreProcessor != null)
{
request.RequestUri = PreProcessor.Transform(request.RequestUri);
}
HttpResponseMessage response = await Forwarder.Forward(request, cancellationToken);
HttpResponseMessage transformedResponse = PostProcessor.Transform(response);
return transformedResponse;
}
#endregion
}
In this case, the DI container supplies a PreProcessor that changes host, port, and prefix of the request to the target service. The Forwarder sends the request to the target using HttpClient. The PostProcessor will be a noop.
I didn't build any controllers. My thinking is that if this pipeline behaves as I expect, there won't be any controller that needs invoking. When I send ant request to this, http://P/anything returns a 404, not htto://S/anything. What am I missing?
Any particular reason you're not just writing a set of matching controllers that accept client requests and then execute the equivalent request on the 3rd arty API using a service that implements a simple web client and then returning the responses - perhaps including some authentication & caching logic to lower the impact on their API?
If your 3rd party API provider is limiting requests by IP, that is likely because they trust (or explicitly require) you to manage requests to their API in order to protect it from excessive load and/or security risks. Directly forwarding all client requests without any logic in your middleware means you're negating this limitation.
If the only purpose of your application is to provide a static IP (and you do not need to add any logic in your code) then you should consider using one of the many off the shelf API gateway products - e.g. Kong, which is an open source and very well established with plenty of community support https://konghq.com/kong-community-edition/
I am building an application using Xamarin (Android), it uses a PCL project as a Service layer. I have a Web Api endpoint and I am using HttpClient to consume it.
Everything works fine, but if I leave my Android app open and idle for a while (like 2 minutes) and I try to make a new request, the first request using the singleton HttpClient won't work. It just never returns and stays there until it timeouts (TaskCancelledException). I also put a breakpoint on my Api and it doesn't get hit. If I try to send the request again, then it works.
After a lot of debugging I found that this only happens if I try to use the HttpClient as a Singleton. If I create a new HttpClient for every request everything works.
At first I thought this was a deadlock issue, I've done a lot of research and double checked everything following the guidelines described in this other answer and Stephen Cleary's excellent post and I'm almost sure this is not the case.
I'm using ConfigureAwait(false) in every call from my PCL project so it doesn't capture the context.
The flow of a request goes like this:
Inside an Android Fragment:
SampleService svc = new SampleService();
response = await svc.GetAllSamples();
The service called (in my PCL project):
public class SampleService
{
public HttpClient Client { get; set; }
public SampleService()
{
// resolves my singleton instance and uses my custom DelegatingHandler
Client = CustomHttpClient.Instance;
}
public async Task<IEnumerable<Sample>> GetAllSamples()
{
IEnumerable<Sample> list = null;
// this never returns and timeouts the first time
using (var response = await Client.GetAsync("samples").ConfigureAwait(false))
{
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
lista = await Task.Run(() => JsonConvert.DeserializeObject<IEnumerable<Sample>>(json)).ConfigureAwait(false);
}
return list;
}
}
}
This is how I build my Singleton instance:
public sealed class CustomHttpClient
{
private static HttpClient _client;
public static HttpClient GetClient()
{
if (_client == null)
{
HttpMessageHandler messageHandler = new HttpClientHandler();
_client = new HttpClient(messageHandler);
_client.Timeout = TimeSpan.FromSeconds(30);
_client.BaseAddress = new Uri("myendpoint");
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
return _client;
}
}
I tried to simplify and isolate the code here, if I can provide any other useful snippets, just let me know.
Am I doing something wrong regarding singletons HttpClient that I'm not aware of?
Update: Just for clarification, I'm trying to use HttpClient as a Singleton just because, as I found in this answer by Darrel Miller and in the book Designing Evolvable Web APIs with ASP.NET (Chapter 14), they were designed to be reusable and thread-safe (in most of the cases). From my research I'm not using anything that is not thread-safe in it.