HttpClient C#- Guidelines and Best Practices
In today’s article, we will discuss HttpClient C# Guidelines and Best Practices that developers can follow for developing HTTP-based APIs or microservices.
We will go through various aspects of how to use/create/instantiate HttpClient C# instances in the .NET /.NET Core ecosystem.
I did touch upon this topic in my last article on the Using HTTPClientFactory in ASP.NET Core Application already, however, today’s article is more on the guidelines and best practices while using HTTPClient instances to access resources over the network.
What is HttpClient
HttpClient is a very important class in the .NET/.NET Core ecosystem. It provides you the ability to send HTTP requests and receive HTTP responses from a resource identified by a URI.
- You can use HttpClient to submit various HTTP requests, including GET, POST, PUT, DELETE, etc., and receive server responses.
- Because it allows asynchronous operations, programs can efficiently make non-blocking HTTP calls.
- It can handle JSON, XML, and other different content types.
Developers may easily use HTTPClient to handle HTTP headers and status codes, download/upload files, use web services, and consume APIs.
HttpClient is designed as a shared instance that is also thread-safe if used properly.
The HttpClient class is designed as a broker that clients can use to access the resource over the network.
In the host-based application under heavy load communication, there could be multiple instances of HTTPClient getting created.
Over the years it has experienced a few common issues like resource exhaustion caused by multiple invocations of the HTTP request object and then recently discovered issues with .NET Core like Stale DNS, etc.
Let’s see, how HttpClient is currently being used in applications and issues related to that with some real-life examples.
HTTPClient AntiPattern 1 – use of new HttpClient()
You instantiate the HttpClient object using a new operator for every request.
Example:
HttpClient httpClient = new HttpClient();
OR
Using Handler
var httpClientHandler = new HttpClientHandler()
{
Credentials = new NetworkCredential(config.User, config.Pwd),
};
HttpClient httpClient = new HttpClient(httpClientHandler);
HttpResponseMessage response = await httpClient.GetAsync(config.Url).Result;
..
..
In the above example, the HttpClient object is created for each request using a new operator.
Here in the above example, the developer’s intention looks to be creating a long-lived instance that will be disposed of/garbage collected depending on the implementation.
Note– If the instances are injected through IOC container pipelines then only it will be disposed of by the framework as default behavior or else it becomes the creator’s responsibility to manage the objects’ lifetime.
HttpClient AntiPattern 2 – use of Using statement
In this anti-pattern example, the developer instantiates the HttpClient object using block,
Other examples without using any handler,
Example: With C# HttpClientHandler
var httpClientHandler = new HttpClientHandler()
{
Credentials = new NetworkCredential(config.User, config.Pwd),
};
HttpResponseMessage response;
using (HttpClient httpClient = new HttpClient(httpClientHandler))
{
response = await httpClient.GetAsync(config.Url);
}
Similar to above example 1, In the above example, the HttpClient object is being created for each request.
Here in the above example, the developer is aware that the HttpClient object is an unmanaged resource and implements IDisposable.
Here instance gets created and called within the ‘using‘ block assuming this will be disposed of/garbage collected immediately but in reality, there is no guarantee that such resources will get disposed of at a given time.
Why Anti-pattern?
Using HTTPClient in the above-mentioned manner is considered to be an anti-pattern due to common problems like Resource Exhaustion and Stale DNS Problems as discussed below,
HttpClient – Resource Exhaustion and Stale DNS Problem
In both, of the above Example1 and Example2 below are the consideration and issues associated.
- Each new HTTPClient object creates a new socket instance.
- Instantiating HTTPClient objects for each request might exhaust the number of sockets available under heavy loads.
- HTTPClient object doesn’t release the sockets immediately even if it is called using the “using” (IDisposable) block.
- This may lead to SocketExceptions.
- Singleton or Static HTTPClient objects as specified above should help to resolve most issues.
- In .NET Core, it was found that Singleton or Static HTTPClient object doesn’t respect the DNS update or change in the .NET core resulting in a Stale DNS problem
Resolutions for HttpClient AntiPatterns
As per Microsoft guidelines,
HttpClient is intended to be instantiated once and re-used throughout the request flow of an application.
Especially in server applications, creating a new HttpClient instance for every request will exhaust the number of sockets available under heavy loads.
This will result in SocketException errors.
Resolution 1 – Use HttpClientFactory
Let’s create a HttpClient instance using HttpClientFactory.(Available only in .NET Core 2.1 and above)
Using HTTPClientFactory resolves the Resource exhaustion problems by pooling HTTPMessageHandler.
It also solves Stale DNS problems by cycling HTTPMessageHandler instances at regular intervals.
Each instance of HttpClient
object created from IHttpClientFactory
, uses an HttpMessageHandler
that’s pooled and reused to reduce resource consumption.
This also depends upon HttpMessageHandler
objects lifetime which has a lifetime of two minutes typically by default.
Basic or Named HttpClient object
For more details see here: Named HTTP Client using HTTPClientFactory
Typed HttpClient objects
For more details see here: Typed HTTP Client using HTTPClientFactory
Resolution 2 – Use Static Instance of HttpClient
It’s recommended to create a single static HttpClient instance and use the same shared instance for all requests.
This approach is more useful and convenient in case you have a no-host application like a batch process or console application.
Also, for applications where dependency injection is a bit difficult to manage, this approach is a possible option.
HttpClient is designed to be thread-safe and intended to be shared.
If you have a console or form application where you need to perform repetitive HTTP connection, this approach is preferable.
The below example shows a static instance created in the controller but the same if you have layered architecture, same can be used in the controller/API or business or data access layer as needed.
[Route("api/[controller]")]
[ApiController]
public class PaymentController : ControllerBase
{
static readonly HttpClient client = new HttpClient();
[HttpGet]
public async Task<IActionResult> Get()
{
HttpClientHandler httpClientHandler = new HttpClientHandler()
{
Credentials = new NetworkCredential("username", "****"),
};
var response = await client.GetAsync("https://www.thecodebuzz.com/api/");
return Ok(response.Content.ReadAsStringAsync().Result);
}
Additionally, you can use Constructor to initialize the HttpClient Instance.
Below is just an example to demonstrate the use. However please use layered architecture and inject services per need.
public class PaymentController : ControllerBase
{
private static HttpClient _client;
public PaymentController() => _client = new HttpClient();
..
}
Use PooledConnectionLifetime – DNS update scenarios,
Additionally, it’s recommended to use the PooledConnectionLifetime settings if DNS entries change regularly,
public class PaymentController : ControllerBase
{
private static readonly HttpClient httpClient;
static PaymentController ()
{
var socketsHandler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};
httpClient = new HttpClient(socketsHandler);
}
}
The above use of PooledConnectionLifetime limits the lifetime of the connection by setting the SocketsHttpHandler.PooledConnectionLifetime property.
In case the DNS is replaced, the connection will perform the DNS lookup and restore the connection.
Other Resolution and Best Practices
Set Timeout on HttpClient Instance
Specify a timeout value to prevent requests from hanging indefinitely.
This helps in handling scenarios where the remote server is unresponsive or experiencing delays.
Example
httpClientInstance.Timeout = TimeSpan.FromSeconds(10);
Use await with async Methods
When making asynchronous HTTP requests using HttpClient, utilize the async/await pattern to ensure responsiveness and avoid blocking the calling thread.
Configuring HTTPClient correctly
Configure the HttpClient instance with appropriate timeouts, default headers, certificates, authentication, and other settings like HttpContext based on your application’s requirements.
Use try-catch blocks or async/await error handling patterns when making requests with HttpClient.
Using HttpClientHandler for customizing behavior
Consider using the HttpClientHandler class to customize the underlying HTTP behavior, such as handling certificates or enabling compression.
Handling exceptions and errors
Handle exceptions such as HttpRequestException and TaskCanceledException to gracefully handle network issues, timeouts, or other errors.
Properly logging or handling exceptions ensures the robustness and reliability of your application.
Implementing Resiliency – Retries and exponential backoff
When dealing with transient errors like network connectivity issues or temporary server failures, consider implementing Httpclient Resiliency retry mechanisms with exponential backoff.
This allows your application to automatically retry failed requests, increasing the chances of successful execution.
HttpClient Security considerations
When making requests to secure endpoints, ensure that you properly handle and validate SSL/TLS certificates.
Follow secure coding practices and validate and sanitize user inputs to prevent security vulnerabilities like injection attacks.
Do you have any comments or ideas or any better suggestions to share?
Please sound off your comments below.
Happy Coding !!
Summary
In this article, we learned about HttpClient instancing usage’s best practices and guidelines. We understood that by following best practices, we can avoid common problems of Resource exhaustion, stale DNS, Memory leaks, or network connectivity issues while using HttpClient objects.
Using HttpClientfactory or A static shared instance is the recommended way to use the HTTPClient object in the application.
Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.
Thanks for the guidelines
Thanks Aby. Glad you find guidelines useful.
Found it to be very useful. Thanks
Hello Flint- Thank you. Glad that the article was helpful!
You instantiate a HttpClientHandler and a HttpClient independant from each other in Example solution 1. The HttpClientHanler is nowhere asociated with the HttpClient or the other way around. What does the HttpClientHandler do in this example?
Hello Bert – Thanks for your query. Those are just an example to put some real-time HttpClient examples and not meant for confusing. Ideally, the concept remains the same irrespective of HttpClientHandler used or not.
I was looking for a proper example of using an HttpClient in conjunction with an HttpClientHandler and could not find out how to ascociate them other than by instantiating the HttpClient with the HttpCientHandler as a parameter like this
HttpClientHandler handler = new HttpClientHandler();
// some handler stuff set here
HttpClient client = new HttpClient(handler);
Making the HttpClient static is fine, but making it readonly prevents you from using an HttpClientHandler, as far as I know. Is there an other way to ascociate them?
Hey Bert, Did you check this examples How to use HttpClientHandler with IHttpClientFactory. Let me know if this resolves your issue.
In classLibrary too (.net framework/ not .net core)?
Why not use IDisposable in HttpClient? Ex:
public class MyClient : IDisposable
{
private HttpClient _client;
public readonly AuthApi Auth;
publuc MyClient (ApiConfig config)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.Add(“Authorization”, “Basic ” + _config.ApiKey);
Auth = new AuthApi(this); //AuthApi use MyClient/HttpClient instance
}
public void Dispose()
{
_client.Dispose();
GC.SuppressFinalize(this);
}
}
Those methods are thread safe
CancelPendingRequests
DeleteAsync
GetAsync
GetByteArrayAsync
GetStreamAsync
GetStringAsync
PostAsync
PutAsync
SendAsync
Hi Rodrigo-Thanks for your query. Yes, these practices can be used for regular .NET or.NET Core core both and any type of project.
It is important to note how many times MyClient would get a call by calling code(Since Its library and would need a process/host).
During a heavy load operation, MyClient instance and hence new instance of HttpClient will be created(due to new HttpClient()), and ultimately those many numbers of the socket will be created.
Garbage collectors would help and but will not guarantee those resources are released at right time (before the DNS exhaustion error).
So as good practice it needs to be created as a Static shared resource like a broker as explained in the article.
Please let me know for any questions!
Very useful indeed. Thank you!
Thank you .. nice one.
Very helpful practices indeed. Doesn’t current HTTPClientFactory also has its own issues to deal with?
Thanks Hugo for your comments. I appreciate your time. I believe with every upgrade of .NET Core version, I do see .NET Core is getting stabilize very well. Coming to HTTPClientFactory even if there are any issues( not I am aware of) should get resolve with a new version.