Cómo usar Dependency Injection correctamente en ASP.NET Core

Cómo usar Dependency Injection correctamente en ASP.NET Core

La Dependency Injection (DI) es una de las características más importantes de ASP.NET Core y una práctica fundamental para desarrollar aplicaciones limpias, escalables y fáciles de mantener. Gracias a este patrón, las clases reciben las dependencias que necesitan en lugar de crearlas directamente, lo que reduce el acoplamiento y facilita las pruebas unitarias.

En ASP.NET Core, el contenedor de Dependency Injection viene integrado, por lo que no es necesario instalar librerías adicionales para comenzar a utilizarlo. Sin embargo, conocer las buenas prácticas marcará la diferencia entre un proyecto organizado y uno difícil de mantener.

¿Qué es Dependency Injection?

La inyección de dependencias consiste en proporcionar a una clase los objetos que necesita para funcionar desde el exterior, normalmente a través del constructor.

Por ejemplo, en lugar de crear un repositorio dentro del servicio:

public class ProductService
{
    private readonly ProductRepository _repository = new ProductRepository();
}

Lo recomendable es recibirlo mediante el constructor:

public class ProductService
{
    private readonly IProductRepository _repository;

    public ProductService(IProductRepository repository)
    {
        _repository = repository;
    }
}

De esta manera, el servicio depende de una interfaz y no de una implementación específica, haciendo el código mucho más flexible.

Registrar los servicios

En ASP.NET Core, todos los servicios se registran en el archivo Program.cs.

builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IProductService, ProductService>();

Una vez registrados, el framework se encargará de crear automáticamente las instancias cuando sean necesarias.

Por ejemplo, un controlador puede recibir el servicio simplemente agregándolo al constructor.

public class ProductsController : ControllerBase
{
    private readonly IProductService _service;

    public ProductsController(IProductService service)
    {
        _service = service;
    }
}

No es necesario crear manualmente el objeto con new, ya que el contenedor de Dependency Injection lo hará por nosotros.

Elegir el ciclo de vida adecuado

Uno de los aspectos más importantes es seleccionar correctamente el ciclo de vida del servicio.

Transient crea una nueva instancia cada vez que el servicio es solicitado. Es recomendable para servicios ligeros que no almacenan información entre llamadas.

builder.Services.AddTransient<IEmailService, EmailService>();

Scoped crea una única instancia por cada petición HTTP. Es la opción más utilizada para servicios de negocio y repositorios.

builder.Services.AddScoped<IProductService, ProductService>();

Singleton crea una sola instancia durante toda la vida de la aplicación. Es ideal para servicios de configuración, caché o componentes compartidos.

builder.Services.AddSingleton<ICacheService, CacheService>();

Elegir un ciclo de vida incorrecto puede provocar errores inesperados o problemas de rendimiento, por lo que es importante entender cuándo utilizar cada uno.

Programa siempre contra interfaces

Una buena práctica consiste en registrar interfaces en lugar de clases concretas.

builder.Services.AddScoped<IEmailSender, SmtpEmailSender>();

Esto permite cambiar la implementación sin modificar el resto del código y facilita la creación de pruebas unitarias utilizando mocks.

Evita crear dependencias manualmente

Uno de los errores más comunes es utilizar el operador new dentro de los servicios.

Incorrecto:

var repository = new ProductRepository();

Correcto:

public ProductService(IProductRepository repository)
{
    _repository = repository;
}

Dejar que el contenedor gestione las dependencias mejora la organización del proyecto y simplifica el mantenimiento.

Mantén los servicios con una única responsabilidad

Si un servicio recibe demasiadas dependencias en el constructor, probablemente está haciendo más trabajo del que debería.

public OrderService(
    ILogger<OrderService> logger,
    IEmailService emailService,
    IPaymentService paymentService,
    IInventoryService inventoryService,
    IUserService userService)

Cuando ocurre esto, es recomendable dividir la lógica en varios servicios más pequeños y especializados.

Inyectar la configuración

ASP.NET Core también permite inyectar valores de configuración mediante el patrón Options.

builder.Services.Configure(
builder.Configuration.GetSection(“Jwt”));

Luego pueden utilizarse de forma sencilla:

public class TokenService
{
    private readonly JwtSettings _settings;

    public TokenService(IOptions<JwtSettings> options)
    {
        _settings = options.Value;
    }
}

Este enfoque mantiene el código limpio y evita acceder continuamente a IConfiguration.

Beneficios de utilizar Dependency Injection

Implementar correctamente Dependency Injection ofrece numerosas ventajas:

  • Reduce el acoplamiento entre las clases.
  • Facilita las pruebas unitarias.
  • Mejora la organización del código.
  • Permite reemplazar implementaciones fácilmente.
  • Hace que la aplicación sea más escalable y mantenible.
  • Aprovecha el contenedor integrado de ASP.NET Core sin necesidad de herramientas externas.

Dependency Injection es una de las bases del desarrollo moderno en ASP.NET Core. Utilizar correctamente los ciclos de vida, programar contra interfaces y dejar que el contenedor gestione las dependencias permitirá construir aplicaciones mucho más limpias, flexibles y fáciles de mantener. Aplicando estas buenas prácticas desde el inicio, tus proyectos estarán preparados para crecer sin que el código se vuelva complejo o difícil de administrar.

Te puede interesar...

Deja un comentario