HttpClient C#- Guidelines and Best Practices

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

httpclient best practices c

       
            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,

httpclient best practices c

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

HTTPClient C Best Practices and Anti Patterns

For more details see here: Named HTTP Client using HTTPClientFactory

Typed HttpClient objects

net 6 httpclient best practices

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);
        }

net framework httpclient best practices

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.

C HttpClient

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.