Nutzen der integrierten Pipeline von IIS 7.0

von Mike Volodarsky

In IIS 6.0 und früheren Versionen konnten .NET-Anwendungskomponenten über die ASP.NET-Plattform entwickelt werden. ASP.NET wurde über eine ISAPI-Erweiterung in IIS integriert und macht ein eigenes Anwendungs- und Anforderungsverarbeitungsmodell verfügbar. Dadurch werden im Grunde zwei separate Serverpipelines verfügbar gemacht: eine für native ISAPI-Filter und Erweiterungskomponenten und eine weitere für verwaltete Anwendungskomponenten. ASP.NET-Komponenten werden vollständig innerhalb der ISAPI-Erweiterungsblase von ASP.NET und nur für Anforderungen ausgeführt, die ASP.NET in der Skriptzuordnungskonfiguration von IIS zugeordnet sind.

Ab IIS 7.0 ist die ASP.NET-Runtime in Core Web Server integriert. Dadurch wird eine einheitliche Anforderungsverarbeitungspipeline bereitgestellt, die sowohl für native als auch für verwaltete Komponenten (sogenannte Module) verfügbar gemacht wird. Diese Integration bietet unter anderem folgende Vorteile:

  • Sie ermöglicht die Anwendung von Diensten, die von nativen und verwalteten Modulen bereitgestellt werden, auf alle Anforderungen – unabhängig vom Handler. Die verwaltete Formularauthentifizierung kann beispielsweise für alle Inhalte verwendet werden, einschließlich ASP-Seiten, CGIs und statischen Dateien.
  • Sie ermöglicht es ASP.NET-Komponenten, Funktionen bereitzustellen, die bislang aufgrund ihrer Platzierung in der Serverpipeline nicht verfügbar waren. Beispielsweise kann ein verwaltetes Modul, das Funktionen zum Umschreiben von Anforderungen bereitstellt, die Anforderung vor jeglicher Bearbeitung durch den Server (einschließlich Authentifizierung) umschreiben.
  • Sie bietet einen zentralen Ort zum Implementieren, Konfigurieren, Überwachen und Unterstützen von Serverfeatures – beispielsweise in Form einer einzelnen Modul- und Handlerzuordnungskonfiguration, einer einzelnen benutzerdefinierten Fehlerkonfiguration und einer einzelnen URL-Autorisierungskonfiguration.

In diesem Artikel erfahren Sie, wie ASP.NET-Anwendungen den integrierten Modus in IIS 7.0 und höheren Versionen nutzen können. Hierzu werden folgende Aufgaben veranschaulicht:

  • Aktivieren und Deaktivieren von Modulen auf Anwendungsebene
  • Hinzufügen verwalteter Anwendungsmodulen zum Server und Ermöglichen ihrer Anwendung auf alle Anforderungstypen
  • Hinzufügen verwalteter Handler

Weitere Informationen zur Erstellung von Modulen für IIS 7.0 und höhere Versionen finden Sie unter Entwickeln von IIS 7.0-Modulen und -Handlern mit .NET Framework.

Der Blog http://www.mvolo.com/ enthält außerdem weitere Tipps zur Nutzung des integrierten Modus und zur Entwicklung von IIS-Modulen, die die ASP.NET-Integration in IIS 7.0 und höheren Versionen nutzen. Dort können Sie eine Reihe solcher Module herunterladen. Hierzu zählen Umleiten von Anforderungen an Ihre Anwendung mit dem HttpRedirection-Modul, Erstellen von ansprechenden Verzeichnislisten für Ihre IIS-Website mit dem DirectoryListingModule und Anzeigen attraktiver Dateisymbole in Ihren ASP.NET-Anwendungen mit IconHandler.

Voraussetzungen

Für die Schritte in diesem Dokument müssen die folgenden Features von IIS 7.0 oder einer höheren Version installiert sein:

ASP.NET

