Im einem meiner letzten Beitrag habe ich Ninject vorgestellt und eine grobe Einweisung gegeben, wie man DI auf Grundlage des kleinen, kostenlosen Frameworks realisieren kann.
An dieser Stelle möchte ich noch ein fortgeschrittenes Thema von Ninject erläutern: “Contextual Bindings”, wie der Name bereits erahnen lässt geht es um die Bindungen (oder auch Mappings), das zentrale Konfigurationsmittel, von Ninject. Was mit dem Zusatz Contextual gemeint wird nochmals exemplarisch dargestellt.
Was ist Contextual Binding?
Contextual Binding bedeutet, dass man dass Binding oder Mapping auf Grundlage von verschiedenen Bedingungen definiert und dadurch nur eine, wenn auch etwas komplexere Mapping-Klasse benötigt. Es gibt zwei unterschiedliche Arten von Bedingungen, die man mit Ninject verwenden kann:
- basierend auf eigenen Attributen
- basierend auf dem Ninject.Tag Attribut
Die Bedingungen werden durch eine Implementierung des Interfaces ICondition<T> dann von Ninject evaluiert. Das Interface ICondition<T> ist zwar in der Ninject.Core.dll enthalten allerdings ist dessen Implementierung, die abstrakte Klasse ConditionBase<T> und die davon abgeleiteten Klassen in der Assembly Ninject.Conditions.dll vorhanden, daher muss man auch diese Bibliothek im Projekt referenzieren.
Ein einfaches Beispiel
Das Beispiel aus meinem vorherigen Post werden wir nun einfach um Contextual Binding erweitern. Die API für das Contextual Binding ist dank der Verwendung von Fluent Interfaces sehr einfach. (Fluent Interfaces habe ich bereits in einem anderem Post erläutert)
Contextual Binding mit eigenen Attributen
Eigene Attribute lassen sich ganz einfach durch vererben von der Klasse System.Attribut realisieren. Für das erste Beispiel benötigen also nur ein neues Attribut
#region Service Types
public class Productive : System.Attribute { }
#endregion
Bisher sahen die beiden Mapping-Module so aus
#region Ninject Modules
public class ProductiveModel : StandardModule
{
public override void Load()
{
Bind<IMediaDevice>().To<WindowsPhone>();
Bind<IAddress>().To<NationalAddress>();
}
}
public class StagingModule : StandardModule
{
public override void Load()
{
Bind<IMediaDevice>().To<IPhone>();
Bind<IAddress>().To<InternationalAddress>();
}
}
#endregion
Der vorletzte Schritt besteht im dekorieren der Customer-Klasse mit dem Productive-Attribut
[Productive]
public class Customer
{
//...
}
Durch die Verwendung von Contextual Bindings kann man das StagingModule an dieser stelle kürzen, so dass nur noch eine Mapping-Modul Klasse benötigt wird.
public class TheOneAndOnlyMappingModule : StandardModule
{
public override void Load()
{
Bind<IMediaDevice>().To<IPhone>();
Bind<IMediaDevice>().To<WindowsPhone>().
Only(When.Context.Target.HasAttribute<Productive>());
Bind<IAddress>().To<InternationalAddress>();
Bind<IAddress>().To<NationalAddress>().
Only(When.Context.Target.HasAttribute<Productive>());
}
}
Neben der Möglichkeit der Prüfung des Zieles (hier Context.Target. …) gibt es in Ninject noch diverse andere Conditions die man setzen kann. Die Verwendung der Bedingungen ist analog zur Verwendung des hier gezeigten Context.Target.HasAttribute.
Contextual Binding mit dem Ninject Tag Attribut
Ninject bringt von Haus aus das Attribut Tag mit, mit dessen Hilfe können ebenfalls Contextual Bindings realisiert werden. Der Tag-Property kann ein beliebiger String-Wert übergeben werden, der dann im Mapping-Modul ausgewertet werden kann.
Die bereits vorhandene Klasse Customer kann demnach auch so aussehen
[Tag("Productive")]
public class Customer
{
//...
}
Auch das Mapping-Modul muss hier nur minimal angepasst werden
public class MyTagMappingModule : StandardModule
{
public override void Load()
{
Bind<IMediaDevice>().To<IPhone>();
Bind<IMediaDevice>().To<WindowsPhone>().
Only(When.Context.Target.Tag == "Productive");
Bind<IAddress>().To<InternationalAddress>();
Bind<IAddress>().To<NationalAddress>().
Only(When.Context.Target.Tag == "Productive");
}
}
Wie immer gilt auch beim Contextual Binding von Ninject “Alle Wege führen nach Rom”. Erlaubt ist also was gefällt…
Fazit
Erst durch die Verwendung von Features wie Contextual Bindings kann man die Fähigkeiten die Ninject bietet ausschöpfen und vom DI-Pattern einen Nutzen schöpfen. Jede Applikation die sich Jenseits von “Hello World” befindet wird wohl einfacher und schneller zu entwickeln und zu testen sein wenn man auf Contextual Bindings zurück greift.
Wer sich allerdings seinen schönen “cleanen” Code nicht noch durch Attribute aufblähen will, die ohne Contextual Bindings nicht existent wären, der kann sich auf den nächsten Ninject-Beitrag über Conventions-Based Binding freuen.
Technorati-Tags:
Ninject,
DI,
.NET