Share via


Benutzerdefinierte SiteMapProvider in ASP.NET 2.0

Veröffentlicht: 17. Nov 2005

Von Morgan Skinner

Dieser Artikel bietet eine Übersicht über das Providermodell in Microsoft ASP.NET 2.0 und stellt eine benutzerdefinierte Implementierung der Klasse SiteMapProvider vor, die von Steuerelementen wie Breadcrumb und TreeView bei der Wiedergabe einer Seite verwendet wird. Dieser Artikel enthält auch Links zu englischsprachigen Seiten. (12 gedruckte Seiten)

Auf dieser Seite

Einführung Einführung
Provider in ASP.NET 2.0 Provider in ASP.NET 2.0
Sitezuordnungen in ASP.NET 2.0 Sitezuordnungen in ASP.NET 2.0
Schreiben eines benutzerdefinierten Sitezuordnungsproviders Schreiben eines benutzerdefinierten Sitezuordnungsproviders
Weitere hilfreiche Informationen Weitere hilfreiche Informationen
Schlussbemerkung Schlussbemerkung
Der Autor Der Autor

Einführung

Bisher blieb beim Erstellen einer Site mit den aktuellen Versionen von ASP.NET (1.0 und 1.1) das Bereitstellen von Navigations-Steuerelementen dem Entwickler überlassen, da Steuerelemente wie Breadcrumb nicht unterstützt wurden. Eine der am häufigsten gefragten Erweiterungen war eine Strukturansicht. Die Version 2.0 von .NET beseitigt diese beiden sowie viele weitere Auslassungen. Zudem ist das bereitgestellte Schema jetzt einfach zu erweitern.

ASP.NET 2.0 enthält nun einige Steuerelemente, die mithilfe einer Providerklasse an die jeweilige Datenquelle gebunden werden. Dieser Provider wird verwendet, um die Daten für die Benutzeroberfläche zu generieren. Er wurde zudem so für Erweiterungen ausgelegt, dass man dafür eine benutzerdefinierte Implementierung definieren kann.

Dieser Artikel bietet eine Übersicht über das Providermodell in ASP.NET 2.0 und stellt eine benutzerdefinierte Implementierung der Klasse SiteMapProvider vor, die von Steuerelementen wie Breadcrumb und TreeView bei der Wiedergabe einer Seite verwendet wird.

 

Provider in ASP.NET 2.0

Das Providermodell besteht im Allgemeinen aus Klassen - einer abstrakten Providerbasisklasse, einer konkreten Implementierung dieser Klasse und einer Hilfsklasse, die Aufrufe an den konkreten Provider delegiert. Um dies zu verdeutlichen, werden die Membership-Klassen erläutert, die verwendet werden, um Benutzer mit Systemzugriff zu erstellen, zu überprüfen und aufzulisten.

Ein häufiges Szenario hierfür ist das Überprüfen von Benutzername und Kennwort bei der Anmeldung. Vor Whidbey musste dieser Code größtenteils selbst geschrieben werden. Jetzt lässt sich dieses Szenario mit minimaler Codierung implementieren. Tatsächlich ist eine vollständige integrierte Implementierung vorhanden, so dass die Site programmiert werden kann, ohne den Authentifizierungscodes schreiben zu müssen.

Membership fügt sich wie in der folgenden Abbildung dargestellt in das Modell ein.

Klassendiagramm der Membership-Klassen in ASP.NET 2.0
Abbildung 1: Klassendiagramm der Membership-Klassen in ASP.NET 2.0

Die Klasse Membership stellt Methoden zur Verfügung, die vom Clientcode aus aufgerufen werden können (z. B. ValidateUser). Dieser Aufruf wird an die konkrete Klasse SqlMembershipProvider weitergeleitet, die die Implementierung bereitstellt. Sie ist von der abstrakten Klasse MembershipProvider abgeleitet und kann so durch jede erforderliche Implementierung ersetzt werden. In einer frühen Alphaversion von .NET 2.0 wurden Schnittstellen anstelle von abstrakten Basisklassen verwendet, um den gewünschten Polymorphismus bereitzustellen. Dies wurde jedoch für die Beta1-Version geändert, da eine Basisklasse einen Teil der für die Klasse Membership erforderlichen Implementierung vorhält und so den für einen benutzerdefinierten Membership-Provider zu schreibenden Code minimiert.

Der jeweilige Membership-Provider wird mit der Klasse Membership verknüpft, indem man die Datei web.config ändert und einen Abschnitt <membership> hinzufügt, der etwa folgendermaßen aussieht.

