System.Net.HttpClient Design Guidance
Published Jun 19 2019 05:12 PM 5,767 Views
Microsoft

Design Pattern Discussion

Initial Problems

Recommendations

docs.microsoft.com makes an important observation about HttpClient usage System.Net.Http.HttpClient

 

HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors. Below is an example using HttpClient correctly.

What Does It Mean?

The language ‘instantiated once and re-used’ invokes in the developer mind, multiple potential solutions. However, the example provided in the documentation may unintentionally mislead developers into making a poor design choice. Take for example the following code sample which describes a design pattern which is in fact, not recommend:

Example 1: Directly contradicts our published guidance

static async Task Main()
{
   // Create a New HttpClient object and dispose it when done, so the app doesn't leak resources
   using (HttpClient client = new HttpClient())
   {
     //This breaks the guidance given already as it does not create a single instance once and re-use. Underload this may cause resource issues with the application.
   }
}

Our next example resolves one part of our recommendations: creating a single instance but fails to advise us on the practical situation the HttpClient is called regularly from many threads

Example 2: Solves one problem; introduces another

public class GoodController : ApiController
{
    // OK
    private static readonly HttpClient HttpClient;

    static GoodController()
    {
        HttpClient = new HttpClient();
    }
}

This solution may not be the best for applications making multiple calls using the same instance of HttpClient from several threads. For example, one issue that arises using this approach could be that several threads contend for the default headers collection of the same instance of the HttpClient class and create a high CPU condition. Using a single static instance of HttpClient will not fix the situation. The developer requires a different solution. At this point in the discussion the developer may start to read about the HttpClientHandler class.

HttpClientHandler

HttpClientHandler is responsible for managing the underlying connection resources of the HttpClient instances. Again, looking into the documentation examples we do not have any improved details for handling creating a single instance and re-usability with HttpClient:

More Misleading Examples

static async Task Main()
{
   // Create an HttpClientHandler object and set to use default credentials
   HttpClientHandler handler = new HttpClientHandler(); //once again we create a new instance each time this method is called
   handler.UseDefaultCredentials = true;

   // Create an HttpClient object
   HttpClient client = new HttpClient(handler); //and we continue to avoid following our recommendations
}

Towards A Solution

What then is a good way to both share the HttpClient resources to enable the high scalability and performance while not exhausting system resources but avoiding the problems that come from using the same instance of HttpClient from several threads? One answer is using a shared instance of HttpClientHandler which is passed into an instance of HttpClient and informing the client that it does not own the underlying handler.

Example 3: One solution

public static System.Net.Http.HttpClientHandler GetHttpClientHandler()
        {
            if (HttpClientHandler == null)
            {
                HttpClientHandler = new System.Net.Http.HttpClientHandler() { MaxConnectionsPerServer = 20 };
                
            }
            return HttpClientHandler;
        }

       

This approach resolves the single instance for the lifetime of the application and still allows for scalability and accessibility from several threads. And it opens the door into finding solutions for other problems with the HttpClient such as, how to properly deal with DNS changes after the client has been instantiated. Without a way of cleaning up a HttpClient or HttpClientHandler after its creation - the moment the client goes bad, if it's not recreated then the application will be stuck until recycled. This is a topic for another chapter but here you can see that using the underlying handler will expose a way to trap for errors and signal to recreate the handler, which will be automatically used on the next HttpClient calls...

1 Comment
Version history
Last update:
‎Jun 19 2019 05:12 PM
Updated by: