1

Ich versuche resolve instances by key mit SimpleInjector.
In meinem Fall sind die Schlüssel Zeichenfolgen, die aus einer Konfigurationsdatei stammen, und ich brauche die Factory, um den richtigen Typ basierend auf der Zeichenfolge zurückzugeben.Auflösen von Instanzen durch Schlüssel und automatische Registrierung mit SimpleInjector

Ich habe eine ähnliche Lösung wie die im obigen Link beschrieben, aber leicht geändert, so dass die Instanzen ihre eigenen Schlüssel bereitstellen können.
(es wird viele Klassen, die IFoo implementieren, so würde Ich mag ihnen mit ihre Schlüssel Auto-Register)

Dies ist die komplette Arbeitsbeispiel (.NET Core-Konsole app):
(ich hielt es für die Lesbarkeit kurz, so gibt es nur eine Klasse, die IFoo implementiert, und ich weggelassen, um die auto-register code)

using SimpleInjector; 
using System; 
using System.Collections.Generic; 

namespace SimpleInjectorTest1 
{ 
    public interface IFoo 
    { 
     string Name { get; } 
    } 

    public class SpecificFoo : IFoo 
    { 
     public string Name { get { return "foooo"; } } 
    } 

    public interface IFooFactory 
    { 
     void Add(IFoo foo); 
     IFoo Create(string fooName); 
    } 

    public class FooFactory : Dictionary<string, IFoo>, IFooFactory 
    { 
     public void Add(IFoo foo) 
     { 
      // use the instance's Name property as dictionary key, so I don't 
      // need to hard-code it in the code which does the registration 
      this.Add(foo.Name, foo); 
     } 

     public IFoo Create(string fooName) 
     { 
      return this[fooName]; 
     } 
    } 

    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      var container = new Container(); 

      // TODO: loop everything that implements IFoo, create 
      // an instance and add it to the factory 
      var factory = new FooFactory(); 
      factory.Add(new SpecificFoo()); 
      container.RegisterSingleton<IFooFactory>(factory); 
      container.Verify(); 

      // usage 
      var factory2 = container.GetInstance<IFooFactory>(); 
      IFoo foo = factory2.Create("foooo"); 
      Console.WriteLine("Success!"); 
     } 
    } 
} 

Diese sehr gut in der begi gearbeitet nning, bis ich erkannte, dass SpecificFoo(und die anderen IFoo s auch) benötigt eine Abhängigkeit über SimpleInjector.

Also, wenn ich SpecificFoo in die Fabrik hinzufügen, muss ich die Instanz über SimpleInjector statt new SpecificFoo() erstellen.

Also änderte ich meinen Code wie unten dargestellt:

using SimpleInjector; 
using System.Collections.Generic; 

namespace SimpleInjectorTest2 
{ 
    // dummy dependency 
    public interface IBar { } 
    public class Bar : IBar { } 

    // marker interface 
    public interface IFoo 
    { 
     string Name { get; } 
    } 

    public interface ISpecificFoo : IFoo 
    { 
     // empty by purpose 
    } 

    public class SpecificFoo : ISpecificFoo, IFoo 
    { 
     private readonly IBar bar; 
     public SpecificFoo(IBar bar) { this.bar = bar; } 

     public string Name { get { return "foooo"; } } 
    } 

    public interface IFooFactory 
    { 
     void Add(IFoo foo); 
     IFoo Create(string fooName); 
    } 

    public class FooFactory : Dictionary<string, IFoo>, IFooFactory 
    { 
     public void Add(IFoo foo) 
     { 
      // use the instance's Name property as dictionary key, so I don't 
      // need to hard-code it in the code which does the registration 
      this.Add(foo.Name, foo); 
     } 

     public IFoo Create(string fooName) 
     { 
      return this[fooName]; 
     } 
    } 

    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      var container = new Container(); 
      container.Register<IBar, Bar>(); 

      var factory = new FooFactory(); 

      // TODO: loop everything that implements IFoo, create 
      // an instance and add it to the factory 
      container.Register<ISpecificFoo, SpecificFoo>(); 
      factory.Add(container.GetInstance<ISpecificFoo>()); 

      // The next line throws an exception because of this: 
      // https://simpleinjector.readthedocs.io/en/latest/decisions.html#the-container-is-locked-after-the-first-call-to-resolve 
      container.RegisterSingleton<IFooFactory>(factory); 
     } 
    } 
} 

Wie bereits oben gesagt , schlägt die Registrierung der Fabrik, weil the container is locked after the GetInstance call.

Ich weiß, dass ich das Werk von Dictionary<string, Func<IFoo>> statt zu erben ändern könnte (wie in Resolve instances by key in der Dokumentation gezeigt), aber dann muß ich den String-Schlüssel für die Registrierung zur Verfügung zu stellen, wie in der Dokumentation im gezeigten Beispiel:

Wie kann ich eine Fabrik verwenden, um Typen nach Schlüssel aufzulösen, aber die Typen ihre Schlüssel selbst zur Verfügung stellen?
Ich möchte nicht eine Zeile wie oben im Registrierungscode hinzufügen müssen, jedes Mal, wenn ich eine neue Klasse hinzufügen, die IFoo implementiert.

