AddTransient, AddScoped y AddSingleton Services Differences

938

Quiero implementar la inyección de dependencia (DI) en ASP.NET Core. Entonces, después de agregar este código al ConfigureServicesmétodo, ambas formas funcionan.

¿Cuál es la diferencia entre los métodos services.AddTransienty service.AddScopeden ASP.NET Core?

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}
Elvin Mammadov
fuente
92
@tmg Los documentos dicen 'Los servicios transitorios de por vida se crean cada vez que se solicitan'. y 'Los servicios de por vida con ámbito se crean una vez por solicitud'. que a menos que mi inglés sea más débil de lo que pensaba, en realidad significa exactamente lo mismo.
Neutrino
70
@ tmg lo sé. Solo estoy señalando que los documentos no están del todo claros en este punto, por lo que señalar a las personas a los documentos no es muy útil.
Neutrino
13
@Neutrino, es por eso que hice esta pregunta.
Elvin Mammadov
55
Tarde a la fiesta, leyendo los comentarios incluso más tarde, pero imprimí ese artículo, lo leí y anoté la misma observación en el margen que ahora veo que @Neutrino hizo aquí. El artículo fue TOTALMENTE vago al ofrecer ese análisis. El ejemplo, afortunadamente, fue menos confuso.
Wellspring
55
Según tengo entendido: los servicios transitorios de por vida se crean cada vez que se solicitan . La palabra solicitada aquí es el significado cotidiano en inglés de pedir algo, en este caso un servicio. Mientras que la palabra solicitud en una vez por solicitud se refiere a una solicitud HTTP. Pero sí entiendo la confusión.
Memet Olsen

Respuestas:

1656

TL; DR

Los objetos transitorios son siempre diferentes; Se proporciona una nueva instancia para cada controlador y cada servicio.

Los objetos de ámbito son los mismos dentro de una solicitud, pero diferentes en diferentes solicitudes.

Los objetos Singleton son los mismos para cada objeto y cada solicitud.

Para más aclaraciones, este ejemplo de la documentación de ASP.NET muestra la diferencia:

Para demostrar la diferencia entre estas opciones de vida y de registro, considere una interfaz simple que representa una o más tareas como una operación con un identificador único, OperationId. Dependiendo de cómo configuremos la vida útil de este servicio, el contenedor proporcionará la misma o diferentes instancias del servicio a la clase solicitante. Para dejar en claro qué duración se solicita, crearemos una opción de tipo por duración:

using System;

namespace DependencyInjectionSample.Interfaces
{
    public interface IOperation
    {
        Guid OperationId { get; }
    }

    public interface IOperationTransient : IOperation
    {
    }

    public interface IOperationScoped : IOperation
    {
    }

    public interface IOperationSingleton : IOperation
    {
    }

    public interface IOperationSingletonInstance : IOperation
    {
    }
}

Implementamos estas interfaces usando una sola clase, Operationque acepta un GUID en su constructor, o usa un nuevo GUID si no se proporciona ninguno:

using System;
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Classes
{
    public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance
    {
        Guid _guid;
        public Operation() : this(Guid.NewGuid())
        {

        }

        public Operation(Guid guid)
        {
            _guid = guid;
        }

        public Guid OperationId => _guid;
    }
}

A continuación, en ConfigureServices, cada tipo se agrega al contenedor de acuerdo con su vida útil nombrada:

services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();

Tenga en cuenta que el IOperationSingletonInstanceservicio está utilizando una instancia específica con un ID conocido de Guid.Empty, por lo que quedará claro cuando este tipo esté en uso. También hemos registrado uno OperationServiceque depende de cada uno de los otros Operationtipos, de modo que quede claro dentro de una solicitud si este servicio está obteniendo la misma instancia que el controlador, o una nueva, para cada tipo de operación. Todo lo que hace este servicio es exponer sus dependencias como propiedades, para que puedan mostrarse en la vista.

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
    public class OperationService
    {
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }

        public OperationService(IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance instanceOperation)
        {
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = instanceOperation;
        }
    }
}

Para demostrar las vidas de los objetos dentro y entre solicitudes individuales separadas a la aplicación, la muestra incluye un OperationsControllerque solicita cada tipo de IOperationtipo, así como un OperationService. La Indexacción luego muestra todos los OperationIdvalores del controlador y del servicio .

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
    public class OperationsController : Controller
    {
        private readonly OperationService _operationService;
        private readonly IOperationTransient _transientOperation;
        private readonly IOperationScoped _scopedOperation;
        private readonly IOperationSingleton _singletonOperation;
        private readonly IOperationSingletonInstance _singletonInstanceOperation;

        public OperationsController(OperationService operationService,
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance singletonInstanceOperation)
        {
            _operationService = operationService;
            _transientOperation = transientOperation;
            _scopedOperation = scopedOperation;
            _singletonOperation = singletonOperation;
            _singletonInstanceOperation = singletonInstanceOperation;
        }

        public IActionResult Index()
        {
            // ViewBag contains controller-requested services
            ViewBag.Transient = _transientOperation;
            ViewBag.Scoped = _scopedOperation;
            ViewBag.Singleton = _singletonOperation;
            ViewBag.SingletonInstance = _singletonInstanceOperation;

            // Operation service has its own requested services
            ViewBag.Service = _operationService;
            return View();
        }
    }
}