Installieren Sie ASP.NET über die Systemsteuerung von Windows Vista. Wählen Sie unter „Programme und Features“ die Option „Windows-Features aktivieren oder deaktivieren“ aus. Öffnen Sie „Internetinformationsdienste“ > „WWW-Dienste“ > „Anwendungsentwicklungsfeatures“, und aktivieren Sie das Kontrollkästchen „ASP.NET“.

Wenn Sie über einen Windows Server® 2008-Build verfügen, öffnen Sie „Server-Manager“ > „Rollen“, und wählen Sie „Webserver (IIS)“ aus. Klicken Sie auf „Rollendienste hinzufügen“. Aktivieren Sie unter „Anwendungsentwicklung“ das Kontrollkästchen „ASP.NET“.

ASP (klassisch)

Wir möchten zeigen, dass ASP.NET-Module jetzt mit allen Inhalten und nicht nur mit ASP.NET-Seiten funktionieren. Installieren Sie daher ASP (klassisch) über die Windows Vista-Systemsteuerung. Wählen Sie „Programme“ > „Windows-Features aktivieren oder deaktivieren“ aus. Öffnen Sie „Internetinformationsdienste“ > „WWW-Dienste“ > „Anwendungsentwicklungsfeatures“, und aktivieren Sie das Kontrollkästchen „ASP“.

Wenn Sie über einen Windows Server 2008-Build verfügen, öffnen Sie „Server-Manager“ > „Rollen“, und wählen Sie „Webserver (IIS)“ aus. Klicken Sie auf „Rollendienste hinzufügen“. Aktivieren Sie unter „Anwendungsentwicklung“ das Kontrollkästchen „ASP“.

Hinzufügen der Formularauthentifizierung zu Ihrer Anwendung

Im Rahmen dieser Aufgabe aktivieren wir die formularbasierte ASP.NET-Authentifizierung für die Anwendung. In der nächsten Aufgabe ermöglichen wir die Ausführung des Formularauthentifizierungsmoduls für alle an Ihre Anwendung gerichteten Anforderungen, unabhängig vom Inhaltstyp.

Konfigurieren Sie zunächst die Formularauthentifizierung wie bei einer normalen ASP.NET-Anwendung.

Erstellen einer Beispielseite

Zur Veranschaulichung des Features fügen wir dem Webstammverzeichnis eine Seite vom Typ „default.aspx“ hinzu. Öffnen Sie den Editor, und erstellen Sie die folgende Datei: %systemdrive%\inetpub\wwwroot\default.aspx. (Stellen Sie sicher, dass Sie Zugriff auf das unten angegebene wwwroot-Verzeichnis haben, indem Sie den Editor als Administrator ausführen. Klicken Sie dazu mit der rechten Maustaste auf das Editor-Symbol unter „Programme\Zubehör“, und klicken Sie auf „Als Administrator ausführen“.) Fügen Sie die folgenden Zeilen ein:

<%=Datetime.Now%> 
<BR> 
Login Name: <asp:LoginName runat="server"/>

„default.aspx“ zeigt lediglich die aktuelle Zeit und den Namen des angemeldeten Benutzers an. Wir verwenden diese Seite später, um die Formularauthentifizierung in Aktion zu zeigen.

Konfigurieren von Formularauthentifizierung und Zugriffssteuerungsregeln

Kommen wir nun zum Schutz von „default.aspx“ mit der Formularauthentifizierung. Erstellen Sie eine Datei vom Typ „web.config“ im Verzeichnis %systemdrive%\inetpub\wwwroot, und fügen Sie die folgende Konfiguration hinzu:

<configuration> 
  <system.web> 
    <!--membership provider entry goes here--> 
    <authorization> 
      <deny users="?"/> 
      <allow users="*"/> 
    </authorization> 
    <authentication mode="Forms"/> 
  </system.web> 
</configuration>