<membership defaultProvider="ASPNET_Auth">
  <providers>
    <add connectionStringName="ASPNET_Auth"
         applicationName="/TestWeb" description="This is a test database"
         requiresUniqueEmail="false" enablePasswordRetrieval="false"
         enablePasswordReset="true" requiresQuestionAndAnswer="false"
         passwordFormat="Hashed" name="ASPNET_Auth"
         type="System.Web.Security.SqlMembershipProvider, System.Web,
               Version=2.0.3500.0, Culture=neutral,
               PublicKeyToken=b03f5f7f11d50a3a" />
  </providers>
</membership>

Dieses Verfahren erfordert außerdem folgende Datenbankverbindungseinstellung in der Datei web.config.

<connectionStrings>
  <add name="ASPNET_Auth"
    connectionString="server=(local);Integrated
      Security=SSPI;database=ASPNET_Auth" />
</connectionStrings>

Sie können diese Einträge entweder manuell oder mit dem neuen ASP.NET-Sitekonfigurationstool hinzufügen.

Eine Zusatzfunktion der Provider ist das optionale Definieren mehrerer Provider für einen festgelegten Funktionsbereich. Dies ermöglicht gegebenenfalls das Speichern von Informationen in separaten Datenquellen. Sie können dem Abschnitt <providers> mehrere Provider hinzufügen, um diese dann gegebenenfalls für ihre Site zu verwenden.

 

Sitezuordnungen in ASP.NET 2.0

Die Navigation einer Website erfolgt häufig über eine hierarchische Struktur, bei der die Seiten beispielsweise wie folgt definiert werden.

Home.aspx
  Articles.aspx
    Article1.aspx
    ...
    ArticleN.aspx
  Tips.aspx
    Tip1.aspx
    ...
    TipN.aspx

Für eine einfache Website lässt sich diese Struktur in der statischen Datei web.sitemap speichern, die anschließend verwendet wird, indem man auf einer Webseite ein SiteMapDataSource-Steuerelement erstellt, das an ein TreeView- oder SiteMapPath-Steuerelement gebunden wird, das anzeigt, wo Sie sich in der Navigationshierarchie befinden.

Die Datei web.sitemap für die oben abgebildeten Seiten sieht wie folgt aus.

<?xml version="1.0" encoding="utf-8" ?>
<siteMap>
  <siteMapNode url="Home.aspx" title="Home">
    <siteMapNode url="Articles.aspx" title="Articles" >
      <siteMapNode url="Article1.aspx" title="Article01" />
      ...
      <siteMapNode url="ArticleN.aspx" title="ArticleN" />
    </siteMapNode>
    <siteMapNode url="Tips.aspx" title="Tips" >
      <siteMapNode url="Tip1.aspx" title="Tip01" />
      ...
      <siteMapNode url="TipN.aspx" title="TipN" />
    </siteMapNode>
  </siteMapNode>
</siteMap>

Bei Verwendung der integrierten Funktionalität benötigen Sie lediglich diese Sitezuordnungsdatei, eine SiteMapDataSource und ein oder mehrere Steuerelemente, die diese Datenquelle verwenden. Damit sind die Vorteile der Sitezuordnungen jedoch noch nicht erschöpft.

Es gibt eine Reihe von Attributen, die auf die Knoten in der Sitezuordnungsdatei angewendet werden können, am häufigsten description und roles. Ersteres ermöglicht das Erstellen von beschreibendem Text für den Inhalt einer bestimmten Seite, mithilfe des zweiten Attributs läst sich die Sichtbarkeit eines Knotens (oder einer Knoten-Gruppe) basierend auf der Rolle des aktuellen Benutzers definieren.

Wenn für einen Sitezuordnungsknoten Rollen verwendet werden, kann eine Sitezuordnungsressource erstellt werden, die von allen Benutzern verwendet wird. Einige Knoten (Nodes) werden für alle Benutzer angezeigt, unabhängig davon, ob sie authentifiziert sind oder nicht, während die Anzeige anderer von den Rollen abhängig sein kann, die den Benutzern bei der Anmeldung zugewiesen werden. Zum Aktivieren dieser Funktion reicht es jedoch nicht aus, die Rollen innerhalb der Sitezuordnung festzulegen - es sind einige weitere Schritte erforderlich.

Das ASP.NET-Team hat sich bemüht, die Verwendung von Sitezuordnungen (und anderer Provider) so einfach wie möglich zu gestalten, um innerhalb der globalen Datei web.config, die im Verzeichnis CONFIG zur Verfügung steht, einen dateibasierten Sitezuordnungs-Provider zur allgemeinen Verwendung zu erstellen. Dies ist für die meisten einfachen Sites ausreichend. Diese Definition enthält jedoch nicht das Attribut securityTrimmingEnabled, das erforderlich ist, damit die Sitezuordnung lediglich Knoten anzeigt, auf die ein bestimmter Benutzer basierend auf seiner Rolle zugreifen kann.

