Inhalt
Erweiterte Features
Container Injection
Es gibt Situationen, da ist es nicht sinnvoll, bzw. gar nicht möglich Dependency Injection zu benutzen.
Stattdessen gibt es die Möglichkeit einen ServiceLocator zu benutzen um Instanzen zu erzeugen.
Das können beispielsweise Instanzen sein, die nur in einem Methodenscope - also für lokale Variablen - gültig sind.
Um eine solche - dynamische - Erzeugung von Instanzen zu ermöglichen, ist die Container-Instanz selbst auf IContainer registriert.
Nehmen wir mal an, das wir einen Artikelservice haben, der auch eine Speichern Methode hat.
Dort benötigt er eine Instanz von "ISaveService" nur für die Dauer der Methode.
public class ArticleService : IArticleService
{
private readonly IContainer _container;
public ArticleService(IContainer container) {
this._container = container;
}
public SaveAll() {
var saveService = this._container.Resolve<ISaveService>();
// do something with the service.
}
}
Da der Container selber auf IContainer registriert ist, erkennt der Container automatisch, ob irgendwo IContainer im Konstruktor gefordert ist.
So ist es einfach möglich, den ServiceLocator bzw. Container der dazu dient punktuell einzusetzen, ohne grossen Mehraufwand.
Mit einfachen Worten: Überall wo ein ServiceLocator benötigt wird, im Konstruktor ein Argument vom Typ IContainer definieren,
ob noch andere Abhängigkeiten oder Argumente dabei sind, stört das nicht.
Argumente zur Laufzeit übergeben
Seit Version 1.4 unterstützt LightCore das Übergeben von Argumenten zur Laufzeit.
Diese können nicht benannt sein, benannt sein und in jedem Fall typisiert.
Bei einer fehlenden (expliziten) Angabe des Types wird automatisch string
benutzt. Das kann bei der Xml-Konfiguration auftreten.
Im folgendem Codeblock finden sich verschiedene Konstruktoren die aufgerufen werden.
Zusätzlich zu diesen Möglichkeiten, kann auch ein Dictionary mit <string, object> direkt übergeben werden.
Dem Key (string) muss der Name des Arguments angegeben werden, beim Wert (object), der Wert des Arguments.
var fooFromAnonymousType = container.Resolve<FooWithArguments>(new AnonymousArgument(new
{
arg1 = "Peter",
arg2 = true
}));
var fooFromPassedArgs = container.Resolve<FooWithArguments>("Peter", true);
var fooFromAList = container.Resolve<FooWithArguments>(new List<object> {"Peter", true});
Registrierungen bei Bedarf erzeugen
LightCore ist in der Lage, on-the-fly Registrierungen zu erstellen.
So ist die Unterstützung von Open Generic Types möglich, und vieles mehr.
Alle dargestellten Möglichkeiten sind explizit, über einen Resolve()-Aufruf, oder implizit
in Form eines Konstruktorarguments oder einer Eigenschaft benutzbar.
Open Generic
Ist ein offener und generischer Typ registriert, kann dieser mit beliebigen Typparametern aufgerufen werden.
Dabei wird implizit jedes Mal eine Registrierung durchgeführt, wenn sie noch nicht vorhanden ist.
IRepository<Foo> repository = conainer.Resolve<IRepository<Foo>>;
IEnumerable / Array
Wird eine Abhängigkeit in Form eines Arrays / IEnumerable angefordert, werden alle
Instanzen zurückgegeben, die registriert wurden.
public FooBar(IEnumerable<IFooPlugin> fooPlugins)
{
/...
}
Factory
LightCore kann bei einer Anforderung eines Func<T> (oder mehr Typparameter),
automatisch eine Factory generieren und diese injizieren.
Damit wird es möglich, anstelle der oben erwähnen Container Injizierung, Factories zu nutzen.
Die Besonderheit liegt auch darin, das die Typargumente (T1, T2, TResult) ausser TResult als Konstruktorargumente
genutzt werden können.
Das heisst, LightCore generiert eine Factory, an die bei der Nutzung dann die Konstruktorargumente
übergeben werden müssen. Die Factory liefert bei jedem Aufruf eine neue Instanz.
Siehe das folgende Beispiel.
public FooBar(Func<FooRepository> factory, Func<string, int, FooRepository> factoryWithArguments)
{
FooRepository repositoryOne = factory();
FooRepository repositoryTwo = factory();
FooRepository repositoryWithArguments = factoryWithArguments("Peter", 4);
}
Lazy (Ab .NET 4.0)
Ab .NET 4.0 steht ein neuer Typ Namens Lazy<T> zur Verfügung.
An eine neue Lazy-Instanz muss eine Factory übergeben werden (Func<T>).
Beim ersten Aufruf des Wertes (Value-Eigenschaft) einer Lazy-Instanz, wird die Factory aufgerufen. Bei allen weitern
Zugriffen, wird die schon abgerufene Instanz zurückgegeben.
So kann Performance gespart werden, wenn es möglich ist das nicht alle Services benötigt werden
und es evt. sogar ziemlich Performance kostet, einen bestimmten Service zu instanziieren.
public FooBar(Lazy<FooService> lazyFooService)
{
FooService fooService = lazyFooService.Value; // factory executed.
FooService fooServiceTwo = lazyFooService.Value; // same value as above.
}
Automatische Auflösung von konkreten Typen (Resolve anything)
Es gibt sicherlich nicht wenige Fälle, wo nur eine konkrete Klasse vorhanden ist,
die aber selber Abhängigkeiten zu Abstraktionen hat.
Wird bspw. FooService angefordert, der selber eine Abhängigkeit zu einem ILogger hat,
so wird automatisch FooService auf sich selber registriert und anschliessend aufgelöst.
Das heisst: Der beste Konstruktor wird ausgewählt und die Abhängigkeiten injiziert, wie bei einer
normalen Abhängigkeit. Auch wenn irgendwo ein konkreter Typ als Abhängigkeit abgegenen wird,
kommt diese Auflösung zum Zug.
Diese Auflösungsart wird erst ganz am Schluss angewandt, hat also die niedrigste Priorität.
FooService fooService = container.Resolve<FooService>(); // FooService is a concrete type.
public FooBar(FooService fooService)
{
ILogger = fooService.Logger; // logger was injected by LightCore.
}