Come sfruttare la pipeline integrata IIS 7.0

di Mike Volodarsky

IIS 6.0 e versioni precedenti hanno consentito lo sviluppo di componenti dell'applicazione .NET tramite la piattaforma ASP.NET. ASP.NET integrato con IIS tramite un'estensione ISAPI e ha esposto il proprio modello di elaborazione dell'applicazione e della richiesta. Questo ha esposto in modo efficace due pipeline server separate, una per i filtri ISAPI nativi e i componenti di estensione e un'altra per i componenti dell'applicazione gestita. ASP.NET componenti vengono eseguiti interamente all'interno della bolla di estensione ISAPI ASP.NET e solo per le richieste mappate a ASP.NET nella configurazione della mappa dello script IIS.

IIS 7.0 e versioni successive integra il runtime di ASP.NET con il server Web principale, fornendo una pipeline di elaborazione richiesta unificata esposta a componenti nativi e gestiti noti come moduli. I numerosi vantaggi dell'integrazione includono:

  • Consentire ai servizi forniti da moduli nativi e gestiti di applicare a tutte le richieste, indipendentemente dal gestore. Ad esempio, l'autenticazione gestita dei moduli può essere usata per tutti i contenuti, incluse pagine ASP, CGIs e file statici.
  • La possibilità di ASP.NET componenti per fornire funzionalità non disponibili in precedenza a causa del posizionamento nella pipeline del server. Ad esempio, un modulo gestito che fornisce funzionalità di riscrittura della richiesta può riscrivere la richiesta prima di qualsiasi elaborazione del server, inclusa l'autenticazione.
  • Un'unica posizione da implementare, configurare, monitorare e supportare le funzionalità del server, ad esempio la configurazione del singolo modulo e il mapping dei gestori, la configurazione degli errori personalizzati, la configurazione dell'autorizzazione a URL singolo.

Questo articolo illustra come le applicazioni ASP.NET possono sfruttare la modalità integrata in IIS 7.0 e versioni successive e illustra le attività seguenti:

  • Abilitazione/disabilitazione dei moduli a livello di applicazione.
  • Aggiunta di moduli applicazione gestiti al server e abilitazione dell'applicazione a tutti i tipi di richiesta.
  • Aggiunta di gestori gestiti.

Per altre informazioni sulla creazione di moduli IIS 7.0 e versioni successive, vedere Sviluppo di moduli e gestori IIS 7.0 e versioni successive con .NET Framework.

Vedere anche il blog, , http://www.mvolo.com/per altri suggerimenti sull'uso della modalità integrata e sullo sviluppo di moduli IIS che sfruttano l'integrazione ASP.NET in IIS 7.0 e versioni successive. È possibile scaricare un numero di tali moduli, tra cui il reindirizzamento delle richieste all'applicazione con il modulo HttpRedirection, le liste di directory più interessanti per il sito Web IIS con DirectoryListingModule e Visualizzazione di icone di file piuttosto belle nelle applicazioni ASP.NET con IconHandler.

Prerequisiti

Per seguire la procedura descritta in questo documento, è necessario installare le funzionalità IIS 7.0 e successive seguenti.

ASP.NET

Installare ASP.NET tramite windows Vista Pannello di controllo. Selezionare "Programmi e funzionalità" - "Attivare o disattivare le funzionalità di Windows". Aprire quindi "Internet Information Services" - "World Wide Web Services" - "Funzionalità di sviluppo applicazioni" e controllare "ASP.NET".

Se si dispone di una compilazione di Windows Server® 2008, aprire "Server Manager" - "Ruoli" e selezionare "Server Web (IIS)". Fare clic su "Aggiungi servizi ruolo". In "Sviluppo applicazioni" selezionare "ASP.NET".

ASP classico

Si vuole mostrare come ASP.NET moduli ora funzionano con tutti i contenuti e non solo ASP.NET pagine, quindi installare ASP classico tramite windows Vista Pannello di controllo. Selezionare "Programmi" - "Attivare o disattivare le funzionalità di Windows". Aprire quindi "Internet Information Services" - "World Wide Web Services" - "Funzionalità di sviluppo applicazioni" e controllare "ASP".