Um sicherzustellen, dass die Sitezuordnung lediglich Knoten anzeigt, die den jeweiligen Rollen der Benutzer entsprechen, muss man in der Datei web.config im Sitezuordnungsabschnitt <providers> einen weiteren Providereintrag hinzufügen und das Attribut dort definieren. Der im Folgenden abgebildete Konfigurationsabschnitt definiert einen Provider, der dieses Attribut enthält. Außerdem wird AspNetXmlSiteMapProvider aus der Providerauflistung entfernt und der Standardprovider als SecuredSiteMapProvider definiert. Das Entfernen des Standardproviders ist nicht unbedingt erforderlich. In diesem Beispiel erfolgte dieser Schritt jedoch, da auf diese Weise keine der Seiten der Site versehentlich den generischen Provider verwenden kann.

<siteMap enabled="true" defaultProvider="SecuredSiteMapProvider">
  <providers>
    <clear/>
    <add name="SecuredSiteMapProvider"
      type="System.Web.XmlSiteMapProvider, System.Web,
      Version=2.0.3600.0, Culture=neutral,
      PublicKeyToken=b03f5f7f11d50a3a"
      siteMapFile="web.sitemap" securityTrimmingEnabled="true"/>
    <add name="AtriclesProvider"
       type="System.Web.XmlSiteMapProvider, System.Web,
       Version=2.0.3600.0, Culture=neutral,
       PublicKeyToken=b03f5f7f11d50a3a"
       siteMapFile="Articles/web.sitemap" securityTrimmingEnabled="true"/>
    </providers>
</siteMap>
<roleManager enabled="true" />

Wenn der Abschnitt <siteMap> definiert ist, ist außerdem der Rollenprovider zu aktivieren. Ein Beispiel hierfür finden Sie in der letzten Zeile der vorhergehenden Abbildung. Das Aktivieren des Rollenproviders stellt sicher, dass innerhalb des Codes Prüfungen wie IsInRole("Administrator") ausgeführt werden können. RoleManager erreicht dies, indem er das RoleManagerModule in die Anwendung einfügt, das dazu dient, ein Objekt einzurichten, das die IPrincipal-Schnittstelle implementiert. Vor Whidbey mussten Entwickler diesen Code selbst schreiben. Jetzt handelt es sich lediglich um eine einfache Ergänzung der Datei web.config.

Wenn RoleManager eingerichtet ist und sich der entsprechende Sitezuordnungsprovider in der Datei web.config befindet, sind die meisten Schritte für die Bereitstellung der auf den aktuellen Benutzer zugeschnittenen Sitezuordnungen durchgeführt. Dennoch sind zwei weitere Schritte erforderlich. Im ersten Schritt müssen Sie die Sitezuordnungsdatei ändern, damit sie die Rollen auflistet, die auf eine bestimmte Seite oder einen Abschnitt der Website zugreifen können. Dazu ist die Datei web.sitemap zu ändern und allen zu sichernden Knoten ein Attribut roles hinzuzufügen.

<siteMapNode url="Editor/Editor.aspx" title="Editor" roles="Editor" />

Als letztes Puzzleteil ist die Datei web.config zu ändern und sind Rollen für bestimmten Seiten (oder Verzeichnisse) festzulegen. Wird ein Node von der Sitezuordnung angefordert, wird die Methode SiteMapNode.IsAccessibleToUser() aufgerufen, um sicherzustellen, dass der Benutzer Zugriff auf diesen Teil der Sitezuordnung hat. Hierzu wird überprüft, ob auf die angeforderte Seite bzw. den angeforderten Ordner zugegriffen werden kann. Wenn dies der Fall ist, kann der Knoten angezeigt werden, wenn nicht, wird er von der Sitezuordnung ausgeschlossen. In diesem Beispiel wurde ein Verzeichnis Editor erstellt und festgelegt, dass auf dieses Verzeichnis lediglich Benutzer mit der Rolle Editor zugreifen können. Diese Änderung wurde in der Datei web.config der obersten Ebene vorgenommen, anstatt eine separate Datei web.config innerhalb des Unterverzeichnisses Editor zu definieren.

<location path="Editor" allowOverride="false">
  <system.web>
    <authorization>
      <allow roles="Editor"/>
      <deny users="*,?"/>
    </authorization>
  </system.web>
</location>

