6

Ich benutze Simple Injector, aber vielleicht ist das, was ich brauche, eher eine konzeptionelle Antwort.Wie kann man mithilfe der Abhängigkeitsinjektion die Konfiguration aus mehreren Quellen beziehen?

Hier ist der Deal, nehme ich an eine Schnittstelle mit meinen Anwendungseinstellungen habe:

public interface IApplicationSettings 
{ 
    bool EnableLogging { get; } 
    bool CopyLocal { get; } 
    string ServerName { get; } 
} 

Dann würde man in der Regel eine Klasse, die IApplicationSettings implementiert, bekommt jedes Feld aus einer Quelle angegeben, zum Beispiel:

public class AppConfigSettings : IApplicationSettings 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]; 
      } 
      return enableLogging; 
     } 
    } 
    ... 
} 

JEDOCH! Nehmen wir an, ich möchte von app.config, CopyLocal aus der Datenbank und ServerName von einer anderen Implementierung erhalten, die den aktuellen Computernamen erhält. Ich möchte meine App-Konfiguration mischen können, ohne 9 Implementierungen zu erstellen, eine für jede Kombination.

Ich gehe davon aus, dass ich keine Parameter übergeben kann, weil die Schnittstellen vom Injektor (Container) gelöst werden.

Ich dachte, dies zunächst:

public interface IApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
where TEnableLogging : IGetValue<bool> 
where TCopyLocal : IGetValue<bool> 
where TServerName : IGetValue<string> 
{ 
    TEnableLogging EnableLog{get;} 
    TCopyLocal CopyLocal{get;} 
    TServerName ServerName{get;} 
} 

public class ApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Container.GetInstance<TEnableLogging>().Value 
      } 
      return enableLogging; 
     } 
    } 
} 

jedoch mit diesem ich ein Hauptproblem haben: Wie kann ich wissen, wie eine Instanz von TEnableLogging erstellen (die ein IGetValue<bool> ist)? Oh, angenommen, dass IGetValue<bool> eine Schnittstelle ist, die eine Value-Eigenschaft hat, die von der konkreten Klasse implementiert wird. Aber die konkrete Klasse benötigt möglicherweise einige Besonderheiten (wie der Name des Schlüssels in app.config) oder nicht (ich möchte einfach immer True zurückgeben).

Ich bin relativ neu in Abhängigkeit Injektion, also denke ich vielleicht falsch. Hat jemand irgendwelche Ideen, wie dies zu erreichen ist?

(Sie beantworten kann eine andere DI-Bibliothek, werde ich nichts dagegen. Ich glaube, ich muss nur das Konzept der es packen.)

Antwort

11

Sie sind hier auf jeden Fall in die falsche Richtung geht.

Vor einigen Jahren erstellte ich eine Anwendung, die eine Schnittstelle ähnlich wie Ihre IApplicationSettings enthielt. Ich glaube, ich nannte es IApplicationConfiguration, aber es enthielt auch alle Konfigurationswerte der Anwendung.

Obwohl es mir half, meine Anwendung zunächst testbar zu machen, kam das Design nach einiger Zeit in die Quere. Viele Implementierungen waren von dieser Schnittstelle abhängig, aber sie änderte sich ständig und mit ihr die Implementierung und die Testversion.

Genau wie Sie habe ich etwas faulen Laden implementiert, aber das hatte eine schreckliche Kehrseite. Wenn einer der Konfigurationswerte fehlte, habe ich nur herausgefunden, dass dies der Fall war, als der Wert zum ersten Mal aufgerufen wurde. Dies führte zu einer Konfiguration, die hard to verify war.

Es dauerte ein paar Iterationen von Refactoring, was der Kern des Problems war. Große Schnittstellen sind ein Problem. Meine IApplicationConfiguration Klasse verletzte die Interface Segregation Principle und das Ergebnis war schlechte Wartbarkeit.

Am Ende fand ich heraus, dass diese Schnittstelle völlig nutzlos war.Neben dem Verstoß gegen den ISP beschrieben diese Konfigurationswerte ein Implementierungsdetail und anstatt eine anwendungsweite Abstraktion vorzunehmen, war es viel besser, jede Implementierung direkt mit dem Konfigurationswert zu versorgen, den sie benötigten.

Wenn Sie dies tun, ist es am einfachsten, diesen Konfigurationswert als Eigenschaftswert zu übergeben. Mit einfachen Injektor können Sie die RegisterInitializer Methode dafür verwenden:

var enableLogging = 
    Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]); 

container.RegisterInitializer<Logger>(logger => 
{ 
    logger.EnableLogging = enableLogging; 
}); 

Wenn dies zu tun, der enableLogging Wert wird nur einmal aus der Konfigurationsdatei gelesen und so beim Anwendungsstart erfolgen. Dies macht es schnell und lässt es beim Start der Anwendung fehlschlagen, wenn der Wert fehlt.

Wenn Sie aus irgendeinem Grund das Lesen (zum Beispiel aus der Datenbank) verzögern, dann können Sie Lazy<T> verwenden:

Lazy<bool> copyLocal = new Lazy<bool>(() => 
    container.GetInstance<IDatabaseManager>().RunQuery(CopyLocalQuery)); 

container.RegisterInitializer<FileCopier>(copier => 
{ 
    copier.CopyLocal = copyLocal.Value; 
}); 

Statt die Werte mit Eigenschaften geben, können Sie auch Konstruktorargumente verwenden können, aber diese ist ein bisschen schwieriger zu erreichen. Nehmen Sie eine look at this article für einige Ideen.

Wenn Sie bei vielen Registrierungen denselben Konfigurationswert registrieren, fehlt Ihnen wahrscheinlich eine Abstraktion. Werfen Sie einen Blick auf diese:

container.RegisterInitializer<UserRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<OrderRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<CustomerRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<DocumentRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 

In diesem Fall werden Sie wahrscheinlich eine IDatabaseFactory oder IDatabaseManager Abstraktion fehlt, und man sollte so etwas tun:

container.RegisterSingle<IDatabaseFactory>(new SqlDatabaseFactory(connectionString)); 
+1

Danke, das sehr viel Sinn gemacht! Interessant, dass wir beide ähnliche Schritte gemacht haben und realisiert haben, dass etwas nicht in Ordnung ist, wie es sein sollte –