Se si dispone di una compilazione di Windows Server 2008, aprire "Server Manager" - "Ruoli" e selezionare "Server Web (IIS)". Fare clic su "Aggiungi servizi ruolo". In "Sviluppo applicazioni" selezionare "ASP".

Aggiunta dell'autenticazione dei moduli all'applicazione

Come parte di questa attività, è possibile abilitare l'autenticazione basata su moduli ASP.NET per l'applicazione. Nell'attività successiva viene abilitato il modulo Autenticazione form per l'esecuzione di tutte le richieste all'applicazione, indipendentemente dal tipo di contenuto.

Configurare prima di tutto l'autenticazione dei moduli come si desidera per un'applicazione di ASP.NET normale.

Creazione di una pagina di esempio

Per illustrare la funzionalità, aggiungere una pagina default.aspx alla directory radice Web. Aprire il blocco note (per assicurarsi di avere accesso alla directory wwwroot seguente, è necessario eseguire come amministratore-destro del mouse sull'icona Programmi\Accessori\Blocco note e fare clic su "Esegui come amministratore") e creare il file seguente: %systemdrive%\inetpub\wwwroot\default.aspx. Incollare le righe seguenti:

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

Tutti i valori default.aspx vengono visualizzati l'ora corrente e il nome dell'utente connesso. Questa pagina viene usata in un secondo momento per visualizzare l'autenticazione dei moduli in azione.

Configurazione dell'autenticazione dei moduli e delle regole di Controllo di accesso

A questo momento, per proteggere default.aspx con l'autenticazione dei moduli. Creare un file web.config nella %systemdrive%\inetpub\wwwroot directory e aggiungere la configurazione riportata di seguito:

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

Questa configurazione imposta la modalità di autenticazione ASP.NET per usare l'autenticazione basata su moduli e aggiunge le impostazioni di autorizzazione per controllare l'accesso all'applicazione. Questa impostazione nega l'accesso agli utenti anonimi (?) e consente solo agli utenti autenticati (*).

Creazione di un provider di appartenenza

Passaggio 1: È necessario fornire un archivio di autenticazione in base al quale verranno verificate le credenziali utente. Per illustrare l'integrazione profonda tra ASP.NET e IIS 7.0 e versioni successive, si usa il proprio provider di appartenenza basato su XML (è anche possibile usare il provider di appartenenza predefinito SQL Server se è installato SQL Server).

Aggiungere la voce seguente dopo l'elemento di configurazione iniziale</<system.web> configuration> nel file di web.config:

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

Passaggio 2: Dopo aver aggiunto la voce di configurazione, è necessario salvare il codice del provider di appartenenza fornito in Appendice come XmlMembershipProvider.cs nella %systemdrive%\inetpub\wwwroot\App_Code directory. Se questa directory non esiste, è necessario crearla.

Nota

Se si usa Blocco note, assicurarsi di impostare Salva con nome: Tutti i file per impedire il salvataggio del file come XmlMembershipProvider.cs.txt.

Passaggio 3: Tutto ciò che rimane è l'archivio credenziali effettivo. Salvare il frammento di codice xml seguente come file MembershipUsers.xml nella %systemdrive%\inetpub\wwwroot\App_Data directory.

Nota

Se si usa Blocco note, assicurarsi di impostare Salva con nome: Tutti i file per impedire il salvataggio del file come MembershipUsers.xml.txt.

<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>

Se la directory App_Data non esiste, è necessario crearla.

Nota

A causa delle modifiche alla sicurezza in Windows Server 2003 e Windows Vista SP1, non è più possibile usare lo strumento amministrazione IIS per creare account utente di appartenenza per i provider di appartenenza non GACed.

Dopo aver completato questa attività, passare allo strumento amministrazione IIS e aggiungere o eliminare utenti per l'applicazione. Avviare "INETMGR" da "Esegui..." Menu. Aprire l'accesso "+" nella visualizzazione albero a sinistra fino a quando non viene visualizzato "Sito Web predefinito". Selezionare "Sito Web predefinito" e quindi passare a destra e fare clic sulla categoria "Sicurezza". Le funzionalità rimanenti mostrano ".NET Users". Fare clic su ".NET Users" e aggiungere uno o più account utente desiderati.

Cercare MembershipUsers.xml per trovare gli utenti appena creati.

Creazione di una pagina di accesso

Per usare l'autenticazione dei moduli, è necessario creare una pagina di accesso. Aprire il blocco note (per assicurarsi di avere accesso alla directory wwwroot seguente, è necessario eseguire come amministratore facendo clic con il pulsante destro del mouse sull'icona Programmi\Accessori\Blocco note e facendo clic su "Esegui come amministratore") e creare il file login.aspx nella %systemdrive%\inetpub\wwwroot directory. Nota: assicurarsi di impostare Salva con nome: tutti i file per impedire che il file venga salvato come login.aspx.txt. Incollare le righe seguenti:

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

