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.