Diese Konfiguration legt den ASP.NET-Authentifizierungsmodus fest, um die formularbasierte Authentifizierung zu verwenden, und fügt Autorisierungseinstellungen hinzu, um den Zugriff auf die Anwendung zu steuern. Diese Einstellung verweigert den Zugriff für anonyme Benutzer (?) und lässt nur authentifizierte Benutzer (*) zu.

Erstellen eines Mitgliedschaftsanbieters

Schritt 1: Wir müssen einen Authentifizierungsspeicher für die Überprüfung der Benutzeranmeldeinformationen bereitstellen. Zur Veranschaulichung der tiefen Integration zwischen ASP.NET und IIS 7.0 sowie höheren Versionen verwenden wir unseren eigenen XML-basierten Mitgliedschaftsanbieter. (Sie können auch den standardmäßigen SQL Server-Mitgliedschaftsanbieter verwenden, wenn SQL Server installiert ist.)

Fügen Sie den folgenden Eintrag direkt nach der ursprünglichen Konfigurationselement „<configuration>/<system.web>“ in der Datei „web.config“ hinzu:

<membership defaultProvider="AspNetReadOnlyXmlMembershipProvider"> 
  <providers> 
    <add name="AspNetReadOnlyXmlMembershipProvider" type="AspNetReadOnlyXmlMembershipProvider" description="Read-only XML membership provider" xmlFileName="~/App_Data/MembershipUsers.xml"/> 
  </providers> 
</membership>

Schritt 2: Nachdem der Konfigurationseintrag hinzugefügt wurde, müssen Sie den im Anhang bereitgestellten Code für den Mitgliedschaftsanbieter als XmlMembershipProvider.cs in Ihrem Verzeichnis %systemdrive%\inetpub\wwwroot\App_Code speichern. Sollte dieses Verzeichnis nicht vorhanden sein, müssen Sie es erstellen.

Hinweis

Bei Verwendung des Editors müssen Sie „Speichern unter:“ auf „Alle Dateien“ festlegen, um zu verhindern, dass die Datei als „XmlMembershipProvider.cs.txt“ gespeichert wird.

Schritt 3: Bleibt nur noch der eigentliche Anmeldeinformationsspeicher. Speichern Sie den folgenden XML-Codeschnipsel als Datei mit der Bezeichnung „MembershipUsers.xml“ im Verzeichnis %systemdrive%\inetpub\wwwroot\App_Data.

Hinweis

Bei Verwendung des Editors müssen Sie „Speichern unter:“ auf „Alle Dateien“ festlegen, um zu verhindern, dass die Datei als „MembershipUsers.xml.txt“ gespeichert wird.

<Users>    
    <User>        
        <UserName>Bob</UserName>
        <Password>contoso!</Password>
        <Email>bob@contoso.com</Email>        
    </User>    
    <User>        
        <UserName>Alice</UserName>        
        <Password>contoso!</Password>        
        <Email>alice@contoso.com</Email>        
    </User>    
</Users>

Sollte das Verzeichnis „App_Data“ nicht vorhanden sein, müssen Sie es erstellen.

Hinweis

Aufgrund von Sicherheitsänderungen in Windows Server 2003 und Windows Vista SP1 kann das IIS-Verwaltungstool nicht mehr zum Erstellen von Mitgliedschaftsbenutzerkonten für Mitgliedschaftsanbieter ohne GAC verwendet werden.

Wechseln Sie nach Abschluss dieser Aufgabe zum IIS-Verwaltungstool, und fügen Sie Benutzer für Ihre Anwendung hinzu, oder löschen Sie Benutzer. Starten Sie „INETMGR“ über das Menü „Ausführen…“. Wählen Sie in der Strukturansicht auf der linken Seite die Pluszeichen (+) aus, bis die Standardwebsite angezeigt wird. Wählen Sie „Standardwebsite“ aus, und klicken Sie anschließend auf der rechten Seite auf die Kategorie „Sicherheit“. Für die übrigen Features wird „.NET-Benutzer“ angezeigt. Klicken Sie auf „.NET-Benutzer“, und fügen Sie mindestens ein Benutzerkonto Ihrer Wahl hinzu.

