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

di Scott Mitchell

Scarica il PDF

Un'applicazione ASP.NET archivia in genere le informazioni di configurazione in un file di Web.config. Alcune di queste informazioni sono sensibili e garantiscono 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 di 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 abbiamo aggiornato le Web.config poche volte. Quando si crea il Northwind dataset tipizzato nella prima esercitazione, ad esempio, stringa di connessione informazioni sono state aggiunte automaticamente nella Web.config<connectionStrings> sezione. Successivamente, nell'esercitazione Master Pages and Site Navigation (Pagine master e Navigazione sito ) è stato aggiornato Web.configmanualmente , aggiungendo un <pages> elemento che indica che tutte le pagine di ASP.NET nel progetto devono usare il DataWebControls tema.

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

Visita Web.config Tramite un browser Restituisce un tipo di pagina non servito

Figura 1: Visita Web.config tramite un browser Restituisce un tipo di pagina non servito Messaggio (Fare clic per visualizzare l'immagine a dimensioni complete)

Ma cosa succede se un utente malintenzionato riesce a trovare un altro exploit che 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 in Web.config non contiene informazioni riservate. Quale danno può causare un utente malintenzionato se conosce il nome del tema predefinito usato dalle pagine di ASP.NET?

Alcune Web.config sezioni, tuttavia, contengono informazioni riservate che possono includere stringhe di connessione, nomi utente, password, nomi del 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 la protezione di 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 termina con un'occhiata alle raccomandazioni di Microsoft per la connessione a un database da un'applicazione ASP.NET. Oltre a crittografare le stringhe di connessione, è possibile proteggere 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 provider che consente agli sviluppatori di scegliere l'implementazione crittografica utilizzata.

.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 il proprio 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 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 protette 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 delle 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 nel machine.config file con i nomi RsaProtectedConfigurationProvider del provider e DataProtectionConfigurationProvider, rispettivamente. Quando si crittografa o decrittografando le informazioni di configurazione, è necessario specificare il nome del provider appropriato ( o ) anziché il nome effettivo del tipo (RsaProtectedConfigurationProviderRSAProtectedConfigurationProvider e DPAPIProtectedConfigurationProvider).DataProtectionConfigurationProvider È 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à brevemente, deve semplicemente fare riferimento a livello di codice alla sezione di configurazione appropriata, 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 che può crittografare e decrittografare le informazioni di configurazione. Verrà esaminata questa utilità della riga di comando nel passaggio 3.

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

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

Sotto la Casella di testo aggiungere due controlli Button denominati EncryptConnStrings e DecryptConnStrings. Impostare le proprietà di testo su Crittografare stringhe di connessione e decrittografare le 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 casella di testo e due controlli Web pulsante alla pagina (fare clic per visualizzare l'immagine full-size)

Successivamente, è necessario scrivere codice che carica e visualizza il contenuto di Web.config in WebConfigContents TextBox quando la pagina viene caricata per la prima volta. 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 leggere 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 System.IO spazio dei nomi. Di conseguenza, è necessario aggiungere un'istruzione usingSystem.IO all'inizio della classe code-behind o, in alternativa, prefissi questi nomi di classe con System.IO. .

È quindi necessario aggiungere gestori eventi per i due eventi di 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. Dalla Designer 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 recuperando informazioni sul file dell'applicazione Web.config corrente tramite il WebConfigurationManager metodo della classe.OpenWebConfiguration Questo metodo restituisce il file di configurazione Web per il percorso virtuale specificato. Successivamente, la Web.config sezione del file viene accessibile tramite il Configuration metodo della GetSection(sectionName)<connectionStrings> 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 delle 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 viene passato 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 metodo oUnprotectSection, è necessario chiamare il metodo dell'oggetto SaveProtectSection(provider)Configuration per rendere persistenti le modifiche. Dopo aver crittografato o decrittografato le informazioni di configurazione 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 verrà 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 casella di testo e due controlli Web pulsante alla pagina (fare clic per visualizzare l'immagine full-size)

Fare ora clic sul pulsante Crittografa stringhe di connessione. Se la convalida della richiesta è abilitata, il markup pubblicato di nuovo da WebConfigContents TextBox produrrà un HttpRequestValidationExceptionoggetto , che visualizza il messaggio, un valore potenzialmente pericoloso Request.Form è stato rilevato dal client. La convalida della richiesta, abilitata per impostazione predefinita in ASP.NET 2.0, impedisce postback che includono html non codificati ed è progettata per impedire attacchi di inserimento di script. Questo controllo può essere disabilitato a livello di pagina o applicazione. Per disattivarlo per questa pagina, impostare l'impostazione ValidateRequest su false nella @Page direttiva. La @Page direttiva viene trovata nella parte superiore del markup dichiarativo della pagina.

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

Per altre informazioni sulla convalida della richiesta, lo scopo, come disabilitarlo a livello di pagina e applicazione, nonché come codificaRE HTML, vedere Convalida della richiesta - Prevenzione degli attacchi di script.

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

Fare clic sul pulsante Crittografa stringhe di connessione Crittografa la <sezione connectionString>

Figura 4: Fare clic sul pulsante Encrypt Connection Strings Encrypts the Section (Fare clic per visualizzare l'immagine<connectionString> full-size)

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 usato per eseguire la crittografia (DataProtectionConfigurationProvider). Queste informazioni vengono usate dal metodo quando viene fatto clic sul UnprotectSection pulsante Decrittografa stringhe di connessione.

Quando le informazioni di stringa di connessione vengono accessibili da Web.config , in base al codice scritto, da un controllo SqlDataSource o dal codice generato automaticamente dai TableAdapters in DataSet tipizzato, viene decrittografato automaticamente. In breve, non è necessario aggiungere codice aggiuntivo o logica per decrittografare la sezione crittografata <connectionString> . Per illustrare questo problema, visitare una delle esercitazioni precedenti in questo momento, ad esempio l'esercitazione Di visualizzazione semplice dalla sezione Report di base (~/BasicReporting/SimpleDisplay.aspx). Come illustrato nella figura 5, l'esercitazione funziona esattamente come ci si aspetta, indicando 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 full-size)

Per ripristinare la <connectionStrings> sezione nella rappresentazione di testo normale, fare clic sul pulsante Decrittografa stringhe di connessione. Nel postback verranno visualizzate le stringhe di connessione in Web.config testo normale. A questo punto la schermata dovrebbe essere simile alla prima visita di 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 cache SQL, ad esempio, è stato esaminato l'uso aspnet_regsql.exe dello strumento della riga di comando per aggiungere l'infrastruttura necessaria per le dipendenze della cache SQL. Un altro strumento della riga di comando utile in questa cartella è lo strumento ASP.NET di registrazione IIS (aspnet_regiis.exe). Come implica 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 mostra la sintassi generale usata per crittografare una sezione di configurazione con lo strumento della 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 della 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 della riga di comando dal computer di sviluppo locale e quindi si carica il file di Web.config crittografato nel server di produzione, il server di produzione non sarà in grado di decrittografare le informazioni di 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 emettere SELECTquery , , INSERTUPDATEo DELETE a un database di microsoft SQL Server, il database deve prima identificare il richiedente. Questo processo è noto come autenticazione e SQL Server fornisce 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 presupponeno l'identità di domainName``\MachineName o domainName``\NETWORK SERVICE, anche se è possibile personalizzare questa operazione.
  • Autenticazione SQL : i valori 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 tramite l'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 risiedono in due computer diversi, le credenziali non vengono inviate tramite la rete in testo normale. Con l'autenticazione SQL, tuttavia, le credenziali di autenticazione vengono hardcoded nella 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 quale modalità di autenticazione viene usata controllando la 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

La sicurezza integrata=True e la mancanza di un nome utente e una password indicano che viene usato autenticazione di Windows. In alcune stringhe di connessione il termine Trusted Connection=Sì o Integrated Security=SSPI viene usato anziché Integrated Security=True, ma tutte 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 vengono incorporate all'interno del stringa di connessione:

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

Si supponga che un utente malintenzionato possa 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 nell'uso Web.config del sistema di configurazione protetta.

Nota

Per altre informazioni sui diversi tipi di autenticazione disponibili in SQL Server, vedere Compilazione di applicazioni di ASP.NET sicure: autenticazione, autorizzazione e comunicazione sicura. Per ulteriori stringa di connessione esempi che illustrano le differenze tra sintassi di autenticazione Windows e SQL, vedere ConnectionStrings.com.

Riepilogo

Per impostazione predefinita, i file con estensione in un'applicazione .config ASP.NET non possono essere accessibili tramite un browser. Questi tipi di file non vengono restituiti perché possono contenere informazioni riservate, ad esempio stringhe di connessione al 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 della 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 Ulteriori letture.

Programmazione felice!

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 Microsoft Web dal 1998. Scott lavora come consulente indipendente, allenatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2,0 in 24 Ore. Può essere raggiunto a mitchell@4GuysFromRolla.com. o tramite il suo blog, che può essere trovato in http://ScottOnWriting.NET.

Grazie speciali

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