Donnerstag, 24. Mai 2012


Artikel

April 2003 | Artikel

Speiseplan

(Link zum Artikel: http://www.entwickler.de/dotnet//000348)

Navigation Controls mit XML und XSLT

Text: von Frank Hofmann
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
Navigationselemente sind integraler Bestandteil moderner Webseiten. Mit .NET Server Controls lassen sich leicht Navigationselemente herstellen, die auf eine gemeinsame Datenquelle zugreifen, HTML mit Hilfe von XSL-Transformationen erzeugen und sich dabei intelligent verhalten.

ASP.NET bietet eine Reihe von vordefinierten Server Controls, die im Wesentlichen ein Pendant in der Windows-Welt haben. Leider vermisst man Controls, die sich direkt mit der Navigation in Webseiten - also innerhalb einer Website - befassen.

Untersucht man Webseiten genauer, so findet man insbesondere auf großen Websites so genannte Sitemaps. Diese beschreiben meist die ersten zwei oder drei Navigationsebenen der gesamten Site und erleichtern dem Benutzer das Auffinden der gewünschten Inhalte. Oftmals werden Sitemaps auch eingesetzt, um Besucher gezielt auf interessante, aber vielleicht schwer zu findende Inhalte aufmerksam zu machen.

In der Konzeptionsphase für eine Website verwendet man häufig sehr früh eine Sitemap als Strukturierungsmittel. Zum Projektende enthält die Sitemap Informationen über jede Seite, zum Beispiel mit einer ID, dem Titel, der URL und beliebigen weiteren Informationen.

Aus einer solchen Baumstruktur kann man eine formale Beschreibung der Website in XML (siehe Listing 1) erstellen. Navigationselemente lassen sich nach ihrer Funktion klassifizieren (siehe Abb. 1).

Listing 1
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <Sitemap>
  3. <DefaultFile>default.aspx</DefaultFile>
  4. <Menu id="0" url="default.aspx" text="Home"></Menu>
  5. <Menu id="1" url="" text="Speiseplan">
  6. <Menu id="1.1" url="" text="Montag">
  7. <Menu id="1.1.1" url="" text="Theke -Vegetarisch-">
  8. <Menu id="1.1.1.1" url="" text="Gemüsesuppe"></Menu>
  9. <Menu id="1.1.1.2" url="" text="Tomatensuppe"></Menu>
  10. <Menu id="1.1.1.3" url="" text="Bohnensuppe"></Menu>
  11. <Menu id="1.1.1.4" url="" text="Algensuppe"></Menu>
  12. </Menu>
  13. <Menu id="1.1.2" url="" text="Theke -Einfach tierisch-">
  14. <Menu id="1.1.2.1" url="" text="Rehbraten"></Menu>
  15. <Menu id="1.1.2.2" url="" text="Sauerbraten"></Menu>
  16. <Menu id="1.1.2.3" url="" text="Hase in Pfeffer"></Menu>
  17. </Menu>
  18. <Menu id="1.1.3" url="" text="Theke -Geniessen-">
  19. ...
  20. </Menu>
  21. </Menu>
  22. ...
  23. </Menu>
  24. <Menu id="2" url="newsletterabo.aspx" text="Newsletter-Abo"></Menu>
  25. <Menu id="3" url="impressum.aspx" text="Impressum"></Menu>
  26. </Sitemap>
Navigation-Controls
Es gibt drei Arten von Navigations-Kontrollelementen:
  • TopNavigation. Dieser Navigationstyp zeichnet sich dadurch aus, dass er auf jeder Seite oben erscheint und statisch ist. Die TopNavigation implementiert die erste Kategorisierungsebene (oder Hierarchieebene) einer Website und ist in vielen Fällen mit der ersten Ebene der Hauptnavigation identisch.
  • NavigationPath. Der NavigationPath (Beispiel: Home>Speiseplan>Montag) zeigt dem Benutzer an, auf welcher Ebene der Website er sich gerade befindet bzw. welchen Weg er bereits genommen hat. Man kann dies mit den Pfadangaben im Windows Explorer vergleichen, nachdem der Benutzer eine gewisse Ordnerstruktur durchlaufen hat um ein Dokument aufzufinden. Eingesetzt wird der NavigationPath häufig, wenn die Website eine tiefe Navigationsstruktur mit vielen Ebenen aufweist.
  • MainNavigation. Die MainNavigation dient der eigentlichen Benutzerführung und ist neben dem Content der flexibelste Teil einer Website.
TopNavigation
Basierend auf diesem Wissen kann man nun die Entwicklung von Navigation-Controls in einer Control-Library angehen. Diese Controls müssen in der Lage sein zu entscheiden, welche Menüpunkte dargestellt werden sollen. Jede der Webseiten einer Site hat feste Menüpunkte, beispielsweise Home, Speiseplan, Newsletter-Abo und Impressum. Aus diesen Informationen lässt sich ein TopNavigation-Control erstellen, das die einzelnen Navigationspunkte aus der ersten Hierarchieebene des XML-Dokumentes bezieht.

.NET bietet zwei Arten von Server Controls: zum einen User Controls (abgeleitet von System.Web.UI.TemplateControl) und zum anderen Web Controls (abgeleitet von System.Web.UI.WebControls.WebControl). User Controls sind meist projektspezifisch und besitzen eine eigene .ascx-Datei während Web Controls meist in Bibliotheken verwaltet werden - und somit unabhängig von einer speziellen Webapplikation sind.

Für die Implementierung bestehen grundsätzlich folgende Möglichkeiten:
  • Methode 1: User Control. Der HTML-Code wird in die .ascx-Datei geschrieben und die Ausgabe der Menüpunkte im Code Behind im PageLoad()-Event implementiert. Dieser Ansatz scheidet aus, weil der HTML-Code keinen anwendungsspezifischen C#-Code beinhalten soll.
  • Methode 2: User Control. Der HTML-Code wird in die Render()-Methode übernommen. Diese Lösung scheidet ebenfalls aus, weil der HTML-Code bei Fehlern oder neuen Anforderungen schnell und unkompliziert änderbar sein muss. Außerdem soll das Control in einer Control-Library eingebettet sein, um flexibel in mehreren Projekten wiederverwendet werden zu können.
  • Methode 3: Web Control. Die XSLT-Transformation wird in der Render()-Methode des Controls durchgeführt. Dieser Ansatz ist vielversprechend: Es befindet sich kein C#-Code im HTML-Quelltext, die Logik liegt im Code Behind und die Web Controls lassen sich in einer Control-Library zusammenfassen.
Zur Transformation der Hauptmenüpunkte in HTML fehlt nun nur noch eine Beschreibung der Darstellung - also des Look and Feels. Hierzu bietet sich die Verwendung von XSLT (siehe Listing 2) an. Das .NET Framework enthält alle benötigten Funktionen und Klassen zum Manipulieren von XML-Dokumenten und zum Erzeugen von XSL-Transformationen. Um diese Klassen nutzen zu können, müssen die Namespaces System.Xml und System.Xml.Xsl referenziert werden. Im folgenden Code-Fragment für die Render()-Methode werden ein XmlDocument-Objekt erzeugt und ein XML-Dokument geladen. Anschließend wird eine XSLT-Transformation auf das Dokument angewendet:
  1. protected override void Render(HtmlTextWriter output)
  2. {
  3. XmlDocument mydata = new XmlDocument();
  4. mydata.Load(menu.xml);
Das Stylesheet laden:
  1. XslTransform xsltransform = new XslTransform();
  2. xsltransform.Load(topnavigation.xslt);
Transformation nach HTML:
  1. xsltransform.Transform(mydata, null, output);
  2. }
Damit die Dateinamen nicht hartkodiert im Quelltext stehen, kann man sie beispielsweise in die web.config auslagern oder als Property verfügbar machen. Der Parameter output ist ein Objekt der Klasse HtmlTextWriter, abgeleitet von System.IO.TextWriter. Die Methode WebControl.Render() stellt ein HtmlTextWriter-Objekt durch den Parameter output bereit. Damit ist die Programmierung des TopNavigation-Controls abgeschlossen.

Listing 2
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  3. <xsl:output method="html"/>
  4. <xsl:template match="//*">
  5. <table border="0" cellspacing="0" cellpadding="5">
  6. <tr>
  7. <xsl:for-each select="Menu">
  8. <td>[<a><xsl:attribute name="href">
  9. <xsl:choose>
  10. <xsl:when test="@url=''">
  11. <xsl:value-of select="//DefaultFile"/>?id=<xsl:value-of select="@id"/>
  12. </xsl:when>
  13. <xsl:otherwise>
  14. <xsl:value-of select="@url"/>
  15. </xsl:otherwise>
  16. </xsl:choose>
  17. </xsl:attribute><xsl:value-of select="@text"/></a>]</td>
  18. </xsl:for-each>
  19. </tr>
  20. </table>
  21. </xsl:template>
  22. </xsl:stylesheet>
NavigationPath
Interessanter ist natürlich der NavigationPath, auch Bread Crumble Path genannt, allein wegen seiner Dynamik. Prinzipiell geht man genauso vor wie bei der Implementierung der TopNavigation. Die wichtigste Information für den NavigationPath ist der aktuelle Menüpunkt, der vom User gewählt wurde, welcher über den Request-Parameter id im PageLoad()-Event des Web Forms ermittelt wird:
  1. NavigationPath1.MenuId = Request["id"];
Der Pfad lässt sich durch Iteration über die Parent-Nodes des Menüpunkt-Knotens ermitteln, wobei erst durch die Methode PrependChild() der Pfad in der richtigen Reihenfolge ermittelt wird.
  1. XmlNode CurrentNode = mydata.SelectSingleNode("//Menu[@id='" + m_MenuId + "']");
  2. while(CurrentNode.Name != "Sitemap")
  3. {
  4. copyNode = outputXml.ImportNode(CurrentNode, false);
  5. outputXml.DocumentElement.PrependChild(copyNode);
  6. CurrentNode = CurrentNode.ParentNode;
  7. }
MainNavigation
Die Implementierung der MainNavigation ist genauso einfach wie bei den zuvor vorgestellten Controls. Durch den Request-Parameter (Ermittlung wie oben) erfährt man, welchen Menüpunkt der User gewählt hatte. Ausgehend von diesem Menüpunkt werden alle direkten ChildNodes ermittelt und für das XSL aufbereitet:
  1. XmlNode CurrentNode = mydata.SelectSingleNode("//Menu[@id='" + m_MenuId + "']");
  2. foreach(XmlNode n in CurrentNode.ChildNodes)
  3. {
  4. copyNode = outputXml.ImportNode(n, false);
  5. outputXml.DocumentElement.AppendChild(copyNode);
  6. }
Jetzt können die Controls kompiliert werden. Über das Kontext-Menü lassen sich die neuen Kontrollelemente in die Toolbox von Visual Studio .NET integrieren. Per Drag and Drop lassen sich diese Controls dann auf die jeweilige Seite ziehen. Dem PageLoad()-Event der Webseite sind nur noch folgende Zeilen hinzuzufügen, damit die Controls zum Leben erweckt werden können:
  1. MainNavigation1.MenuId = thisMenuId;
  2. NavigationPath1.MenuId = thisMenuId;
Die Angabe von thisMenuId ermöglicht dem entsprechenden Navigationselement, immer die korrekten Menüeinträge zu ermitteln und darzustellen.
Optimierungen
Die vorgestellte Lösung lässt sich dahingehend erweitern, dass in den Menu-Tags der menu.xml noch ein target-Frame angegeben werden kann oder auf Basis der Rechte eines Benutzers Menüpunkte ein- oder ausgeblendet werden.

Quasi ganz nebenbei kann man mit dem hier vorgestellten Ansatz auch das Thema Internationalisierung abdecken. Die Sitemap in der Datei menu.xml erweitert man durch geeignete Knoten, sodass neben den deutschsprachigen Navigationspunkten auch englischsprachige oder gar chinesischsprachige angeboten werden. Wichtig hierbei ist nur das korrekte Matching im Stylesheet, denn das Verhalten der Navigation-Controls bleibt unverändert.

Weiterhin müssen die XML-Daten nicht aus dem Dateisystem gelesen werden, sondern können auch aus einer Datenbank oder von einem Web Service geliefert werden. Caching spielt in diesem Zusammenhang eine große Rolle hinsichtlich der erzielbaren Performance.
Ausblick
Mit den Navigation-Controls ist die Modularisierung der Seite noch nicht abgeschlossen. Die Content-Bereiche einer Website lassen sich nach dem gleichen Prinzip aus XML-Daten aufbauen und mittels XSL transformieren. Schlussendlich benötigt man theoretisch nur eine aspx-Seite, um eine komplette Website aufbauen zu können. Über den Request-Parameter id lassen sich dann der entsprechende Content sowie das zugehörige XSLT laden.

Kommentare