Es ist sinnvoll, Zugriffsberechtigungen in der Datei web.config der obersten Ebene zu definieren. Dies gilt insbesondere für größere Sites, bei denen mehrere Teams Inhalte für verschiedene Verzeichnisse bereitstellen. Diesen Schritt an einer einzigen Stelle durchzuführen (und nicht zuzulassen, dass dieser versehentlich überschrieben wird, indem allowOverride=false festgelegt wird), stellt sicher, dass Benutzer lediglich die Inhalte anzeigen können, die sie anzeigen sollen.

Im Folgenden finden Sie eine Liste der für das Einrichten einer auf den aktuellen Benutzer zugeschnittenen Sitezuordnung erforderlichen Schritte:

  • Fügen Sie der Datei web.config einen neuen Sitezuordnungsprovider hinzu und stellen Sie sicher, dass das Flag securityTrimmingEnabled auf true gesetzt ist.

  • Aktivieren Sie RoleManager, indem Sie die Datei web.config ändern.

  • Definieren Sie Zugriffsberechtigungen zu Seiten/Verzeichnissen in der Datei web.config.

  • Legen Sie Rollen für Sitezuordnungsknoten fest, auf die nicht alle Benutzer zugreifen können.

Zusätzlich zur Definition der Sicherheit für Knoten einer Sitezuordnung möchten Sie möglicherweise mehrere Sitezuordnungs-Dateien erstellen. So können Sie beispielsweise eine Hauptsitezuordnung, die die Gesamtstruktur der Site definiert, sowie sekundäre Dateien erstellen, die bestimmte Teile der Site definieren. Dieses Verfahren ist insbesondere bei einer Site sinnvoll, deren Bereiche von verschiedenen Teams verwaltet werden. Wenn ein Team einen Teil der Site aktualisiert, kann es die Sitezuordnung für diesen Bereich ändern, ohne andere Teile der Site zu beeinflussen.

Um dies zu erreichen, sind zwei Schritte notwendig. Zunächst ist es erforderlich, im Abschnitt für die Sitezuordnungs-Provider der Konfigurationsdatei einige Provider zu definieren. Beim Definieren eines Providers kann optional der Name der Sitezuordnungsdatei eingeschlossen werden. Hier können Sie verschiedene Dateinamen (oder Pfade) angeben, die bestimmten Bereichen der Site zugeordnet sind. Ein Beispiel hierfür finden Sie oben im Abschnitt <siteMap>, in dem zwei Sitezuordnungsprovider erstellt wurden: SecuredSiteMapProvider und ArticlesProvider. Der zweite kann als Verknüpfung zu einer anderen Sitezuordnung verwendet werden (in diesem Fall in einem separaten Unterverzeichnis der Site).

Anschließend kann man in der Hauptsitezuordnungsdatei einen Knoten einfügen, der folgendermaßen eine Verknüpfung zu dieser sekundären Zuordnung herstellt.

<?xml version="1.0" encoding="utf-8" ?>
<siteMap>
  <siteMapNode url="Home.aspx" title="Home">
    <siteMapNode provider="ArticlesProvider" />
  </siteMapNode>
<siteMap>

Beim Provider dieser sekundären Sitezuordnung kann es sich um eine standardmäßige XML-Datei handeln. Sie können jedoch auch (wie in diesem Artikel geschehen) einen benutzerdefinierten Sitezuordnungs-Provider erstellen, der die erforderlichen Konfigurations-Informationen aus der Datenbank liest. Dieses Szenario ist bei größer angelegten Websites verbreitet, insbesondere wenn der Inhalt dynamisch generiert wird. Im Beispielcode wird erläutert, wie Sie eine Website so einrichten, dass eine dynamische, auf den Datensätzen einer Datenbank basierende Sitezuordnung entstehen kann. Wenn der Datenbank neue Datensätze hinzugefügt werden, spiegelt sich dies in der Sitezuordnung wieder.

 

Schreiben eines benutzerdefinierten Sitezuordnungsproviders

Nach all diesen Schritten lässt sich nun ein benutzerdefinierter Sitezuordnungs-Provider erstellen, der in die Anwendung integriert werden kann. Die Sitezuordnungs-Hierarchie besteht aus verschiedenen Klassen, die in der folgenden Abbildung dargestellt sind.

Klassen in der Sitezuordnungshierarchie
Abbildung 2: Klassen in der Sitezuordnungshierarchie

Die Klasse ProviderBase ist die Basisklasse für alle Provider in .NET 2.0. Sie stellt die Methode Initialize sowie die beiden Eigenschaften Name und Description zur Verfügung. Damit .NET auf einen bestimmten Provider zugreifen kann, wird das Attribut type verwendet, um dynamisch eine Instanz der Klasse zu erstellen. Anschließend wird die Methode Initialize() aufgerufen, die die anderen im Konfigurationsabschnitt definierten Attribute durchläuft.

