Inyección de Dependencias en ASP.NET Core: El Contenedor de Servicios

La configuración del contenedor de servicios es un aspecto crucial del desarrollo de aplicaciones en ASP.NET Core. La inyección de dependencias facilita la creación de aplicaciones modulares, escalables y mantenibles. En este artículo, vamos a sumergirnos en cómo configurar el contenedor de servicios para la inyección de dependencias, explorando los métodos de registro de servicios y profundizando en la selección del tiempo de vida de los servicios.

La Inyección de Dependencias y el Contenedor de Servicios

La inyección de dependencias es un patrón de diseño que promueve el desacoplamiento entre componentes al pasar las dependencias necesarias como argumentos en lugar de crearlas dentro del componente. Por otro lado, el contenedor de servicios es responsable de administrar estas dependencias, resolviéndolas y proporcionándolas cuando se solicitan.

En ASP.NET Core, el contenedor de servicios está representado por la interfaz IServiceProvider, que puede ser utilizada para resolver dependencias en cualquier parte de la aplicación.

 

Configuración del Contenedor de Servicios

En ASP.NET Core, la configuración del contenedor de servicios se realiza en el método ConfigureServices del archivo Startup.cs. Este método se invoca automáticamente durante el inicio de la aplicación y es donde registramos todos los servicios que nuestra aplicación necesita. El contenedor de servicios es responsable de gestionar el ciclo de vida de los servicios y de resolver las dependencias cuando se necesitan.

La configuración del contenedor de servicios se realiza mediante el objeto IServiceCollection, que se pasa como parámetro al método ConfigureServices. Aquí es donde utilizamos métodos como AddTransient, AddScoped y AddSingleton para registrar los servicios necesarios para nuestra aplicación.

Por ejemplo, supongamos que tenemos una interfaz IServicio y una implementación concreta ImplementacionServicio. Podemos registrar este servicio en el contenedor de servicios de la siguiente manera:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IServicio, ImplementacionServicio>();
}

Importante: En este código, estamos utilizando el método AddTransient para registrar ImplementacionServicio como una implementación de la interfaz IServicio. Esto significa que cada vez que se solicita un objeto que implementeIServicio, se crea y se devuelve una nueva instancia de ImplementacionServicio.

 

Métodos de Registro de Servicios

ASP.NET Core ofrece varios métodos para registrar servicios en el contenedor de servicios, cada uno con un tiempo de vida diferente.

AddTransient

El método AddTransient se utiliza para registrar un servicio con un tiempo de vida transitorio. Cada vez que se solicita el servicio, se crea una nueva instancia. Esto es útil para servicios ligeros que no mantienen estado y pueden ser recreados fácilmente en cada solicitud.

services.AddTransient<IServicio, ImplementacionServicio>();
AddScoped

El método AddScoped registra un servicio con un tiempo de vida scoped. Esto significa que se crea una instancia del servicio por cada ámbito de servicio, generalmente una instancia por solicitud HTTP en una aplicación web. Es útil para servicios que requieren un estado por solicitud y deben compartirse entre los componentes dentro del mismo ámbito.

services.AddScoped<IServicio, ImplementacionServicio>();
AddSingleton

El método AddSingleton registra un servicio con un tiempo de vida singleton. Se crea una única instancia del servicio y se reutiliza para todas las solicitudes posteriores en la aplicación. Es adecuado para servicios que requieren un estado compartido entre todas las partes de la aplicación y que pueden ser inicializados de forma costosa o deben mantener un estado persistente.

services.AddSingleton<IServicio, ImplementacionServicio>();

 

Selección del Tiempo de Vida

La elección del tiempo de vida adecuado para un servicio es crucial y depende de varios factores, como el estado del servicio, la frecuencia de uso y los requisitos de rendimiento. Aquí, vamos a ampliar las explicaciones sobre cómo seleccionar el tiempo de vida apropiado y proporcionar ejemplos de cada caso.

Factores a Considerar

  1. Estado del Servicio: ¿El servicio mantiene algún tipo de estado interno?

  2. Frecuencia de Uso: ¿Con qué frecuencia se utiliza el servicio en la aplicación?

  3. Requisitos de Rendimiento: ¿Cuáles son los requisitos de rendimiento de la aplicación?

Tiempos de Vida

Transient: Use servicios transitorios para operaciones ligeras y sin estado que se crean y se utilizan rápidamente. Por ejemplo, operaciones matemáticas básicas o generación de identificadores únicos.

services.AddTransient<ICalculationService, CalculationService>();

Scoped: Los servicios scoped son ideales para operaciones relacionadas con una solicitud específica, como el acceso a la sesión del usuario o la realización de operaciones en una transacción de base de datos.

services.AddScoped<IUserSessionService, UserSessionService>();

Singleton: Utilice servicios singleton para recursos compartidos y globales, como la configuración de la aplicación o la caché de datos.

services.AddSingleton<IConfigurationService, ConfigurationService>();

 

Ejemplos prácticos de Código

