A Semantic Version Provider My focus going forward has shifted

Dependency Injection: das große Bild

Published on Monday, July 14, 2014 9:00:00 AM UTC in Programming & Tools

Beim Thema Dependency Injection kann man leicht mal den Überblick verlieren, wenn man sich in größeren Projekten bewegt. Dabei meine ich diesmal nicht unbedingt die Abhängigkeiten selbst, sondern die Zuständigkeiten dafür, wer diese eigentlich verwaltet. In größeren Solutions existieren verschiedenste Projekte oft nebeneinander, von Webanwendungen über REST-APIs bis hin zu Clients, und alle scheinen unterschiedliche Mechanismen zu verwenden. Aber es gibt auch Lichtblicke.

Stand der Dinge

ASP.NET-MVC-Anwendungen unterstützen Dependency Injection durch das Interface System.Web.Mvc.IDependencyResolver. Da das Interface sehr einfach gehalten ist, kann man sehr schnell und einfach Implementierungen für so gut wie alle DI-Frameworks selbst erstellen. Oft ist das aber gar nicht nötig, da entsprechende Implementierungen mit diesen Frameworks schon mitgeliefert werden.

Für Web Services, die mittels der Web API erstellt werden, gibt es ein gleichnamiges Interface. Allerdings ist das im Namespace System.Web.Http.Dependencies angesiedelt und schon dadurch inkompatibel zur MVC-Variante. Außerdem fügt es weitere Aspekte (Scope und Disposal) zum Thema hinzu, die bei der MVC-Variante so gar nicht zum Tragen kommen. Auch für die Web API-Version gibt es viele existierende Implementierungen zu bekannten Frameworks, und mit nur wenig mehr Mühe lassen sich ebenfalls so gut wie alle auch händisch implementieren.

Schließlich bleibt oft noch die Situation, dass man Services händisch auflösen möchte (anstatt durch die Laufzeitumgebung per Constructor- oder Property-Injection). Meist wird das bei generischen Implementierungen nötig, oder wenn es sich um Factories o.ä. handelt, bei denen zur Compile-Zeit die benötigten Typen noch nicht bekannt sind. Was tun? Hängt man sich an den Dependency Resolver von MVC an? An jenen der Web API? Beide sind durch Singletons zugreifbar oder können durch Injection des jeweiligen Interfaces ein wenig sauberer genutzt werden. Oder verwendet man stattdessen den Mechanismus des jeweils verwendeten Frameworks? Auch diese bieten dafür eigene Möglichkeiten an.

Common Service Locator

Bei meiner Recherche zu Best Practices und vorhandenen Möglichkeiten dazu, wie man alle Varianten elegant unter einen Hut bekommen könnte, stolperte ich auf eine interessante Überladung einer Methode im DependencyResolver von ASP.NET MVC:

public static void SetResolver(Object commonServiceLocator)

Statt einer konkreten Implementierung von IDependencyResolver gibt es also auch die Möglichkeit, ein Objekt als Resolver zu konfigurieren. Wie soll das denn funktionieren? Die Anmerkungen in der Dokumentation geben die entscheidenden Hinweise:

The commonServiceLocator parameter can be an instance of IDependencyResolver or an object that implements the Common Service Locator IServiceLocator interface. For more information about Common Service Locator, see Common Service Locator Library on the CodePlex Web site.

Langsam kam die Erinnerung zurück: ich hatte das Projekt "Common Service Locator" früher schon einmal gesehen, als ich als Advisor bei der Enterprise Library mitgewirkt habe. Es ist Teil der Microsoft Patterns & Practices-Initiative und versucht, eine einheitliche Schnittstelle für diesen Anwendungsfall zu definieren.

Was mir nicht bekannt war ist, dass ASP.NET MVC dieses Konzept unterstützt. Interessanterweise wurde aber durch die Wahl des "Object"-Typs als Argument keine direkte Abhängigkeit gebaut. Ein kurzer Test ergab, dass es tatsächlich funktioniert: man kann eine Implementierung der Common Service Locator-Abstraktion verwenden, um Dependency Resolution für MVC zu konfigurieren. Damit lassen sich zwei Fliegen mit einer Klappe schlagen:

  • Für die Verwendung eines Service Locators im eigenen Code, etwa für eigene generische Framework-Komponenten wie oben beschrieben, kann man das Framework seiner Wahl hinter dem Common Service Locator verstecken und vermeidet so die direkte Kopplung mit einer konkreten Dependency-Injection-Implementierung.
  • Dieselbe Fassade kann man auch für die Konfiguration von MVC verwenden und hat sich damit einen weiteren Adapter (die Implementierung von IDependencyResolver) erspart und nutzt eine völlig identische Umsetzung für seine eigene Funktionalität und jene der Laufzeitumgebung (MVC).

Und Web API?

Leider gibt es keine analoge Möglichkeit zur Konfiguration des Mechanismus für die Web API. Hier erfolgt das Setzen des Resolvers über die entsprechende Property von HttpConfiguration, die leider fest typisiert ist auf das IDependencyResolver-Interface der Web API. Man kommt also nicht drum herum, die Konfiguration hier manuell über die Implementierung eines weiteren Adapters vorzunehmen.

Ausblick in die Zukunft

Richtig interessant wird es in der nächsten Version von ASP.NET ("ASP.NET vNext"). Diese wird die momentan noch mehr oder weniger voneinander getrennten Teile MVC, Web API und Web Pages zusammenführen und vereinheitlichen.

Dazu gehört auch explizit die Handhabung des Themas Dependency Injection. In einem gänzlich neuen Namespace (momentan "Microsoft.Framework.DependencyInjection") wird ein generelles Konzept entwickelt, das sogar noch deutlich weiter greifen wird: nicht nur die Web-Technologien von Microsoft sollen diesen Mechanismus verwenden, sondern auch andere Bereiche, wie etwa das Entity Framework.

Wer die Entwicklung mitverfolgen möchte, kann bereits heute einen Blick in den Quellcode auf GitHub werfen. Dort stellt man mit Wohlwollen fest, dass auch schon diverse Adapter für bekannte Frameworks (Autofac, Ninject, StructureMap, Unity, Windsor) in Arbeit sind. Man darf also gespannt sein!

Tags: CommonServiceLocator · Dependency Injection · Ioc