Beziehung des Providers zur Datei "sitemap.config"
Abbildung 3: Beziehung des Providers zur Datei "sitemap.config"

ProviderBase verwendet das erforderliche Attribut name, speichert dies intern und liest das optionale Attribut description aus. In der Konfigurationsdatei können weitere gewünschte Attribute definiert werden. So wird beispielsweise XmlSiteMapProvider mit dem Namen der Sitezuordnungsdatei versehen.

Beim Arbeiten mit einer neuen Reihe von Klassen muss insbesondere bei Betaprodukten häufig die Reihenfolge ermittelt werden, in der Funktionen und Eigenschaften aufgerufen werden (oder ob sie überhaupt aufgerufen werden). Um selbst Klassen zu schreiben, ist es mitunter hilfreich, zu verstehen, wie diese zu verwenden sind. SiteMapProvider stellt in dieser Instanz keine Ausnahme dar. Es soll mit minimalem Programmieraufwand eine funktionierende Klasse eingerichtet werden, um sie zu einem späteren Zeitpunkt auszuarbeiten, wenn zusätzliche Funktionalität hinzugefügt werden soll. Um die inneren Funktionen einer Reihe von Klassen zu untersuchen, kann Lutz Roeders Reflector verwendet werden. Ohne ein funktionierendes Beispiel ist es jedoch schwierig, die exakte Reihenfolge der Aufrufe zu ermitteln.

In diesem Fall wurden zwei Klassen konstruiert - eine abgeleitet von SiteMapProvider und eine von SiteMapNode - und alle abstrakten oder virtuellen Funktionen und Eigenschaften implementiert, um die Aufrufe und Parameter im aktuellen Antwortstream zu protokollieren. Im Folgenden finden Sie ein Beispiel für eine der Eigenschaften von SiteMapProvider zusammen mit dem Protokollierungscode.

public override SiteMapNode RootNode
{
  get
  {
    Log("get_RootNode");
    return new MySiteMapNode(this, "Key", "default.aspx", "Root");
  }
}

[Conditional("VERBOSE")]
private static void Log(string caption, params object[] args)
{
    string s = string.Format(caption, args);

    HttpContext.Current.Trace.Write(
      string.Format("MySiteMap::{0}<br/>", s));
}

Wenn eine vollständig geordnete Liste der aufgerufenen Funktionen vorhanden ist, können Sie mit dem Implementieren der Funktionen beginnen, bis ein funktionsfähiges Beispiel vorliegt. Beachten Sie, dass in der Implementierung der Funktion Log das Attribut Conditional verwendet wurde, um sicherzustellen, dass diese Funktion und alle an sie gerichteten Aufrufe nicht in Releasebuilds vorhanden sind. In diesem Fall sollten sie eine andere Konstante als DEBUG (z. B. VERBOSE) verwenden, da so die Protokollanweisungen im Quellcode verbleiben können, ohne Probleme zu verursachen.

Bei Webprojekten gibt es bei bedingten Kompilierkonstanten das Problem, dass nicht einfach eigene Konstanten definiert werden können. Die Konstante muss entweder auf der Seite mithilfe von <%@ Page ... CompilerOptions="/d:DEBUG" %> oder durch Ändern der Datei web.config und Hinzufügen eines Abschnitts <compilers> definiert werden, der dem in der globalen Datei web.config ähnelt. Wenn die Konstante der Seite hinzugefügt wird, gilt diese nur für den Code innerhalb dieser Seite. Daher wird für das Definieren einer anwendungsglobalen Kompilierkonstante eine Definition innerhalb der Datei web.config empfohlen (siehe unten).

<compilation defaultLanguage="cs" debug="true">
  <compilers>
    <compiler language="C#;csharp;cs"
       extension=".cs;cs"
       type="Microsoft.CSharp.CSharpCodeProvider,
         System, Version=2.0.0.0, Culture=neutral,
         PublicKeyToken=b77a5c561934e089"
       compilerOptions="/define:VERBOSE" />
  </compilers>
</compilation>

Anschließend lässt sich die Reihenfolge anzeigen, in der die Methoden für den benutzerdefinierten Provider aufgerufen werden.

Die erste für den benutzerdefinierten Provider aufgerufene Methode ist Initialize(). An diese werden sämtliche in der Datei web.config definierten Attribute mit Ausnahme des Providertyps übergeben. Daher können dem Provider zusätzliche Verarbeitungsfunktionen hinzugefügt werden, indem man andere nicht standardmäßige Attribute unterstützt. Ein Beispiel hierfür finden Sie weiter unten in diesem Artikel.