Si tratta della pagina di accesso a cui si viene reindirizzati quando le regole di autorizzazione negano l'accesso a una determinata risorsa.

Test

Aprire una finestra di Internet Explorer e richiedere http://localhost/default.aspx. Si noterà che l'utente viene reindirizzato a login.aspx, perché inizialmente non è stato autenticato e si è negato l'accesso agli utenti non autenticati in precedenza. Se si accede correttamente con una delle coppie nome utente/password specificate in MembershipUsers.xml, viene eseguito il reindirizzamento alla pagina default.aspx richiesta originariamente. Questa pagina mostra quindi l'ora corrente e l'identità utente con cui è stata eseguita l'autenticazione.

A questo punto, è stata distribuita correttamente una soluzione di autenticazione personalizzata usando l'autenticazione dei moduli, i controlli di accesso e l'appartenenza. Questa funzionalità non è nuova in IIS 7.0 o versione successiva, è disponibile da ASP.NET 2.0 nelle versioni precedenti di IIS.

Tuttavia, il problema è che solo il contenuto gestito da ASP.NET è protetto.

Se si chiude e si riapri la finestra del browser e si richiede , non viene richiesta la richiesta http://localhost/iisstart.htmdi credenziali. ASP.NET non partecipa a una richiesta per un file statico come iisstart.htm. Pertanto, non può proteggerlo con l'autenticazione dei moduli. Viene visualizzato lo stesso comportamento con pagine ASP classiche, programmi CGI, PHP o Perl script. L'autenticazione dei moduli è una funzionalità ASP.NET e semplicemente non è disponibile durante le richieste a tali risorse.

Abilitazione dell'autenticazione dei moduli per l'intera applicazione

In questa attività si elimina la limitazione di ASP.NET nelle versioni precedenti e si abilita la funzionalità di autenticazione e autorizzazione url ASP.NET form per l'intera applicazione.

Per sfruttare ASP.NET integrazione, l'applicazione deve essere configurata per l'esecuzione in modalità integrata. La modalità di integrazione ASP.NET è configurabile per ogni pool di applicazioni, consentendo alle applicazioni di ASP.NET in modalità diverse di essere ospitate side-by-side nello stesso server. Il pool di applicazioni predefinito in cui l'applicazione usa già la modalità integrata per impostazione predefinita, quindi non è necessario eseguire alcuna operazione qui.

Quindi, perché non è stato possibile sperimentare i vantaggi della modalità integrata quando si è tentato di accedere alla pagina statica in precedenza? La risposta risiede nelle impostazioni predefinite per tutti i moduli ASP.NET forniti con IIS 7.0 e versioni successive.

Sfruttare i vantaggi della pipeline integrata

La configurazione predefinita per tutti i moduli gestiti forniti con IIS 7.0 e versioni successive, inclusi i moduli autenticazione basata su form e autorizzazione URL, usa una precondizione in modo che questi moduli si applichino solo al contenuto gestito da un gestore (ASP.NET). Questa operazione viene eseguita per motivi di compatibilità con le versioni precedenti.

Rimuovendo la precondizione, il modulo gestito desiderato viene eseguito per tutte le richieste all'applicazione, indipendentemente dal contenuto. Ciò è necessario per proteggere i file statici e qualsiasi altro contenuto dell'applicazione con l'autenticazione basata su form.

