Abelardo Ruben Irarrázabal Díaz
Posted on October 21, 2022
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:
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
Lo primero es crear un proyecto con la plantilla de tipo ASP .NET Core Web API
El nombre no es relevante pero para esta ocasión será HttpClientApi
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.
3. Implementemos IHttpClientFactory
Ya tenemos lista nuestra estructura !!!
Ahora vamos al código
Clases de la capa Models
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; }
}
namespace HttpClientApi.Models;
public class Images
{
public string Main { get; set; }
public string Full { get; set; }
public string Thumb { get; set; }
}
namespace HttpClientApi.Models;
public class Links
{
public string Self { get; set; }
public string Parent { get; set; }
}
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:
{
"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:
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
var configuration = builder.Configuration;
- En el siguiente bloque implementamos
IHttpClientFactory
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 elHttpClient
.-
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:
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:
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
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 deHttpClient
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.
Posted on October 21, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.