Im Anschluss an die Methode Initialize() wird die Methode GetRootNodeCore aufgerufen. Von hier muss eine Instanz von SiteMapNode oder einer abgeleiteten Klasse zurückgegeben werden. Diese wird als oberstes Element in der Hierarchie verwendet. Das Implementieren einer abgeleiteten Klasse ist recht einfach, da diese Basisklasse alle erforderlichen Funktionen bereitstellt. In diesem Beispiel wurde SqlSiteMapNode erstellt. Hierbei handelt es sich jedoch um eine einfache Ableitung der Basisklasse ohne zusätzliche Funktionen.

Wenn der Stammknoten zurückgegeben wurde, ruft ASP.NET die Setmethode der Eigenschaft ParentProvider auf. In diesem Beispiel ist XmlSiteMapProvider der übergeordnete Provider, da die benutzerdefinierte Sitezuordnung als Knoten innerhalb der Haupt-XML-Sitezuordnungsdatei eingeschlossen ist. Wenn zusätzliche Funktionalität bereitgestellt werden soll, muss diese Eigenschaft lediglich überschrieben werden.

Jeder Sitezuordnungsknoten verfügt über die Eigenschaften Title, Description und URL. Anschließend werden diese Eigenschaften ausgelesen, um den Benutzern eine entsprechende Anzeige zur Verfügung zu stellen. Ein Grund für das Überschreiben von Title ist die Möglichkeit, den Text basierend auf der Sprache des aktuell angemeldeten Benutzers zu lokalisieren. Eine Lokalisierung wird teilweise von der Klasse SiteMapProvider unterstützt, da sie die boolesche Eigenschaft EnableLocalization definiert, die vom Provider festgelegt werden kann. XmlSiteMapProvider verwendet ein Attribut enableLocalization (das in der Funktion BuildSitemap aus der Sitezuordnungsdatei gelesen wird). Daher sollten Sie aus Konsistenzgründen ein Attribut mit demselben Namen nutzen, wenn Sie diese Lokalisierung unterstützen wollen.

Vor der Anzeige eines Knotens wird die Eigenschaft IsAccessibleToUser aufgerufen. Dies erfolgt für XmlSiteMapNode, um zu überprüfen, ob der aktuelle Benutzer auf die für einen Knoten definierte URL zugreifen kann. Eine Unterstützung für diese Methode ist leicht erreichbar, da die Basisklasse sämtliche Schritte automatisch durchführt. Solange Sie nicht ein spezielles Verhalten benötigen, das von der aktuellen Implementierung nicht zur Verfügung gestellt wird, müssen Sie diese Methode nicht überschreiben.

Eine Besonderheit der Implementierung von IsAccessibleToUser ist die Art, wie sie funktioniert. Wenn für einen Knoten eine Liste von Rollen definiert wird, ist vernünftigerweise davon auszugehen, dass ein bestimmter Benutzer, für den diese Rolle nicht definiert ist, den Knoten nicht sehen kann. Sie können dies testen, indem Sie eine einfache XML-Sitezuordnung erstellen und eine Rolle für einen der Knoten einrichten, über die der aktuelle Benutzer nicht verfügt (etwa sausage), und anschließend die Sitezuordnung anzeigen. Dieser Knoten wird jedoch nicht ausgeblendet, sondern angezeigt.

Dies liegt daran, dass die Ausführung des Codes in SiteMapProvider.IsAccessibleToUser nicht sofort beendet wird, wenn es sich bei der Rolle um keine handelt, auf die der Benutzer Zugriff hat. Stattdessen überprüft der Code URL-Berechtigungen, um zu ermitteln, ob der Benutzer auf die Seite zugreifen darf (oder nicht). Daher muss zusätzlich zu den im Sitezuordnungsknoten definierten Rollen in der Datei web.config ein Abschnitt <authorization> definiert werden. In der Beispielimplementierung wird diese Verarbeitung umgangen und unmittelbar false von IsAccessibleToUser zurückgegeben, wenn der aktuelle Benutzer nicht über die für einen Knoten definierten Rollen verfügt.

Dies stellt sicher, dass in der Sitezuordnung Knoten für Seiten nicht angezeigt werden, auf die bestimmte Benutzer keinen Zugriff haben sollen. Die Benutzer können jedoch eine URL eingeben und immer noch versuchen, auf diese Seite zuzugreifen. In einem solchen Szenario müssen Sie nach wie vor URL-basierte Berechtigungen in der Datei web.config definiert oder ein anderes Schema auswählen. So lassen sich die Rollen beispielsweise im Seitencode überprüfen. Damit werden Benutzer umgeleitet, wenn sie nicht über ausreichende Zugriffsberechtigungen für diese Seite verfügen.

Im Beispiel wurden für SqlSiteMapProvider folgende Methoden/Eigenschaften implementiert. Weitere Details der Implementierung finden Sie im Artikelquelltext.