Die neu erstellten Benutzer finden Sie in „MembershipUsers.xml“.

Erstellen einer Anmeldeseite

Um die Formularauthentifizierung verwenden zu können, müssen wir eine Anmeldeseite erstellen. Öffnen Sie den Editor, und erstellen Sie die Datei „login.aspx“ im Verzeichnis %systemdrive%\inetpub\wwwroot. (Stellen Sie sicher, dass Sie Zugriff auf das unten angegebene wwwroot-Verzeichnis haben, indem Sie den Editor als Administrator ausführen. Klicken Sie dazu mit der rechten Maustaste auf das Editor-Symbol unter „Programme\Zubehör“, und klicken Sie auf „Als Administrator ausführen“.) Hinweis: Legen Sie „Speichern unter:“ auf „Alle Dateien“ fest, um zu verhindern, dass die Datei als „login.aspx.txt“ gespeichert wird. Fügen Sie die folgenden Zeilen ein:

<%@ Page language="c#" %>    
<form id="Form1" runat="server">    
    <asp:LoginStatus runat="server" />        
    <asp:Login runat="server" />    
</form>

Dies ist die Anmeldeseite, zu der Sie umgeleitet werden, wenn Ihre Autorisierungsregeln den Zugriff auf eine bestimmte Ressource verweigern.

Testen

Öffnen Sie ein Internet Explorer-Fenster, und fordern Sie http://localhost/default.aspx an. Sie sehen, dass Sie zu „login.aspx“ umgeleitet werden, da Sie anfänglich nicht authentifiziert wurden und wir für zuvor nicht authentifizierte Benutzer keinen Zugriff gewährt haben. Wenn Sie sich erfolgreich mit einer der in „MembershipUsers.xml“ angegebenen Kombinationen aus Benutzername und Kennwort anmelden, werden Sie wieder zur ursprünglich angeforderten Seite „default.aspx“ umgeleitet. Auf dieser Seite werden dann die aktuelle Zeit und die Benutzeridentität angezeigt, mit der Sie sich authentifiziert haben.

An diesem Punkt haben wir erfolgreich eine benutzerdefinierte Authentifizierungslösung mit Formularauthentifizierung, Anmeldesteuerungen und Mitgliedschaft bereitgestellt. Diese Funktion ist in IIS 7.0 und höheren Versionen nicht neu, sondern steht bereits seit ASP.NET 2.0 in früheren IIS-Releases zur Verfügung.

Das Problem ist jedoch, dass nur Inhalte geschützt sind, die von ASP.NET verarbeitet werden.

Wenn Sie das Browserfenster schließen, erneut öffnen und http://localhost/iisstart.htm anfordern, werden Sie nicht zur Eingabe von Anmeldeinformationen aufgefordert. ASP.NET ist nicht an einer Anforderung für eine statische Datei wie „iisstart.htm“ beteiligt. Daher ist kein Schutz mit der Formularauthentifizierung möglich. Das gleiche Verhalten ist bei klassischen ASP-Seiten, CGI-Programmen und PHP- oder Perl-Skripts zu beobachten. Die Formularauthentifizierung ist ein ASP.NET-Feature und bei Anforderungen für diese Ressourcen einfach nicht verfügbar.

Aktivieren der Formularauthentifizierung für die gesamte Anwendung

In dieser Aufgabe beseitigen wir die Einschränkung von ASP.NET in früheren Releases und aktivieren die ASP.NET-Formularauthentifizierung sowie die URL-Autorisierungsfunktion für die gesamte Anwendung.

