Unity 2.0 in eine ASP.NET MVC 2 Umgebung integrieren

Für ein aktuelles Projekt entwickeln wir mit .NET Framework 3.5 und ASP.NET MVC 2. Um von der fest-codierten Instanzierung von Objekten Abstand zu nehmen (u.a. für das Mocking) und die Applikation frei konfigurierbar zu machen, habe ich mir mal Unity 2.0 angeschaut. Leider beziehen sich die meisten Tutorials und Blog-Posts zu Unity auf eine andere Version, deshalb folgt hier ein kleines Beispiel.

Zuerst einmal müssen die DLLs von Unity dem Projekt hinzugefügt werden. Dann erstellen wir unser DAL:

namespace Test.Dal
{
    public class Benutzer
    {
    }

    public class BenutzerDal : IBenutzerDal
    {
        public IEnumeration<Benutzer> FindAll()
        {
            List<Benutzer> r = new List<Benutzer>();
            r.add(new Benutzer());
            return r;
        }
    }

    public interface IBenutzerDal
    {
        IEnumeration<Benutzer> FindAll();
    }
}

Natürlich haben wir – wie es sich für eine Web-Anwendung gehört, auch den passenden Controller, der folgendermaßen aufgebaut ist:

namespace Test.Controller
{
    public class BenutzerController : Controller
    {
        private IBenutzerDal benutzerDal;

        public BenutzerController(IBenutzerDal benutzerDal)
        {
            this.benutzerDal = benutzerDal;
        }

        public ActionResult Index()
        {
            return View(benutzerDal.FindAll());
        }
    }
}

Auf die passende View habe ich mal verzichtet, da dies selbsterklärend sein sollte.

Standardmäßig werden alle Controller von ASP.NET automatisch erstellt. Damit nun Unity die Factory-Methode für die Controller übernimmt, müssen wir in der Global.asax.cs eine neue Controller-Factory schreiben:

namespace Test
{
    public class MvcApplication : System.Web.HttpApplication
    {
        // Konstante, um später darauf zugreifen zu können
        public const string UNITY_CONTAINER = "unityContainer";

        protected void Application_Start()
        {
            // RegisterRoutes(RouteTable.Routes); u.s.w.

            // Container erstellen
            var container = new UnityContainer();
            // Sektion "unity" aus der Web.config laden
            UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");

            // XML-Sektion in den Container laden
            container.LoadConfiguration(section);
            // Neue Controller-Factory erzeugen
            var controllerFactory = new UnityControllerFactory(container);
            // Standard ControllerFactory durch unsere eigene ersetzen
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
            // Den Unity-Container in der gesamten Applikation verfügbar machen
            Application[UNITY_CONTAINER] = container;
        }

    public class UnityControllerFactory : DefaultControllerFactory
    {
        private readonly IUnityContainer container;

        public UnityControllerFactory(IUnityContainer container)
        {
            this.container = container;
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            IController r = container.Resolve(controllerType) as IController;
            return r;
        }
    }
}

Mit diesem Code wird die Standard-ControllerFactory durch unsere eigene Implementierung ersetzt, die wiederum an den ApplicationContext Unity-Container delegiert.

Den Container selbst habe ich noch einmal global in der Applikation verfügbar gemacht, so dass wir darauf auch bei Filterklassen zurückgreifen können.
Vorstellbar wäre, dass bei jedem Request die aktuellen Rechte des Benutzers in der Datenbank überprüft werden sollen (ja, geht auch mit AuthorizationAttribute…). Unser Filter schaut folgendermaßen aus:

namespace Test.Filter
{
    public class MyFilter : IHttpModule
    {
        private HttpApplication httpApplication;

        private UnityContainer unityContainer;

        private IBenutzerDal benutzerDal;

        public void Init(HttpApplication httpApplication)
        {
            this.httpApplication = httpApplication;
            // globalen Container aus der Applikation ziehen
            unityContainer = (UnityContainer)this.httpApplication.Application[MvcApplication.UNITY_CONTAINER];
            // Unity anweisen, dass eine Instanz der Klasse geladen werden soll, die IBenutzerDal implementiert
            benutzerDal = unityContainer.Resolve<IBenutzerDal>();
            // Handler zuweisen... etc.
        }
    }
}

Mit diesem Code sorgen wir für die Verfügbarkeit des Unity-Containers innerhalb eigener Filter.

Letztendlich fehlen noch die Einträge in der web.xml bzw. dispachter-servlet.xml Web.config:

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
  </configSections>
  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <alias alias="IBenutzerDal" type="Test.Dal.IBenutzerDal, Test" />
    <alias alias="BenutzerController" type="Test.Controller.BenutzerController, Test" />
    <container>
      <register type="IBenutzerDal" mapTo="Test.Dal.BenutzerDal, Test "/>
      <register type="BenutzerController" />
    </container>
 </unity>
 <system.web>
 <!-- ... -->
 <httpModules>
 <!-- andere Module ... und dann unser Filter -->
 <add name="meinFilter" type="Test.Filter.MyFilter, Test"/>
 </httpModules>
 </system.web>
</configuration>

Über das Tag alias lassen sich für Klassen oder Interfaces Aliase definieren, so dass man diese nicht immer wieder vollständig anzugeben braucht. Das Attribut type legt fest, welche Klasse/welches Interface (Test.Dal.IBenutzerDal) in welchem Namespace (Test) hinter dem Alias steckt.
Im Abschnitt container wird nun jede Klasse eingetragen, die in der Applikation verfügbar sein soll. In der ersten Zeile wird ein Typ mit dem Interface Test.Dal.IBenutzerDal registriert, hinter dem sich die Konkrete Klasse (mapTo) Test.Dal.BenutzerDal versteckt. In der zweiten Zeile wird ein Typ Test.Controller.BenutzerController registriert, hinter  dem sich eine Instanz von Test.Controller.BenutzerController versteckt.

Unity überprüft bei jeder Initalisierung (d.h. Erzeugung der Objekte), welche Argumente der Konstruktor des Typs entgegen nimmt und löst diese anhand der bereits registrierten Typen auf. Aus diesem Grund brauche ich beim BenutzerController auch nicht das IBenutzerDal als Konstruktor-Parameter übergeben.
Weiterhin werden alle Objekte standardmäßig als Singleton registriert. Das Verhalten lässt sich aber ändern.

Mehr zu Unity gibt es in der Dokumentation, besonders empfehlenswert ist die Erklärung zum XML-Schema von Microsoft, wo dann u.a. auch Elemente wie property zum Injizieren von Eigenschaften nach Objekterzeugung besprochen werden.

Leave a reply

Your email address will not be published.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>