Tabelle 1: Methoden/Eigenschaften von "SqlSiteMapProvider"

Methode/Eigenschaft

Beschreibung

Initialize

Erhält alle Attribute des Abschnitts <providers> der Datei web.config. Hier wird die Methode Initialize() der Basisklasse aufgerufen. Anschließend werden sämtliche benutzerdefinierten Attribute gelesen und innerhalb des Providers gespeichert.

FindSiteMapNode

Nach Erhalt des übergebenen URL gibt diese Methode einen Sitezuordnungsknoten zurück, der dieser URL entspricht.

GetChildNodes

Fragt die Datenbank nach untergeordneten Knoten ab, nachdem sie den übergebenen Knoten erhalten hat. Als Optimierung überprüft diese Methode die Eigenschaft HasChildNodes von SiteMapNodes, die für alle untergeordneten Knoten den Wert False enthält.

GetParentNode

Gibt für einen angegebenen Knoten den übergeordneten Knoten zurück.

GetRootNodeCore

Wird von der Eigenschaft RootNode aufgerufen, um den obersten Knoten der Sitezuordnung abzurufen.

IsAccessibleToUser

Überprüft, ob der aktuelle Benutzer auf einen angegebenen Knoten zugreifen kann.

Wie Sie sehen, müssen nicht allzu viele Elemente implementiert werden. Dennoch gilt es, noch einige andere Punkte zu beachten. Die folgende Datenbankstruktur bietet geeignete Informationen zu Knoten und der Hierarchie dieser Knoten.

Knotentabelle
Abbildung 4: Knotentabelle

Zusätzlich zu dieser Tabelle erstellten wir eine Reihe von gespeicherten Prozeduren, die Knoten aus dieser Tabelle für die Verwendung durch den Sitezuordnungsprovider auswählen. Anschließend erzeugten wir zwei verschiedene SQL-basierte Provider. Der erste führt keine Zwischenspeicherung durch und kehrt für jede Anfrage zur Datenbank zurück, der zweite legt die vollständige Sitezuordnung auf einmal im Arbeitsspeicher ab.

Da die Tabelle hierarchisch organisiert ist, wurde ein Common Table Expression (CTE) verwendet, um die gesamte Datenhierarchie in einem Durchlauf abzurufen. CTEs sind nur in SQL Server 2005 verfügbar.

CREATE PROCEDURE SelectTreeNodes AS
  set nocount on ;

  WITH NodeTree ( NodeID, Caption, URL, ParentNodeID, Roles) AS
  (
    SELECT NodeID, Caption, URL, ParentNodeID, Roles
      FROM Node
      WHERE ( ParentNodeID IS NULL )
    UNION ALL
      SELECT child.NodeID, child.Caption, child.URL,
          child.ParentNodeID, child.Roles
        FROM Node AS child INNER JOIN
          NodeTree AS parent ON parent.NodeID = child.ParentNodeID
  )
  SELECT NodeID, Caption, URL, ParentNodeID, Roles
    FROM NodeTree

Hier wird als CTE der Ausdruck WITH NodeTree verwendet, der einen Ergebnissatz generiert, der an den Provider zurückgegeben und verarbeitet wird, um sämtliche Knoten der Sitezuordnung zu generieren. Der Ergebnissatz wurde hier nicht ausdrücklich geordnet, so dass er in der entsprechenden hierarchischen Reihenfolge zurückgegeben wird (übergeordnete Zeile zuerst, dann sämtliche untergeordneten Zeilen der ersten Ebene, anschließend der zweiten Ebene, usw.).

Um den CTE-basierten Provider im Beispielcode verwenden zu können, müssen Sie TipsProvider auskommentieren und die Kommentarzeichen für CTEProvider entfernen (siehe unten).

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="https://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode url="default.aspx" title="Home" description="">
    <siteMapNode provider="ArticlesProvider" />
    <!--siteMapNode provider="TipsProvider" /-->
    <siteMapNode provider="CTEProvider" />
    <siteMapNode url="Editor/Editor.aspx" title="Editor" roles="Editor" />
  </siteMapNode>
</siteMap>

 

Weitere hilfreiche Informationen

Weiter oben in diesem Artikel wurde erläutert, was in der Methode Initialize() des Providers ausgeführt wird. Kurz zur Wiederholung: Diese Methode erhält sämtliche in der Datei web.config definierten Attribute (mit Ausnahme des Attributs type). Sämtliche benutzerdefinierten XML-Attribute in der Konfigurationszeile für den Provider werden an den Provider übergeben. Dadurch lässt sich das Verhalten des Providers ändern, wodurch er sich für mehr potenzielle Kunden eignet.

