I'm relatively new to using HttpClient and API's but I've done some searching around and it seems that the general consensus even from microsoft docs is that HttpClient should be instantiated once. I have multiple places where I need to make calls to an API endpoint in my project so I made an HttpClient utility class that can be called.
public static class HttpClientManager
{
private static HttpClient _httpClient;
private static HttpClientHandler _handler;
private static readonly string _apiBaseUrl = ConfigurationManager.AppSettings["ApiUrl"];
static HttpClientManager()
{
if (_handler == null) _handler = new HttpClientHandler
{
UseDefaultCredentials = true,
PreAuthenticate = true,
AllowAutoRedirect = true,
ClientCertificateOptions = ClientCertificateOption.Automatic
};
if (_httpClient == null) _httpClient = new HttpClient(_handler, false);
}
public static HttpClient GetHttpClient()
{
if(_httpClient.BaseAddress == null)
{
_httpClient.BaseAddress = new Uri(_apiBaseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return _httpClient;
}
}
The general idea is that the static constructor initializes the http client and then I have a static method GetHttpClient that will set the address and headers and return the client back which other classes in my project can then make a call like so HttpClientManager.GetHttpClient().GetAsync(.....)....
In theory this should ensure that the client gets initialized once on the first call in the static constructor and should re-use it throughout the life of the application. Is this the proper way of doing something like this or are there better designs?
Any advice would be appreciated, thanks!
Related
In the following code I receive 200 in the first request and 500 for all after that. I am new to .Net Core C# so I learned I should assign certain HttpClient attributes only once i.e. timeout.
However, my automated tests are successful first time but 500 after that. I am using HttpClientFactory.
if (httpClient.Timeout == null)
{
httpClient.Timeout = TimeSpan.FromSeconds(Foo.timeout);
}
if (httpClient.BaseAddress == null) {
httpClient.BaseAddress = new Uri(Foo.url);
}
response = await httpClient.PostAsync("", content);
I found the issue with the code. I should have created a new instance of the HttpClient using the HttpClientFactory for every Http request:
_httpClient = _httpClientFactory.CreateClient(command.ClientId);
public class Foo
{
private readonly IHttpClientFactory _httpClientFactory;
public Foo(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<HttpRequestMessage> Bar()
{
var httpClient = _httpClientFactory.CreateClient("Foo");
using var response = await httpClient.PostAsync("example.com", new StringContent("Foo"));
return response.RequestMessage;
// here as there is no more reference to the _httpclient, the garbage
//collector will clean
// up the _httpclient and release that instance. Next time the method is
//called a new
// instance of the _httpclient is created
}
}
When working with a HttpClient instance and HttpClientHandler instance (.Net Framework, not using Core), is it possible to access the HttpClientHandler instance/its properties later in any way (for read-only purposes)?
Unfortunately creating the HttpClientHandler instance as a variable for referencing later isnt possible as the HttpClient instance is being passed to a library as a param and we cannot change calling client.
For example, I would like to achieve something like so:
// Created in client we cant modify
var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = True, PreAuthenticate = True });
// Class we can modify
public void DoSomething(HttpClient client)
{
if (client.HttpClientHandler.UseDefaultCredentials == True) Console.WriteLine("UseDefaultCredentials: True");
}
Let me re-edit my answer. Using the constructor
public class MyClass {
private readonly HttpClientHander _handler;
private readonly HttpClient _client;
public MyClass() {
_handler = new HttpClientHander() { /* Initialize the properties */ }
_client = new HttpClient(_handler);
}
public void MyMethod() {
if (_handler.TheProperty == true;) // Do something
}
}
I hope this makes sense. I'm sure it works because of the way objects are referrenced.
We've got 20 microservices that are all making use of a client library.
This library simply provides a way for the microservice to connect to its destination: CRM Dynamics.
Within each service we have a wrapper that calls this library:
public static class Client
{
public static HttpClient Instance;
static Client()
{
Instance = new TheCrmClient<AdfsAuthenticationHelper>(MyConfigSettings).HttpClient;
}
}
Within each service we use the client like so:
//Issue request
var response = Client.Instance.SendAsync(request).Result;
After the ADFS session has timed out, we will get a 401 response.
How do we force re-authentication when receiving a 401 response?
The authentication occurs in GetHttpsClient():
public class TheCrmClient<T> where T : IAdfsAuthenticator
{
private readonly RestCRMClientConfiguration _configuration;
private IAdfsAuthenticator _authenticator;
private HttpClient _https;
public TheCrmClient(RestCRMClientConfiguration configuration)
{
_configuration = configuration;
}
public HttpClient HttpClient => GetClientHttps();
private void InitializeAuthenticator(HttpClient client)
{
_authenticator = Activator.CreateInstance(typeof(T), client) as IAdfsAuthenticator;
}
private HttpClient GetClientHttps()
{
var clientHandler = new HttpClientHandler
{
UseCookies = true,
CookieContainer = new CookieContainer(),
AllowAutoRedirect = false
};
_https = new HttpClient(clientHandler)
{
BaseAddress = _configuration.GetServiceUri(RestCRMClientConfiguration.UrlProtocolType.Https)
};
if (_authenticator == null)
InitializeAuthenticator(_https);
_authenticator.Authenticate(_configuration.GetNetworkCredential(),
_configuration.GetAuthenticationTestEndpointUrl(RestCRMClientConfiguration.UrlProtocolType.Https));
return _https;
}
}
To clarify the lifecycle of the request is:
Microservice.Controller --> Microservice.ClientWrapper --> TheCrmClientLibrary --> CrmDestination
Is it possible to create one instance of httpclient in Xamarin Forms application in OnStart() and use it everywhere in my application?
Yes you can use the same httpclient for all request in your app. But you will need to take note that if there is API that is having different base URL or headers information, then you will need to create another httpclient for that.
What I do is I have a class to manage the HttpClient instances. If there is no instance that is matching the HttpConfig, it will create and store it. If there is already an existing instance, it will just return it.
Example of code (HttpService is dependency injected):
public class HttpService : IHttpService
{
private static readonly int MAX_CLIENT = 5;
private Dictionary<HttpConfig, HttpClient> mClients;
private Queue<HttpConfig> mClientSequence;
public HttpService()
{
mClients = new Dictionary<HttpConfig, HttpClient>();
mClientSequence = new Queue<HttpConfig>();
}
private HttpClient CreateHttpClientAsync(HttpConfig config)
{
HttpClient httpClient;
if (mClients.ContainsKey(config))
{
httpClient = mClients[config];
}
else
{
// TODO: Create HttpClient...
if (mClientSequence.Count >= MAX_CLIENT)
{
// Remove the first item
var removingConfig = mClientSequence.Dequeue();
mClients.Remove(removingConfig);
}
mClients[config] = httpClient;
mClientSequence.Enqueue(config);
}
return httpClient;
}
...
}
HttpConfig is class where I store BaseUrl, Timeout, Headers, Auth info, etc. You will need to override the Equals method in the class for comparison whether there is existing same config.
public override bool Equals(object obj)
{
// Logic to determine whether it is same config
}
I want to set a default header for every method in the UserHttpClient but I don`t want that every method is doing that, I want to do it in a general way.
The problem I see with the current implementation is, that when I call one method the _client gets disposed thus at the next call within a Http Request the _client is not initialized, as this happens within the constructor.
The UserHttpClient is registered via DI as per Http Request.
I also do not want to create a private/base method where I pass the _client and do the header addition there.
How would you solve that problem?
public class UserHttpClient : IUserRemoteRepository
{
private readonly string baseUrl = ConfigurationManager.AppSettings["baseUrl"];
private readonly string header = ConfigurationManager.AppSettings["userHeader"];
private readonly HttpClient _client;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, token);
}
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
public async Task<UserDto> GetUser(Guid userId)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
}
The class UserHttpClient has a member that is IDisposable (private readonly HttpClient _client;). That means that the UserHttpClient should also implement IDisposable:
public void Dispose()
{
_client.Dispose();
}
Then, the class/code that is using UserHttpClient is responsible for Disposing it after it's done with it. If the instance is injected, then the DI framework you use probably handles disposing it automatically at the end of the request. What's left for you then is to simply remove the using blocks from the implementation:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
// do stuff
var users = await _client.GetAsync("url here");
}
---- EDIT ----
You could also work around the issue by not reusing the HttpClient:
private string _toolSystemKeyHeader;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_toolSystemKeyHeader = toolSystemKeyHeader
}
private HttpClient GetClientInstance()
{
HttpClient _client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, _toolSystemKeyHeader); //?? in your original code, the toolSystemKeyHeader is not used, but I guess it is the token..?
return _client;
}
And:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (var _client = GetClientInstance())
{
// do stuff
var users = await _client.GetAsync("url here");
}
}