Sonntag, 17. Januar 2010

Erste Schritte in der Konfiguration von StructureMap

Seit kurzer Zeit nutze ich nun StructureMap und entdecke immer mehr Features, die ich besonders gelungen finde. Das automatische Registrieren auf Basis von Convention over Configuration (CoC) und das Schreiben von eigenen Interceptors, die auch ein automatisches Registrieren erlauben, sind die Ersten die mir aufgefallen sind und über die ich schreibe.

Convention over Configuration (CoC)

Bisher habe ich zum Registrieren der Typen von der Basisklasse Registry abgeleitet und alle notwendigen Typen in einem  Bootstrapper registriert.

public class RibbonRegistry : Registry
{
    public RibbonRegistry()
    {
    …
    }
}

ObjectFactory.Initialize(x =>
{
    x.AddRegistry(new RibbonRegistry());
});

Es folgte eine endlose Liste an Anweisungen, die in etwa diese Form hatten (nur noch deutlich mehr…):

ForRequestedType<IRibbonView>()
    .TheDefaultIsConcreteType<RibbonView>();
ForRequestedType<IRibbonModel>()
    .TheDefaultIsConcreteType<RibbonModel>();
ForRequestedType<IRibbonPresenter>()
    .TheDefaultIsConcreteType<RibbonPresenter>();

ForRequestedType<IRevisionView>()
    .TheDefaultIsConcreteType<RevisionView>();
ForRequestedType<IRevisionModel>()
    .TheDefaultIsConcreteType<RevisionModel>();
ForRequestedType<IRevisionPresenter>()
    .TheDefaultIsConcreteType<RevisionPresenter>();

Sehr vereinfacht wird die Registrierung von Typen durch die konsequente Anwendung von Convention over Configuration (CoC). Aktuell eines der spannenden Themenfelder, die sich großer Beliebtheit erfreuen. Eine der Standardkonventionen lautet: Der Kontrakt IKonkreterTypeName besitzt eine konkrete Implementierung in Form von KonkreterTypeName. Wird diese Konvention eingehalten, wird die Menge an Ceremony Code deutlich reduziert und die Registrierung lässt sich massiv vereinfachen.

Scan(x =>
{
    x.TheCallingAssembly();
    x.WithDefaultConventions(); 
});

Wenn das nicht einfach ist?! Alle Typen innerhalb der aufrufenden Assembly, die den Standardkonventionen entsprechen, werden nun automatisch dem IoC Container hinzugefügt. –Großartig, und so einfach. Weitere Beispiele zur automatischen Registrierung in StructureMap mit CoC finden sich hier. Ein besonderes Highlight ist meines Erachtens die Konfiguration des Scan Befehls als nested closure. Wenn das nicht elegant ist!? Ein Blick auf die Scan(Action<AssemblyScanner>) Methode macht deutlich: Ein Action<> delegate ist notwendig, um dieses Pattern in C# zu realisieren. In diesem Fall ist der Parameter vom Typ AssemblyScanner. Die Methode wird dann mit einem neuen Scanner als Parameter aufgerufen.

public void Scan(Action<AssemblyScanner> action)
{
    var scanner = new AssemblyScanner();
    action(scanner);

    AddScanner(scanner);
}

Jetzt kann jeder Typ, der dem Container hinzugefügt wurde, mit dem folgenden Aufruf geholt werden.

ObjectFactory.GetInstance<IRibbonPresenter>();

Custom Interceptor

Das Schreiben eines eigenen Interceptors mit automatischer Registrierung von abhängigen Typen ist der zweite Aspekt über den ich schreibe. Ich bin mir nicht ganz sicher, aber ich glaube, Castle Windsor bietet einen ähnlichen Mechanismus zum Schreiben eigener Interceptors.

Sehr prägnant ist das Beispiel des EventAggregators für dieses Szenario. Auf meinen Wegen durch den Quellcode von StoryTeller ist mir aufgefallen, dass der EventAggregator zwar ständig genutzt wird, aber nirgendwo die Registrierung eines Listeners zu finden ist. In der Klasse UserInterfaceRegistration wurde mir dann auch klar weshalb. Nach dem EventAggregator wurde ein EventAggregatorInterceptor registriert.

For<IEventAggregator>().Singleton().Use<EventAggregator>();
RegisterInterceptor(new EventAggregatorInterceptor());

Dieser Interceptor implementiert die notwendige Logik zum Registrieren der Listener. Alle Typen, die beim Scannen gefunden werden und die Schnittstelle IListener<> implementieren, werden an dem EventAggregator registriert. Zum Schreiben eines eigenen Interceptors müssen nur zwei Methoden implementiert werden. Die Methode MatchesType(Type) entscheidet, ob ein TypeInterceptor auf der aktuellen Instanz angewendet wird. Die Process() Methode agiert dann als EnrichmentHandler delegate, um mit der original Instanz zu arbeiten. Am konkreten Beispiel des EventAggregatorInterceptor sieht das wie folgt aus:

public class EventAggregatorInterceptor : TypeInterceptor
{
    #region TypeInterceptor Members

    public object Process(object target, IContext context)
    {
        context.GetInstance<IEventAggregator>().AddListener(target);
        return target;
    }

    public bool MatchesType(Type type)
    {
        if (typeof (IHandler).IsAssignableFrom(type)) return false;

        return
            type.ImplementsInterfaceTemplate(typeof (IListener<>)) ||
            type.CanBeCastTo(typeof (ICloseable));
    }

    #endregion
}

Wow, so ist innerhalb der Anwendung selbst kein Code notwendig, um die Listener zu registrieren. Die Registrierung läuft völlig autonom und vor allem absolut generisch ab. Alle Typen, die die notwendige Schnittstelle implementieren, werden registriert – Ich bin begeistert.

Demnächst werde ich sicher noch mehr über die Juwelen der Registrierung in StructureMap schreiben.

Kick It on dotnet-kicks.de