Über die Anforderung mit dem IoC Container Unity UserControls in ASP.NET dynamisch zu laden (per Page.LoadControl) habe ich mich auf die Suche gemacht. Den ersten Treffer hatte ich in der Dokumentation von Castle.MicroKernel. Einen weiteren wichtigen Hinweis gab mir Erich Eichinger (Spring.NET) in der ALTNETDE Maillingliste, der mich auf die Seite http://www.orbifold.net/ des Authors Francois ‘Swa’ M.Vanderseypen führte. Er beschreibt hier schön in einem Artikel (Teil 1, Teil 2) wie Unity aufgebaut ist.
Da mir die Innereien von Unity bisher verborgen geblieben sind und zum Zweck der Erweiterung das Wissen über ObjectBuilder2 aber notwendig ist will ich hier für mich zum Verständnis festhalten, was da passiert.
Dazu leihe ich mir mal von orbitfold einiges an Inhalt und Bilder nach dem DRY-Prinzip.
ObjectBuilder2
Builder und BuilderContext
Unity hat eine Pipeline, die abgearbeitet wird, um ein Objekt zu erzeugen. Das passt auch gut zu dem EVA-Prinzip, das ich in meinem vorherigen Post zu den Compiler-Grundlagen aufgegriffen habe. Es werden also mehrere Verarbeitungsschritte hintereinander ausgeführt, um zum Schluss ein Objekt zu erzeugen.
Wie Francois schön beschreibt, gibt es ein paar zentrale Bestandteile im Container, die zum Erzeugen eines Objektes gebraucht werden.
Die Builder Klasse ist der Haupteinstiegspunkt um eine Instanz in Unity zu erzeugen. Dazu gesellt sich als leichtgewichtiger und schmaler Wrapper BuilderContext
Dazu schauen wir uns einmal das Interface IBuilderContext
public interface IBuilderContext
{
IStrategyChain Strategies { get; }
ILifetimeContainer Lifetime { get; }
IReadWriteLocator Locator { get; }
IPolicyList PersistentPolicies { get; }
IPolicyList Policies { get; }
IRecoveryStack RecoveryStack { get; }
bool BuildComplete { get; set; }
object OriginalBuildKey { get; }
object BuildKey { get; set; }
object Existing { get; set; }
IBuilderContext CloneForNewBuild(object newBuildKey, object newExistingObject);
}
und ein einfaches Beispiel an, das mit ObjectBuilder eine Instanz erzeugt.
Builder builder = new Builder();
Locator locator = new Locator();
LifetimeContainer lifetime = new LifetimeContainer();
PolicyList policies = new PolicyList();
StrategyChain strategyChain = new StrategyChain();
builder.BuildUp(locator, lifetime, policies, strategyChain, typeof(MyClass), null)
In diesem Fall passiert aber nichts, da die einzelnen Instanzen (locator, lifetime, policies etc.) keine Einträge haben und somit NICHTS ausgeführt wird. Um etwas brauchbares zu bekommen müssen wir die Listen mit entsprechenden Strategien, Richtlinien (Policies)Was liegt uns mit diesen Listen und Kollektionen denn eigentlich vor?
- Builder - Haupteinstiegspunkt, Erzeugt das Objekt.
- StrategyChain - Collection von Aktionen, die hintereinander ausgeführt werden
- in der Reihenfolge, wie sie zum Container hinzugefügt werden
- PolicyList - Collection von Status-/Kontextinformationen
- wird zwischen den Strategien weitergereicht
- besteht nur solange ein Objekt gebaut wird (innerhalb der Ausführung einer StrategyChain für ein Objekt)
- Locator - Collection von Status-/Kontextinformationen
- zum speichern von Informationen, die zwischen den einzelnen Strategien weitergereicht werden /abrufbar sein sollen
- besteht beim BuilderContext nicht über den Build-Prozess eines Objektes hinweg. Muss verwaltet werden
- LifetimeContainer - Collection von Status-/Kontextinformationen
- zum Verwalten der Lebenszeit der erzeugten Objekte (z.B. Singleton, per Thread, per AppDomain ...)
BuilderStrategy und die StrategyChain
Die Pipeline (StrategyChain) wird in einzelnen Phasen durchlaufen. Pro Phase (BuilderStage) können mehrere Strategien ausgeführt werden.
public enum BuilderStage
{
PreCreation,
Creation,
Initialization,
PostInitialization
}
Für jeden Wert in der Enumeration kann es mehrere Strategien geben. Dabei wendet Unity selbst das Dependency Inversion Prinzip an und gibt eine Struktur vor. In diese Struktur können wir uns dann über die Extensions von Unity einhängen und den Buildprozess beeinflussen.
Zuerst werden die Strategien aufsteigend, in der Reihenfolge wie sie hinzugefügt wurden durchlaufen (PreBuildUp). Danach in absteigender Reihenfolge (PostBuildUp). Sowohl beim Erzeugen (BuildUp), als auch beim Zerstören (TearDown) der Instanzen kommt das IBuilderStrategy Interface zum Zuge:
public abstract class BuilderStrategy : IBuilderStrategy
{
public virtual void PreBuildUp(IBuilderContext context)
public virtual void PostBuildUp(IBuilderContext context)
public virtual void PreTearDown(IBuilderContext context)
public virtual void PostTearDown(IBuilderContext context)
}
Was können wir noch in einer Strategie beeinflussen? Sehen wir uns dazu den unteren Teil des IBuilderContext Interface nochmal an.
public interface IBuilderContext
{
//...
bool BuildComplete { get; set; }
object OriginalBuildKey { get; }
object BuildKey { get; set; }
object Existing { get; set; }
//...
}
Soll der Build-Prozess innerhalb einer Strategie unterbrochen werden, genügt es context.BuildComplete auf true zu setzen. Damit wird die Phase (PreBuildUp oder PostBuildUp) beendet und keine weitere Strategie mehr in der Pipeline durchgeführt. Wie auch bei den anderen bekannten IoC Containern muss im Prinzip immer eine Schnittstelle und ihre Implementierung zum automatischen auflösen hinterlegt werden (ausser bei Factory-Methoden).
Mit diesem Beispiel wird zu einer Schnittstelle IMyInterface ein Instanz der Klasse MyImplementation erstellt
IUnityContainer container = new UnityContainer();
container.Register< IMyInterface , MyImplementation >();
MyImplementation implementationInstance = container.Resolve< IMyInterface >();
Nun wollen wir mal das Beispiel in Relation zu IBuilderContext setzen:
| IBuilderContext | Sourcecode | Zeile |
| OriginalBuildKey | IMyInterface | 2. |
| BuildKey | MyImplementation | 2. |
| Existing | implementationInstance | 3. |
Über die Eigenschaft Existing wird die erzeugte Instanz über die Pipeline hinweg anderen Strategien zur Verfügung gestellt. Wie aus dem Interface zu erkennen ist, kann der OriginalBuildKey nur gelesen werden. Der BuildKey, also die Klasse die erzeugt wird, kann hingegen ausgetauscht werden.
Fazit
Wir haben einen kurzen Ausflug in das Reich des ObjectBuilders gemacht und wissen nun, wie die Basis von Unity tickt. Im nächsten Teil will ich dann auf Unity selbst eingehen. Denn Unity ist nichts weiter, als die Konfiguration und das Management um den ObjectBuilder2 herum, der mit den Strategien und Policies erweitert wird.