Diferenças dos serviços AddTransient, AddScoped e AddSingleton

938

Quero implementar injeção de dependência (DI) no ASP.NET Core. Então, depois de adicionar esse código ao ConfigureServicesmétodo, as duas maneiras funcionam.

Qual é a diferença entre os métodos services.AddTransiente service.AddScopedno ASP.NET Core?

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

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}
Elvin Mammadov
fonte
92
@tmg Os documentos dizem 'Serviços transitórios por toda a vida são criados sempre que solicitados.' e 'Serviços vitalícios com escopo definido são criados uma vez por solicitação.' que, a menos que minha compreensão do inglês seja mais fraca do que eu pensava, na verdade significa exatamente a mesma coisa.
Neutrino
70
@ eu sei. Estou apenas apontando que os documentos não são claros quanto a esse ponto, portanto, apontar pessoas para os documentos não é muito útil.
Neutrino
13
@ Neutrino, é por isso que eu fiz essa pergunta.
Elvin Mammadov
5
Tarde para a festa, lendo os comentários ainda mais tarde, mas imprimi o artigo, li e fiz a mesma observação na margem que agora vejo o @Neutrino feito aqui. O artigo foi totalmente vago ao oferecer essa análise. Felizmente, o exemplo foi menos confuso.
Wellspring
5
Tanto quanto eu entendo: Serviços de vida útil transitória são criados sempre que solicitados . A palavra solicitada aqui é o significado cotidiano em inglês de pedir algo, neste caso, um serviço. Considerando que a palavra pedido em vez do pedido per refere-se a uma solicitação HTTP. Mas eu entendo a confusão.
Memet Olsen 8/08/19

Respostas:

1655

TL; DR

Objetos transitórios são sempre diferentes; uma nova instância é fornecida para todo controlador e todo serviço.

Objetos com escopo definido são os mesmos em uma solicitação, mas diferentes entre solicitações diferentes.

Objetos Singleton são os mesmos para todos os objetos e solicitações.

Para mais esclarecimentos, este exemplo da documentação do ASP.NET mostra a diferença:

Para demonstrar a diferença entre essas opções de duração e registro, considere uma interface simples que represente uma ou mais tarefas como uma operação com um identificador exclusivo OperationId. Dependendo de como configuramos a vida útil desse serviço, o contêiner fornecerá a mesma ou diferentes instâncias do serviço para a classe solicitante. Para deixar claro qual tempo de vida está sendo solicitado, criaremos um tipo por opção de tempo de vida:

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 essas interfaces usando uma única classe, Operationque aceita um GUID em seu construtor ou usa um novo GUID se nenhum for fornecido:

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;
    }
}

Em seguida, em ConfigureServices, cada tipo é adicionado ao contêiner de acordo com sua vida útil nomeada:

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

Observe que o IOperationSingletonInstanceserviço está usando uma instância específica com um ID conhecido de Guid.Empty, portanto ficará claro quando esse tipo estiver em uso. Também registramos um OperationServiceque depende de cada um dos outros Operationtipos, para que fique claro em uma solicitação se esse serviço está recebendo a mesma instância que o controlador, ou um novo, para cada tipo de operação. Tudo que esse serviço faz é expor suas dependências como propriedades, para que possam ser exibidas na exibição.

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 demonstrar a vida útil do objeto dentro e entre solicitações individuais separadas para o aplicativo, a amostra inclui uma OperationsControllerque solicita cada tipo de IOperationtipo e também uma OperationService. A Indexação exibe todos os OperationIdvalores do controlador e do serviço .

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();
        }
    }
}

Agora, dois pedidos separados são feitos para esta ação do controlador:

Primeiro Pedido

Segundo pedido

Observe qual dos OperationIdvalores varia dentro de uma solicitação e entre solicitações.

  • Objetos transitórios são sempre diferentes; uma nova instância é fornecida para todo controlador e todo serviço.

  • Objetos com escopo definido são os mesmos em uma solicitação, mas diferentes em solicitações diferentes

  • Objetos Singleton são iguais para todos os objetos e solicitações (independentemente de uma instância ser fornecida em ConfigureServices)

akazemis
fonte
14
Eu entendi as funções de cada um deles, mas alguém pode explicar o impacto de usar um em vez do outro. Que problemas isso pode causar se não for usado corretamente ou escolher um em vez de outro.
pawan nepal
2
Digamos que você esteja criando um objeto relacionado ao contexto da solicitação (como o usuário atual) com escopo singleton, ele permanecerá a mesma instância em todas as solicitações http que não são desejadas. O COI tem tudo a ver com a criação de instâncias, portanto, precisamos especificar qual é o escopo da instância criada.
akazemis
1
é !, mencionei o link na parte superior do tópico! o código de amostra é cópia / colado de documentos MS
akazemis
1
obrigado. Sim, o Singleton será o mesmo em todo o aplicativo, independentemente da sessão / usuário. Obviamente, se o seu aplicativo está usando microservices arquitetura e cada serviço é executado em um processo separado, o Singleton será o mesmo em cada processo
akazemis
1
Você pode nos dar um exemplo de uso addTransient, por favor? porque eu não encontrou quaisquer utilitários para usá-lo enquanto ele usa muitos recursos
Terai
319

Na injeção de dependência do .NET, há três grandes vidas úteis:

Singleton que cria uma única instância em todo o aplicativo. Ele cria a instância pela primeira vez e reutiliza o mesmo objeto em todas as chamadas.

Escopo serviços vida útil são criados uma vez por solicitação no escopo. É equivalente a um singleton no escopo atual. Por exemplo, no MVC, ele cria uma instância para cada solicitação HTTP, mas usa a mesma instância nas outras chamadas na mesma solicitação da Web.

Transitório serviços de tempo de vida são criados sempre que solicitados. Esta vida útil funciona melhor para serviços leves e sem estado.

Aqui você pode encontrar e exemplos para ver a diferença:

Injeção de dependência do ASP.NET 5 MVC6 em 6 etapas (link do arquivo da web devido ao link inoperante)

Sua injeção de dependência pronta ASP.NET: ASP.NET 5

E este é o link para a documentação oficial:

Injeção de dependência no ASP.NET Core

akazemis
fonte
22
Você poderia explicar por que o Transient é o mais leve? Eu pensei que o Transient é o trabalho mais pesado, porque ele precisa criar uma instância sempre para cada injeção.
Expert quer ser
17
Você está certo. Transitória não é o mais leve, eu só disse que é adequado para serviços RESTful leves :)
akazemis
3
Então, em que cenário poderíamos usar o escopo e em qual transitório no exemplo do controlador, por exemplo, se estamos recuperando algumas linhas do banco de dados? Estou tentando entender o cenário de uso transitório e com escopo neste caso.
sensei
4
realmente depende da lógica que você está esperando. Por exemplo, se for uma única chamada de banco de dados, na verdade não faz diferença qual deles você está usando. mas se você estiver chamando o db várias vezes na mesma solicitação, poderá usar a vida útil com escopo, pois ele mantém o mesmo objeto de repositório na memória e reutiliza várias vezes no mesmo contexto de solicitação de HTTP. Enquanto o transitório cria um novo objeto de repositório várias vezes (e consome mais memória). Se você explicar seu cenário específico, seria fácil julgar qual deles melhor se adequa.
akazemis
3
Um ponto importante a destacar aqui é o Singleton, o Scoped e o Transient, como os russos, um dentro do outro. Não é possível reverter sua ordem ao aninhar, por exemplo. um escopo ou singleton não podem estar contidos em um transitório, porque estenderíamos a vida útil do pai que vai contra a contenção!
DL Narasimhan
34

Transiente, com escopo definido e singleton definem o processo de criação de objeto no DI principal do ASP.NET MVC quando vários objetos do mesmo tipo precisam ser injetados. Caso você seja novo na injeção de dependência, pode ver este vídeo DI IoC .

Você pode ver o código do controlador abaixo, no qual solicitei duas instâncias de "IDal" no construtor. Transiente, com escopo definido e Singleton definem se a mesma instância será injetada em "_dal" e "_dal1" ou diferente.

public class CustomerController : Controller
{
    IDal dal = null;

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

Transitório: em transitório, novas instâncias de objeto serão injetadas em uma única solicitação e resposta. Abaixo está uma imagem de instantâneo em que eu exibi valores GUID.

Digite a descrição da imagem aqui

Escopo: no escopo, a mesma instância do objeto será injetada em uma única solicitação e resposta.

Digite a descrição da imagem aqui

Singleton: no singleton, o mesmo objeto será injetado em todas as solicitações e respostas. Nesse caso, uma instância global do objeto será criada.

Abaixo está um diagrama simples que explica visualmente o fundamental acima.

Imagem MVC DI

A imagem acima foi desenhada pela equipe do SBSS quando eu estava fazendo o treinamento do ASP.NET MVC em Mumbai . Um grande obrigado à equipe do SBSS por criar a imagem acima.

Shivprasad Koirala
fonte
9
Essa é a explicação mais complicada de um serviço transitório que eu já vi. Transitório = Sempre que esse serviço for resolvido, é equivalente a atribuir sua variável new TService. O escopo armazenará em cache a primeira inicialização dele para esse "escopo" (solicitação http na maioria dos casos). O Singleton armazenará em cache apenas uma instância por toda a vida útil do aplicativo, simples assim. Os diagramas acima são muito complicados.
Mardoxx
2
Desculpe, pensei em simplificá-lo com diagramas e snapshot de código :-) Mas entendi seu ponto.
Shivprasad Koirala 4/18
30
  • Singleton é uma instância única para a vida útil do domínio do aplicativo.
  • O escopo é uma instância única pela duração da solicitação do escopo, o que significa por HTTP solicitação no ASP.NET.
  • Transitório é uma instância única por solicitação de código .

Normalmente, a solicitação de código deve ser feita através de um parâmetro construtor, como em

public MyConsumingClass(IDependency dependency)

Eu queria ressaltar na resposta de @ akazemis que "serviços" no contexto de DI não implica serviços RESTful; serviços são implementações de dependências que fornecem funcionalidade.

user1969177
fonte
16

AddSingleton ()

AddSingleton () cria uma única instância do serviço quando é solicitada pela primeira vez e reutiliza a mesma instância em todos os locais onde esse serviço é necessário.

AddScoped ()

Em um serviço com escopo definido, com cada solicitação HTTP, obtemos uma nova instância. No entanto, na mesma solicitação HTTP, se o serviço for necessário em vários locais, como na visualização e no controlador, a mesma instância será fornecida para todo o escopo dessa solicitação HTTP. Mas cada nova solicitação HTTP receberá uma nova instância do serviço.

AddTransient ()

Com um serviço transitório, uma nova instância é fornecida sempre que uma instância de serviço é solicitada, seja no escopo da mesma solicitação HTTP ou em diferentes solicitações HTTP.

Yasser Shaikh
fonte
5

Depois de procurar uma resposta para essa pergunta, encontrei uma explicação brilhante com um exemplo que gostaria de compartilhar com você.

Você pode assistir a um vídeo que demonstra as diferenças AQUI

Neste exemplo, temos este código:

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;
    }
}

Página inicial

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();
    }
}

Criar 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 e cole esse código e pressione o botão criar na visualização e alterne entre eles AddSingleton, AddScopede AddTransientvocê obterá cada vez um resultado diferente que poderá ajudá-lo a entender esta explicação:

AddSingleton () - como o nome indica, o método AddSingleton () cria um serviço Singleton. Um serviço Singleton é criado quando é solicitado pela primeira vez. Essa mesma instância é usada por todos os pedidos subseqüentes. Portanto, em geral, um serviço Singleton é criado apenas uma vez por aplicativo e essa instância única é usada durante toda a vida útil do aplicativo.

AddTransient () - este método cria um serviço temporário . Uma nova instância de um serviço temporário é criada sempre que solicitada.

AddScoped () - este método cria um serviço com escopo. Uma nova instância de um serviço com escopo definido é criada uma vez por solicitação dentro do escopo. Por exemplo, em um aplicativo Web, ele cria 1 instância por cada solicitação HTTP, mas usa a mesma instância nas outras chamadas dentro da mesma solicitação Web.

Offir Pe'er
fonte
2

Qual usar

Transitório

  • desde que eles são criados toda vez que eles usam mais memória e recursos e podem ter um impacto negativo no desempenho
  • use isso para o serviço leve com pouco ou nenhum estado .

Escopo

  • melhor opção quando você deseja manter o estado dentro de uma solicitação.

Singleton

  • vazamentos de memória nesses serviços se acumularão com o tempo.
  • também memória eficiente, pois são criados uma vez reutilizados em todos os lugares.

Use Singletons onde for necessário manter o estado geral do aplicativo. Configuração ou parâmetros de aplicativos, Serviço de Log, armazenamento em cache de dados são alguns dos exemplos em que você pode usar singletons.

Injetar serviço com vida útil diferente em outro

  1. Nunca injete serviços com escopo e transitórios no serviço Singleton. (Isso efetivamente converte o serviço transitório ou com escopo definido no singleton.)
  2. Nunca injete serviços temporários no serviço com escopo (isso converte o serviço transitório no escopo).
bereket gebredingle
fonte
Esta é a melhor resposta. Eu gosto da parte em que você dá exemplos. Não é tão difícil entender como eles funcionam. É muito mais difícil pensar em qual serviço colocar e como e quando a memória foi limpa deles. Seria ótimo se você explicasse mais sobre isso.
valentasm
1

Conforme descrito aqui (este link é muito útil) com um exemplo,

Esse mapeamento entre a interface e o tipo concreto define que, toda vez que você solicitar um tipo de IContryService, receberá uma nova instância do CountryService. É isso que transitório significa neste caso. Você também pode adicionar mapeamentos singleton (usando AddSingleton) e mapeamentos com escopo (usando AddScoped). O escopo neste caso significa o escopo de uma solicitação HTTP, o que também significa que é um singleton enquanto a solicitação atual está em execução. Você também pode adicionar uma instância existente ao contêiner de DI usando o método AddInstance. Estas são as maneiras quase completas de se registrar no IServiceCollection

Rehmanali Momin
fonte
1

A diferença entre AddSingleton vs AddScoped vs AddTransient

Registrando Serviços

O núcleo do ASP.NET fornece os três métodos a seguir para registrar serviços no contêiner de injeção de dependência. O método que usamos determina a vida útil do serviço registrado.

AddSingleton () - como o nome indica, o método AddSingleton () cria um serviço Singleton. Um serviço Singleton é criado quando é solicitado pela primeira vez. Essa mesma instância é usada por todos os pedidos subseqüentes. Portanto, em geral, um serviço Singleton é criado apenas uma vez por aplicativo e essa instância única é usada durante toda a vida útil do aplicativo.

AddTransient () - este método cria um serviço temporário . Uma nova instância de um serviço temporário é criada sempre que solicitada.

AddScoped () - este método cria um serviço com escopo. Uma nova instância de um serviço com escopo definido é criada uma vez por solicitação dentro do escopo. Por exemplo, em um aplicativo Web, ele cria 1 instância por cada solicitação HTTP, mas usa a mesma instância nas outras chamadas dentro da mesma solicitação Web.

Alexander Zaldostanov
fonte