Um die ASP.NET-Integration nutzen zu können, muss die Anwendung so konfiguriert werden, dass sie im integrierten Modus ausgeführt wird. Der ASP.NET-Integrationsmodus kann pro Anwendungspool konfiguriert werden, sodass ASP.NET-Anwendungen in verschiedenen Modi parallel auf dem gleichen Server gehostet werden können. Der Standardanwendungspool, in dem sich unsere Anwendung befindet, verwendet bereits standardmäßig den integrierten Modus. Daher ist hier keine Aktion erforderlich.

Aber warum sind wir nicht in den Genuss der Vorteile des integrierten Modus gekommen, als wir vorhin versucht haben, auf die statische Seite zuzugreifen? Dies ist auf die Standardeinstellungen für alle ASP.NET-Module zurückzuführen, die mit IIS 7.0 und höheren Versionen bereitgestellt werden.

Nutzen der integrierten Pipeline

Die Standardkonfiguration für alle verwalteten Module, die mit IIS 7.0 und höheren Versionen bereitgestellt werden (und zu denen auch die Formularauthentifizierung und die URL-Autorisierung gehören), verwendet eine Vorbedingung, sodass diese Module nur für Inhalte gelten, die von einem (ASP.NET-)Handler verwaltet werden. Der Grund hierfür ist die Abwärtskompatibilität.

Durch Entfernen der Vorbedingung wird das gewünschte verwaltete Modul unabhängig vom Inhalt für alle an die Anwendung gerichteten Anforderungen ausgeführt. Dies ist erforderlich, um unsere statischen Dateien sowie alle anderen Anwendungsinhalte mit formularbasierter Authentifizierung zu schützen.

Öffnen Sie hierzu die Datei „web.config“ der Anwendung im Verzeichnis %systemdrive%\inetpub\wwwroot, und fügen Sie unmittelbar unterhalb des ersten Elements vom Typ „<configuration>“ die folgenden Zeilen ein:

<system.webServer> 
<modules> 
    <remove name="FormsAuthenticationModule" />    
    <add name="FormsAuthenticationModule" type="System.Web.Security.FormsAuthenticationModule" />    
    <remove name="UrlAuthorization" />    
    <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />    
    <remove name="DefaultAuthentication" />    
    <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />    
</modules> 
</system.webServer>

Diese Konfiguration fügt die Modulelemente ohne Vorbedingung neu hinzu, sodass sie für alle an die Anwendung gerichteten Anforderungen ausgeführt werden können.

Testen

Schließen Sie alle Instanzen von Internet Explorer, damit die zuvor eingegebenen Anmeldeinformationen aus dem Cache entfernt werden. Öffnen Sie Internet Explorer, und senden Sie eine Anforderung an die Anwendung unter der folgenden URL:

http://localhost/iisstart.htm

Sie werden zur Seite „login.aspx“ umgeleitet, um sich anzumelden.

Melden Sie sich mit einer zuvor verwendeten Kombination aus Benutzername und Kennwort an. Nach erfolgreicher Anmeldung werden Sie wieder zur ursprünglichen Ressource umgeleitet, und die IIS-Willkommensseite wird angezeigt.

Hinweis

Das Modul für die verwaltete Formularauthentifizierung und das URL-Autorisierungsmodul haben ihre Dienste bereitgestellt, um Ihre Ressource zu schützen, obwohl Sie eine statische Datei angefordert haben.

Um dies noch besser zu veranschaulichen, fügen wir eine klassische ASP-Seite hinzu und schützen sie mit der Formularauthentifizierung.

Öffnen Sie den Editor, und erstellen Sie eine Datei vom Typ page.asp im Verzeichnis %systemdrive%\inetpub\wwwroot. (Stellen Sie sicher, dass Sie Zugriff auf das unten angegebene wwwroot-Verzeichnis haben, indem Sie den Editor als Administrator ausführen. Klicken Sie dazu mit der rechten Maustaste auf das Editor-Symbol unter „Programme\Zubehör“, und klicken Sie auf „Als Administrator ausführen“.)

Hinweis

