27 June 2010 Castle, DI, HowTo, Windsor Robert Muehsig

image

Der Titel klingt recht "kompliziert”, ist es aber eigentlich gar nicht. Grundproblem: Wir haben ein Interface und mehrere Implementationen davon. In unserer Applikation wollen diese über Konstruktor-Injektion holen und nacheinander aufrufen. Mit dem ArrayResolver und Castle Windsor dies sehr einfach zu bewerkstelligen. Der Blogpost darf auch als "realer” Einstieg in das Thema Dependency Injection angesehen werden.

Grundlagen

Einen kleinen Einstieg hab ich hier bereits gegeben. In meinem Beispiel nutze ich Castle Windsor, genauso gut hätte ich wahrscheinlich ein anderes DI Framework nehmen können.

Benötigte Assemblies

image

Der Download von Castle Windsor kann erstmal etwas erschreckend sein. Benötigt werden diese 4 Dlls (Core/DynamicProxy2/MicroKernel/Windsor).

 

Mein (reales) Beispiel

Wir haben bei uns eine Applikation die auf einige Backend Systeme zugreift. Da die Backend Systeme ab und an "einschlafen” haben wir ein "WakeUpTool” geschrieben, welches nach einem bestimmten Intervall die Systeme auf Stand-by hält.

Wir haben bei uns ein "IWakeUpCommand” mit der simplen Methode "WakeUp”. Pro Backendsystem gibt es eine Implementation davon. Die einzelnen Aufwach-Befehle können völlig getrennt voneinander agieren. Wichtig ist nur, dass immer alle geladen werden.
Da es sein kann, dass nun noch ein weiteres Backend System dazu kommt (oder eins wegfällt), wollten wir die Verbindung zwischen ApplicationRunner und den einzelnen Commands so lose wie möglich gestalten.

Fake Beispiel

image In meinem Beispiel, welches ihr ganz unten auch runterladen könnt, wird einfach nur ein ConsoleWriteLine ausgegeben.

In dem Beispiel gibt es das Interface "ICommand” mit der Methode "Exceute” und 3 Implementationen.

 

Das Interface "IApplicationRunner” beinhaltet nur eine "Run” Methode. Dies ist mein eigentlicher Eintrittspunkt in die Applikationslogik.

Beispiel Command:

namespace CastleWindsorFindAllImplementations.Commands
{
    public class WakeUpCommand : ICommand
    {
        public void Execute()
        {
            Console.WriteLine("WakeUpCommand");
        }
    }
}

Der ApplicationRunner nimmt einfach ein Array an ICommands entgegen und ruft diese nacheinander auf. Keine große Magie.

namespace CastleWindsorFindAllImplementations
{
    public class ApplicationRunner : IApplicationRunner
    {
        private ICommand[] _commands;

        public ApplicationRunner(ICommand[] commands)
        {
            this._commands = commands;
        }

        public void Run()
        {
            foreach (ICommand command in _commands)
            {
                command.Execute();
            }
        }
    }
}

Castle Windsor Magie - wie der ApplicationRunner zu den Implementationen kommt!

Ganz am Anfang registrieren wir einen ArrayResolver für Windsor Castle. Nur damit kann unser "container” auch Arrays auflösen.

Die Zeile 7 erspart eine Menge tipparbeit. Damit wird dem Container (so wie ich es verstanden habe ;) ) gesagt, dass er alle Interfaces suchen soll und dazu die passenden Implementationen registrieren soll. Suchen soll er zudem nur in dieser Assembly. Nur durch diesen Automatismus ist es auch erst elegant, weil man so einfach nur eine neuen Command hinzufügen muss und das Framework kümmert sich um die Auflösung. Man muss kein Setup etc. anpassen. Sehr praktisch, aber sicherlich wird es auch zu Problemen führen wenn es komplexer wird.

In Zeile 10 holen wir uns über den "container” unseren IApplicationRunner und sagen dann einfach nur "Run”. Windsor Castle hat als einzige Implementation unsere "ApplicationRunner” Klasse gefunden. Diese wiederrum braucht ein Array aus ICommands. Voodoo :)

    class Program
    {
        static void Main(string[] args)
        {
            IWindsorContainer container = new WindsorContainer();
            container.Kernel.Resolver.AddSubResolver(new ArrayResolver(container.Kernel));
            container.Register(AllTypes.Pick().FromAssembly(typeof(ApplicationRunner).Assembly)
                    .WithService.FirstInterface());

            IApplicationRunner runner = container.Resolve<IApplicationRunner>();
            runner.Run();

            Console.ReadLine();
        }
    }

Was ist wenn ich einen konkreten Typ von ICommand haben will?

Dann hat man erst mal ein Problem, weil es so nicht vorgesehen ist. Was man machen kann ist bestimmten Komponenten innerhalb des "containers” einen Namen zu geben.

Fazit

Diese Methode ist äußerst praktisch wenn man einfach nur eine Liste von Implementationen nutzen will ohne sie explizit erst zu registrieren. Einen genauen Typen daraus wieder herauszupuzzeln ist allerdings schwieriger.

[ Download Democode ]


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de