Transient 

Los servicios registrados como transitorios (AddTransient) se crean cada vez que se solicitan. Esto es útil para servicios que no mantienen estado y pueden ser reutilizados en diferentes partes de la aplicación sin problemas.

1. Definición de la Interfaz y la Implementación del Servicio:

Primero, definimos una interfaz que describe el contrato del servicio y una clase que implementa esta interfaz.

public interface IServicio
{
    void RealizarAccion();
}

public class ImplementacionServicio : IServicio
{
    public void RealizarAccion()
    {
        Console.WriteLine("Acción realizada por ImplementacionServicio.");
    }
}

En este ejemplo, IServicio define un método RealizarAccion que es implementado por ImplementacionServicio.

2. Registro del Servicio en ConfigureServices:

Luego, registramos ImplementacionServicio como un servicio transitorio en el contenedor de servicios dentro del método ConfigureServices del archivo Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    // Registro de ImplementacionServicio como un servicio transitorio
    services.AddTransient<IServicio, ImplementacionServicio>();
}

Nota: Aquí, AddTransient indica que cada vez que se solicita IServicio, se creará una nueva instancia de ImplementacionServicio.

3. Inyección del Servicio en un Controlador:

A continuación, inyectamos el servicio en un controlador. Esto se hace a través del constructor del controlador.

public class HomeController : Controller
{
    private readonly IServicio _servicio;

    // El servicio IServicio se inyecta en el constructor del controlador
    public HomeController(IServicio servicio)
    {
        _servicio = servicio;
    }

    public IActionResult Index()
    {
        // Utilizar el servicio
        _servicio.RealizarAccion();
        return View();
    }
}

En este ejemplo, HomeController tiene una dependencia de IServicio. Al inyectar IServicio en el constructor, podemos utilizar el método RealizarAccion en la acción Index del controlador.

4. Comportamiento del Servicio Transient:

Para demostrar el comportamiento de un servicio transitorio, podemos modificar ImplementacionServicio para que genere un identificador único para cada instancia.

public class ImplementacionServicio : IServicio
{
    private readonly Guid _instanceId;

    public ImplementacionServicio()
    {
        _instanceId = Guid.NewGuid();
    }

    public void RealizarAccion()
    {
        Console.WriteLine($"Acción realizada por ImplementacionServicio. ID de instancia: {_instanceId}");
    }
}

Cada vez que se crea una nueva instancia de ImplementacionServicio, se genera un nuevo Guid. Esto nos permite ver que cada solicitud de IServicio produce una nueva instancia.

 

Scoped

Los servicios registrados con ámbito (AddScoped) se crean una vez por cada solicitud (HTTP request). Esto significa que dentro de una misma solicitud, todas las instancias de un servicio con ámbito serán las mismas. Sin embargo, en solicitudes diferentes, se crean nuevas instancias.

1. Definición de la Interfaz y la Implementación del Servicio:

Primero, definimos una interfaz que describe el contrato del servicio y una clase que implementa esta interfaz.

public interface IServicio
{
    void RealizarAccion();
}

public class ImplementacionServicio : IServicio
{
    private readonly Guid _instanceId;

    public ImplementacionServicio()
    {
        _instanceId = Guid.NewGuid();
    }

    public void RealizarAccion()
    {
        Console.WriteLine($"Acción realizada por ImplementacionServicio. ID de instancia: {_instanceId}");
    }
}

En este ejemplo, IServicio define un método RealizarAccion que es implementado por ImplementacionServicio. Cada instancia de ImplementacionServicio tendrá un identificador único generado mediante Guid.NewGuid().

2. Registro del Servicio en ConfigureServices:

Luego, registramos ImplementacionServicio como un servicio con ámbito en el contenedor de servicios dentro del método ConfigureServices del archivo Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    // Registro de ImplementacionServicio como un servicio con ámbito (scoped)
    services.AddScoped<IServicio, ImplementacionServicio>();
}

Nota: Aquí, AddScoped indica que cada vez que se solicita IServicio dentro de una solicitud (HTTP request), se utilizará la misma instancia de ImplementacionServicio. En solicitudes diferentes, se crearán nuevas instancias.

3. Inyección del Servicio en un Controlador:

A continuación, inyectamos el servicio en un controlador. Esto se hace a través del constructor del controlador.

public class HomeController : Controller
{
    private readonly IServicio _servicio1;
    private readonly IServicio _servicio2;

    public HomeController(IServicio servicio1, IServicio servicio2)
    {
        _servicio1 = servicio1;
        _servicio2 = servicio2;
    }

    public IActionResult Index()
    {
        _servicio1.RealizarAccion();
        _servicio2.RealizarAccion();
        return View();
    }
}

En este ejemplo, el controlador HomeController tiene dos dependencias de IServicio. Al inyectar IServicio dos veces en el constructor, podemos observar el comportamiento de los servicios con ámbito dentro de una misma solicitud.

4. Comportamiento del Servicio Scoped:

