Irgendwann werden es zu viele – für große Webanwendungen nach dem MVC-Muster reicht die Strukturierung mittels Controllern häufig nicht aus. Das ASP.NET MVC-Framework bietet daher mit den Areas eine übergeordnete Einheit, mit der sich die Controller in Fachkomponenten gruppieren lassen.
Dieser Beitrag gibt eine kurze Einführung in das Konzept der Areas. Mit dem hier vorgestellten Setup können darüber hinaus die einzelnen Areas in separaten Visual Studio-Projekten entwickelt werden, was sich positiv auf die Quelltextorganisation und Kapselung der Komponenten auswirkt. Zudem wird gezeigt, wie gemeinsam genutzte Elemente in Basiskomponenten angesiedelt werden können.
Areas – Komponentenbildung in der Webanwendung
ASP.NET MVC-Anwendungen lassen sich mithilfe von Areas in Komponenten gliedern. Jedes Area stellt eine separate Instanz der MVC-Struktur dar, welche eigene Controller, Models und Views enthält.
Ein neues Area kann über das Kontextmenü des Webprojekts hinzugefügt werden.
Dabei wird ein eigener Ordner für das Area angelegt, dessen Unterordner die MVC-Struktur widerspiegeln.
Zudem enthält jede Area eine Registrierungsklasse (<Name>AreaRegistration), in der Area-spezifische Routen definiert werden können.
Das Framework fügt dabei den Namen des Area den DataTokens der Route hinzu. Um auf eine MVC-Action zu verweisen – z.B. zur Generierung eines Action-Links –, wird das gewünschte Area in den Route-Werten mit dem Schlüssel area angegeben. Gehört die referenzierte Action zum aktuellen Area, kann die Angabe weggelassen werden.
Beim Start der MVC-Anwendung werden die Areas in der Route Table des Frameworks registriert. Die in der Visual Studio-Vorlage verwendete Methode AreaRegistration.RegisterAllAreas() spürt zu diesem Zweck pauschal alle Area-Registrierungsklassen in den referenzierten Assemblies auf.
Zu beachten ist, dass sich hierdurch Einschränkungen bei der Definition der Routen ergeben, da die Reihenfolge der Registrierungen auf diese Weise nicht beeinflusst werden kann. Für den ASP.NET Routing-Mechanismus ist nämlich die Reihenfolge, in der die Routen registriert werden, relevant. Je nach Anforderungen an das Routing müssen die Routen daher ggf. stattdessen händisch registriert werden.
Ein Projekt pro Komponente
Nach der Strukturierung der Anwendung selbst soll nun die Visual Studio-Solution entsprechend gegliedert werden: Für jedes Area ist ein separates Projekt vorgesehen. Hierfür werden Class Library-Projekte verwendet, in denen jeweils das NuGet-Paket Microsoft.AspNet.Mvc inklusive Abhängigkeiten installiert wird.
Ein schöner Nebeneffekt dieser Organisation des Quelltextes ist, dass externe Web-Ressourcen wie jQuery oder Bootstrap nun pro einzelner Komponente per NuGet verwaltet werden können.
Die Auslagerung von Views und Web-Ressourcen aus einem Webprojekt in andere Projekte bringt allerdings generell ein Problem mit sich: es muss sichergestellt werden, dass diese Dateien am korrekten Ort auf dem Webserver ausgeliefert werden. Denn in Webprojekten wird diese Art von Ressourcen nicht in Assemblies eingebettet, sondern die Dateien werden direkt der Webanwendung beigelegt. Schließlich wird der Razor-Code der Views auch erst zur Laufzeit kompiliert.
Die zusätzlichen Dateien in den Area-Projekten werden hierfür mittels xcopy in das Webprojekt kopiert. Wie im folgenden Skript gezeigt, müssen dabei pro Area nur bestimmte Verzeichnisse ausgewählt werden:
Das Skript nimmt den Area-Namen als Argument entgegen und kopiert die Views und Web-Ressourcen aus dem entsprechenden Area-Projekt in das Hauptprojekt.
Über die Projektdatei wird das Skript in den Build-Vorgang eingebunden.
Lokal im Visual Studio kann die Anwendung somit bereits ausgeführt werden. Zur Auslieferung müssen die Dateien allerdings auch noch in die Content-Item Group des Webprojekts eingetragen werden. Erst damit werden sie im Rahmen des Publish-Target dem Web Deploy- bzw. Cloud Service-Paket hinzugefügt. In der Projektdatei wird eine Wildcard benutzt, um alle Dateien unterhalb des Ordners Areas zu erfassen.
Die Wildcard darf während des Build-Vorgangs erst nach dem Kopieren der Dateien ausgewertet werden. Aus diesem Grund wird die CreateItem-Task verwendet, um die Dateien der Item Group hinzuzufügen. Bei direkter Verwendung der Wildcard innerhalb eines ItemGroup-Elements würde sie bereits beim Parsen der Projektdatei ausgewertet – dies hätte schließlich zur Folge, dass die losen Dateien der Areas nicht mit in das Auslieferungspaket gepackt würden.
Keep it DRY – Basiskomponenten für gemeinsame Elemente
Bei der Aufteilung einer Webanwendung in Komponenten werden sich häufig auch gemeinsame Bausteine identifizieren lassen. Hierfür können Basis-Areas gebildet werden, in denen der entsprechende Code sowie die losen Dateien anzusiedeln sind. Im gezeigten Beispiel soll etwa für die beiden Areas FrontDesk und Portal die gleiche Layout-Datei verwendet werden. Der Solution wird hierfür ein weiteres Projekt namens Areas.Common hinzugefügt, welches die gemeinsame _Layout.cshtml enthält.
In den _ViewStart.cshtml-Dateien der beiden spezialisierten Komponenten wird jeweils auf die gemeinsame Layout-Datei der Basiskomponente verwiesen.
Die von der Layout-Datei eingebundenen Web-Ressourcen, wie z.B. Bootstrap, werden nun ausschließlich im Common-Area benötigt. Aus den anderen Projekten können die NuGet-Pakete deinstalliert werden.
Da das Common-Area keine eigenen MVC-Actions bereitstellt, benötigt es keine Registrierungsklasse – es nimmt nicht am Routing-Mechanismus des Frameworks teil. Über die Funktion als Klassenbibliothek hinaus dient das Basis-Area lediglich zur Erzeugung einer separaten Ordnerhierarchie für die gemeinsamen Views und Web-Ressourcen.