Bei Verwendung des Editors müssen Sie „Speichern unter:“ auf „Alle Dateien“ festlegen, um zu verhindern, dass die Datei als „page.asp.txt“ gespeichert wird. Fügen Sie die folgenden Zeilen ein:

<% 
for each s in Request.ServerVariables
   Response.Write s & ": "&Request.ServerVariables(s) & VbCrLf
next
%>

Schließen Sie erneut alle Internet Explorer-Instanzen, da Ihre Anmeldeinformationen andernfalls noch zwischengespeichert sind. Fordern Sie anschließend http://localhost/page.asp an. Sie werden erneut zur Anmeldeseite umgeleitet, und nach erfolgreicher Authentifizierung wird die ASP-Seite angezeigt.

Herzlichen Glückwunsch! Sie haben dem Server erfolgreich verwaltete Dienste hinzugefügt und sie für alle Anforderungen an den Server aktiviert – unabhängig vom Handler.

Zusammenfassung

In dieser exemplarischen Vorgehensweise wurde gezeigt, wie der integrierte ASP.NET-Modus genutzt werden kann, um leistungsstarke ASP.NET-Features nicht nur für ASP.NET-Seiten, sondern für die gesamte Anwendung verfügbar zu machen.

Noch wichtiger ist jedoch, dass Sie jetzt neue verwaltete Module mit den vertrauten ASP.NET 2.0-APIs erstellen können, die für alle Anwendungsinhalte ausgeführt werden können, und Sie haben eine Reihe weiterer Anforderungsverarbeitungsdienste für Ihre Anwendung bereitgestellt.

Im Blog https://www.mvolo.com/ finden Sie weitere Tipps zur Nutzung des integrierten Modus und zur Entwicklung von IIS-Modulen, die die ASP.NET-Integration in IIS 7 und höheren Versionen nutzen. Dort können Sie auch eine Reihe solcher Module herunterladen. Hierzu zählen Umleiten von Anforderungen an Ihre Anwendung mit dem HttpRedirection-Modul, Erstellen von ansprechenden Verzeichnislisten für Ihre IIS-Website mit dem DirectoryListingModule und Anzeigen attraktiver Dateisymbole in Ihren ASP.NET-Anwendungen mit IconHandler.

Anhang

Dieser Mitgliedschaftsanbieter basiert auf dem exemplarischen XML-Mitgliedschaftsanbieter aus diesen Mitgliedschaftsanbietern.

Um diesen Mitgliedschaftsanbieter zu verwenden, speichern Sie den Code als XmlMembershipProvider.cs in Ihrem Verzeichnis %systemdrive%\inetpub\wwwroot\App\_Code. Sollte dieses Verzeichnis noch nicht vorhanden sein, müssen Sie es erstellen. Hinweis: Bei Verwendung des Editors müssen Sie „Speichern unter:“ auf „Alle Dateien“ festlegen, um zu verhindern, dass die Datei als „XmlMembershipProvider.cs.txt“ gespeichert wird.

Hinweis

Dieses Beispiel für einen Mitgliedschaftsanbieter ist nur für diese Demo vorgesehen. Es entspricht nicht den bewährten Methoden und Sicherheitsanforderungen für einen Mitgliedschaftsanbieter in der Produktion (beispielsweise hinsichtlich der sicheren Speicherung von Kennwörtern und der Überwachung von Benutzeraktionen). Verwenden Sie diesen Mitgliedschaftsanbieter nicht in Ihrer Anwendung!

using System; 
using System.Xml; 
using System.Collections.Generic; 
using System.Collections.Specialized; 
using System.Configuration.Provider; 
using System.Web.Security; 
using System.Web.Hosting; 
using System.Web.Management; 
using System.Security.Permissions; 
using System.Web; 

public class AspNetReadOnlyXmlMembershipProvider : MembershipProvider 
{ 
    private Dictionary<string, MembershipUser> _Users; 
    private string _XmlFileName; 
                  // MembershipProvider Properties 