So verfügen die Standardprovider beispielsweise über das Attribut securityTrimmingEnabled, das definiert, ob die für einen Knoten definierten Rollen vor der Anzeige des Knotens ausgewertet werden. Da die Klasse SiteMapProvider diese Funktion bereits unterstützt, kann stattdessen ein Attribut definiert werden, das es ermöglicht, den Knoten in der Datenbank auszuwählen, an dem die Sitezuordnung beginnt.

Die Tabelle besteht aus verschiedenen hierarchisch strukturierten Knoten-Datensätzen. In der Standardeinstellung werden sämtliche Knoten von oben nach unten ausgewählt und dem Benutzer angezeigt. Durch einen kleinen Zusatz beginnt die Auswahl an einem bestimmten Knoten in der Hierarchie. Hierzu muss lediglich die eingehende Attributauflistung verarbeitet und nach benutzerdefinierten XML-Attributen gesucht werden. In diesem Beispiel wurde Code für die Verarbeitung des Attributs startFrom hinzugefügt. Dadurch wird die numerische ID eines Knotens innerhalb der Datenbank definiert. Wird diese verwendet, gilt für die Sitezuordnung dieser Knoten als Stammknoten.

Abschließend will ich erklären, wie bei der Anzeige einer großen Sitezuordnung das Verhalten von TreeView geändert werden kann. Wenn ein TreeView-Steuerelement an eine hierarchische Datenquelle wie z. B. SiteMapDataSource gebunden ist, kann definiert werden, wann Knoten abgerufen werden. In der Standardeinstellung werden sämtliche Knoten in der Hierarchie ausgewählt und an den Aufrufer zurückgegeben. Bei einer großen Site kann es jedoch sinnvoll sein, die Sitezuordnung lediglich bei Bedarf aufzubauen. Betrachten Sie beispielsweise die MSDN-Site: Wenn zur Laufzeit der vollständige TreeView heruntergeladen würde, könnten nur wenige Benutzer die Site verwenden. Hier ist ein bedarfsgesteuertes Laden von Knoten in der Struktur erforderlich.

Hierzu muss TreeView ein Abschnitt <DataBindings> und anschließend ein Element <asp:TreeNodeBinding> hinzugefügt werden.

<asp:TreeView ID="siteHierarchy" runat="server"
  DataSourceID="MainSiteMap" ExpandDepth=1>
  <DataBindings>
    <asp:TreeNodeBinding PopulateOnDemand=true
     TextField="Title" NavigateUrlField="URL" />
  </DataBindings>
</asp:TreeView>

Im Beispiel wurde das Attribut ExpandDepth mit dem Wert 1 definiert. Dadurch wird sichergestellt, dass in der Standardeinstellung die Stammebene und die direkt untergeordnete Ebene angezeigt werden. Im Element <asp:TreeNodeBinding> wurde definiert, welche Felder für den Text des Knotens und der URL verwendet werden. Das wichtige Attribut ist PopulateOnDemand. Wenn dieses Attribut auf true gesetzt ist, generiert ASP.NET Javascript-Code, der ausgeführt wird, wenn ein Knoten erweitert wird. Dadurch wird ein Aufruf zurück an den Server ausgeführt, sämtliche Knoten unterhalb des aktuell ausgewählten Knotens abgerufen und diese Knoten auf dem Client angezeigt.

 

Schlussbemerkung

Das Providermodell in ASP.NET 2.0 ist sehr leistungsstark, da man Standardfunktionalitäten durch benutzerdefinierte Komponenten ersetzen kann und es dennoch eine benutzerfreundliche Oberfläche bereitstellt. Einer der Hauptvorteile: Back-End-Komponenten wie z. B. Mitgliedschaft, Rollenverwaltung und Sitezuordnung sind ohne eine Änderung der Benutzeroberfläche zu ersetzen. In diesem Artikel wurde erläutert, wie ein benutzerdefinierter Sitezuordnungsprovider erstellt und mit einer ASP.NET-Site verknüpft werden kann.

Sie können die Idee, Standardfunktionalitäten durch benutzerdefinierte Komponenten zu ersetzen, auch in .NET 1.x verwenden. Dieser Ansatz kann dadurch auch die Aktualisierung auf ASP.NET 2.0 vereinfachen.

 

Der Autor

Morgan Skinner arbeitet in Großbritannien als Berater für Anwendungsentwicklung für Microsoft. Seine Spezialgebiete sind C#, ASP.NET und Windows Forms. Er arbeitet seit der PDC-Version aus dem Jahr 2000 mit .NET. Seine Homepage finden Sie unter http://www.morganskinner.com/ (in englischer Sprache).