Ahora se realizan dos solicitudes separadas para esta acción del controlador:

Primera solicitud

Segundo requisito

Observe cuál de los OperationIdvalores varía dentro de una solicitud y entre solicitudes.

  • Los objetos transitorios son siempre diferentes; Se proporciona una nueva instancia para cada controlador y cada servicio.

  • Los objetos de ámbito son los mismos dentro de una solicitud, pero diferentes en diferentes solicitudes

  • Los objetos Singleton son los mismos para cada objeto y cada solicitud (independientemente de si se proporciona una instancia ConfigureServices)

akazemis
fuente
14
Comprendí las funciones de cada uno de ellos, pero ¿alguien puede explicar el impacto de usar uno en lugar del otro? ¿Qué problemas puede causar si no se usa correctamente o elige uno en lugar de otro?
Pawan Nepal
2
Supongamos que está creando un objeto relacionado con el contexto de solicitud (como el usuario actual) con un alcance único, entonces seguirá siendo la misma instancia en todas las solicitudes http que no se desea. IOC se trata de crear instancias, por lo que debemos especificar cuál es el alcance de la instancia creada.
akazemis
1
¡es! ¡He mencionado el enlace en la parte superior del tema! el código de muestra se copia / pega desde documentos de MS
akazemis
1
Gracias. Sí, Singleton será el mismo en toda la aplicación, independientemente de la sesión / usuario. obviamente, si su aplicación está utilizando la arquitectura de microservicios y cada servicio se ejecuta en un proceso separado, el singleton será el mismo en cada proceso
akazemis
1
¿Puede darnos un ejemplo del uso de addTransient, por favor? porque no encontré ninguna utilidad para usarlo mientras usa demasiados recursos
Terai
319

En la inyección de dependencia de .NET hay tres vidas principales:

Singleton que crea una sola instancia en toda la aplicación. Crea la instancia por primera vez y reutiliza el mismo objeto en todas las llamadas.

Los servicios de por vida con ámbito se crean una vez por solicitud dentro del alcance. Es equivalente a un singleton en el alcance actual. Por ejemplo, en MVC crea una instancia para cada solicitud HTTP, pero usa la misma instancia en las otras llamadas dentro de la misma solicitud web.

Los servicios transitorios de por vida se crean cada vez que se solicitan. Esta vida útil funciona mejor para servicios ligeros y sin estado.

Aquí puedes encontrar ejemplos para ver la diferencia:

Inyección de dependencia de ASP.NET 5 MVC6 en 6 pasos (enlace a archivo web debido a enlace inactivo )

Su inyección de dependencia lista ASP.NET: ASP.NET 5

Y este es el enlace a la documentación oficial:

Inyección de dependencias en ASP.NET Core

akazemis
fuente
22
¿Podría explicar por qué el transitorio es el más ligero? Pensé que el transitorio es el trabajo más pesado porque necesita crear una instancia cada vez para cada inyección.
Experto quiere ser
17
Tienes razón. Transitoria no es el más ligero, que acabo de decir que es adecuado para los servicios REST ligeros :)
akazemis
3
Entonces, ¿en qué escenario podríamos usar el ámbito y en qué ejemplo transitorio en el controlador, por ejemplo, si estamos recuperando algunas filas de la base de datos? Estoy tratando de entender el escenario de uso con alcance vs transitorio en este caso.
sensei
44
realmente depende de la lógica que esperas. Por ejemplo, si se trata de una única llamada db, en realidad no hace ninguna diferencia cuál está usando. pero si está llamando a db varias veces en la misma solicitud, puede usar la duración de ámbito, ya que mantiene el mismo objeto de repositorio en la memoria y lo reutiliza varias veces dentro del mismo contexto de solicitud Http. Mientras que el transitorio crea un nuevo objeto de repositorio varias veces (y consume más memoria). Si explica su escenario específico, sería fácil juzgar cuál se adapta mejor.
akazemis
3
Un punto importante para resaltar aquí es Singleton, Scoped y Transient son como doills rusos, uno dentro del otro. No es posible revertir su orden al anidar, por ejemplo. un alcance o singleton no puede estar contenido en un transitorio, porque estaríamos extendiendo la vida útil del padre, lo que va en contra de la contención.
DL Narasimhan
34