    public override string ApplicationName 
    { 
        get { throw new NotSupportedException(); } 
        set { throw new NotSupportedException(); } 
    } 

    public override bool EnablePasswordRetrieval 
    { 
        get { return false; } 
    } 

    public override bool EnablePasswordReset 
    { 
        get { return false; } 
    } 

    public override int MaxInvalidPasswordAttempts 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override int MinRequiredNonAlphanumericCharacters 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override int MinRequiredPasswordLength 
    { 
        get { throw new NotSupportedException(); } 
    } 
   
    public override int PasswordAttemptWindow 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override MembershipPasswordFormat PasswordFormat 
    { 
        get { throw new NotSupportedException(); } 
    } 

    public override string PasswordStrengthRegularExpression 
    { 
        get { throw new NotSupportedException(); } 
    } 
   
    public override bool RequiresQuestionAndAnswer 
    { 
        get { return false; } 
    } 

    public override bool RequiresUniqueEmail 
    { 
        get { throw new NotSupportedException(); } 
    } 
  
   // MembershipProvider Methods 

    public override void Initialize(string name, 
        NameValueCollection config) 
    { 
        // Verify that config isn't null 
        if (config == null) 
            throw new ArgumentNullException("config"); 

        // Assign the provider a default name if it doesn't have one 
        if (String.IsNullOrEmpty(name)) 
            name = "ReadOnlyXmlMembershipProvider"; 
  
        // Add a default "description" attribute to config if the 
        // attribute doesn't exist or is empty 
        if (string.IsNullOrEmpty(config["description"])) 
        { 
            config.Remove("description"); 
            config.Add("description", 
                "Read-only XML membership provider"); 
        } 
  
        // Call the base class's Initialize method 
        base.Initialize(name, config); 
  
        // Initialize _XmlFileName and make sure the path 
        // is app-relative 
        string path = config["xmlFileName"]; 

        if (String.IsNullOrEmpty(path)) 
            path = "~/App_Data/MembershipUsers.xml"; 

        if (!VirtualPathUtility.IsAppRelative(path)) 
            throw new ArgumentException 
                ("xmlFileName must be app-relative"); 

        string fullyQualifiedPath = VirtualPathUtility.Combine 
            (VirtualPathUtility.AppendTrailingSlash 
            (HttpRuntime.AppDomainAppVirtualPath), path); 

        _XmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath); 
        config.Remove("xmlFileName"); 
  
        // Make sure we have permission to read the XML data source and 
        // throw an exception if we don't 
        FileIOPermission permission = 
            new FileIOPermission(FileIOPermissionAccess.Read, 
            _XmlFileName); 
        permission.Demand(); 
  
