Ein Thema, was mir nach ersten halbwegs erfolgreichen Versuchen Anfang des Jahres ständig unter den Nägeln brannte, schob sich am Donnerstag in meiner ToDo-Liste wieder nach oben, weshalb ich es dann auch endlich mal richtig in Angriff genommen habe: das Erstellen eines Custom-SiteMap-Providers, der die Daten für eine SiteMap on the fly aus der Datenbank holt.
Damit kann man dann die vielfältigen fertigen Controls, die ASP.NET 2.0 mit bringt, auch in seiner eigenen dynamischen Anwendung für Einsatzzwecke verwenden, die die Standardfunktionalität der Controls bei weitem übersteigen. In meinem Fall geht es zum Beispiel darum eine Menüstruktur, die vollständig in der Datenbank über eine einfache Child-Parent-Beziehung gehalten wird, für die Menü- und SiteMapPath-Controls zugängig zu machen. Damit kann ich dann im Produktionsprozess innerhalb weniger Minuten anspruchsvolle Menüs und Navigationslösungen in unser CMS-Frontend implementieren.
Die Anforderungen, die sich daraus ergeben sehen wie folgt aus:
- Aufgrund teilweise recht tiefer Menüstrukturen muss das Ganze cachebar und auch wieder invalidierbar sein, d.h. man muss den Cache automatisiert selbst löschen können, Stichwort CacheDependency.
- Es müssen externe Links auf andere Websites möglich sein.
- Es muss möglich sein, ein und den selben Link mehrfach halten zu können. Das ist zum Beispiel dann notwendig, wenn man von unterschiedlichen Punkten in der Navigation auf die Startseite verweist. Dann gibt es z.B. in der englischen Version einen Link auf /Default.aspx und auch in der Deutschen einen auf /Default.aspx.
- Es muss die volle Funktionalität der WebControls die mit einer SiteMapDatasource umgehen können erhalten bleiben.
- Last but really not least: es muss möglich sein, mehrere Versionen der SiteMap vorzuhalten und diese dem aktuellen Anforderungen des Requests entsprechend zurück zu liefern. In meinem Fall gibt es z.B. ein einfaches Login-Modul. Wenn sich die Leute auf einer Website einloggen, sollen Sie eine andere Navigationsstruktur erhalten. Im besten Fall löst man so etwas mit Rollen - wovor ich mich aber noch gedrückt habe, da ich das Ganze nicht individualisieren muss, es gibt nur eingeloggt oder ausgelogt. Wichtig dabei ist natürlich, dass die oben genannten Anforderungen weiter bestehen - d.h. die unterschiedlichen Versionen müssen auch im Cache landen können.
Warum das Ganze so einen Aufwand bedeutet hat? Ganz einfach - weil ich keine Lösung zum Thema im Netz finden konnte. Auch der Artikel von Jeff Prosise war nur als Einstieg in die Thematik zu gebrauchen, da hieß es wohl zu früh gefreut. Der Grund: auch er setzt, wie die meisten anderen Beispielimplementierungen, auf den StaticSiteMapProvider.
Der hat aber ein paar ganz gravierende Nachteile:
- Eine URL fungiert quasi auch als Key. Sie muss eindeutig sein. Es ist nicht möglich den gleichen Link mehrfach vorzuhalten. Dafür gibt es zwar Workarounds - aber wozu schöne URLs bauen, um sie sich dann wieder von irgendwelchen angehängten QueryStrings kaputt machen zu lassen. Im Hintergrund macht das Verhalten übrigens durchaus Sinn - wenn es dann darum geht mit "securityTrimming=true" zu arbeiten ... aber das war hier nicht gegeben und Bestandteil der Aufgabe.
- Es funktionieren keine externen URLs.
So, was nun? Die Lösung lag in der Verwendung der Basisklasse SiteMapProvider, anstatt StaticSiteMapProvider. Diese erscheint zwar auf den ersten Blick schwieriger zu implementieren, auf den Zweiten hielt sich der Aufwand dann aber doch in Grenzen.
Anbei habe ich ein Beispielprojekt gepackt, was aus einer SQLExpress-Datenbank ein Menü generiert:
Das Ganze wird noch in den Cache gelegt (allerdings über eine Textfile-Dependency invalidiert) und es ist auch möglich mehrere Versionen der SiteMap anzulegen. Außerdem ist es bereits (in einer etwas komplexeren Form) erfolgreich in einer Multi-User-Umgebung getestet worden, d.h. wenn sich User 1 einloggt bekommt User 2 nicht plötzlich auch dessen Menü mit womöglich geheimen Punkten angezeigt, so viel ist sicher. ;-)
Ich werde hier nicht näher auf den Code eingehen, da ich davon ausgehe, dass jeder, den das Thema interessiert, und der so etwas selbst implementieren will, es schaffen wird sich in den Code einzulesen. Wer Fragen hat, kann mir die aber natürlich trotzdem gerne stellen - die Kontaktdaten stehen auf der linken Seite (im Blog, lieber RSS-Leser ;-)).
Hier das Beispielprojekt, viel Spaß:
SqlSiteMapProvider.rar (156,73 KB)