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 registrarImplementacionServicio
como una implementación de la interfazIServicio
. Esto significa que cada vez que se solicita un objeto que implementeIServicio
, se crea y se devuelve una nueva instancia deImplementacionServicio
.
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
-
Estado del Servicio: ¿El servicio mantiene algún tipo de estado interno?
-
Frecuencia de Uso: ¿Con qué frecuencia se utiliza el servicio en la aplicación?
-
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 solicitaIServicio
, se creará una nueva instancia deImplementacionServicio
.
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 solicitaIServicio
dentro de una solicitud (HTTP request), se utilizará la misma instancia deImplementacionServicio
. 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 deImplementacionServicio
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.
Nuevo comentario
Comentarios
No hay comentarios para este Post.