Duración de los Servicios de Inyección de Dependencias en ASP.NET Core
La vida útil de un servicio define cuánto dura su instancia, o qué tan actualizado debe estar. Por ejemplo, ¿Hay que crear una nueva instancia del servicio cada vez que se necesita? O ¿Hay que crearla por cada solicitud? o ¿Hay que usar la misma instancia en toda la aplicación? La vida útil del servicio puede afectar el comportamiento de la aplicación; por lo que es muy importante comprender cómo funciona.
Los servicios se pueden registrar con una de las duraciones siguientes:
- Transitorio o Transient.
- Con ámbito o Scoped.
- Singleton.
Transitorio o Transient
Los servicios de duración transitoria se crean cada vez que el contenedor de servicios recibe una solicitud, sin importar el contexto. Es indiferente si se solicitan dos o más veces en el mismo contexto, siempre se devolverá una nueva instancia.
Considere la siguiente interfaz ITransient
:
using System;
public interface ITransient
{
Guid LifetimeId { get; }
}
El código C# anterior:
- Define la interfaz o el servicio
ITransient
con la propiedadLifetimeId
.
El siguiente código crea la clase Transient
, e implementa la interfaz ITransient
a partir del código anterior:
using System;
public class Transient : ITransient
{
public Guid LifetimeId { get; }
public Transient()
{
LifetimeId = Guid.NewGuid();
}
}
El código C# anterior:
- Genera un nuevo identificador único global (GUID) en el constructor, con el objetivo de mostrar las veces que el contenedor de servicios resuelve la dependencia.
- Asigna el nuevo identificador GUID a la propiedad
LifetimeId
.
El siguiente código muestra el registro del servicio ITransient
en el contenedor de servicios:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<ITransient, Transient>();
//Se omitió código por razón de brevedad.
var app = builder.Build();
//Se omitió código por razón de brevedad.
app.Run();
El código C# anterior:
- Registra el servicio
ITransient
con duración Transient en el contenedor de servicios mediante el métodoAddTransient
.
La siguiente Razor Page muestra el uso del servicio con ciclo de vida Transient:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
public class IndexModel : PageModel
{
private readonly ITransient _transient1;
private readonly ITransient _transient2;
public IndexModel(ITransient transient1,
ITransient transient2)
{
_transient1 = transient1;
_transient2 = transient2;
}
public ContentResult OnGet()
{
return Content($"LifetimeId Servicio Transient 1: '{_transient1.LifetimeId}' \n" +
$"LifetimeId Servicio Transient 2: '{_transient2.LifetimeId}'");
}
}
El código C# anterior:
- Solicita dos instancias del servicio
ITransient
:_transient1
y_transient2
. - Muestra el identificador GUID de cada uno de los servicios resueltos.
LifetimeId Servicio Transient 1: 'ec622efc-ad55-4346-9672-eb11b44584aa'
LifetimeId Servicio Transient 2: '43565c50-e207-418e-b1c0-dfbf21ba07a8'
Al refrescar la página en el navegador (F5):
LifetimeId Servicio Transient 1: 'fb475c79-f16a-48d3-b0d0-a5779fc32318'
LifetimeId Servicio Transient 2: 'cacadf83-1bb9-477c-b8a1-8100ee250677'
Al refrescar nuevamente la página en el navegador (F5):
LifetimeId Servicio Transient 1: '6ddc83fa-8eac-453d-92a1-125a224345cc'
LifetimeId Servicio Transient 2: '6844b978-3138-458d-a857-6e63cc003a03'
Como se puede apreciar en los 3 resultados, cada vez que se solicita el servicio ITransient
al contenedor, devuelve diferentes instancias y, por lo tanto, diferentes identificadores GUID. Indiferente si es la misma solicitud o una nueva.
Con Ámbito o Scoped
Los servicios con duración de ámbito se crean una vez por cada solicitud de cliente (conexión), y se eliminan al final de la solicitud.
Considere la siguiente interfaz IScoped
:
using System;
public interface IScoped
{
Guid LifetimeId { get; }
}
El código C# anterior:
- Define la interfaz o el servicio
IScoped
con la propiedadLifetimeId
.
El siguiente código crea la clase Scoped
, e implementa la interfaz IScoped
a partir del código anterior:
using System;
public class Scoped : IScoped
{
public Guid LifetimeId { get; }
public Scoped()
{
LifetimeId = Guid.NewGuid();
}
}
El código C# anterior:
- Genera un nuevo identificador único global (GUID) en el constructor, con el objetivo de mostrar las veces que el contenedor de servicios resuelve la dependencia.
- Asigna el nuevo identificador GUID a la propiedad
LifetimeId
.
El siguiente código muestra el registro del servicio IScoped
en el contenedor de servicios:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IScoped, Scoped>();
//Se omitió código por razón de brevedad.
var app = builder.Build();
//Se omitió código por razón de brevedad.
app.Run();
El código C# anterior:
- Registra el servicio
IScoped
con duración Scoped en el contenedor de servicios mediante el métodoAddScoped
.
La siguiente Razor Page muestra el uso del servicio con ciclo de vida Scoped:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
public class IndexModel : PageModel
{
private readonly IScoped _scoped1;
private readonly IScoped _scoped2;
public IndexModel(IScoped scoped1,
IScoped scoped2)
{
_scoped1 = scoped1;
_scoped2 = scoped2;
}
public ContentResult OnGet()
{
return Content($"LifetimeId Servicio Scoped 1: '{_scoped1.LifetimeId}' \n" +
$"LifetimeId Servicio Scoped 2: '{_scoped2.LifetimeId}'");
}
}
El código C# anterior:
- Solicita dos instancias del servicio
IScoped
:_scoped1
y_scoped2
. - Muestra el identificador GUID de cada uno de los servicios resueltos.
LifetimeId Servicio Scoped 1: '0af62a42-106e-4c97-9242-9ea939f1c690'
LifetimeId Servicio Scoped 2: '0af62a42-106e-4c97-9242-9ea939f1c690'
Al refrescar la página en el navegador (F5):
LifetimeId Servicio Scoped 1: 'db2c67f9-4143-47a1-9b0a-8e875ed04587'
LifetimeId Servicio Scoped 2: 'db2c67f9-4143-47a1-9b0a-8e875ed04587'
Al refrescar nuevamente la página en el navegador (F5):
LifetimeId Servicio Scoped 1: 'cb931726-1cf5-456b-8bf3-8b766d5c5288'
LifetimeId Servicio Scoped 2: 'cb931726-1cf5-456b-8bf3-8b766d5c5288'
Como se puede apreciar en los 3 resultados, cada vez que se solicita el servicio IScoped
al contenedor, devuelve el mismo identificador GUID. Sin embargo, este cambia cada vez que se actualiza la página en el navegador.
Singleton
Los servicios de duración de singleton se crean la primera vez que se solicitan. Con cada solicitud, el contenedor de inserción de dependencias utiliza la misma instancia. Se registran mediante el método AddSingleton
.
Los servicios con duración singleton proveen la misma instancia cada vez que se solicita, independientemente del alcance.
Los servicios singleton deben ser seguros para los subprocesos y se suelen usar en servicios sin estado.
En las aplicaciones que procesan solicitudes, los servicios singleton se eliminan cuando ServiceProvider se elimina al cerrarse la aplicación. Como no se libera memoria hasta que se apaga la aplicación, se debe tener en cuenta el uso de memoria con un servicio singleton.
Este tipo de duración es adecuada para procesos que no necesitan memoria, ya que no cambian con demasiada frecuencia. Es adecuado escenarios relacionados con la configuración de ASP.NET Core.
Considere la siguiente interfaz ISingleton
:
using System;
public interface ISingleton
{
Guid LifetimeId { get; }
}
El código C# anterior:
- Define la interfaz o el servicio
ISingleton
con la propiedadLifetimeId
.
El siguiente código crea la clase Singleton
, e implementa la interfaz ISingleton
a partir del código anterior:
using System;
public class Singleton : ISingleton
{
public Guid LifetimeId { get; }
public Singleton()
{
LifetimeId = Guid.NewGuid();
}
}
El código C# anterior:
- Genera un nuevo identificador único global (GUID) en el constructor, con el objetivo de mostrar las veces que el contenedor de servicios resuelve la dependencia.
- Asigna el nuevo identificador GUID a la propiedad
LifetimeId
.
El siguiente código muestra el registro del servicio ISingleton
en el contenedor de servicios:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ISingleton, Singleton>();
//Se omitió código por razón de brevedad.
var app = builder.Build();
//Se omitió código por razón de brevedad.
app.Run();
El código C# anterior:
- Registra el servicio
ISingleton
con duración Singleton en el contenedor de servicios mediante el métodoAddSingleton
.
La siguiente Razor Page muestra el uso del servicio con ciclo de vida Singleton:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
public class IndexModel : PageModel
{
private readonly ISingleton _singleton1;
private readonly ISingleton _singleton2;
public IndexModel(ISingleton singleton1,
ISingleton singleton2)
{
_singleton1 = singleton1;
_singleton2 = singleton2;
}
public ContentResult OnGet()
{
return Content($"LifetimeId Servicio Singleton 1: '{_singleton1.LifetimeId}' \n" +
$"LifetimeId Servicio Singleton 2: '{_singleton2.LifetimeId}'");
}
}
El código C# anterior:
- Solicita dos instancias del servicio
ISingleton
:_singleton1
y_singleton2
. - Muestra el identificador GUID de cada uno de los servicios resueltos.
LifetimeId Servicio 1: '2c3b0c75-78ea-4d6a-96be-582f190774a3'
LifetimeId Servicio 2: '2c3b0c75-78ea-4d6a-96be-582f190774a3'
Como se puede apreciar en el resultado, cada vez que se solicita el servicio ISingleton
al contenedor, devuelve el mismo identificador GUID, lo que significa que siempre resuelve la misma instancia de la clase Singleton
, independiente de las veces que se refresque (F5) la página.
Escenarios
La siguiente tabla resume los escenarios de estado disponibles y la vida útil del servicio adecuada para cada uno.
Estado del Servicio | Duración a Elegir |
---|---|
Nivel de aplicación global | Singleton |
Por solicitud o acción | Con Ámbito o Scoped |
No tiene estado o no lo comparte | Transitorio o Transient |
Artículos Relacionados
Recursos Adicionales
- Inserción de dependencias en ASP.NET Core.
- Inserción de dependencias .NET.
- Duraciones de servicios.
- Instrucciones para la inserción de dependencias.
- Inserción de dependencias en controladores en ASP.NET Core.
- Principios de la arquitectura.
- Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler.
- Dependency Injection Is NOT The Same As The Dependency Inversion Principle by Derick Bailey.
- Design Patterns: Elements of Reusable Object-Oriented Software.