El proceso de creación de objetos transitorio, de ámbito y de definición única en ASP.NET MVC core DI cuando se deben inyectar varios objetos del mismo tipo. En caso de que sea nuevo en la inyección de dependencias, puede ver este video DI IoC .

Puede ver el siguiente código de controlador en el que solicité dos instancias de "IDal" en el constructor. Transient, Scoped y Singleton definen si la misma instancia se inyectará en "_dal" y "_dal1" o diferente.

public class CustomerController : Controller
{
    IDal dal = null;

    public CustomerController(IDal _dal,
                              IDal _dal1)
    {
        dal = _dal;
        // DI of MVC core
        // inversion of control
    }
}

Transitorio: en transitorio, se inyectarán nuevas instancias de objeto en una sola solicitud y respuesta. A continuación se muestra una imagen instantánea donde mostré valores GUID.

Ingrese la descripción de la imagen aquí

Ámbito: en ámbito, la misma instancia de objeto se inyectará en una sola solicitud y respuesta.

Ingrese la descripción de la imagen aquí

Singleton: en singleton, se inyectará el mismo objeto en todas las solicitudes y respuestas. En este caso, se creará una instancia global del objeto.

A continuación se muestra un diagrama simple que explica los principios fundamentales anteriores visualmente.

Imagen MVC DI

La imagen de arriba fue dibujada por el equipo de SBSS cuando estaba tomando el entrenamiento ASP.NET MVC en Mumbai . Muchas gracias al equipo de SBSS por crear la imagen de arriba.

Shivprasad Koirala
fuente
99
Esta es la explicación más complicada de un servicio transitorio que he visto. Transitoria = Cada vez que se resuelve este servicio es equivalente a asignar su variable new TService. Scoped almacenará en caché la primera inicialización de ese "alcance" (solicitud http en la mayoría de los casos). Singleton almacenará en caché solo una instancia para toda la vida de la aplicación, así de simple. Los diagramas anteriores son muy complicados.
Mardoxx
2
Lo siento, pensé que lo haría más simple con diagramas e instantáneas de código :-) Pero entiendo tu punto.
Shivprasad Koirala
30
  • Singleton es una instancia única para la vida útil del dominio de la aplicación.
  • Ámbito es una instancia única para la duración de la solicitud de ámbito, lo que significa por solicitud HTTP en ASP.NET.
  • Transitorio es una instancia única por solicitud de código .

Normalmente, la solicitud de código debe hacerse a través de un parámetro constructor, como en

public MyConsumingClass(IDependency dependency)

Quería señalar en la respuesta de @ akazemis que "servicios" en el contexto de DI no implica servicios RESTful; Los servicios son implementaciones de dependencias que proporcionan funcionalidad.

usuario1969177
fuente
16

AddSingleton ()

AddSingleton () crea una única instancia del servicio cuando se solicita por primera vez y reutiliza esa misma instancia en todos los lugares donde se necesita ese servicio.

AddScoped ()

En un servicio con alcance, con cada solicitud HTTP, obtenemos una nueva instancia. Sin embargo, dentro de la misma solicitud HTTP, si el servicio se requiere en varios lugares, como en la vista y en el controlador, se proporciona la misma instancia para todo el alcance de esa solicitud HTTP. Pero cada nueva solicitud HTTP obtendrá una nueva instancia del servicio.

AddTransient ()

Con un servicio transitorio, se proporciona una nueva instancia cada vez que se solicita una instancia de servicio, ya sea que esté dentro del alcance de la misma solicitud HTTP o en diferentes solicitudes HTTP.

Yasser Shaikh
fuente
5

Después de buscar una respuesta a esta pregunta, encontré una explicación brillante con un ejemplo que me gustaría compartir con ustedes.

Puedes ver un video que demuestra las diferencias AQUÍ

En este ejemplo tenemos este código dado:

public interface IEmployeeRepository
{
    IEnumerable<Employee> GetAllEmployees();
    Employee Add(Employee employee);
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MockEmployeeRepository : IEmployeeRepository
{
    private List<Employee> _employeeList;

    public MockEmployeeRepository()
    {
        _employeeList = new List<Employee>()
    {
        new Employee() { Id = 1, Name = "Mary" },
        new Employee() { Id = 2, Name = "John" },
        new Employee() { Id = 3, Name = "Sam" },
    };
    }

    public Employee Add(Employee employee)
    {
        employee.Id = _employeeList.Max(e => e.Id) + 1;
        _employeeList.Add(employee);
        return employee;
    }

    public IEnumerable<Employee> GetAllEmployees()
    {
        return _employeeList;
    }
}

HomeController

public class HomeController : Controller
{
    private IEmployeeRepository _employeeRepository;

    public HomeController(IEmployeeRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }

    [HttpGet]
    public ViewResult Create()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Create(Employee employee)
    {
        if (ModelState.IsValid)
        {
            Employee newEmployee = _employeeRepository.Add(employee);
        }

        return View();
    }
}

Crear vista

@model Employee
@inject IEmployeeRepository empRepository

<form asp-controller="home" asp-action="create" method="post">
    <div>
        <label asp-for="Name"></label>
        <div>
            <input asp-for="Name">
        </div>
    </div>

    <div>
        <button type="submit">Create</button>
    </div>

    <div>
        Total Employees Count = @empRepository.GetAllEmployees().Count().ToString()
    </div>
</form>

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<IEmployeeRepository, MockEmployeeRepository>();
}

Copie y pegue este código y presione el botón crear en la vista y cambie entre ellos AddSingleton, AddScopedy AddTransientobtendrá cada vez un resultado diferente que podría ayudarlo a comprender esta explicación:

AddSingleton () : como su nombre lo indica, el método AddSingleton () crea un servicio Singleton. Se crea un servicio Singleton cuando se solicita por primera vez. Esta misma instancia es utilizada por todas las solicitudes posteriores. Por lo tanto, en general, un servicio Singleton se crea solo una vez por aplicación y esa instancia única se usa durante toda la vida útil de la aplicación.

AddTransient () : este método crea un servicio transitorio. Cada vez que se solicita, se crea una nueva instancia de un servicio Transitorio.

AddScoped () : este método crea un servicio con ámbito. Se crea una nueva instancia de un servicio con ámbito una vez por solicitud dentro del alcance. Por ejemplo, en una aplicación web crea 1 instancia por cada solicitud http pero usa la misma instancia en las otras llamadas dentro de esa misma solicitud web.

Offir Pe'er
fuente
2

Cual usar

Transitorio

  • ya que se crean cada vez que usarán más memoria y recursos y pueden tener un impacto negativo en el rendimiento
  • use esto para el servicio ligero con poco o ningún estado .

Alcance

  • mejor opción cuando desea mantener el estado dentro de una solicitud.

único

  • Las pérdidas de memoria en estos servicios se acumularán con el tiempo.
  • también memoria eficiente ya que se crean una vez reutilizados en todas partes.

Use Singletons donde necesite mantener el estado amplio de la aplicación. La configuración o los parámetros de la aplicación, el Servicio de registro y el almacenamiento en caché de datos son algunos de los ejemplos en los que puede usar singletons.

Inyectar servicio con diferentes vidas en otro

  1. Nunca inyecte servicios de alcance y transitorios en el servicio Singleton. (Esto convierte efectivamente el servicio transitorio o de ámbito en el singleton).
  2. Nunca inyecte servicios transitorios en el servicio con ámbito (Esto convierte el servicio transitorio en el ámbito).
bereket gebredingle
fuente
Esta es la mejor respuesta. Me gusta la parte donde das ejemplos. No es tan difícil entender cómo funcionan. Es mucho más difícil pensar qué servicio colocar y cómo y cuándo se limpió la memoria de ellos. Sería genial si explicas más sobre eso.
valentasm
1

Como se describe aquí (este enlace es muy útil) con un ejemplo,

Esta asignación entre la interfaz y el tipo concreto define que cada vez que solicite un tipo de IContryService, obtendrá una nueva instancia del CountryService. Esto es lo que significa transitorio en este caso. También puede agregar asignaciones de singleton (usando AddSingleton) y asignaciones de ámbito (usando AddScoped). El alcance en este caso significa el alcance de una solicitud HTTP, lo que también significa que es un singleton mientras se ejecuta la solicitud actual. También puede agregar una instancia existente al contenedor DI utilizando el método AddInstance. Estas son las formas casi completas de registrarse en IServiceCollection

Rehmanali Momin
fuente
1

La diferencia entre AddSingleton vs AddScoped vs AddTransient

Registrar servicios

ASP.NET core proporciona los siguientes 3 métodos para registrar servicios con el contenedor de inyección de dependencia. El método que utilizamos determina la vida útil del servicio registrado.

AddSingleton (): como su nombre lo indica, el método AddSingleton () crea un servicio Singleton. Se crea un servicio Singleton cuando se solicita por primera vez. Esta misma instancia es utilizada por todas las solicitudes posteriores. Por lo tanto, en general, un servicio Singleton se crea solo una vez por aplicación y esa instancia única se usa durante toda la vida útil de la aplicación.

AddTransient (): este método crea un servicio transitorio. Cada vez que se solicita, se crea una nueva instancia de un servicio Transitorio.

AddScoped (): este método crea un servicio con ámbito. Se crea una nueva instancia de un servicio con ámbito una vez por solicitud dentro del alcance. Por ejemplo, en una aplicación web crea 1 instancia por cada solicitud http pero usa la misma instancia en las otras llamadas dentro de esa misma solicitud web.

Alexander Zaldostanov
fuente