        // Throw an exception if unrecognized attributes remain 
        if (config.Count > 0) 
        { 
            string attr = config.GetKey(0); 
            if (!String.IsNullOrEmpty(attr)) 
                throw new ProviderException 
                    ("Unrecognized attribute: " + attr); 
        } 
    } 

    public override bool ValidateUser(string username, string password) 
    { 
        // Validate input parameters 
        if (String.IsNullOrEmpty(username) || 
            String.IsNullOrEmpty(password)) 
            return false; 
  
        // Make sure the data source has been loaded 
        ReadMembershipDataStore(); 
  
        // Validate the user name and password 
        MembershipUser user; 

        if (_Users.TryGetValue(username, out user)) 
        { 
            if (user.Comment == password) // Case-sensitive 
            { 
                return true; 
            } 
        } 

        return false; 
    } 
   
    public override MembershipUser GetUser(string username, 
        bool userIsOnline) 
    { 
  
        // Note: This implementation ignores userIsOnline 
        // Validate input parameters 

        if (String.IsNullOrEmpty(username)) 
            return null; 
  
        // Make sure the data source has been loaded 
        ReadMembershipDataStore(); 
  
        // Retrieve the user from the data source 
        MembershipUser user; 

        if (_Users.TryGetValue(username, out user)) 
            return user; 

        return null; 
    } 
   
    public override MembershipUserCollection GetAllUsers(int pageIndex, 
        int pageSize, out int totalRecords) 
    { 
        // Note: This implementation ignores pageIndex and pageSize, 
        // and it doesn't sort the MembershipUser objects returned 
        // Make sure the data source has been loaded 

        ReadMembershipDataStore(); 

        MembershipUserCollection users = 
            new MembershipUserCollection(); 

        foreach (KeyValuePair<string, MembershipUser> pair in _Users) 
            users.Add(pair.Value); 

        totalRecords = users.Count; 

        return users; 
    } 

    public override int GetNumberOfUsersOnline() 
    { 
        throw new NotSupportedException(); 
    } 

    public override bool ChangePassword(string username, 
        string oldPassword, string newPassword) 
    { 
        throw new NotSupportedException(); 
    } 

    public override bool 
        ChangePasswordQuestionAndAnswer(string username, 
        string password, string newPasswordQuestion, 
        string newPasswordAnswer) 
    { 
        throw new NotSupportedException(); 
    } 

    public override MembershipUser CreateUser(string username, 
        string password, string email, string passwordQuestion, 
        string passwordAnswer, bool isApproved, object providerUserKey, 
        out MembershipCreateStatus status) 
    { 
        throw new NotSupportedException(); 
    } 

    public override bool DeleteUser(string username, 
        bool deleteAllRelatedData) 
    { 
        throw new NotSupportedException(); 
    } 

    public override MembershipUserCollection 
        FindUsersByEmail(string emailToMatch, int pageIndex, 
        int pageSize, out int totalRecords) 
    { 
        throw new NotSupportedException(); 
    } 

    public override MembershipUserCollection 
        FindUsersByName(string usernameToMatch, int pageIndex, 
        int pageSize, out int totalRecords) 
    { 
        throw new NotSupportedException(); 
    } 

    public override string GetPassword(string username, string answer) 
    { 
        throw new NotSupportedException(); 
    } 
   
    public override MembershipUser GetUser(object providerUserKey, 
        bool userIsOnline) 
    { 
        throw new NotSupportedException(); 
    } 

    public override string GetUserNameByEmail(string email) 
    { 
        throw new NotSupportedException(); 
    } 
   
    public override string ResetPassword(string username, 
        string answer) 

    { 
        throw new NotSupportedException(); 
    } 
   
    public override bool UnlockUser(string userName) 
    { 
        throw new NotSupportedException(); 
    } 
   
    public override void UpdateUser(MembershipUser user) 
    { 
        throw new NotSupportedException(); 

    } 
   
    // Helper method 

    private void ReadMembershipDataStore() 
    { 
        lock (this) 
        { 
            if (_Users == null) 
            { 
                _Users = new Dictionary<string, MembershipUser> 
                   (16, StringComparer.InvariantCultureIgnoreCase); 
                XmlDocument doc = new XmlDocument(); 
                doc.Load(_XmlFileName); 
                XmlNodeList nodes = doc.GetElementsByTagName("User"); 
  
                foreach (XmlNode node in nodes) 
                { 
                    MembershipUser user = new MembershipUser( 
                        Name,                       // Provider name 
                        node["UserName"].InnerText, // Username 
                        null,                       // providerUserKey 
                        node["Email"].InnerText,    // Email 
                        String.Empty,               // passwordQuestion 
                        node["Password"].InnerText, // Comment 
                        true,                       // isApproved 
                        false,                      // isLockedOut 
                        DateTime.Now,               // creationDate 
                        DateTime.Now,               // lastLoginDate 
                        DateTime.Now,               // lastActivityDate 
                        DateTime.Now,               // lastPasswordChangedDate 
                        new DateTime(1980, 1, 1)    // lastLockoutDate 
                 ); 

                 _Users.Add(user.UserName, user); 

                } 
            } 
        } 
    } 
}