A tale scopo, aprire il file web.config dell'applicazione che si trova nella %systemdrive%\inetpub\wwwroot directory e incollare le righe seguenti immediatamente sotto il primo <elemento di configurazione> :

<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>

Questa configurazione aggiunge nuovamente gli elementi del modulo senza la precondizione, consentendo l'esecuzione per tutte le richieste all'applicazione.

Test

Chiudere tutte le istanze di Internet Explorer in modo che le credenziali immesse prima non vengano più memorizzate nella cache. Aprire Internet Explorer e effettuare una richiesta all'applicazione all'URL seguente:

http://localhost/iisstart.htm

Si viene reindirizzati alla pagina login.aspx per accedere.

Accedere con una coppia nome utente/password usata in precedenza. Al termine dell'accesso, viene eseguito il reindirizzamento alla risorsa originale, che visualizza la pagina iniziale di IIS.

Nota

Anche se è stato richiesto un file statico, il modulo di autenticazione basata su moduli gestiti e il modulo di autorizzazione URL hanno fornito i servizi per proteggere la risorsa.

Per illustrare ulteriormente questa operazione, si aggiunge una pagina ASP classica e la si protegge con l'autenticazione basata su form.

Aprire il Blocco note (per assicurarsi di avere accesso alla directory wwwroot seguente, è necessario eseguire come amministratore facendo clic con il pulsante destro del mouse sull'icona Programmi\Accessori\Blocco note e scegliere "Esegui come amministratore") e creare un file page.asp nella %systemdrive%\inetpub\wwwroot directory.

Nota

Se si usa blocco note, assicurarsi di impostare Salva con nome: tutti i file per impedire il salvataggio del file come page.asp.txt. Incollare le righe sottostanti:

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

Chiudere nuovamente tutte le istanze di Internet Explorer. In caso contrario, le credenziali vengono ancora memorizzate nella cache e richiedono http://localhost/page.asp. Viene nuovamente eseguito il reindirizzamento alla pagina di accesso e, dopo l'autenticazione completata, visualizzare la pagina ASP.

Congratulazioni: sono stati aggiunti correttamente servizi gestiti al server, abilitandoli per tutte le richieste al server indipendentemente dal gestore.

Riepilogo

Questa procedura dettagliata ha illustrato come è possibile sfruttare la modalità integrata ASP.NET per rendere disponibili funzionalità ASP.NET avanzate non solo per ASP.NET pagine, ma per l'intera applicazione.

Ancora più importante, è ora possibile creare nuovi moduli gestiti usando le note API di ASP.NET 2.0 che hanno la possibilità di eseguire per tutto il contenuto dell'applicazione e hanno fornito un set avanzato di servizi di elaborazione delle richieste all'applicazione.

È possibile consultare il blog, https://www.mvolo.com/, per altri suggerimenti sull'uso della modalità integrata e sullo sviluppo di moduli IIS che sfruttano l'integrazione ASP.NET in IIS 7 e versioni successive. È anche possibile scaricare diversi moduli di questo tipo, tra cui Reindirizzamento delle richieste all'applicazione con il modulo HttpRedirection, elenchi di directory interessanti per il sito Web IIS con DirectoryListingModule e Visualizzazione di icone di file abbastanza nelle applicazioni ASP.NET con IconHandler.

Appendice

Questo provider di appartenenze si basa sul provider di appartenenze XML di esempio trovato in questo provider di appartenenze.

Per usare questo provider di appartenenze, salvare il codice come XmlMembershipProvider.cs nella %systemdrive%\inetpub\wwwroot\App\_Code directory. Se questa directory non esiste, sarà necessario crearla. Nota: assicurarsi di impostare Salva con nome: tutti i file se si usa il Blocco note per impedire il salvataggio del file come XmlMembershipProvider.cs.txt.

Nota

Questo esempio di provider di appartenenze è solo ai fini di questa demo. Non è conforme alle procedure consigliate e ai requisiti di sicurezza per un provider di appartenenze di produzione, inclusa l'archiviazione delle password in modo sicuro e il controllo delle azioni utente. Non usare questo provider di appartenenze nell'applicazione.

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); 

                } 
            } 
        } 
    } 
}