Para demostrar el comportamiento de un servicio Scoped, podemos observar los mensajes generados por las instancias del servicio.

Cuando navegues a la acción Index, verás algo similar a:

Acción realizada por ImplementacionServicio. ID de instancia: 8a74b8e3-10b4-4c2b-b9d2-3a1e9a0763f8
Acción realizada por ImplementacionServicio. ID de instancia: 8a74b8e3-10b4-4c2b-b9d2-3a1e9a0763f8

Esto demuestra que dentro de una misma solicitud, _servicio1 y _servicio2 son la misma instancia de ImplementacionServicio.

Si realizas una nueva solicitud, verás un nuevo identificador de instancia:

Acción realizada por ImplementacionServicio. ID de instancia: 2c74a8f3-11b6-4f3b-b9e3-4b2e9a0764f9
Acción realizada por ImplementacionServicio. ID de instancia: 2c74a8f3-11b6-4f3b-b9e3-4b2e9a0764f9

Esto indica que una nueva instancia de ImplementacionServicio se creó para la nueva solicitud.

 

Singleton

Los servicios registrados como singleton (AddSingleton) se crean una única vez y se reutilizan durante toda la vida de la aplicación. Esto es útil para servicios que mantienen estado y se utilizan en múltiples lugares a lo largo del ciclo de vida de la aplicación.

1. Definición de la Interfaz y la Implementación del Servicio:

Primero, definimos una interfaz que describe el contrato del servicio y una clase que implementa esta interfaz.

public interface IServicio
{
    void RealizarAccion();
}

public class ImplementacionServicio : IServicio
{
    private readonly Guid _instanceId;

    public ImplementacionServicio()
    {
        _instanceId = Guid.NewGuid();
    }

    public void RealizarAccion()
    {
        Console.WriteLine($"Acción realizada por ImplementacionServicio. ID de instancia: {_instanceId}");
    }
}

En este ejemplo, IServicio define un método RealizarAccion que es implementado por ImplementacionServicio. Cada instancia de ImplementacionServicio tendrá un identificador único generado mediante Guid.NewGuid().

2. Registro del Servicio en ConfigureServices:

Luego, registramos ImplementacionServicio como un servicio singleton en el contenedor de servicios dentro del método ConfigureServices del archivo Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    // Registro de ImplementacionServicio como un servicio singleton
    services.AddSingleton<IServicio, ImplementacionServicio>();
}

Nota: Aquí, AddSingleton indica que solo se creará una única instancia de ImplementacionServicio y se reutilizará en todas las solicitudes de la aplicación.

3. Inyección del Servicio en un Controlador:

A continuación, inyectamos el servicio en un controlador. Esto se hace a través del constructor del controlador.

public class HomeController : Controller
{
    private readonly IServicio _servicio1;
    private readonly IServicio _servicio2;

    public HomeController(IServicio servicio1, IServicio servicio2)
    {
        _servicio1 = servicio1;
        _servicio2 = servicio2;
    }

    public IActionResult Index()
    {
        _servicio1.RealizarAccion();
        _servicio2.RealizarAccion();
        return View();
    }
}

En este ejemplo, el controlador HomeController tiene dos dependencias de IServicio. Al inyectar IServicio dos veces en el constructor, podemos observar el comportamiento de los servicios singleton dentro de una misma solicitud y entre diferentes solicitudes.

4. Comportamiento del Servicio Singleton:

Para demostrar el comportamiento de un servicio singleton, podemos observar los mensajes generados por las instancias del servicio.

Cuando navegues a la acción Index, verás algo similar a:

Acción realizada por ImplementacionServicio. ID de instancia: 8a74b8e3-10b4-4c2b-b9d2-3a1e9a0763f8
Acción realizada por ImplementacionServicio. ID de instancia: 8a74b8e3-10b4-4c2b-b9d2-3a1e9a0763f8

Esto demuestra que _servicio1 y _servicio2 son la misma instancia de ImplementacionServicio porque fueron registrados como singleton. La instancia es la misma en todas las solicitudes de la aplicación.

Si realizas una nueva solicitud, verás el mismo identificador de instancia:

Acción realizada por ImplementacionServicio. ID de instancia: 8a74b8e3-10b4-4c2b-b9d2-3a1e9a0763f8
Acción realizada por ImplementacionServicio. ID de instancia: 8a74b8e3-10b4-4c2b-b9d2-3a1e9a0763f8

Esto indica que la instancia de ImplementacionServicio se reutiliza en todas las solicitudes.

 

Conclusiones

La configuración del contenedor de servicios y la selección adecuada del tiempo de vida de los servicios son aspectos cruciales del desarrollo de aplicaciones en ASP.NET Core. Al entender estos conceptos y aplicarlos correctamente, los desarrolladores pueden construir aplicaciones más eficientes, escalables y mantenibles. La inyección de dependencias facilita la modularidad del código, mejora la testabilidad y promueve un diseño limpio y desacoplado.

 

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

  Comentarios

No hay comentarios para este Post.



Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto