Card image cap

Exploring ASP.NET Core Fundamentals - HttpClient Issues and Best Practices

ASP.NET Core  • Posted 2 months ago

HttpClient is one of those older libraries from the times of .NET Framework, which help in making API calls easier. It is provided as a part of the System.Net.Http package which is a part of the base .NET or even the AspNetCore sdk libraries. It provides a basic implementation for sending Http requests and receiving responses from a valid resource identified by a URI, making it one of the most commonly used class for invoking APIs in a dotnet application.

An instance of HttpClient contains all the settings and configurations required for making requests and defines its own isolated set of sockets and connection pool, and hence is isolated from requests sent by any other instance defined.

How HttpClient works?

Any API call to be made from a client to the server involves creating a socket connection from the client to the server, followed by passing in a well formed request with defined media-types and headers onto the server via the socket channel created in a TCP/IP fashion. A HttpClient based API request works in this fashion in the following steps:

  1. Create a new instance of HttpClient which allocates a socket connection for the API request to be made by the client to the server
  2. Create a HttpRequestMessage which is a structured Request containing all the metadata (Content-Type related information and other headers) and payload data which needs to be sent to the server
  3. Call the Send() method passing in this created HttpRequestMessage which is broken into packets and sent from the client via the socket connection.
  4. After a specific timeout (generally around 30 seconds or so) start reading the stream from the socket channel for response that is written by the server in the form of a stream.
  5. Once the entire stream is read, the socket is closed and the received data is parsed into a HttpResponseMessage which is a structured format used by the HttpClient for wrapping the response. This object is later used for reading the content and the functionality continues.

While the actual operation might involve more complexities, in a nutshell the above steps happen at the client when invoking a HttpClient to send request to an API for response.

Example:

// create an instance that packs 
// with it a socket for communication
using (HttpClient client = new HttpClient()) 
{
	// create a Request and prepare 
	// it with the data to be sent
	var httpRequestMessage = new HttpRequestMessage();
	httpRequestMessage.Uri = "http://myapi.com/api/getstatus";
	httpRequestMessage.Method = HttpMethod.Get;
	
	// push the request onto the server via the client socket
	var response = await client.SendAsync(httpRequestMessage);

	// read the response from the socket after a timeout
	var responseContent = await response.Content.ReadAsStringAsync();
}

Like mentioned above, the code snippet here demonstrates the steps of making a basic API call using HttpClient. The approach used above, although looks tidy and straight forward, is not scalable. As the application scales up with multiple connections being made to the APIs for data the above approach breaks due to a phenomenon known as the "Socket Shortage Problem".

The Socket Shortage Problem:

Every general implementation of HttpClient wraps the client instance under a using() block because of the misconception that the HttpClient needs to be disposed once used - similar to other libraries which implement IDisposable.

Each instance created of a HttpClient comes with its own isolated connection, and so for every time a new instance is created a socket connection is established. Since an application is permitted by the host system a limited set of socket ports available to use for itself (there's a max cap on the number of connections that can be made by a server; can be tweaked but not a good practise), the approach of using a HttpClient under a using section is not recommended.

"HttpClient is expected to be instantiated only once and re-used throughout the life of an application. Instantiating for every request will exhaust the number of available sockets for the application and results in SocketExceptions."

Instead, it is suggested that a singleton approach or only a single instance of a HttpClient be made and be reused for all the consecutive calls.

The Static Constructor Approach:

One popular approach is to create a HttpClient instance inside a "static constructor" of a class, which is called only once in its lifetime and is persisted for all future uses.

public class TestController 
{
	private static readonly HttpClient client;

	// called only once in its lifetime
	static TestController() {
		client = new HttpClient();
	}

	private async Task SomeLogic() 
	{
		var httpRequestMessage = new HttpRequestMessage();
		httpRequestMessage.Uri = "http://myapi.com/api/getstatus";
		httpRequestMessage.Method = HttpMethod.Get;

		// use the reusable client for all requests
		var response = await client.SendAsync(httpRequestMessage);
		var responseContent = await response.Content.ReadAsStringAsync();
	}
}

In the above source code, since the static constructor is called only once for any instance, the static HttpClient variable is instantiated only once and is reused where ever is necessary. It improves code performance and results in reduced socket errors, if not eliminated.

AspNetCore and HttpClient as a Service:

DotnetCore addresses this problem in a better way and provides a cleaner and easier approach for handling HttpClient instances, by means of registering it as a service and injecting it whenever required.

public void ConfigureServices(IServiceCollection services) {
	// register as a service
	services.AddHttpClient();
}
public class TestController 
{
	private readonly HttpClient client;

	// client instance injected like a service
	public TestController(HttpClient client) {
		this.client = client;
	}

	private async Task SomeLogic() 
	{
		var httpRequestMessage = new HttpRequestMessage();
		httpRequestMessage.Uri = "http://myapi.com/api/getstatus";
		httpRequestMessage.Method = HttpMethod.Get;

		// use the reusable client for all requests
		var response = await client.SendAsync(httpRequestMessage);
		var responseContent = await response.Content.ReadAsStringAsync();
	}
}

In this approach, it the AspNetCore runtime which keeps a track of the number of instances of HttpClient which are in use and is responsible for managing the available sockets, making our lives easier. Although this is a most basic version of injecting HttpClient as a service in an AspNetCore applications, we can make use of an abstraction IHttpClientFactory which empowers us to configure, customize and even control the HttpClient instantiations. In the next article, we'll discuss much more about how IHttpClientFactory works and various ways to register and use HttpClient in our applications apart from the approach we used above.

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept