3
votes

Injection de dépendances étendues / transitoires garantissant que la même instance pour l'interface et l'implémentation est renvoyée

N'hésitez pas à suggérer un meilleur titre pour la question. Je n'arrive pas à trouver un bon nom pour décrire le problème.

J'avais besoin de rendre une classe accessible via l'injection de dépendances au démarrage. La classe ne doit pas seulement être disponible via son implémentation concrète mais aussi via l'interface qu'elle implémente. Et je dois m'assurer qu'il s'agit de la même instance d'objet renvoyée via les deux injections.

Le scénario du monde réel qui m'a conduit au cas singleton était un pépite d'abstractions fournissant l'interface (IStore), plusieurs pépites contenant du béton implémentations (DBStore, RedisStore). Lorsque j'ai essayé d'implémenter les vérifications de l'état pour chaque implémentation de magasin, j'ai pu obtenir IStore injecté mais pas l'implémentation concrète. Et je voulais utiliser certaines variables qui ont été initialisées et modifiées dans l'implémentation concrète (c'est pourquoi j'ai besoin de la même instance pour les deux injections). Puisque les magasins sont (espérons-le) utilisés comme des singletons, cela a fonctionné. Je ne dis pas qu'il existe un scénario du monde réel pour la voie cadrée et transitoire. Je suis juste curieux de savoir si cela serait possible s'ils ne sont pas des singletons.

Les codes suivants décrivent comment j'ai réussi à faire cela avec des singletons.

Le chemin qui m'a conduit à la solution singleton:

Avoir cette interface:

TestImplementation instance = new TestImplementation(Configuration.GetTestSettings());
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);

et cette implémentation concrète p >

TestImplementation instance = new TestImplementation();
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);
//services.AddSingleton<TestImplementation>(instance);

Ils sont utilisés dans deux (disons) services. L'un veut que l'interface soit injectée dans le constructeur et l'autre a besoin de l'implémentation concrète.

Essai et erreurs dans la méthode Startup.ConfigureServices pour faire injecter la même instance dans les deux cas:

Essayez 1:

// both are injected but they are not singleton any more (counters increment independently)
services.AddSingleton<ITestInterface, TestImplementation>();
services.AddSingleton<TestImplementation, TestImplementation>();

Essayez 2:

//only TestImplementation is injected (DI does not recognize it implements the Interface)
services.AddSingleton<TestImplementation>();

Essayez 3:

// only ITestInterface is injected but not TestImplemenation
services.AddSingleton<ITestInterface, TestImplementation>();

Essai 4:

public class TestImplementation : ITestInterface
{
  private int counter = 0;
  public string ReturnAString() {return "a string"; }
  public int ReturnAnInt() { return counter++; }
}

Eh bien, à l'essai 4, j'ai la même instance pour les deux injections.

Supposons maintenant que TestImplementation a besoin de quelques ( par exemple, connection) injectés.

Je peux écrire une méthode d'extension pour obtenir les paramètres de la configuration et la transmettre à l'instance singleton.

public interface ITestInterface
{
  string ReturnAString();
  int ReturnAnInt(); 
}

Alors, comment pourrais-je obtenir que les deux injections soient la même instance en utilisant les mêmes paramètres avec portée ou transitoire? Puisque je ne pense pas pouvoir créer l'instance à la main / code ici.


1 commentaires

J'avais des exigences similaires et je suis tombé sur le billet de blog d'Andre Lock qui décrit comment y parvenir en utilisant ASP.NET Core DI. Voici le lien - andrewlock.net/...


3 Réponses :


3
votes

En gros, vous voulez enregistrer un seul type d'implémentation de service sous la forme de deux contrats de service (classe concrète + interface). C'est un cas assez courant, mais malheureusement, le conteneur Microsoft DI par défaut (ServiceCollection) a des fonctionnalités limitées et la seule façon que je vois pour obtenir l'effet souhaité est d'utiliser des délégués d'usine:

services.AddScoped<TestImplementation>();
services.AddScoped<ITestInterface>(s => s.GetRequiredService<TestImplementation>());

Bien que cela fonctionne l'astuce (pour un coût d'exécution supplémentaire) Je recommande vivement d'utiliser l'un des conteneurs DI complets tels que Autofac ou Ninject


0 commentaires

1
votes

Si je ne me trompe pas, Autofac est capable de faire ce que vous souhaitez en utilisant décorateurs .

Cependant, ajouter une dépendance à une bibliothèque tierce est souvent indésirable ou même interdit en raison des politiques de l'entreprise.

Dans ce cas, je pense que vous feriez mieux de créer une usine. Par exemple:

services.AddSingleton<TestFactory>();

Et puis, dans votre Startup.cs , vous l'injecteriez comme:

public class TestFactory
{
    public ITestInterface Create(string flavor)
    {
        if (flavor == "concrete")
        {
            return new TestImplementation();
        }
        else
        {
            return new OtherTestImplementation();
        }
    }
}


1 commentaires

Bonne réponse! voir supplémentaire: stackoverflow.com/a/2697810/6844481



1
votes

En utilisant Autofac , vous pourrez ajouter à la fois l'interface et l'interface de mise en œuvre en utilisant AsSelf () méthode:

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf().SingleInstance();

Consultez cette réponse pour plus d'explications.

Dans votre cas - l'utiliser comme Singleton avec: SingleInstance().

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf();


0 commentaires