Utilizando HttpClient con IHttpClientFactory en .Net 6
Tags: spanish, csharp, latam
Published: 2022-10-21 15:46:32

Para los que están entrando o ya llevamos un tiempo en el mundo de .Net y necesitan consumir una api rest, es muy común que hayan utilizado ***HttpClient*** para realizarlo. Hay otras librerías como ***RestSharp*** que también permiten consumir servicios, pero RestSharp sigue utilizando HttpClient.

Uno de los grandes problemas que nos podemos encontrar al implementar ***HttpClient*** es la mala implementación, volviendo nuestra aplicación inestable, como en el siguiente código:

```csharp
using(var client = new HttpClient())
{
    //Realizamos solicitudes HTTP
}
```

En este post no profundizaremos en los errores que pueden ocasionarse con una mala implementación, nos centraremos en como realizar una implementación mas limpia y estable con IHttpClientFactory.

## Definición de IHttpClientFactory

La documentación de Microsoft nos define lo siguiente:

>Se trata de una interfaz que se usa para configurar y crear HttpClient instancias en una aplicación mediante inserción de dependencias (DI). También proporciona extensiones para el middleware basado en Polly a fin de aprovechar los controladores de delegación en HttpClient.

Lo que quiere decir que a través de esta interfaz generaremos instancias que podremos inyectar en nuestro servicio o clase en donde utilizaremos IHttpClientFactory asegurando una mayor estabilidad y de forma centralizada.

Tambien nos menciona Polly, que en resumen es una librería que nos permite realizar reintentos de request cuando el codigo http de un response coincida con algún codigo que le indiquemos. Es mucho mas sencilla su implementación con `IHttpClientFactory`, pero la política de reintentos ya es material para otro post.

## Implementemos la interfaz

Ahora que ya sabemos el concepto básico de *IHttpClientFactory*, procedamos a implementarlo en el código

### Prerequisitos

- Visual Studio 2022

### 1\. Creando el proyecto

1.  Lo primero es crear un proyecto con la plantilla de tipo *ASP .NET Core Web API*
    
2.  El nombre no es relevante pero para esta ocasión será *HttpClientApi*
    
3.  Seleccionamos el framework .NET 6.0 y habilitamos compatibilidad con OpenAPI (swagger)

### 2\. Estructura del proyecto

La estructura del proyecto quedar según la siguiente imagen, creando carpetas por cada capa de nuestra aplicación. Esto es solo para efecto de ejemplo, ya que en vez de carpetas podríamos generar librerías de clases por cada capa.

![Estructura del proyecto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/boipnc735t8zn84frmjq.png)

### 3\. Implementemos IHttpClientFactory

Ya tenemos lista nuestra estructura !!!
Ahora vamos al código

Clases de la capa Models

```csharp
namespace HttpClientApi.Models;

public class Bird
{
    public string Uid { get; set; }
    public Name Name { get; set; }
    public Images Images { get; set; }
    public Links _Links { get; set; }
    public int Sort { get; set; }
}
```

```csharp
namespace HttpClientApi.Models;

public class Images
{
    public string Main { get; set; }
    public string Full { get; set; }
    public string Thumb { get; set; }
}
```

```csharp
namespace HttpClientApi.Models;

public class Links
{
    public string Self { get; set; }
    public string Parent { get; set; }
}

```

```csharp
namespace HttpClientApi.Models
{
    public class Name
    {
        public string Spanish { get; set; }
        public string English { get; set; }
        public string Latin { get; set; }
    }
}

```

Ahora  debemos modificar el `appsettings.json` para dejar la url que consumiremos con nuestro `HttpClient`:

**appsettings.json:**

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Endpoint": {
    "UrlBirds": "https://aves.ninjas.cl/api/birds"
  }
}
```

- Agregamos la propiedad `Endpoint:UrlBirds` donde se encuentra la url.

Para implementar IHttpClientFactory modificaremos el archivo Program.cs

**Program.cs:**

```csharp
using HttpClientApi.Services;

var builder = WebApplication.CreateBuilder(args);

var configuration = builder.Configuration;
builder.Services.AddControllers();