Ich lese bereits Registration of open generic types (und Antworten wie this one als auch), aber ich denke, es trifft nicht auf meine Situation zu, weil ich nach String-Schlüssel auflösen muss.

Antwort

2

Unter Berücksichtigung Ihrer aktuellen Design, die einfachste Lösung ist, einfach den Code zu bewegen, dass die Foo auffüllt erst nach der Anmeldung in der Einfachen Injector ist komplett:

container.Verify(); 
factory.Add(container.GetInstance<ISpecificFoo>()); 
factory.Add(container.GetInstance<ISomeOtherFoo>()); 

Aber beachten Sie, dass, da die Fabrik hält an zu Instanzen auf unbestimmte Zeit sollten Sie zumindest alle Ihre Foo-Instanzen als Singleton im Container registrieren; Dadurch kann der Container diese Instanzen analysieren und diagnostizieren, und es wird Ihnen sofort angezeigt, dass in Ihren aktuellen Registrierungen ein Lifestyle Mismatch vorhanden ist.

Aber anstatt die Fabrik zu lassen, eine Reihe von Instanzen, ein wahrscheinlich flexiblerer Ansatz ist die Fabrik zu lassen wieder lösen aus dem Behälter:

public interface IFooFactory { 
    IFoo Create(string fooName); 
} 

public class FooFactory : Dictionary<string, Type>, IFooFactory 
{ 
    private readonly Container container; 
    public FooFactory(Container container) { this.container = container; } 

    public void Register<T>(string fooName) where T : IFoo { 
     this.container.Register(typeof(T)); 
     this.Add(name, typeof(T)); 
    } 

    public IFoo Create(string fooName) => this.container.GetInstance(this[fooName]); 
} 

// Registration 
var factory = new FooFactory(container); 

container.RegisterSingleton<IFooFactory>(factory); 

factory.Register<SpecificFoo>("foooo"); 
factory.Register<AnotherFoo>("another"); 

Hier wird das Wörterbuch nicht nur Instanzen Caches Typen, sondern macht die Registrierungen im Container. Dies ermöglicht dem Container, eine Analyse des vollständigen Objektgraphen durchzuführen. Später leitet das Werk die Anforderung Create an den Container weiter. Der Container kann das vollständige Objektdiagramm erstellen.

Beachten Sie, dass die Add Methode aus der IFooFactory Abstraktion entfernt wird. Da der Anwendungscode dem Dictionary niemals Instanzen hinzufügen sollte, sollte diese Methode entfernt werden (dies vereinfacht sofort das Testen). Da der Anwendungscode wahrscheinlich niemals IFoo.Name aufruft (da er nur von der Fabrik verwendet wird), sollte er ebenfalls entfernt werden. In meinem Beispiel gebe ich den Namen der Register<T>-Methode an, aber eine andere Option besteht darin, ein Attribut in die Foo-Implementierungen einzufügen. Die Factory-Methode kann dieses Attribut aus der mitgelieferten Implementierung lesen und verhindert, dass Sie es selbst bereitstellen müssen. Da diese Werte jedoch aus einer Konfigurationsdatei stammen, erscheint es sinnvoll, die Implementierung nicht von diesem Namen abhängig zu machen. Wenn das Attribut in den Implementierungen enthalten ist, wird die Namensgebung vor der Abstraktion verborgen, was gut ist, da die Konsumenten nicht über diesen Namen Bescheid wissen müssen (oder sie sowieso schon über diesen Namen wissen).

Nachteil dieser Lösung ist, dass Dekoratoren für IFoo hier nicht angewendet werden können, da Typen durch ihren konkreten Typ aufgelöst werden. Wenn dies eine Voraussetzung ist, können Sie dies lösen, indem Sie das RequestHandlerFactory Beispiel aus resolve instances by key anwenden, das InstanceProcucer Instanzen als Wörterbuchwert speichert.

+0

Große Antwort! Eine Frage, würden Sie Tests für die 'FooFactory' schreiben? Ich hatte Tests bis jetzt * (vergewissere dich, dass die Registrierung von neuem "IFoo" funktioniert/vergewissere dich, dass die Fabrik abwirft, wenn ein nicht existierender Schlüssel angefordert wird) *, aber mit deiner zweiten Lösung benötigt der Fabrik-Konstruktor jetzt einen "Container" es ist schwieriger zu testen. –

+0

@ChristianSpechr Normalerweise würde ich keine Uni-Tests schreiben, da ich weiß, dass ein Integrationstest diesen Code abdeckt. Aber im Allgemeinen habe ich diese Art von Fabriken nie in meinen Anwendungen; Ich kenne Ihren speziellen Anwendungsfall nicht, aber ich stelle fest, dass sie meistens aus den falschen Gründen verwendet werden. – Steven

+0

@ChristianSpecht Ich habe den zweiten Teil Ihrer Frage vergessen, also die automatische Registrierung. Sehen Sie sich die Methode 'Container.GetTypesToRegister' an. Es ermöglicht das Abrufen von Typen in Assemblys, die von Ihrer IFoo-Schnittstelle abgeleitet sind. – Steven