Implementar HttpClient de forma correcta en C#

14 Dec 2020 012 min (0) Comentarios

 

1 - Qué es HttpClient?

HttpClient es la clase que nos proporciona C#  para hacer llamadas a través del protocolo HTTP.

En el mundo laboral utilizamos HttpClient cada vez que consultamos o enviamos información de una API. 

 

Como funciona a través de HTTP el resultado (o response) nos indicará también el HttpStatusCode el cual es muy útil para comprender el resultado obtenido.

 

Por ejemplo, los Status code de 200 a 299 son códigos que indican que todo ha ido bien. Siendo el 200 que todo está perfecto o 201 que lo que has intentado crear se ha creado con éxito.

Por ejemplo cuando realizamos una compra en una tienda, la API del back end devuelve al front end un 201 indicando que esa orden de compra se ha creado correctamente. 

 

 

2 - Utilizar HttpClient correctamente

Para utilizar HttpClient debemos comprender primero la interfaz IDisposable, si no sabes lo que es, te dejo un enlace donde consultarlo.

Esto es debido a que HttpClient implementa IDisposable, por lo que debemos implementar nuestro método Dispose o quizá no?

 

Antes de ver una vista más detallada o más en profundidad de cómo funciona HttpClient, vamos a ver como funciona a rasgos generales. 

Cabe indicar que como todo en C# podemos invocarlo desde cualquier parte de nuestro código pero yo, recomiendo incluirlo o bien en su propia librería o al menos en una estructura de carpetas dentro de nuestro código. 

 

 

2.1 - Ejemplo HttpClient en C#

Como he indicado podemos construir nuestro HttpClient desde cualquier lugar del código.

using (var client = new HttpClient())
{
    //Aquí el código
}

Como sabemos del post de dispose en C# debemos poner nuestra instancia en el using para posteriormente utilizar el código, por ejemplo para leer una web directamente. 

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

 

2.1.1 - Ejemplo Get objeto utilizando HttClient 

O si por ejemplo queremos leer un objeto directamente con net5 es posible; para este ejemplo voy a utlizar un proyecto en localhost que devuelve PerfilPersonalDto en formato Json.

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

 

nota: podemos utilizar el mismo cliente para cualquiera de las acciones CRUD.

 

 

3 - Aplicar HttpClient Correctamente 

Para este caso de uso y la explicación del problema de utilizar HttpClient dentro de un bloque using vamos a volver a nuestro caso anterior donde consultamos el código de respuesta de netmentor. 

Pero esta vez, vamos a ponerlo en un bucle que lo ejecuta 10 veces.

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

Como vemos si ejecutamos, la consola nos devuelve que todo está ok.

ok response from api

 

Pero si corremos el comando netstat en la máquina servidor veremos que no está tan bien. 

 

Si vemos el resultado es un poco diferente:

netstat in the server

Como podemos ver un montón de conexiones siguen abiertas pero nuestra aplicación ha terminado. esto es porque la conexión ha sido cerrada en un lado (el código) pero el server sigue teniendo la conexión abierta por cierto tiempo por si hay algun delay o algo en en el proceso. 

 

Aqui podemos ver un diagrama de como funciona el protocolo TCP/IP el cual lo he cogido de la siguente web -https://www4.cs.fau.de/Projects/JX/Projects/TCP/tcpstate.html - 

tpc ip workflow

Una posible solución a este problema es ir al servidor y reducir el tiempo que mantiene las conexiones abiertas, pero obviamente NO debemos hacerlo, primero, porque no siempre controlamos el servidor y segundo porque según google y diferentes foros es una muy mala práctica. (yo personalmente NUNCA lo he bajado).

 

Además tenemos un número máximo de sockets que podemos crear por lo que tampoco solucionaría mucho. (recordemos un socket por cada `using` que utilizamos)

 

3.1 - Aplicar la solución correcta

La solución en este caso es utilizar una única instancia de HttpClient para la aplicación completa, así podemos reducir el número de sockets.

 

Lo que quiere decir que si vamos a utilizar inyeccion de dependencias debemos añadir la dependencia como singleton, aunque no es la mejor opción, ya que tendremos problemas con el DNS.

 

La solución mas común es incluirlo como static y finalmente no utilzes la funcionalidad de dispose. De esta forma lo que estamos haciendo es compartir el mismo 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 - Aplicar HttpClientFacotry

Microsoft, se dio cuenta del problema y de que la comunidad se quejaba constantemente. Por ello en la versión de .NET Core 2.1 introdujeron un nuevo tipo. HttpClientFactory el cual nos hace la vida mucho más sencilla ya que trata  por sí solo el tiempo de vida.

 

Para ello en nuestra clase startup.cs debemos incluir en los services services.AddHttpClient();

E inyectar la dependencia cuando vayamos a utilizarla, pero para utilizarla deberemos utilizar  _httpClientFactory.CreateClient() e indicar la 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);
    }
}

obviamente tener que configurar la base address en cada una de nuestras, es mucho mas fácil configurarlo una vez y tirar siempre del mismo, para ello podemos configurar uno o varios clientes, definiendo un “id” dentro del AddHttpClient() del startup.cs

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

Únicamente debemos indicar que “key” vamos a utilizar cuando hacemos “CreateClient()

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

y el resultado es el mismo.

 

 

Conclusión

El uso de HttpClient para mi cambio desde que descubrí este bug, ya que es muy difícil de detectar, de hecho, es posible que no lo veas fallar nunca, pero donde yo lo descubrí fue en un pico de uso de uno  de nuestros servicios serverless, básicamente nos quedamos sin sockets. 

 

Durante toda mi vida he hecho el dispose en todo objeto que implementa IDisposable, lo que no entiendo es porque el propio lenguaje te indica que lo estás haciendo mal, ya bien sea en tiempo de compilación o en tiempo de ejecución, ya que si haces siempre dispose HttpClient lo único que puede pasar es que tu aplicación no funcione como esperabas. 

 

Y desde luego la solución más sencilla si estamos en código que va más allá de net core 2.1 es utilizar HttpClientFactory.

 


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 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café