ASP.NET CORE

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 Subir

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:

ITransient.cs
using System;

public interface ITransient
{
   Guid LifetimeId { get; }
}

El código C# anterior:

El siguiente código crea la clase Transient, e implementa la interfaz ITransient a partir del código anterior:

Transient.cs
using System;

public class Transient : ITransient
{
    public Guid LifetimeId { get; }

    public Transient()
    {
        LifetimeId = Guid.NewGuid();
    }
}

El código C# anterior:

El siguiente código muestra el registro del servicio ITransient en el contenedor de servicios:

Program.cs
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:

La siguiente Razor Page muestra el uso del servicio con ciclo de vida Transient:

Program.cs
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:

Resultado 1
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):

Resultado 2
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):

Resultado 3
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 Subir

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:

IScoped.cs
using System;

public interface IScoped
{
   Guid LifetimeId { get; }
}

El código C# anterior:

El siguiente código crea la clase Scoped, e implementa la interfaz IScoped a partir del código anterior:

Scoped.cs
using System;

public class Scoped : IScoped
{
    public Guid LifetimeId { get; }

    public Scoped()
    {
       LifetimeId = Guid.NewGuid();
    }
}

El código C# anterior:

El siguiente código muestra el registro del servicio IScoped en el contenedor de servicios:

Program.cs
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:

La siguiente Razor Page muestra el uso del servicio con ciclo de vida Scoped:

Index.razor
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:

Resultado 1
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):

Resultado 2
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):

Resultado 3
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 Subir

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:

ISingleton.cs
using System;

public interface ISingleton
{
   Guid LifetimeId { get; }
}

El código C# anterior:

El siguiente código crea la clase Singleton, e implementa la interfaz ISingleton a partir del código anterior:

Singleton.cs
using System;

public class Singleton : ISingleton
{
   public Guid LifetimeId { get; }

   public Singleton()
   {
      LifetimeId = Guid.NewGuid();
   }
}

El código C# anterior:

El siguiente código muestra el registro del servicio ISingleton en el contenedor de servicios:

Program.cs
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:

La siguiente Razor Page muestra el uso del servicio con ciclo de vida Singleton:

Index.razor
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:

Resultado
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 Subir

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