Protezione delle stringhe di connessione e di altre informazioni di configurazione (C#)

di Scott Mitchell

Scarica PDF

Un'applicazione ASP.NET archivia in genere le informazioni di configurazione in un file Web.config. Alcune di queste informazioni sono sensibili e giustificano la protezione. Per impostazione predefinita, questo file non verrà servito a un visitatore del sito Web, ma un amministratore o un hacker può accedere al file system del server Web e visualizzare il contenuto del file. In questa esercitazione si apprenderà che ASP.NET 2.0 consente di proteggere le informazioni riservate crittografando le sezioni del file Web.config.

Introduzione

Le informazioni di configurazione per le applicazioni ASP.NET vengono in genere archiviate in un file XML denominato Web.config. Nel corso di queste esercitazioni sono state aggiornate alcune Web.config volte. Quando si crea il Northwind set di dati tipizzato nella prima esercitazione, ad esempio, stringa di connessione informazioni sono state aggiunte automaticamente a Web.config nella <connectionStrings> sezione . Più avanti, nell'esercitazione Pagine master e navigazione sito, è stato aggiornato Web.configmanualmente , aggiungendo un <pages> elemento che indica che tutte le pagine ASP.NET nel progetto devono usare il DataWebControls tema.

Poiché Web.config può contenere dati sensibili, ad esempio stringa di connessione, è importante che il contenuto di Web.config venga mantenuto al sicuro e nascosto ai visualizzatori non autorizzati. Per impostazione predefinita, qualsiasi richiesta HTTP a un file con estensione .config viene gestita dal motore di ASP.NET, che restituisce il messaggio Questo tipo di pagina non viene fornito nella figura 1. Ciò significa che i visitatori non possono visualizzare il Web.config contenuto del file semplicemente immettendo http://www.YourServer.com/Web.config nella barra degli indirizzi del browser.

Visitare Web.config tramite un browser Restituisce un messaggio di questo tipo di pagina non servito

Figura 1: La visita Web.config tramite un browser restituisce un messaggio di questo tipo di pagina non servito (fare clic per visualizzare l'immagine a dimensione intera)

Ma cosa succede se un utente malintenzionato è in grado di trovare un altro exploit che le consente di visualizzare il Web.config contenuto del file? Cosa può fare un utente malintenzionato con queste informazioni e quali passaggi possono essere eseguiti per proteggere ulteriormente le informazioni riservate all'interno Web.configdi ? Fortunatamente, la maggior parte delle sezioni di Web.config non contiene informazioni riservate. Quale danno può essere commesso da un utente malintenzionato se conoscono il nome del tema predefinito usato dalle pagine ASP.NET?

Alcune Web.config sezioni, tuttavia, contengono informazioni riservate che possono includere stringa di connessione, nomi utente, password, nomi dei server, chiavi di crittografia e così via. Queste informazioni sono in genere disponibili nelle sezioni seguenti Web.config :

  • <appSettings>
  • <connectionStrings>
  • <identity>
  • <sessionState>

In questa esercitazione verranno esaminate le tecniche per proteggere tali informazioni di configurazione riservate. Come si vedrà, .NET Framework versione 2.0 include un sistema di configurazioni protette che esegue la crittografia e la decrittografia delle sezioni di configurazione selezionate a livello di codice.

Nota

Questa esercitazione si conclude con un'analisi delle raccomandazioni di Microsoft per la connessione a un database da un'applicazione ASP.NET. Oltre a crittografare i stringa di connessione, è possibile rafforzare il sistema assicurandosi di connettersi al database in modo sicuro.

Passaggio 1: Esplorazione delle opzioni di configurazione protette di ASP.NET 2.0

ASP.NET 2.0 include un sistema di configurazione protetto per crittografare e decrittografare le informazioni di configurazione. Sono inclusi i metodi in .NET Framework che possono essere usati per crittografare o decrittografare le informazioni di configurazione a livello di codice. Il sistema di configurazione protetto usa il modello di provider che consente agli sviluppatori di scegliere l'implementazione crittografica usata.

.NET Framework viene fornito con due provider di configurazione protetti:

Poiché il sistema di configurazione protetto implementa il modello di progettazione del provider, è possibile creare un provider di configurazione protetto e collegarlo all'applicazione. Per altre informazioni su questo processo, vedere Implementazione di un provider di configurazione protetto.

I provider RSA e DPAPI usano le chiavi per le routine di crittografia e decrittografia e queste chiavi possono essere archiviate a livello di computer o utente. Le chiavi a livello di computer sono ideali per scenari in cui l'applicazione Web viene eseguita nel proprio server dedicato o se sono presenti più applicazioni in un server che devono condividere informazioni crittografate. Le chiavi a livello di utente sono un'opzione più sicura negli ambienti di hosting condiviso in cui altre applicazioni nello stesso server non devono essere in grado di decrittografare le sezioni di configurazione protetta dell'applicazione.

In questa esercitazione gli esempi useranno il provider DPAPI e le chiavi a livello di computer. In particolare, verrà esaminata la crittografia della <connectionStrings> sezione in Web.config, anche se il sistema di configurazione protetto può essere usato per crittografare la maggior parte delle Web.config sezioni. Per informazioni sull'uso di chiavi a livello di utente o sull'uso del provider RSA, consultare le risorse nella sezione Ulteriori letture alla fine di questa esercitazione.

Nota

I RSAProtectedConfigurationProvider provider e DPAPIProtectedConfigurationProvider vengono registrati rispettivamente nel machine.config file con i nomi dei provider RsaProtectedConfigurationProvider e DataProtectionConfigurationProvider. Quando si crittografa o si decrittografano le informazioni di configurazione, sarà necessario specificare il nome del provider appropriato (RsaProtectedConfigurationProvider o DataProtectionConfigurationProvider) anziché il nome effettivo del tipo (RSAProtectedConfigurationProvider e DPAPIProtectedConfigurationProvider). È possibile trovare il machine.config file nella $WINDOWS$\Microsoft.NET\Framework\version\CONFIG cartella .

Passaggio 2: Crittografare e decrittografare le sezioni di configurazione a livello di codice

Con alcune righe di codice è possibile crittografare o decrittografare una determinata sezione di configurazione usando un provider specificato. Il codice, come si vedrà a breve, deve semplicemente fare riferimento alla sezione di configurazione appropriata a livello di codice, chiamare il ProtectSection metodo o UnprotectSection e quindi chiamare il Save metodo per rendere persistenti le modifiche. Inoltre, .NET Framework include un'utilità della riga di comando utile in grado di crittografare e decrittografare le informazioni di configurazione. Questa utilità della riga di comando verrà esaminata nel passaggio 3.

Per illustrare la protezione a livello di codice delle informazioni di configurazione, creare una pagina di ASP.NET che include pulsanti per crittografare e decrittografare la <connectionStrings> sezione in Web.config.

Per iniziare, aprire la EncryptingConfigSections.aspx pagina nella AdvancedDAL cartella . Trascinare un controllo TextBox dalla casella degli strumenti nella finestra di progettazione, impostandone la ID proprietà su WebConfigContents, la relativa TextMode proprietà su MultiLinee le relative proprietà e Width Rows rispettivamente su 95% e 15. Questo controllo TextBox visualizzerà il contenuto che consente di Web.config verificare rapidamente se il contenuto è crittografato o meno. Naturalmente, in un'applicazione reale non si vuole mai visualizzare il contenuto di Web.config.

Sotto il controllo TextBox aggiungere due controlli Button denominati EncryptConnStrings e DecryptConnStrings. Impostare le relative proprietà di testo su Crittografa stringhe di connessione e Decrittografa stringhe di connessione.

A questo punto la schermata dovrebbe essere simile alla figura 2.

Screenshot che mostra Visual Studio aperto alla pagina EncryptingConfigSections.aspx con un nuovo controllo TextBox e due controlli Button.

Figura 2: Aggiungere un controllo Web TextBox e due pulsanti alla pagina (fare clic per visualizzare l'immagine a dimensione intera)

Successivamente, è necessario scrivere codice che carica e visualizza il contenuto di Web.config in WebConfigContents TextBox al primo caricamento della pagina. Aggiungere il codice seguente alla classe code-behind della pagina. Questo codice aggiunge un metodo denominato DisplayWebConfig e lo chiama dal Page_Load gestore eventi quando Page.IsPostBack è false:

protected void Page_Load(object sender, EventArgs e)
{
    // On the first page visit, call DisplayWebConfig method
    if (!Page.IsPostBack)
        DisplayWebConfig();
}
private void DisplayWebConfig()
{
    // Reads in the contents of Web.config and displays them in the TextBox
    StreamReader webConfigStream = 
        File.OpenText(Path.Combine(Request.PhysicalApplicationPath, "Web.config"));
    string configContents = webConfigStream.ReadToEnd();
    webConfigStream.Close();
    WebConfigContents.Text = configContents;
}

Il DisplayWebConfig metodo usa la File classe per aprire il file dell'applicazione Web.config , la StreamReader classe per leggerne il contenuto in una stringa e la Path classe per generare il percorso fisico del Web.config file. Queste tre classi sono tutte disponibili nello spazio dei System.IO nomi . Di conseguenza, sarà necessario aggiungere un'istruzione using System.IO all'inizio della classe code-behind o, in alternativa, anteporre questi nomi di classe con System.IO. .

Successivamente, è necessario aggiungere gestori eventi per i due eventi dei controlli Click Button e aggiungere il codice necessario per crittografare e decrittografare la <connectionStrings> sezione usando una chiave a livello di computer con il provider DPAPI. Nella finestra di progettazione fare doppio clic su ognuno dei pulsanti per aggiungere un Click gestore eventi nella classe code-behind e quindi aggiungere il codice seguente:

protected void EncryptConnStrings_Click(object sender, EventArgs e)
{
    // Get configuration information about Web.config
    Configuration config = 
        WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
    // Let's work with the <connectionStrings> section
    ConfigurationSection connectionStrings = config.GetSection("connectionStrings");
    if (connectionStrings != null)
        // Only encrypt the section if it is not already protected
        if (!connectionStrings.SectionInformation.IsProtected)
        {
            // Encrypt the <connectionStrings> section using the 
            // DataProtectionConfigurationProvider provider
            connectionStrings.SectionInformation.ProtectSection(
                "DataProtectionConfigurationProvider");
            config.Save();
            
            // Refresh the Web.config display
            DisplayWebConfig();
        }
}
protected void DecryptConnStrings_Click(object sender, EventArgs e)
{
    // Get configuration information about Web.config
    Configuration config = 
        WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
    // Let's work with the <connectionStrings> section
    ConfigurationSection connectionStrings = 
        config.GetSection("connectionStrings");
    if (connectionStrings != null)
        // Only decrypt the section if it is protected
        if (connectionStrings.SectionInformation.IsProtected)
        {
            // Decrypt the <connectionStrings> section
            connectionStrings.SectionInformation.UnprotectSection();
            config.Save();
            // Refresh the Web.config display
            DisplayWebConfig();
        }
}

Il codice usato nei due gestori eventi è quasi identico. Entrambi iniziano ottenendo informazioni sul file dell'applicazione Web.config corrente tramite ilWebConfigurationManager metodo della classe .OpenWebConfiguration Questo metodo restituisce il file di configurazione Web per il percorso virtuale specificato. Successivamente, la Web.config sezione s del <connectionStrings> file è accessibile tramite il Configuration metodo della GetSection(sectionName) classe , che restituisce un ConfigurationSection oggetto .

L'oggetto ConfigurationSection include una SectionInformation proprietà che fornisce informazioni e funzionalità aggiuntive relative alla sezione di configurazione. Come illustrato nel codice precedente, è possibile determinare se la sezione di configurazione è crittografata controllando la SectionInformation proprietà della IsProtected proprietà . Inoltre, la sezione può essere crittografata o decrittografata tramite i metodi e UnprotectSection della SectionInformation proprietà ProtectSection(provider) .

Il ProtectSection(provider) metodo accetta come input una stringa che specifica il nome del provider di configurazione protetto da usare durante la crittografia. EncryptConnString Nel gestore eventi button s passiamo DataProtectionConfigurationProvider al ProtectSection(provider) metodo in modo che venga usato il provider DPAPI. Il UnprotectSection metodo può determinare il provider usato per crittografare la sezione di configurazione e pertanto non richiede parametri di input.

Dopo aver chiamato il ProtectSection(provider) metodo o UnprotectSection , è necessario chiamare il metodo dell'oggetto Configuration Save per rendere persistenti le modifiche. Dopo che le informazioni di configurazione sono state crittografate o decrittografate e le modifiche salvate, viene chiamato DisplayWebConfig per caricare il contenuto aggiornato Web.config nel controllo TextBox.

Dopo aver immesso il codice precedente, testarlo visitando la EncryptingConfigSections.aspx pagina tramite un browser. Inizialmente dovrebbe essere visualizzata una pagina che elenca il contenuto di Web.config con la <connectionStrings> sezione visualizzata in testo normale (vedere la figura 3).

Screenshot che mostra la pagina EncryptingConfigSections.aspx caricata in un Web browser.

Figura 3: Aggiungere un controllo Web TextBox e due pulsanti alla pagina (fare clic per visualizzare l'immagine a dimensione intera)

Fare ora clic sul pulsante Crittografa stringhe di connessione. Se la convalida della richiesta è abilitata, il markup inviato da WebConfigContents TextBox genererà un HttpRequestValidationExceptionoggetto , che visualizza il messaggio, un valore potenzialmente pericoloso Request.Form rilevato dal client. La convalida delle richieste, abilitata per impostazione predefinita in ASP.NET 2.0, impedisce i postback che includono codice HTML non codificato ed è progettata per impedire attacchi di tipo script injection. Questo controllo può essere disabilitato a livello di pagina o applicazione. Per disattivarla per questa pagina, impostare l'impostazione ValidateRequest su false nella @Page direttiva . La @Page direttiva si trova nella parte superiore del markup dichiarativo della pagina.

<%@ Page ValidateRequest="False" ... %>

Per altre informazioni sulla convalida delle richieste, lo scopo, come disabilitarlo a livello di pagina e applicazione, nonché su come codificare codice HTML, vedere Convalida delle richieste - Prevenzione degli attacchi di script.

Dopo aver disabilitato la convalida della richiesta per la pagina, provare a fare di nuovo clic sul pulsante Crittografa stringhe di connessione. Al postback, si accederà al file di configurazione e alla relativa <connectionStrings> sezione crittografata usando il provider DPAPI. Il controllo TextBox viene quindi aggiornato per visualizzare il nuovo Web.config contenuto. Come illustrato nella figura 4, le <connectionStrings> informazioni sono ora crittografate.

Facendo clic sul pulsante Crittografa stringhe di connessione viene crittografata la <sezione connectionString>

Figura 4: Fare clic sul pulsante Crittografa stringhe di connessione crittografa la sezione (fare clic per visualizzare l'immagine <connectionString>a dimensione intera)

La sezione crittografata <connectionStrings> generata nel computer segue, anche se alcuni dei contenuti nell'elemento <CipherData> sono stati rimossi per brevità:

<connectionStrings 
    configProtectionProvider="DataProtectionConfigurationProvider">
  <EncryptedData>
    <CipherData>
      <CipherValue>AQAAANCMnd8BFdERjHoAwE/...zChw==</CipherValue>
    </CipherData>
  </EncryptedData>
</connectionStrings>

Nota

L'elemento <connectionStrings> specifica il provider utilizzato per eseguire la crittografia (DataProtectionConfigurationProvider). Queste informazioni vengono utilizzate dal UnprotectSection metodo quando si fa clic sul pulsante Decrittografa stringhe di connessione.

Quando si accede alle informazioni stringa di connessione da Web.config , dal codice scritto, da un controllo SqlDataSource o dal codice generato automaticamente dagli oggetti TableAdapter nei set di dati tipizzato, viene decrittografato automaticamente. In breve, non è necessario aggiungere codice aggiuntivo o logica per decrittografare la sezione crittografata <connectionString> . Per dimostrare questo problema, visitare una delle esercitazioni precedenti in questo momento, ad esempio l'esercitazione Visualizzazione semplice della sezione Report di base (~/BasicReporting/SimpleDisplay.aspx). Come illustrato nella figura 5, l'esercitazione funziona esattamente come ci si aspetterebbe, a indicare che le informazioni stringa di connessione crittografate vengono decrittografate automaticamente dalla pagina ASP.NET.

Il livello di accesso ai dati decrittografa automaticamente le informazioni sulla stringa di connessione

Figura 5: Il livello di accesso ai dati decrittografa automaticamente le informazioni sulla stringa di connessione (fare clic per visualizzare l'immagine a dimensione intera)

Per ripristinare la relativa rappresentazione in testo normale, <connectionStrings> fare clic sul pulsante Decrittografa stringhe di connessione. Al postback dovrebbero essere visualizzati i stringa di connessione in Web.config testo normale. A questo punto la schermata dovrebbe essere simile alla prima volta che si visita questa pagina (vedere la figura 3).

Passaggio 3: Crittografia delle sezioni di configurazione tramite aspnet_regiis.exe

.NET Framework include un'ampia gamma di strumenti da riga di comando nella $WINDOWS$\Microsoft.NET\Framework\version\ cartella . Nell'esercitazione Uso delle dipendenze della cache SQL, ad esempio, è stato esaminato l'uso dello aspnet_regsql.exe strumento da riga di comando per aggiungere l'infrastruttura necessaria per le dipendenze della cache SQL. Un altro strumento da riga di comando utile in questa cartella è lo strumento di registrazione IIS ASP.NET (aspnet_regiis.exe). Come suggerisce il nome, lo strumento di registrazione IIS ASP.NET viene usato principalmente per registrare un'applicazione ASP.NET 2.0 con il server Web di livello professionale Microsoft, IIS. Oltre alle funzionalità correlate a IIS, lo strumento di registrazione IIS ASP.NET può essere usato anche per crittografare o decrittografare le sezioni di configurazione specificate in Web.config.

L'istruzione seguente illustra la sintassi generale usata per crittografare una sezione di configurazione con lo strumento da aspnet_regiis.exe riga di comando:

aspnet_regiis.exe -pef section physical_directory -prov provider

sezione è la sezione di configurazione per crittografare (ad esempio connectionStrings), il physical_directory è il percorso fisico completo della directory radice dell'applicazione Web e il provider è il nome del provider di configurazione protetto da usare , ad esempio DataProtectionConfigurationProvider . In alternativa, se l'applicazione Web è registrata in IIS, è possibile immettere il percorso virtuale anziché il percorso fisico usando la sintassi seguente:

aspnet_regiis.exe -pe section -app virtual_directory -prov provider

L'esempio seguente aspnet_regiis.exe crittografa la <connectionStrings> sezione usando il provider DPAPI con una chiave a livello di computer:

aspnet_regiis.exe -pef
"connectionStrings" "C:\Websites\ASPNET_Data_Tutorial_73_CS"
-prov "DataProtectionConfigurationProvider"

Analogamente, lo strumento da aspnet_regiis.exe riga di comando può essere usato per decrittografare le sezioni di configurazione. Anziché usare l'opzione -pef , usare -pdf (o invece di -pe, usare -pd). Si noti inoltre che il nome del provider non è necessario durante la decrittografia.

aspnet_regiis.exe -pdf section physical_directory
  -- or --
aspnet_regiis.exe -pd section -app virtual_directory

Nota

Poiché si usa il provider DPAPI, che usa chiavi specifiche per il computer, è necessario eseguire aspnet_regiis.exe dallo stesso computer da cui vengono gestite le pagine Web. Ad esempio, se si esegue questo programma da riga di comando dal computer di sviluppo locale e quindi si carica il file Web.config crittografato nel server di produzione, il server di produzione non sarà in grado di decrittografare le informazioni stringa di connessione poiché è stato crittografato usando chiavi specifiche per il computer di sviluppo. Il provider RSA non ha questa limitazione perché è possibile esportare le chiavi RSA in un altro computer.

Informazioni sulle opzioni di autenticazione del database

Prima che qualsiasi applicazione possa eseguire SELECTquery , INSERT, UPDATEo DELETE a un database di Microsoft SQL Server, il database deve prima identificare il richiedente. Questo processo è noto come autenticazione e SQL Server offre due metodi di autenticazione:

  • Autenticazione di Windows: processo in cui viene eseguita l'applicazione per comunicare con il database. Quando si esegue un'applicazione ASP.NET tramite Visual Studio 2005 s ASP.NET Development Server, l'applicazione ASP.NET presuppone l'identità dell'utente attualmente connesso. Per ASP.NET applicazioni in Microsoft Internet Information Server (IIS), ASP.NET applicazioni in genere presuppongono l'identità di domainName``\MachineName o domainName``\NETWORK SERVICE, anche se può essere personalizzata.
  • Autenticazione SQL: i valori di ID utente e password vengono forniti come credenziali per l'autenticazione. Con l'autenticazione SQL, l'ID utente e la password vengono forniti nella stringa di connessione.

autenticazione di Windows è preferibile rispetto all'autenticazione SQL perché è più sicura. Con autenticazione di Windows il stringa di connessione è gratuito da un nome utente e una password e se il server Web e il server di database si trovano in due computer diversi, le credenziali non vengono inviate in rete in testo normale. Con l'autenticazione SQL, tuttavia, le credenziali di autenticazione vengono hardcoded nel stringa di connessione e vengono trasmesse dal server Web al server di database in testo normale.

Queste esercitazioni hanno usato autenticazione di Windows. È possibile indicare la modalità di autenticazione usata esaminando il stringa di connessione. Il stringa di connessione in Web.config per le esercitazioni è stato:

Data Source=.\SQLEXPRESS; AttachDbFilename=|DataDirectory|\NORTHWND.MDF; Integrated Security=True; User Instance=True

Integrated Security=True e la mancanza di un nome utente e di una password indicano che viene usato autenticazione di Windows. In alcuni stringa di connessione il termine Trusted Connection=Yes o Integrated Security=SSPI viene usato invece di Integrated Security=True, ma tutti e tre indicano l'uso di autenticazione di Windows.

Nell'esempio seguente viene illustrato un stringa di connessione che usa l'autenticazione SQL. $CREDENTIAL_PLACEHOLDER$ è un segnaposto per la coppia chiave-valore della password. Si noti che le credenziali sono incorporate all'interno del stringa di connessione:

Server=serverName; Database=Northwind; uid=userID; $CREDENTIAL_PLACEHOLDER$

Si supponga che un utente malintenzionato sia in grado di visualizzare il file dell'applicazione Web.config . Se si usa l'autenticazione SQL per connettersi a un database accessibile tramite Internet, l'utente malintenzionato può usare questa stringa di connessione per connettersi al database tramite SQL Management Studio o dalle pagine di ASP.NET nel proprio sito Web. Per attenuare questa minaccia, crittografare le informazioni di stringa di connessione in Web.config usando il sistema di configurazione protetto.

Nota

Per altre informazioni sui diversi tipi di autenticazione disponibili in SQL Server, vedere Building Secure ASP.NET Applications: Authentication, Authorization e Secure Communication. Per altri stringa di connessione esempi che illustrano le differenze tra la sintassi di autenticazione di Windows e SQL, vedere ConnectionStrings.com.

Riepilogo

Per impostazione predefinita, non è possibile accedere ai file con estensione .config in un'applicazione ASP.NET tramite un browser. Questi tipi di file non vengono restituiti perché possono contenere informazioni riservate, ad esempio stringa di connessione di database, nomi utente e password e così via. Il sistema di configurazione protetto in .NET 2.0 consente di proteggere ulteriormente le informazioni riservate consentendo la crittografia delle sezioni di configurazione specificate. Esistono due provider di configurazione protetti predefiniti: uno che usa l'algoritmo RSA e uno che usa l'API Protezione dati di Windows (DPAPI).

In questa esercitazione è stato illustrato come crittografare e decrittografare le impostazioni di configurazione usando il provider DPAPI. Questa operazione può essere eseguita sia a livello di codice, come illustrato nel passaggio 2, sia tramite lo strumento da aspnet_regiis.exe riga di comando, illustrato nel passaggio 3. Per altre informazioni sull'uso di chiavi a livello di utente o sull'uso del provider RSA, vedere invece le risorse nella sezione Altre informazioni.

Buon programmatori!

Altre informazioni

Per altre informazioni sugli argomenti illustrati in questa esercitazione, vedere le risorse seguenti:

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, ha lavorato con le tecnologie Web Microsoft dal 1998. Scott lavora come consulente indipendente, formatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2.0 in 24 ore. Può essere raggiunto all'indirizzo mitchell@4GuysFromRolla.com. o tramite il suo blog, che può essere trovato all'indirizzo http://ScottOnWriting.NET.

Grazie speciale a

Questa serie di esercitazioni è stata esaminata da molti revisori utili. I revisori principali per questa esercitazione erano Teresa Murphy e Randy Schmidt. Si è interessati a esaminare i prossimi articoli MSDN? In tal caso, rilasciarmi una riga in mitchell@4GuysFromRolla.com.