Correctly Implementing HttpClient in C#

 

1 - What is HttpClient?

HttpClient is the class provided by C# to make calls using the HTTP protocol.

In the workplace, we use HttpClient every time we need to fetch or send information to an API.

 

Since it works through HTTP, the result (or response) will also indicate the HttpStatusCode, which is very useful to understand the outcome obtained.

 

For example, status codes from 200 to 299 indicate everything went well, where 200 means everything is perfect and 201 means what you tried to create has been created successfully.

For example, when making a purchase in a store, the backend API returns a 201 to the frontend indicating that the purchase order was created successfully.

 

 

2 - Using HttpClient Correctly

To use HttpClient, we must first understand the IDisposable interface. If you are not familiar with it, here is a link where you can read more about it.

This is important because HttpClient implements IDisposable, so should we implement our own Dispose method or maybe not?

 

Before we look deeper into how HttpClient works, let’s see how it functions in general terms.

It’s important to note that, as with everything in C#, you can invoke it from anywhere in your code, but I recommend including it either in its own library or at least in a folder structure within your code.

 

 

2.1 - HttpClient Example in C#

As mentioned, we can create our HttpClient from anywhere in the code.

using (var client = new HttpClient())
{
    //Here goes the code
}

As noted in the post about dispose in C#, we need to put our instance inside a using statement to later use the code, for example, to read a website directly.

using (var client = new HttpClient())
{
    var result = await client.GetAsync("https://www.netmentor.es");
    Console.WriteLine(result.StatusCode);
}

 

2.1.1 - Example: Getting an object using HttpClient

Or, for example, if we want to directly read an object with net5 it is possible; for this example, I will use a local project that returns a PersonalProfileDto in Json format.

using (var client = new HttpClient())
{
    var result = await client.GetFromJsonAsync<PersonalProfileDto>("https://localhost:44363/api/perfilpersonal/ivanabad");
    Console.WriteLine(result.Website);
}

 

Note: we can use the same client for any CRUD action.

 

 

3 - Applying HttpClient Correctly

For this use case, and to explain the problem of using HttpClient inside a using block, let’s go back to our previous example where we check the response code from netmentor.

But this time, let's put it inside a loop that runs 10 times.

static async Task Main(string[] args)
{
    for(int i = 0; i<10; i++)
    {
        using var client = new HttpClient();
        var result = await client.GetAsync("https://www.netmentor.es");
        Console.WriteLine(result.StatusCode);
    }
}

As we can see, if we run this, the console tells us that everything is ok.

ok response from api

 

However, if we run the netstat command on the server machine we will see that things are not so good.

 

The result is a bit different:

netstat in the server

As we can see, a lot of connections remain open even after our application ends. This is because the connection was closed on one end (the code), but the server keeps the connection open for some time in case there is any delay or pending process.

 

Here’s a diagram showing how the TCP/IP protocol works, which I took from the following website: -https://www4.cs.fau.de/Projects/JX/Projects/TCP/tcpstate.html-

tpc ip workflow

One possible solution to this problem is to go to the server and lower the time connections are kept open, but obviously we SHOULD NOT do this. First, because we don’t always control the server and second, according to Google and various forums, it’s a very bad practice (I personally NEVER lower it).

 

Moreover, there is a maximum number of sockets we can create, so that would not help much either (remember, one socket per each `using` we use).

 

3.1 - Applying the Correct Solution

The solution here is to use a single instance of HttpClient for the entire application, thus reducing the number of sockets.

 

This means that, if you are going to use dependency injection, you should add the dependency as a singleton. However, this isn’t the best option because you might run into DNS issues.

 

The most common solution is to include it as static and finally not use the dispose functionality. This way, what you’re doing is sharing the same socket.

class Program
{
    private static HttpClient Client = new HttpClient();

    static async Task Main(string[] args)
    {
        for(int i = 0; i<10; i++)
        {
            var result = await Client.GetAsync("https://www.netmentor.es");
            Console.WriteLine(result.StatusCode);
        }
    }
}

 

3.2 - Using HttpClientFactory

Microsoft realized the problem and that the community kept complaining. That's why in .NET Core 2.1 they introduced a new type. HttpClientFactory makes our lives much easier as it handles the lifecycle by itself.

 

To do this, in our startup.cs class, we need to include in services services.AddHttpClient();

And inject the dependency when we’re going to use it. To use it, remember to call _httpClientFactory.CreateClient() and indicate the BaseAddress.

public class EjemploHttpClientFac
{
    private readonly IHttpClientFactory _httpClientFactory;

    public EjemploHttpClientFac(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task Test()
    {
        var client = _httpClientFactory.CreateClient();
        client.BaseAddress = new Uri("https://localhost:44363/");
        var result =await  client.GetAsync("api/perfilpersonal/ivanabad");
        Console.WriteLine(result.StatusCode);
    }
}

Obviously, configuring the base address every time is not very efficient, so it’s much easier to configure it once and always use the same one. For this, you can configure one or multiple clients by defining an "id" within AddHttpClient() in startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("BackEnd", client =>
    {
        client.BaseAddress = new Uri("https://localhost:44363/");
    });
}

You just need to specify which "key" to use when you call "CreateClient()"

public async Task Test()
{
    var client = _httpClientFactory.CreateClient("BackEnd");
    var result =await  client.GetAsync("api/perfilpersonal/ivanabad");
    Console.WriteLine(result.StatusCode);
}

And the result is the same.

 

 

Conclusion

My use of HttpClient changed after I discovered this bug, as it’s very difficult to detect. In fact, you may never see it fail, but where I discovered it was during a usage spike in one of our serverless services, basically, we ran out of sockets.

 

Throughout my life I have always disposed of every object that implements IDisposable. What I don't understand is why the language itself does not warn you in some way, either at compile time or runtime, as if you always dispose of HttpClient, the only thing that can happen is that your application won't work as expected.

 

And certainly, the simplest solution if you are working with code beyond .NET Core 2.1 is to use HttpClientFactory.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café