Différences entre les services AddTransient, AddScoped et AddSingleton


938

Je veux implémenter l' injection de dépendance (DI) dans ASP.NET Core. Donc, après avoir ajouté ce code à la ConfigureServicesméthode, les deux méthodes fonctionnent.

Quelle est la différence entre les méthodes services.AddTransientet service.AddScopeddans ASP.NET Core?

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

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}

92
@tmg Les docs disent que les services transitoires à vie sont créés chaque fois qu'ils sont demandés. et "Les services à vie étendus sont créés une fois par demande." à moins que ma maîtrise de l'anglais ne soit plus faible que je ne le pensais, cela signifie exactement la même chose.
Neutrino

70
@tmg je sais. Je souligne simplement que les documents ne sont pas du tout clairs sur ce point, donc pointer les gens vers les documents n'est pas très utile.
Neutrino

13
@Neutrino, c'est pourquoi j'ai posé cette question.
Elvin Mammadov

5
Tard dans la soirée, lisant les commentaires encore plus tard, mais j'ai imprimé cet article, je l'ai lu et j'ai noté la même observation dans la marge que je vois maintenant @Neutrino faite ici. L'article était ENTIÈREMENT vague en offrant cette analyse. Heureusement, l'exemple était moins déroutant.
Wellspring

5
Pour autant que je comprends: des services transitoires à vie sont créés chaque fois qu'ils sont demandés . Le mot demandé ici est le sens anglais courant de demander quelque chose, dans ce cas un service. Alors que le mot demande en une fois par demande fait référence à une demande HTTP. Mais je comprends la confiance.
Memet Olsen

Réponses:


1656

TL; DR

Les objets transitoires sont toujours différents; une nouvelle instance est fournie à chaque contrôleur et à chaque service.

Les objets étendus sont les mêmes dans une demande, mais différents selon les demandes.

Les objets singleton sont les mêmes pour chaque objet et chaque demande.

Pour plus de précisions, cet exemple de la documentation ASP.NET montre la différence:

Pour démontrer la différence entre ces vie et les options d' enregistrement, pensez à une interface simple qui représente une ou plusieurs tâches comme une opération avec un identifiant unique, OperationId. Selon la façon dont nous configurons la durée de vie de ce service, le conteneur fournira la même instance ou des instances différentes du service à la classe demandeuse. Pour indiquer clairement quelle durée de vie est demandée, nous allons créer un type par option à vie:

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

Nous implémentons ces interfaces à l'aide d'une seule classe Operation,, qui accepte un GUID dans son constructeur, ou utilise un nouveau GUID si aucun n'est fourni:

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

Ensuite, dans ConfigureServices, chaque type est ajouté au conteneur en fonction de sa durée de vie nommée:

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

Notez que le IOperationSingletonInstanceservice utilise une instance spécifique avec un ID connu de Guid.Empty, il sera donc clair lorsque ce type sera utilisé. Nous avons également enregistré un OperationServicequi dépend de chacun des autres Operationtypes, afin qu'il soit clair dans une demande si ce service obtient la même instance que le contrôleur, ou une nouvelle, pour chaque type d'opération. Tout ce service fait est d'exposer ses dépendances en tant que propriétés, afin qu'elles puissent être affichées dans la vue.

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

Pour illustrer les durées de vie des objets dans et entre des demandes individuelles distinctes à l'application, l'exemple inclut un OperationsControllerqui demande chaque type de IOperationtype ainsi qu'un OperationService. L' Indexaction affiche ensuite toutes les OperationIdvaleurs du contrôleur et du service .

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

Maintenant, deux demandes distinctes sont effectuées pour cette action de contrôleur:

Première demande

Seconde demande

Observez laquelle des OperationIdvaleurs varie au sein d'une demande et entre les demandes.

  • Les objets transitoires sont toujours différents; une nouvelle instance est fournie à chaque contrôleur et à chaque service.

  • Les objets délimités sont les mêmes dans une demande, mais différents selon les demandes

  • Les objets singleton sont les mêmes pour chaque objet et chaque demande (indépendamment du fait qu'une instance soit fournie dans ConfigureServices)


14
J'ai compris les fonctions de chacun d'eux, mais quelqu'un peut-il expliquer l'impact de l'utilisation de l'un au lieu de l'autre. Quels problèmes peut-il causer s'il n'est pas utilisé correctement ou en choisir un au lieu d'un autre.
pawan népal

2
Supposons que vous créez un objet lié au contexte de la demande (comme l'utilisateur actuel) avec une portée singleton, alors il restera la même instance pour toutes les demandes http, ce qui n'est pas souhaité. IOC est tout au sujet de la création d'instances, nous devons donc spécifier quelle est la portée de l'instance créée.
akazemis

1
c'est !, j'ai mentionné le lien en haut du sujet! l'exemple de code est copié / collé à partir de MS docs
akazemis

1
Merci. ouais singleton sera le même dans toute l'application, quelle que soit la session / l'utilisateur. évidemment, si votre application utilise une architecture de microservices et que chaque service s'exécute dans un processus distinct, le singleton sera le même dans chaque processus
akazemis

1
Pouvez-vous nous donner un exemple d'utilisation addTransient s'il vous plaît? parce que je n'ai trouvé aucun utilitaire pour l'utiliser alors qu'il utilise trop de ressources
Terai

319

Dans l'injection de dépendances de .NET, il existe trois durées de vie principales:

Singleton qui crée une seule instance dans toute l'application. Il crée l'instance pour la première fois et réutilise le même objet dans tous les appels.

Les services à vie étendus sont créés une fois par demande dans le cadre. Il équivaut à un singleton dans la portée actuelle. Par exemple, dans MVC, il crée une instance pour chaque demande HTTP, mais il utilise la même instance dans les autres appels au sein de la même demande Web.

Des services transitoires à vie sont créés chaque fois qu'ils sont demandés. Cette durée de vie fonctionne mieux pour les services légers et sans état.

Ici vous pouvez trouver et des exemples pour voir la différence:

ASP.NET 5 MVC6 Injection de dépendances en 6 étapes (lien d'archive Web en raison d'un lien mort)

ASP.NET prêt pour l'injection de dépendances: ASP.NET 5

Et voici le lien vers la documentation officielle:

Injection de dépendances dans ASP.NET Core


22
Pourriez-vous expliquer pourquoi le Transitoire est le plus léger? Je pensais que le transitoire est le travail le plus lourd car il doit créer une instance à chaque fois pour chaque injection.
expert veut être le

17
Tu as raison. Transient n'est pas le plus léger, je viens de dire qu'il convient aux services RESTful légers :)
akazemis

3
Donc, dans quel scénario nous pourrions utiliser la portée et dans quel transitoire dans l'exemple de contrôleur par exemple si nous récupérons quelques lignes de la base de données? J'essaie de comprendre le scénario d'utilisation scoped vs transitoire dans ce cas.
sensei

4
cela dépend vraiment de la logique que vous attendez. Par exemple, s'il s'agit d'un seul appel db, cela ne fait aucune différence lequel vous utilisez. mais si vous appelez db plusieurs fois dans la même demande, vous pouvez utiliser la durée de vie étendue, car elle conserve le même objet de référentiel dans la mémoire et réutilise plusieurs fois dans le même contexte de demande Http. Alors que le transitoire crée un nouvel objet de référentiel plusieurs fois (et consomme plus de mémoire). Si vous expliquez votre scénario spécifique, il serait facile de juger lequel convient le mieux.
akazemis

3
Un point important à souligner ici est Singleton, Scoped et Transient sont comme des doills russes, l'un dans l'autre. Il n'est pas possible d'inverser leur ordre lors de l'imbrication, par exemple. une portée ou un singleton ne peut pas être contenu dans un transitoire, car nous prolongerions la durée de vie du parent, ce qui va à l'encontre du confinement!
DL Narasimhan

34

Les processus transitoires, étendus et singleton définissent le processus de création d'objets dans ASP.NET MVC core DI lorsque plusieurs objets du même type doivent être injectés. Dans le cas où vous êtes nouveau dans l'injection de dépendance, vous pouvez voir cette vidéo DI IoC .

Vous pouvez voir le code de contrôleur ci-dessous dans lequel j'ai demandé deux instances de "IDal" dans le constructeur. Transient, Scoped et Singleton définissent si la même instance sera injectée dans "_dal" et "_dal1" ou différent.

public class CustomerController : Controller
{
    IDal dal = null;

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

Transitoire: En transitoire, de nouvelles instances d'objets seront injectées dans une seule demande et réponse. Ci-dessous est une image instantanée où j'ai affiché des valeurs GUID.

Entrez la description de l'image ici

Portée: dans la portée, la même instance d'objet sera injectée dans une seule demande et réponse.

Entrez la description de l'image ici

Singleton: en singleton, le même objet sera injecté dans toutes les demandes et réponses. Dans ce cas, une instance globale de l'objet sera créée.

Voici un schéma simple qui explique visuellement les principes fondamentaux ci-dessus.

Image MVC DI

L'image ci-dessus a été dessinée par l'équipe SBSS lorsque je suivais une formation ASP.NET MVC à Mumbai . Un grand merci à l'équipe SBSS pour la création de l'image ci-dessus.


9
C'est l'explication la plus compliquée d'un service transitoire que j'ai jamais vue. Transitoire = Chaque fois que ce service est résolu équivaut à affecter votre variable new TService. Scoped mettra en cache sa première initialisation pour cette "portée" (requête http dans la plupart des cas). Singleton ne mettra en cache qu'une seule instance pour la durée de vie de l'application, aussi simple que cela. Les diagrammes ci-dessus sont si compliqués.
Mardoxx

2
Donc désolé, j'ai pensé que je vais le rendre plus simple avec des diagrammes et un instantané de code :-) Mais je comprends votre point.
Shivprasad Koirala

30
  • Singleton est une instance unique pour la durée de vie du domaine d'application.
  • Scoped est une instance unique pour la durée de la requête scoped, ce qui signifie par requête HTTP dans ASP.NET.
  • Transitoire est une instance unique par demande de code .

Normalement, la demande de code doit être effectuée via un paramètre constructeur, comme dans

public MyConsumingClass(IDependency dependency)

Je voulais souligner dans la réponse de @ akazemis que les «services» dans le contexte de la DI n'impliquent pas des services RESTful; les services sont des implémentations de dépendances qui fournissent des fonctionnalités.


16

AddSingleton ()

AddSingleton () crée une seule instance du service lors de sa première demande et réutilise cette même instance à tous les endroits où ce service est nécessaire.

AddScoped ()

Dans un service de portée, à chaque demande HTTP, nous obtenons une nouvelle instance. Cependant, dans la même demande HTTP, si le service est requis à plusieurs endroits, comme dans la vue et dans le contrôleur, la même instance est fournie pour toute l'étendue de cette demande HTTP. Mais chaque nouvelle requête HTTP obtiendra une nouvelle instance du service.

AddTransient ()

Avec un service transitoire, une nouvelle instance est fournie chaque fois qu'une instance de service est demandée, qu'elle soit dans le cadre de la même requête HTTP ou sur différentes requêtes HTTP.


5

Après avoir cherché une réponse à cette question, j'ai trouvé une explication brillante avec un exemple que je voudrais partager avec vous.

Vous pouvez regarder une vidéo qui montre les différences ICI

Dans cet exemple, nous avons ce code donné:

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

Créer une vue

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

Copiez-collez ce code et appuyez sur le bouton créer dans la vue et basculez entre AddSingleton, AddScopedet AddTransientvous obtiendrez à chaque fois un résultat différent qui pourrait vous aider à comprendre cette explication:

AddSingleton () - Comme son nom l'indique, la méthode AddSingleton () crée un service Singleton. Un service Singleton est créé lors de sa première demande. Cette même instance est ensuite utilisée par toutes les requêtes suivantes. Ainsi, en général, un service Singleton n'est créé qu'une seule fois par application et cette seule instance est utilisée tout au long de la durée de vie de l'application.

AddTransient () - Cette méthode crée un service transitoire. Une nouvelle instance d'un service transitoire est créée chaque fois qu'il est demandé.

AddScoped () - Cette méthode crée un service Scoped. Une nouvelle instance d'un service Scoped est créée une fois par demande dans l'étendue. Par exemple, dans une application Web, il crée 1 instance pour chaque demande http mais utilise la même instance dans les autres appels de cette même demande Web.


2

Lequel utiliser

Transitoire

  • car ils sont créés à chaque fois qu'ils utiliseront plus de mémoire et de ressources et peuvent avoir un impact négatif sur les performances
  • utilisez-le pour le service léger avec peu ou pas d'état .

Portée

  • meilleure option lorsque vous souhaitez conserver l'état dans une demande.

Singleton

  • des fuites de mémoire dans ces services s'accumuleront avec le temps.
  • également efficace en mémoire car ils sont créés une fois réutilisés partout.

Utilisez des singletons lorsque vous avez besoin de maintenir un état étendu de l'application. Configuration ou paramètres d'application, service de journalisation, mise en cache des données sont quelques-uns des exemples où vous pouvez utiliser des singletons.

Injecter un service avec des durées de vie différentes dans un autre

  1. N'injectez jamais de services Scoped & Transient dans le service Singleton. (Cela convertit efficacement le service transitoire ou de portée en singleton.)
  2. N'injectez jamais de services transitoires dans un service de portée (ceci convertit le service transitoire en portée.)

C'est la meilleure réponse. J'aime une partie où vous donnez des exemples. Il n'est pas si difficile de comprendre comment ils fonctionnent. Il est beaucoup plus difficile de savoir quel service mettre et comment et quand la mémoire a été nettoyée. Ce serait formidable si vous expliquez plus à ce sujet.
valentasme

1

Comme décrit ici (ce lien est très utile) avec un exemple,

Ce mappage entre l'interface et le type concret définit que chaque fois que vous demandez un type de IContryService, vous obtiendrez une nouvelle instance du CountryService. C'est ce que signifie transitoire dans ce cas. Vous pouvez également ajouter des mappages singleton (à l'aide d'AddSingleton) et des mappages à portée (à l'aide d'AddScoped). La portée dans ce cas signifie la portée d'une demande HTTP, ce qui signifie également qu'il s'agit d'un singleton pendant l'exécution de la demande actuelle. Vous pouvez également ajouter une instance existante au conteneur DI à l'aide de la méthode AddInstance. Ce sont les moyens presque complets de s'inscrire à IServiceCollection


1

La différence entre AddSingleton vs AddScoped vs AddTransient

Enregistrement des services

ASP.NET core fournit les 3 méthodes suivantes pour enregistrer des services avec le conteneur d'injection de dépendance. La méthode que nous utilisons détermine la durée de vie du service enregistré.

AddSingleton () - Comme son nom l'indique, la méthode AddSingleton () crée un service Singleton. Un service Singleton est créé lors de sa première demande. Cette même instance est ensuite utilisée par toutes les requêtes suivantes. Ainsi, en général, un service Singleton n'est créé qu'une seule fois par application et cette seule instance est utilisée tout au long de la durée de vie de l'application.

AddTransient () - Cette méthode crée un service transitoire. Une nouvelle instance d'un service transitoire est créée chaque fois qu'il est demandé.

AddScoped () - Cette méthode crée un service Scoped. Une nouvelle instance d'un service Scoped est créée une fois par demande dans l'étendue. Par exemple, dans une application Web, il crée 1 instance pour chaque demande http mais utilise la même instance dans les autres appels de cette même demande Web.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.