builder.Services.AddHttpClient<IBirdsService, BirdsService>(client =>
{
    client.BaseAddress = new Uri(configuration.GetValue<string>("Endpoint:UrlBirds"));
    client.Timeout = TimeSpan.FromSeconds(20);
    client.DefaultRequestHeaders.Add("HEADER_API_KEY", "claveficticia");
});
// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
```

- En el codigo primero traemos la configuración que permitirá traer la url desde appsettings.json

```csharp
var configuration = builder.Configuration;
```

- En el siguiente bloque implementamos `IHttpClientFactory`

```csharp
builder.Services.AddHttpClient<IBirdsService, BirdsService>(client =>
{
    client.BaseAddress = new Uri(configuration.GetValue<string>("Endpoint:UrlBirds"));
    client.Timeout = TimeSpan.FromSeconds(20);
    client.DefaultRequestHeaders.Add("HEADER_API_KEY", "claveficticia");
});
```

- Con el metodo `AddHttpClient` le indicamos la interfaz y la clase donde inyectaremos el `HttpClient`.
    
- Luego vamos a asignar tres propiedades BaseAddress, Tiemout, DefaultRequestHeaders
    
    - BaseAddress: asignamos la url.
        
    - Timeout: asignamos el máximo tiempo de respuesta de una solicitud.
        
    - DefaultRequestHeaders: Agregamos una cabecera extra a nuestro Request, que para efectos prácticos puede ser una api key.
        

Llamando al servicio:

Tenemos una interfaz y una clase en la carpeta Services

**IBirdsService:**

```csharp
using HttpClientApi.Models;

namespace HttpClientApi.Services
{
    public interface IBirdsService
    {
        Task<List<Bird>> Get();
    }
}
```

- La interfaz con el metodo Get nos permitirá implementar la inyección de dependencia.

**BirdsService:**

```csharp
using HttpClientApi.Models;

namespace HttpClientApi.Services
{
    public class BirdsService : IBirdsService
    {
        private readonly HttpClient _httpClient;

        public BirdsService(HttpClient httpClient) => (_httpClient) = (httpClient);

        public async Task<List<Bird>> Get()
        {
            var listBirds = await _httpClient.GetFromJsonAsync<List<Bird>>(_httpClient.BaseAddress);

            return listBirds;
        }
    }
```

- La clase BirdsService implementa la interfaz IBirdsService.
- En el constructor declaramos `HttpClient httpClient` para que pueda ser inyectado desde el Program.cs como lo vimos en el codigo de mas arriba.

Listo ya tenemos el servicio conectado solo nos falta llamarlo desde el controlador `BirdsController`

```csharp
using HttpClientApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace HttpClientApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BirdsController : ControllerBase
    {
        private readonly IBirdsService _birdsService;

        public BirdsController(IBirdsService birdsService) => _birdsService = birdsService;

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            return Ok(await _birdsService.Get());
        }
    }
}
```

Y listo ahora ya estamos en condiciones de realizar el get  Birds

## Conclusiones

- Como vimos en el ejemplo y por la experiencia que tengo utilizándolo es mucho mas limpio con `AddHttpCLient`. Por que como vimos en la clase BirdsService en ningún momento declaramos la url, el timeout o si fuera necesario alguna cabecera adicional.
- Le damos la responsabilidad a la inyección de dependencia que lo haga.
- Para agregar `IHttpClientFactory` en este ejemplo lo hicimos directo en el Program.cs pero si quisieras dejar este archivo mas limpio podemos alojar esta configuración en otro archivo .cs, con la implementación ya lista como un metodo estático y solo la llamamos desde Program.cs. Esto es una buena opción cuando necesitas inyectar distintos servicios, es ahi donde nuestro código se nos puede extender.
- *Administra la agrupación y la duración de las instancias de `HttpClientMessageHandler` subyacentes. La administración automática evita los problemas comunes de DNS (Sistema de nombres de dominio) que se producen al administrar la duración de `HttpClient` de forma manual.* Esto quiere decir que los problemas que indicamos al principio de este post, que nos provocaba trabajar con HttpClient de forma manual del riesgo de solicitudes que queden abiertas y colapso de los sockets, ya no existirían.