Archiviazione di informazioni utente aggiuntive (C#)
Nota
Poiché questo articolo è stato scritto, i provider di appartenenza ASP.NET sono stati sostituiti da ASP.NET Identity. È consigliabile aggiornare le app per usare la ASP.NET Identity Platform anziché i provider di appartenenza in primo piano al momento della scrittura di questo articolo. ASP.NET Identity offre numerosi vantaggi rispetto al sistema di appartenenza ASP.NET, tra cui :
- Prestazioni migliori
- Miglioramento dell'estendibilità e della testability
- Supporto per OAuth, OpenID Connect e autenticazione a due fattori
- Supporto delle identità basate sulle attestazioni
- Interoperabilità migliore con ASP.Net Core
Scaricare codice o scaricare pdf
Questa esercitazione risponderà a questa domanda creando un'applicazione guestbook molto rudimentaria. A tale scopo, verranno esaminate diverse opzioni per la modellazione delle informazioni utente in un database e quindi verrà illustrato come associare questi dati agli account utente creati dal framework Di appartenenza.
Introduzione
ASP. Il framework di appartenenza di NET offre un'interfaccia flessibile per la gestione degli utenti. L'API Appartenenza include metodi per la convalida delle credenziali, il recupero di informazioni sull'utente attualmente connesso, la creazione di un nuovo account utente e l'eliminazione di un account utente, tra gli altri. Ogni account utente nel framework di appartenenza contiene solo le proprietà necessarie per convalidare le credenziali ed eseguire attività essenziali relative all'account utente. Ciò è evidenziato dai metodi e dalle proprietà della MembershipUser
classe, che modella un account utente nel framework Di appartenenza. Questa classe include proprietà come UserName
, Email
e IsLockedOut
e metodi come GetPassword
e UnlockUser
.
Spesso, le applicazioni devono archiviare informazioni utente aggiuntive non incluse nel framework di appartenenza. Ad esempio, un rivenditore online potrebbe dover consentire a ogni utente di archiviare gli indirizzi di spedizione e fatturazione, le informazioni di pagamento, le preferenze di consegna e il numero di telefono di contatto. Inoltre, ogni ordine nel sistema è associato a un determinato account utente.
La MembershipUser
classe non include proprietà come PhoneNumber
o DeliveryPreferences
PastOrders
. Come è quindi possibile tenere traccia delle informazioni utente necessarie dall'applicazione e integrarla con il framework di appartenenza? Questa esercitazione risponderà a questa domanda creando un'applicazione guestbook molto rudimentaria. A tale scopo, verranno esaminate diverse opzioni per la modellazione delle informazioni utente in un database e quindi verrà illustrato come associare questi dati agli account utente creati dal framework Di appartenenza. È possibile iniziare subito.
Passaggio 1: Creazione del modello di dati dell'applicazione Guestbook
Esistono diverse tecniche che possono essere usate per acquisire informazioni utente in un database e associarlo agli account utente creati dal framework Di appartenenza. Per illustrare queste tecniche, sarà necessario aumentare l'applicazione Web dell'esercitazione in modo che acquisisca alcuni tipi di dati correlati all'utente. Attualmente, il modello di dati dell'applicazione contiene solo le tabelle dei servizi applicazione necessarie da SqlMembershipProvider
.)
Verrà creata un'applicazione guestbook molto semplice in cui un utente autenticato può lasciare un commento. Oltre a archiviare i commenti del guestbook, è possibile consentire a ogni utente di archiviare la propria città, la home page e la firma. Se specificato, la città principale dell'utente, la home page e la firma verranno visualizzati su ogni messaggio che ha lasciato nel guestbook.
Aggiunta dellaGuestbookComments
tabella
Per acquisire i commenti del guestbook, è necessario creare una tabella di database denominata GuestbookComments
con colonne come CommentId
, Subject
, Body
e CommentDate
. È anche necessario avere ogni record nella GuestbookComments
tabella che fa riferimento all'utente che ha lasciato il commento.
Per aggiungere questa tabella al database, passare a Esplora database in Visual Studio ed eseguire il drill-down nel SecurityTutorials
database. Fare clic con il pulsante destro del mouse sulla cartella Tabelle e scegliere Aggiungi nuova tabella. In questo modo viene visualizzata un'interfaccia che consente di definire le colonne per la nuova tabella.
Figura 1: Aggiungere una nuova tabella al SecurityTutorials
database (fare clic per visualizzare l'immagine full-size)
Definire quindi le GuestbookComments
colonne di . Iniziare aggiungendo una colonna denominata CommentId
di tipo uniqueidentifier
. Questa colonna identificherà in modo univoco ogni commento nel guestbook, quindi non consentire NULL
e contrassegnarlo come chiave primaria della tabella. Anziché specificare un valore per il CommentId
campo in ogni INSERT
oggetto , è possibile indicare che un nuovo uniqueidentifier
valore deve essere generato automaticamente per questo campo impostando INSERT
il valore predefinito della colonna su NEWID()
. Dopo aver aggiunto questo primo campo, contrassegnandolo come chiave primaria e impostandone il valore predefinito, la schermata dovrebbe essere simile alla schermata visualizzata nella figura 2.
Figura 2: Aggiungere una colonna primaria denominata CommentId
(fare clic per visualizzare l'immagine full-size)
Aggiungere quindi una colonna denominata di tipo e una colonna denominata Subject
Body
di tipo nvarchar(50)
nvarchar(MAX)
, disallowing NULL
s in entrambe le colonne. Successivamente, aggiungere una colonna denominata CommentDate
di tipo datetime
. Non consentire NULL
s e impostare il CommentDate
valore predefinito della colonna su getdate()
.
Tutto ciò che rimane consiste nell'aggiungere una colonna che associa un account utente a ogni commento del guestbook. Un'opzione consiste nell'aggiungere una colonna denominata UserName
di tipo nvarchar(256)
. Questa è una scelta adatta quando si usa un provider di appartenenza diverso da SqlMembershipProvider
. Tuttavia, quando si usa SqlMembershipProvider
, come si è in questa serie di esercitazioni, la UserName
colonna nella aspnet_Users
tabella non è garantita univoca. La aspnet_Users
chiave primaria della tabella è e è UserId
di tipo uniqueidentifier
. Pertanto, la GuestbookComments
tabella richiede una colonna denominata UserId
di tipo uniqueidentifier
(non consentiti NULL
valori). Andare avanti e aggiungere questa colonna.
Nota
Come illustrato nell'esercitazione Creazione dello schema di appartenenza in SQL Server, il framework di appartenenza è progettato per abilitare più applicazioni Web con account utente diversi per condividere lo stesso archivio utenti. Questa operazione viene eseguita partizionando gli account utente in applicazioni diverse. Mentre ogni nome utente è garantito essere univoco all'interno di un'applicazione, lo stesso nome utente può essere usato in applicazioni diverse usando lo stesso archivio utenti. Esiste un vincolo composito UNIQUE
nella aspnet_Users
tabella UserName
sui campi e ApplicationId
, ma non uno solo sul UserName
campo. Di conseguenza, è possibile che la tabella aspnet_Users disponga di due record (o più) con lo stesso UserName
valore. Esiste tuttavia un UNIQUE
vincolo sul aspnet_Users
campo della UserId
tabella (poiché è la chiave primaria). Un UNIQUE
vincolo è importante perché senza non è possibile stabilire un vincolo di chiave esterna tra le GuestbookComments
tabelle e aspnet_Users
.
Dopo aver aggiunto la colonna, salvare la UserId
tabella facendo clic sull'icona Salva nella barra degli strumenti. Denominare la nuova tabella GuestbookComments
.
È necessario GuestbookComments
creare un vincolo di chiave esterna tra la colonna e la GuestbookComments.UserId
aspnet_Users.UserId
colonna. A questo scopo, fare clic sull'icona Relazione nella barra degli strumenti per avviare la finestra di dialogo Relazioni chiave esterna. In alternativa, è possibile avviare questa finestra di dialogo passando al menu Tabella Designer e scegliendo Relazioni.
Fare clic sul pulsante Aggiungi nell'angolo inferiore sinistro della finestra di dialogo Relazioni chiave esterna. Verrà aggiunto un nuovo vincolo di chiave esterna, anche se è comunque necessario definire le tabelle che partecipano alla relazione.
Figura 3: Usare la finestra di dialogo Relazioni chiave esterna per gestire i vincoli di chiave esterna di una tabella (fare clic per visualizzare l'immagine full-size)
Fare quindi clic sull'icona dei puntini di sospensione nella riga "Specifiche tabella e colonne" a destra. Verrà avviata la finestra di dialogo Tabelle e colonne, da cui è possibile specificare la tabella e la colonna chiave primaria e la colonna chiave esterna dalla GuestbookComments
tabella. In particolare, selezionare aspnet_Users
e UserId
come tabella e colonna chiave primaria e UserId
dalla GuestbookComments
tabella come colonna chiave esterna (vedere la figura 4). Dopo aver definito le tabelle e le colonne chiave primaria ed esterna, fare clic su OK per tornare alla finestra di dialogo Relazioni chiave esterna.
Figura 4: Stabilire un vincolo di chiave esterna tra le aspnet_Users
tabelle e GuesbookComments
(fare clic per visualizzare l'immagine full-size)
A questo punto è stato stabilito il vincolo di chiave esterna. La presenza di questo vincolo garantisce l'integrità relazionale tra le due tabelle garantendo che non vi sarà mai una voce del guestbook che fa riferimento a un account utente non esistente. Per impostazione predefinita, un vincolo di chiave esterna non consente l'eliminazione di un record padre se sono presenti record figlio corrispondenti. In questo caso, se un utente effettua uno o più commenti del guestbook e quindi si tenta di eliminare l'account utente, l'eliminazione avrà esito negativo a meno che i commenti del guestbook non vengano eliminati prima.
I vincoli di chiave esterna possono essere configurati per eliminare automaticamente i record figlio associati quando viene eliminato un record padre. In altre parole, è possibile configurare questo vincolo di chiave esterna in modo che le voci del guestbook di un utente vengano eliminate automaticamente quando l'account utente viene eliminato. A tale scopo, espandere la sezione "INSERT and UPDATE Specification" e impostare la proprietà "Delete Rule" su Cascade.
Figura 5: Configurare il vincolo chiave esterna in Elimina a catena (fare clic per visualizzare l'immagine a dimensioni complete)
Per salvare il vincolo di chiave esterna, fare clic sul pulsante Chiudi per uscire dalle relazioni con chiavi esterne. Fare quindi clic sull'icona Salva nella barra degli strumenti per salvare la tabella e la relazione.
Archiviazione della home town, della home page e della firma dell'utente
La GuestbookComments
tabella illustra come archiviare informazioni che condividono una relazione uno-a-molti con gli account utente. Poiché ogni account utente può avere un numero arbitrario di commenti associati, questa relazione viene modellata creando una tabella per contenere il set di commenti che include una colonna che collega ogni commento a un determinato utente. Quando si usa SqlMembershipProvider
, questo collegamento è meglio stabilito creando una colonna denominata UserId
di tipo uniqueidentifier
e un vincolo di chiave esterna tra questa colonna e aspnet_Users.UserId
.
È ora necessario associare tre colonne a ogni account utente per archiviare la città principale, la home page e la firma dell'utente, che verrà visualizzata nei commenti del suo guestbook. Esistono diversi modi per eseguire questa operazione:
Aggiungere nuove colonne all'oggetto
aspnet_Users
Oaspnet_Membership
Tabelle. Non è consigliabile questo approccio perché modifica lo schema usato daSqlMembershipProvider
. Questa decisione potrebbe tornare a stentare la strada. Ad esempio, cosa accade se una versione futura di ASP.NET usa uno schema diversoSqlMembershipProvider
. Microsoft può includere uno strumento per eseguire la migrazione dei dati ASP.NET 2.0 al nuovo schema, ma se è stato modificato lo schema ASP.NET 2.0SqlMembershipProvider
SqlMembershipProvider
, tale conversione potrebbe non essere possibile.Usare ASP. Framework profilo di NET, definizione di una proprietà del profilo per la città principale, la home page e la firma. ASP.NET include un framework di profilo progettato per archiviare dati specifici dell'utente aggiuntivi. Analogamente al framework Di appartenenza, il framework del profilo viene compilato in cima al modello del provider. .NET Framework viene fornito con uno sthat archivia i dati del profilo in un
SqlProfileProvider
database SQL Server. Infatti, il database ha già la tabella usata daSqlProfileProvider
(aspnet_Profile
), come è stato aggiunto quando sono stati aggiunti i servizi dell'applicazione nell'esercitazione Creazione dello schema di appartenenza in SQL Server.
Il vantaggio principale del framework profile è che consente agli sviluppatori di definire le proprietà del profilo inWeb.config
- nessun codice deve essere scritto per serializzare i dati del profilo da e verso l'archivio dati sottostante. In breve, è incredibilmente facile definire un set di proprietà del profilo e usarle nel codice. Tuttavia, il sistema profilo lascia molto da desiderare quando si tratta del controllo delle versioni, quindi se si dispone di un'applicazione in cui si prevede che le nuove proprietà specifiche dell'utente vengano aggiunte in un secondo momento o quelle esistenti da rimuovere o modificare, il framework del profilo potrebbe non essere l'opzione migliore. Inoltre, archiviaSqlProfileProvider
le proprietà del profilo in modo altamente denormalizzato, rendendo quindi impossibile eseguire query direttamente sui dati del profilo, ad esempio quanti utenti hanno una città principale di New York.
Per altre informazioni sul framework del profilo, consultare la sezione "Ulteriori letture" alla fine di questa esercitazione.Aggiungere queste tre colonne a una nuova tabella nel database e stabilire una relazione uno-a-uno tra questa tabella e
aspnet_Users
. Questo approccio comporta un po' di lavoro maggiore rispetto al framework Profile, ma offre massima flessibilità nel modo in cui le proprietà utente aggiuntive vengono modellate nel database. Questa è l'opzione che verrà usata in questa esercitazione.
Verrà creata una nuova tabella chiamata UserProfiles
per salvare la città principale, la home page e la firma per ogni utente. Fare clic con il pulsante destro del mouse sulla cartella Tabelle nella finestra Esplora database e scegliere di creare una nuova tabella. Assegnare un nome alla prima colonna UserId
e impostarne il tipo su uniqueidentifier
. Non consentire i valori e contrassegnare NULL
la colonna come chiave primaria. Aggiungere quindi colonne denominate: HomeTown
di tipo ; HomepageUrl
di tipo nvarchar(100)
nvarchar(50)
; e Firma di tipo nvarchar(500)
. Ognuna di queste tre colonne può accettare un NULL
valore.
Figura 6: Creare la tabella (fare clic per visualizzare l'immagineUserProfiles
full-size)
Salvare la tabella e denominarla UserProfiles
. Infine, stabilire un vincolo di chiave esterna tra il UserProfiles
campo della UserId
tabella e il aspnet_Users.UserId
campo. Come è stato fatto con il vincolo di chiave esterna tra le GuestbookComments
tabelle e aspnet_Users
, è possibile eliminare questo vincolo. Poiché il campo in UserProfiles
è la UserId
chiave primaria, ciò garantisce che non siano presenti più record nella UserProfiles
tabella per ogni account utente. Questo tipo di relazione viene definito uno-a-uno.
Dopo aver creato il modello di dati, è possibile usarlo. Nei passaggi 2 e 3 si esaminerà come l'utente attualmente connesso può visualizzare e modificare le informazioni sulla propria città, home page e firma. Nel passaggio 4 verrà creata l'interfaccia per gli utenti autenticati per inviare nuovi commenti al guestbook e visualizzare quelli esistenti.
Passaggio 2: Visualizzazione della home town, della home page e della firma dell'utente
Esistono diversi modi per consentire all'utente attualmente connesso di visualizzare e modificare la propria città, la home page e le informazioni sulla firma. È possibile creare manualmente l'interfaccia utente con i controlli TextBox e Label oppure usare uno dei controlli Web dati, ad esempio il controllo DetailsView. Per eseguire le istruzioni e UPDATE
il databaseSELECT
, è possibile scrivere ADO.NET codice nella classe code-behind della pagina o, in alternativa, usare un approccio dichiarativo con SqlDataSource. Idealmente l'applicazione contiene un'architettura a livelli, che è possibile richiamare a livello di codice dalla classe code-behind della pagina o dichiarativamente tramite il controllo ObjectDataSource.
Poiché questa serie di esercitazioni è incentrata sull'autenticazione dei moduli, l'autorizzazione, gli account utente e i ruoli, non sarà presente una discussione approfondita su queste diverse opzioni di accesso ai dati o sul motivo per cui un'architettura a livelli è preferibile per l'esecuzione di istruzioni SQL direttamente dalla pagina ASP.NET. Verrà illustrato l'uso di un oggetto DetailsView e SqlDataSource, l'opzione più rapida e più semplice, ma i concetti illustrati possono essere certamente applicati ai controlli Web alternativi e alla logica di accesso ai dati. Per altre informazioni sull'uso dei dati in ASP.NET, vedere Uso dei dati nella serie di esercitazioni di ASP.NET 2.0 .
Aprire la AdditionalUserInfo.aspx
pagina nella Membership
cartella e aggiungere un controllo DetailsView alla pagina, impostandone ID
la proprietà su UserProfile
e cancellandone Width
le proprietà e Height
. Espandere lo Smart Tag di DetailsView e scegliere di associarlo a un nuovo controllo origine dati. Verrà avviata la Configurazione guidata origine dati (vedere la figura 7). Il primo passaggio chiede di specificare il tipo di origine dati. Poiché ci si connette direttamente al SecurityTutorials
database, scegliere l'icona Database, specificando come ID
UserProfileDataSource
.
Figura 7: Aggiungere un nuovo controllo SqlDataSource denominato UserProfileDataSource
(fare clic per visualizzare l'immagine full-size)
La schermata successiva richiede l'uso del database. Per il SecurityTutorials
database è già stata definita una stringa Web.config
di connessione. Questo nome della stringa di connessione: SecurityTutorialsConnectionString
deve trovarsi nell'elenco a discesa. Selezionare questa opzione e fare clic su Avanti.
Figura 8: Scegliere SecurityTutorialsConnectionString
dall'elenco Drop-Down (fare clic per visualizzare l'immagine a dimensioni complete)
La schermata successiva chiede di specificare la tabella e le colonne da eseguire per la query. Scegliere la UserProfiles
tabella dall'elenco a discesa e controllare tutte le colonne.
Figura 9: Ripristinare tutte le colonne dalla UserProfiles
tabella (fare clic per visualizzare l'immagine full-size)
La query corrente nella figura 9 restituisce tutti i record in UserProfiles
, ma siamo interessati solo al record dell'utente attualmente connesso. Per aggiungere una WHERE
clausola, fare clic sul pulsante per visualizzare la WHERE
finestra di dialogo Aggiungi WHERE
clausola (vedere Figura 10). Qui è possibile selezionare la colonna da filtrare, l'operatore e l'origine del parametro di filtro. Selezionare UserId
come colonna e "=" come Operatore.
Purtroppo non esiste un'origine dei parametri predefinita per restituire il valore dell'utente UserId
attualmente connesso. Sarà necessario afferrare questo valore a livello di codice. Impostare quindi l'elenco a discesa Origine su "Nessuno", fare clic sul pulsante Aggiungi per aggiungere il parametro e quindi fare clic su OK.
Figura 10: Aggiungere un parametro di filtro nella colonna (fare clic per visualizzare l'immagineUserId
a dimensioni complete)
Dopo aver fatto clic su OK, verrà restituito alla schermata visualizzata nella figura 9. Questa volta, tuttavia, la query SQL nella parte inferiore della schermata deve includere una WHERE
clausola. Fare clic su Avanti per passare alla schermata "Query di test". Qui è possibile eseguire la query e visualizzare i risultati. Fare clic su Fine per completare la procedura guidata.
Al termine della configurazione guidata DataSource, Visual Studio crea il controllo SqlDataSource in base alle impostazioni specificate nella procedura guidata. Inoltre, aggiunge manualmente BoundFields a DetailsView per ogni colonna restituita dall'oggetto SqlDataSource.SelectCommand
Non è necessario visualizzare il campo in DetailsView, poiché l'utente UserId
non deve conoscere questo valore. È possibile rimuovere questo campo direttamente dal markup dichiarativo del controllo DetailsView oppure facendo clic sul collegamento "Modifica campi" dal relativo Smart Tag.
A questo punto il markup dichiarativo della pagina dovrebbe essere simile al seguente:
<asp:DetailsView ID="UserProfile" runat="server"
AutoGenerateRows="False" DataKeyNames="UserId"
DataSourceID="UserProfileDataSource">
<Fields>
<asp:BoundField DataField="HomeTown" HeaderText="HomeTown"
SortExpression="HomeTown" />
<asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"
SortExpression="HomepageUrl" />
<asp:BoundField DataField="Signature" HeaderText="Signature"
SortExpression="Signature" />
</Fields>
</asp:DetailsView>
<asp:SqlDataSource ID="UserProfileDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:SecurityTutorialsConnectionString %>"
SelectCommand="SELECT [UserId], [HomeTown], [HomepageUrl], [Signature] FROM
[UserProfiles] WHERE ([UserId] = @UserId)">
<SelectParameters>
<asp:Parameter Name="UserId" Type="Object" />
</SelectParameters>
</asp:SqlDataSource>
È necessario impostare a livello di codice il parametro del UserId
controllo SqlDataSource sul parametro dell'utente UserId
attualmente connesso prima dell'selezione dei dati. Questa operazione può essere eseguita creando un gestore eventi per l'evento Selecting
sqlDataSource e aggiungendo il codice seguente:
protected void UserProfileDataSource_Selecting(object sender,
SqlDataSourceSelectingEventArgs e)
{
// Get a reference to the currently logged on user
MembershipUser currentUser = Membership.GetUser();
// Determine the currently logged on user's UserId value
Guid currentUserId = (Guid)currentUser.ProviderUserKey;
// Assign the currently logged on user's UserId to the @UserId parameter
e.Command.Parameters["@UserId"].Value = currentUserId;
}
Il codice precedente inizia ottenendo un riferimento all'utente attualmente connesso chiamando il Membership
metodo della GetUser
classe. Restituisce un MembershipUser
oggetto, la cui ProviderUserKey
proprietà contiene .UserId
Il UserId
valore viene quindi assegnato al parametro sqlDataSource @UserId
.
Nota
Il Membership.GetUser()
metodo restituisce informazioni sull'utente attualmente connesso. Se un utente anonimo sta visitando la pagina, restituirà un valore di null
. In questo caso, questo comporta un NullReferenceException
oggetto nella riga di codice seguente quando si tenta di leggere la ProviderUserKey
proprietà. Naturalmente, non è necessario preoccuparsi di Membership.GetUser()
restituire un null
valore nella AdditionalUserInfo.aspx
pagina perché è stata configurata l'autorizzazione url in un'esercitazione precedente in modo che solo gli utenti autenticati possano accedere alle risorse ASP.NET in questa cartella. Se è necessario accedere alle informazioni sull'utente attualmente connesso in una pagina in cui è consentito l'accesso anonimo, assicurarsi di verificare che un oggetto nonnull MembershipUser
venga restituito dal GetUser()
metodo prima di fare riferimento alle relative proprietà.
Se si visita la AdditionalUserInfo.aspx
pagina tramite un browser, verrà visualizzata una pagina vuota perché è ancora necessario aggiungere qualsiasi riga alla UserProfiles
tabella. Nel passaggio 6 verrà illustrato come personalizzare il controllo CreateUserWizard per aggiungere automaticamente una nuova riga alla UserProfiles
tabella quando viene creato un nuovo account utente. Per il momento, tuttavia, sarà necessario creare manualmente un record nella tabella.
Passare a Esplora database in Visual Studio e espandere la cartella Tabelle. Fare clic con il pulsante destro del mouse sulla aspnet_Users
tabella e scegliere "Mostra dati tabella" per visualizzare i record nella tabella. Eseguire la stessa operazione per la UserProfiles
tabella. La figura 11 mostra questi risultati quando viene riquadri verticalmente. Nel database sono attualmente aspnet_Users
presenti record per Bruce, Fred e Tito, ma non sono presenti record nella UserProfiles
tabella.
Figura 11: il contenuto delle aspnet_Users
tabelle e UserProfiles
viene visualizzato (fare clic per visualizzare l'immagine a dimensioni complete)
Aggiungere un nuovo record alla UserProfiles
tabella digitando manualmente i valori per i HomeTown
campi , HomepageUrl
e Signature
. Il modo più semplice per ottenere un valore valido nel nuovo UserProfiles
record consiste nel selezionare il UserId
campo da un determinato account utente nella aspnet_Users
tabella e copiarlo e incollarlo nel UserId
campo in UserProfiles
.UserId
La figura 12 mostra la tabella dopo l'aggiunta UserProfiles
di un nuovo record per Bruce.
Figura 12: Un record è stato aggiunto a UserProfiles
Bruce (fare clic per visualizzare l'immagine a dimensioni complete)
Tornare alla AdditionalUserInfo.aspx
pagina, connesso come Bruce. Come illustrato nella figura 13, vengono visualizzate le impostazioni di Bruce.
Figura 13: l'utente attualmente in visita mostra le impostazioni (fare clic per visualizzare l'immagine a dimensioni complete)
Nota
Andare avanti e aggiungere manualmente record nella UserProfiles
tabella per ogni utente di appartenenza. Nel passaggio 6 verrà illustrato come personalizzare il controllo CreateUserWizard per aggiungere automaticamente una nuova riga alla UserProfiles
tabella quando viene creato un nuovo account utente.
Passaggio 3: Consentire all'utente di modificare la propria città di casa, la home page e la firma
A questo punto l'utente attualmente connesso può visualizzare la propria città, la home page e l'impostazione di firma, ma non possono ancora modificarle. Aggiornare il controllo DetailsView in modo che i dati possano essere modificati.
La prima cosa da eseguire consiste nell'aggiungere un oggetto UpdateCommand
per SqlDataSource, specificando l'istruzione UPDATE
da eseguire e i relativi parametri corrispondenti. Selezionare SqlDataSource e, nel Finestra Proprietà, fare clic sui puntini di sospensione accanto alla proprietà UpdateQuery per visualizzare la finestra di dialogo Editor comandi e parametri. Immettere l'istruzione seguente UPDATE
nella casella di testo:
UPDATE UserProfiles SET
HomeTown = @HomeTown,
HomepageUrl = @HomepageUrl,
Signature = @Signature
WHERE UserId = @UserId
Fare quindi clic sul pulsante "Aggiorna parametri", che creerà un parametro nell'insieme del UpdateParameters
controllo SqlDataSource per ognuno dei parametri nell'istruzione UPDATE
. Lasciare l'origine per tutti i parametri impostati su Nessuno e fare clic sul pulsante OK per completare la finestra di dialogo.
Figura 14: Specificare sqlDataSource e UpdateParameters
(fare clic per visualizzare l'immagineUpdateCommand
a dimensioni complete)
A causa delle aggiunte apportate al controllo SqlDataSource, il controllo DetailsView può ora supportare la modifica. Nella casella di controllo "Abilita modifica" di DetailsView selezionare la casella di controllo "Abilita modifica". In questo modo viene aggiunto un CommandField all'insieme del Fields
controllo con la relativa ShowEditButton
proprietà impostata su True. In questo modo viene eseguito il rendering di un pulsante Modifica quando detailsView viene visualizzato in modalità di sola lettura e i pulsanti Aggiorna e Annulla quando vengono visualizzati in modalità di modifica. Anziché richiedere all'utente di fare clic su Modifica, è possibile avere il rendering DetailsView in uno stato "sempre modificabile" impostando la proprietà del DefaultMode
controllo DetailsView su Edit
.
Con queste modifiche, il markup dichiarativo del controllo DetailsView dovrebbe essere simile al seguente:
<asp:DetailsView ID="UserProfile" runat="server"
AutoGenerateRows="False" DataKeyNames="UserId"
DataSourceID="UserProfileDataSource" DefaultMode="Edit">
<Fields>
<asp:BoundField DataField="HomeTown" HeaderText="HomeTown"
SortExpression="HomeTown" />
<asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"
SortExpression="HomepageUrl" />
<asp:BoundField DataField="Signature" HeaderText="Signature"
SortExpression="Signature" />
<asp:CommandField ShowEditButton="True" />
</Fields>
</asp:DetailsView>
Si noti l'aggiunta dell'oggetto CommandField e della DefaultMode
proprietà .
Andare avanti e testare questa pagina tramite un browser. Quando si visita con un utente con un record corrispondente in UserProfiles
, le impostazioni dell'utente vengono visualizzate in un'interfaccia modificabile.
Figura 15: DetailsView esegue il rendering di un'interfaccia modificabile (fare clic per visualizzare un'immagine full-size)
Provare a modificare i valori e fare clic sul pulsante Aggiorna. Sembra come se non accadesse nulla. È presente un postback e i valori vengono salvati nel database, ma non è presente alcun feedback visivo che si è verificato il salvataggio.
Per risolvere questo problema, tornare a Visual Studio e aggiungere un controllo Label sopra DetailsView. ID
SettingsUpdatedMessage
Impostare su , la relativa Text
proprietà su "Le impostazioni sono state aggiornate" e le relative Visible
proprietà e EnableViewState
su false
.
<asp:Label ID="SettingsUpdatedMessage" runat="server"
Text="Your settings have been updated."
EnableViewState="false"
Visible="false"></asp:Label>
È necessario visualizzare l'etichetta SettingsUpdatedMessage
ogni volta che detailsView viene aggiornato. A questo scopo, creare un gestore eventi per l'evento ItemUpdated
DetailsView e aggiungere il codice seguente:
protected void UserProfile_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
{
SettingsUpdatedMessage.Visible = true;
}
Tornare alla AdditionalUserInfo.aspx
pagina tramite un browser e aggiornare i dati. Questa volta viene visualizzato un messaggio di stato utile.
Figura 16: Viene visualizzato un breve messaggio quando vengono aggiornate le impostazioni (fare clic per visualizzare l'immagine a dimensioni complete)
Nota
L'interfaccia di modifica del controllo DetailsView lascia molto da desiderare. Usa caselle di testo di dimensioni standard, ma il campo Firma dovrebbe probabilmente essere una casella di testo a più righe. È consigliabile usare Un regularExpressionValidator per assicurarsi che l'URL della home page, se immesso, inizi con "http://" o "https://". Inoltre, poiché il controllo DetailsView ha la proprietà DefaultMode
impostata su Edit
, il pulsante Annulla non esegue alcuna operazione. Deve essere rimosso o, quando si fa clic, reindirizzare l'utente ad altre pagine (ad esempio ~/Default.aspx
). Lascio questi miglioramenti come esercizio per il lettore.
Aggiunta di un collegamento allaAdditionalUserInfo.aspx
pagina nella pagina master
Attualmente, il sito Web non fornisce collegamenti alla AdditionalUserInfo.aspx
pagina. L'unico modo per raggiungere è immettere l'URL della pagina direttamente nella barra degli indirizzi del browser. Aggiungiamo un collegamento a questa pagina nella Site.master
pagina master.
Tenere presente che la pagina master contiene un controllo Web LoginView nel relativo LoginContent
ContentPlaceHolder che visualizza markup diverso per i visitatori autenticati e anonimi. Aggiornare il controllo LoggedInTemplate
LoginView per includere un collegamento alla AdditionalUserInfo.aspx
pagina. Dopo aver apportato queste modifiche, il markup dichiarativo del controllo LoginView dovrebbe essere simile al seguente:
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
Welcome back,
<asp:LoginName ID="LoginName1" runat="server" />.
<br />
<asp:HyperLink ID="lnkUpdateSettings" runat="server"
NavigateUrl="~/Membership/AdditionalUserInfo.aspx">
Update Your Settings</asp:HyperLink>
</LoggedInTemplate>
<AnonymousTemplate>
Hello, stranger.
</AnonymousTemplate>
</asp:LoginView>
Si noti l'aggiunta del controllo HyperLink all'oggetto lnkUpdateSettings
LoggedInTemplate
. Con questo collegamento, gli utenti autenticati possono passare rapidamente alla pagina per visualizzare e modificare le impostazioni di home town, homepage e firma.
Passaggio 4: Aggiunta di nuovi commenti guestbook
La Guestbook.aspx
pagina è in cui gli utenti autenticati possono visualizzare il guestbook e lasciare un commento. Iniziamo con la creazione dell'interfaccia per aggiungere nuovi commenti del guestbook.
Aprire la Guestbook.aspx
pagina in Visual Studio e costruire un'interfaccia utente costituita da due controlli TextBox, uno per l'oggetto del nuovo commento e uno per il relativo corpo. Impostare la prima proprietà del ID
controllo TextBox su e sulla relativa Columns
proprietà Subject
su 40; impostare Body
ID
rispettivamente su , Width
TextMode
MultiLine
Rows
su e le relative proprietà e su "95%" e su 8. Per completare l'interfaccia utente, aggiungere un controllo Web Button denominato PostCommentButton
e impostarne la Text
proprietà su "Post Your Comment".
Poiché ogni commento del guestbook richiede un oggetto e un corpo, aggiungere un oggetto RequiredFieldValidator per ognuna delle Caselle di testo. Impostare la proprietà di questi controlli su "EnterComment"; analogamente, impostare la ValidationGroup
PostCommentButton
proprietà del ValidationGroup
controllo su "EnterComment". Per altre informazioni su ASP. Controlli di convalida di NET, vedere Convalida modulo in ASP.NET.
Dopo aver creato l'interfaccia utente del markup dichiarativo della pagina, dovrebbe essere simile al seguente:
<h3>Leave a Comment</h3>
<p>
<b>Subject:</b>
<asp:RequiredFieldValidator ID="SubjectReqValidator" runat="server"
ErrorMessage="You must provide a value for Subject"
ControlToValidate="Subject" ValidationGroup="EnterComment">
</asp:RequiredFieldValidator><br/>
<asp:TextBox ID="Subject" Columns="40" runat="server"></asp:TextBox>
</p>
<p>
<b>Body:</b>
<asp:RequiredFieldValidator ID="BodyReqValidator" runat="server"
ControlToValidate="Body"
ErrorMessage="You must provide a value for Body" ValidationGroup="EnterComment">
</asp:RequiredFieldValidator><br/>
<asp:TextBox ID="Body" TextMode="MultiLine" Width="95%"
Rows="8" runat="server"></asp:TextBox>
</p>
<p>
<asp:Button ID="PostCommentButton" runat="server"
Text="Post Your Comment"
ValidationGroup="EnterComment" />
</p>
Con il completamento dell'interfaccia utente, l'attività successiva consiste nell'inserire un nuovo record nella GuestbookComments
tabella quando viene PostCommentButton
fatto clic su . Questa operazione può essere eseguita in diversi modi: è possibile scrivere ADO.NET codice nel gestore eventi del pulsante. È possibile aggiungere un controllo SqlDataSource alla pagina, configurarne InsertCommand
e quindi chiamare Insert
il relativo metodo dal Click
gestore eventi; oppure è possibile creare un livello intermedio responsabile dell'inserimento Click
di nuovi commenti del guestbook e richiamare questa funzionalità dal Click
gestore eventi. Poiché è stato esaminato l'uso di sqlDataSource nel passaggio 3, è possibile usare ADO.NET codice qui.
Nota
Le classi ADO.NET usate per accedere a livello di codice ai dati da un database di SQL Server Microsoft si trovano nello System.Data.SqlClient
spazio dei nomi. Potrebbe essere necessario importare questo spazio dei nomi nella classe code-behind della pagina , ad esempio using System.Data.SqlClient;
.
Creare un gestore eventi per l'evento PostCommentButton
e Click
aggiungere il codice seguente:
protected void PostCommentButton_Click(object sender, EventArgs e)
{
if (!Page.IsValid)
return;
// Determine the currently logged on user's UserId
MembershipUser currentUser = Membership.GetUser();
Guid currentUserId = (Guid)currentUser.ProviderUserKey;
// Insert a new record into GuestbookComments
string connectionString =
ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
string insertSql = "INSERT INTO GuestbookComments(Subject, Body, UserId) VALUES(@Subject,
@Body, @UserId)";
using (SqlConnection myConnection = new SqlConnection(connectionString))
{
myConnection.Open();
SqlCommand myCommand = new SqlCommand(insertSql, myConnection);
myCommand.Parameters.AddWithValue("@Subject", Subject.Text.Trim());
myCommand.Parameters.AddWithValue("@Body", Body.Text.Trim());
myCommand.Parameters.AddWithValue("@UserId", currentUserId);
myCommand.ExecuteNonQuery();
myConnection.Close();
}
// "Reset" the Subject and Body TextBoxes
Subject.Text = string.Empty;
Body.Text = string.Empty;
}
Il Click
gestore eventi inizia controllando che i dati forniti dall'utente siano validi. In caso contrario, il gestore eventi viene chiuso prima di inserire un record. Supponendo che i dati forniti siano validi, il valore dell'utente UserId
attualmente connesso viene recuperato e archiviato nella currentUserId
variabile locale. Questo valore è necessario perché è necessario specificare un valore quando si inserisce un UserId
record in GuestbookComments
.
In seguito, viene recuperata Web.config
la stringa di connessione per il SecurityTutorials
database e viene specificata l'istruzione INSERT
SQL. Viene quindi creato e aperto un SqlConnection
oggetto. Successivamente, viene costruito un SqlCommand
oggetto e i valori per i parametri usati nella INSERT
query vengono assegnati. L'istruzione INSERT
viene quindi eseguita e la connessione chiusa. Alla fine del gestore eventi, le Subject
proprietà e Body
TextBoxes vengono cancellate in modo che i valori dell'utente non vengano mantenuti Text
nel postback.
Andare avanti e testare questa pagina in un browser. Poiché questa pagina si trova nella Membership
cartella non è accessibile ai visitatori anonimi. Pertanto, sarà necessario eseguire prima l'accesso (se non è già presente). Immettere un valore nelle Subject
caselle di testo e Body
fare clic sul PostCommentButton
pulsante . In questo modo verrà aggiunto un nuovo record a GuestbookComments
. Dopo il postback, il soggetto e il corpo forniti vengono cancellati da TextBoxes.
Dopo aver fatto clic sul PostCommentButton
pulsante non sono presenti commenti visivi che il commento è stato aggiunto al guestbook. È comunque necessario aggiornare questa pagina per visualizzare i commenti del guestbook esistenti, che verranno impostati nel passaggio 5. Al termine, il commento appena aggiunto verrà visualizzato nell'elenco dei commenti, fornendo commenti visivi adeguati. Per ora, verificare che il commento del GuestbookComments
guestbook sia stato salvato esaminando il contenuto della tabella.
La figura 17 mostra il contenuto della GuestbookComments
tabella dopo che sono stati lasciati due commenti.
Figura 17: È possibile visualizzare i commenti del guestbook nella GuestbookComments
tabella (fare clic per visualizzare l'immagine a dimensioni complete)
Nota
Se un utente tenta di inserire un commento guestbook contenente markup potenzialmente pericoloso, ad esempio HTML, ASP.NET genererà un'eccezione HttpRequestValidationException
. Per altre informazioni su questa eccezione, sui motivi per cui viene generata e su come consentire agli utenti di inviare valori potenzialmente pericolosi, vedere il white paper sulla convalida delle richieste.
Passaggio 5: Presentazione dei commenti del guestbook esistente
Oltre a lasciare i commenti, un utente che visita la Guestbook.aspx
pagina deve anche essere in grado di visualizzare i commenti esistenti del guestbook. A tale scopo, aggiungere un controllo ListView denominato CommentList
alla fine della pagina.
Nota
Il controllo ListView è una novità di ASP.NET versione 3.5. È progettato per visualizzare un elenco di elementi in un layout molto personalizzabile e flessibile, ma offre ancora funzionalità di modifica predefinite, inserimento, eliminazione, paging e ordinamento come GridView. Se si usa ASP.NET 2.0, sarà invece necessario usare il controllo DataList o Repeater. Per altre informazioni sull'uso di ListView, vedere l'articolo del blog di Scott Guthrie, The asp:ListView Control e my article, Displaying Data with the ListView Control.For more information on using the ListView, see Scott Guthrie's blog entry, The asp:ListView Control, and my article, Displaying Data with the ListView Control.
Aprire lo smart tag di ListView e, nell'elenco a discesa Scegli origine dati, associare il controllo a una nuova origine dati. Come illustrato nel passaggio 2, verrà avviata la Configurazione guidata origine dati. Selezionare l'icona Database, assegnare un nome a SqlDataSource CommentsDataSource
risultante e fare clic su OK. Selezionare quindi la SecurityTutorialsConnectionString
stringa di connessione dall'elenco a discesa e fare clic su Avanti.
A questo punto nel passaggio 2 sono stati specificati i dati per la query selezionando la UserProfiles
tabella dall'elenco a discesa e selezionando le colonne da restituire (fare riferimento alla figura 9). Questa volta, tuttavia, si vuole creare un'istruzione SQL che estrae non solo i record da GuestbookComments
, ma anche la città principale del commento, la home page, la firma e il nome utente del commento. Selezionare quindi il pulsante di opzione "Specificare un'istruzione SQL personalizzata o una stored procedure" e fare clic su Avanti.
Verrà visualizzata la schermata "Definisci istruzioni personalizzate o stored procedure". Fare clic sul pulsante Generatore query per compilare graficamente la query. Il generatore di query inizia richiedendo di specificare le tabelle da cui eseguire la query. Selezionare le GuestbookComments
tabelle , UserProfiles
e aspnet_Users
e fare clic su OK. Verranno aggiunte tutte e tre le tabelle all'area di progettazione. Poiché sono presenti vincoli di chiave esterna tra le GuestbookComments
tabelle , UserProfiles
e aspnet_Users
, Il generatore di query esegue automaticamente JOIN
queste tabelle.
Tutto ciò che rimane consiste nel specificare le colonne da restituire. GuestbookComments
Nella tabella selezionare le Subject
colonne , Body
e CommentDate
, restituire le HomeTown
colonne , HomepageUrl
e Signature
dalla UserProfiles
tabella e restituire UserName
da aspnet_Users
. Aggiungere anche "ORDER BY CommentDate DESC
" alla fine della SELECT
query in modo che i post più recenti vengano restituiti per primi. Dopo aver eseguito queste selezioni, l'interfaccia di Generatore query dovrebbe essere simile alla schermata nella figura 18.
Figura 18: Query costruita JOIN
nelle GuestbookComments
tabelle , UserProfiles
e aspnet_Users
(fare clic per visualizzare l'immagine a dimensioni intere)
Fare clic su OK per chiudere la finestra Generatore query e tornare alla schermata "Definisci istruzioni personalizzate o stored procedure". Fare clic su Avanti per passare alla schermata "Test query", in cui è possibile visualizzare i risultati della query facendo clic sul pulsante Query di test. Quando si è pronti, fare clic su Fine per completare la procedura guidata Configura origine dati.
Al termine della procedura guidata Configura origine dati nel passaggio 2, la raccolta del Fields
controllo DetailsView associata è stata aggiornata per includere un BoundField per ogni colonna restituita da SelectCommand
. Il controllo ListView, tuttavia, rimane invariato; dobbiamo ancora definirne il layout. Il layout di ListView può essere costruito manualmente tramite il markup dichiarativo o dall'opzione "Configura ListView" nel relativo Smart Tag. In genere preferisco definire il markup manualmente, ma usare qualsiasi metodo sia più naturale per te.
Ho finito di usare il seguente LayoutTemplate
, ItemTemplate
e ItemSeparatorTemplate
per il controllo ListView:
<asp:ListView ID="CommentList" runat="server" DataSourceID="CommentsDataSource">
<LayoutTemplate>
<span ID="itemPlaceholder" runat="server" />
<p>
<asp:DataPager ID="DataPager1" runat="server">
<Fields>
<asp:NextPreviousPagerField ButtonType="Button"
ShowFirstPageButton="True"
ShowLastPageButton="True" />
</Fields>
</asp:DataPager>
</p>
</LayoutTemplate>
<ItemTemplate>
<h4><asp:Label ID="SubjectLabel" runat="server"
Text='<%# Eval("Subject") %>' /></h4>
<asp:Label ID="BodyLabel" runat="server"
Text='<%# Eval("Body").ToString().Replace(Environment.NewLine, "<br />") %>' />
<p>
---<br />
<asp:Label ID="SignatureLabel" Font-Italic="true" runat="server"
Text='<%# Eval("Signature") %>' />
<br />
<br />
My Home Town:
<asp:Label ID="HomeTownLabel" runat="server"
Text='<%# Eval("HomeTown") %>' />
<br />
My Homepage:
<asp:HyperLink ID="HomepageUrlLink" runat="server"
NavigateUrl='<%# Eval("HomepageUrl") %>'
Text='<%# Eval("HomepageUrl") %>' />
</p>
<p align="center">
Posted by
<asp:Label ID="UserNameLabel" runat="server"
Text='<%# Eval("UserName") %>' /> on
<asp:Label ID="CommentDateLabel" runat="server"
Text='<%# Eval("CommentDate") %>' />
</p>
</ItemTemplate>
<ItemSeparatorTemplate>
<hr />
</ItemSeparatorTemplate>
</asp:ListView>
Definisce LayoutTemplate
il markup generato dal controllo , mentre esegue il ItemTemplate
rendering di ogni elemento restituito da SqlDataSource. Il ItemTemplate
markup risultante viene inserito nel LayoutTemplate
controllo del .itemPlaceholder
Oltre a itemPlaceholder
, include LayoutTemplate
un controllo DataPager, che limita listView a visualizzare solo 10 commenti del guestbook per pagina (impostazione predefinita) ed esegue il rendering di un'interfaccia di paging.
Il mio ItemTemplate
visualizza l'oggetto di ogni commento del guestbook in un <h4>
elemento con il corpo situato sotto l'oggetto. Si noti che la sintassi usata per visualizzare il corpo accetta i dati restituiti dall'istruzione Eval("Body")
databinding, li converte in una stringa e sostituisce le interruzioni di riga con l'elemento <br />
. Questa conversione è necessaria per visualizzare le interruzioni di riga immesse durante l'invio del commento poiché lo spazio vuoto viene ignorato da HTML. La firma dell'utente viene visualizzata sotto il corpo in corsivo, seguita dalla città principale dell'utente, da un collegamento alla home page, dalla data e dall'ora in cui è stato effettuato il commento e dal nome utente della persona che ha lasciato il commento.
Dedicare qualche minuto alla visualizzazione della pagina tramite un browser. Verranno visualizzati i commenti aggiunti al guestbook nel passaggio 5 visualizzato qui.
Figura 19: Guestbook.aspx
Visualizza ora i commenti del guestbook (fare clic per visualizzare l'immagine a dimensione intera)
Provare ad aggiungere un nuovo commento al guestbook. Quando si fa clic sul pulsante viene eseguito il PostCommentButton
postback della pagina e il commento viene aggiunto al database, ma il controllo ListView non viene aggiornato per visualizzare il nuovo commento. Questo problema può essere risolto in uno dei due casi seguenti:
- Aggiornamento del
PostCommentButton
gestore eventi delClick
pulsante in modo che richiami il metodo delDataBind()
controllo ListView dopo aver inserito il nuovo commento nel database o - Impostazione della proprietà del
EnableViewState
controllo ListView sufalse
. Questo approccio funziona perché disabilitando lo stato di visualizzazione del controllo, deve essere riassociato ai dati sottostanti in ogni postback.
Il sito Web dell'esercitazione scaricabile da questa esercitazione illustra entrambe le tecniche. La proprietà del EnableViewState
controllo ListView su false
e il codice necessario per riassociare i dati a livello di codice a ListView sono presenti nel Click
gestore eventi, ma viene impostato come commento.
Nota
Attualmente la AdditionalUserInfo.aspx
pagina consente all'utente di visualizzare e modificare le impostazioni di home page, home page e firma. Potrebbe essere utile aggiornare per visualizzare AdditionalUserInfo.aspx
i commenti del guestbook dell'utente connesso. Ovvero, oltre a esaminare e modificare le informazioni, un utente può visitare la AdditionalUserInfo.aspx
pagina per vedere quali commenti del libro guest ha fatto in passato. Lascio questo come esercizio per il lettore interessato.
Passaggio 6: Personalizzazione del controllo CreateUserWizard per includere un'interfaccia per la home town, la home page e la firma
La SELECT
query utilizzata dalla Guestbook.aspx
pagina usa un oggetto INNER JOIN
per combinare i record correlati tra le GuestbookComments
tabelle , UserProfiles
e aspnet_Users
. Se un utente che non dispone di record in UserProfiles
effettua un commento al guestbook, il commento non verrà visualizzato in ListView perché restituisce GuestbookComments
INNER JOIN
solo i record quando sono presenti record corrispondenti in UserProfiles
e aspnet_Users
. E come abbiamo visto nel passaggio 3, se un utente non dispone di un record in UserProfiles
non può visualizzare o modificare le impostazioni nella AdditionalUserInfo.aspx
pagina.
Inutile dire, a causa delle nostre decisioni di progettazione è importante che ogni account utente nel sistema di appartenenza disponga di un record corrispondente nella UserProfiles
tabella. Ciò a cui si vuole aggiungere UserProfiles
un record corrispondente ogni volta che viene creato un nuovo account utente di appartenenza tramite CreateUserWizard.
Come illustrato nell'esercitazione Creazione di account utente, dopo la creazione del nuovo account utente di appartenenza, il controllo CreateUserWizard genera l'eventoCreatedUser
. È possibile creare un gestore eventi per questo evento, ottenere l'UserId per l'utente appena creato e quindi inserire un record nella UserProfiles
tabella con i valori predefiniti per le HomeTown
colonne , HomepageUrl
e Signature
. Per altre informazioni, è possibile richiedere all'utente questi valori personalizzando l'interfaccia del controllo CreateUserWizard per includere caselle di testo aggiuntive.
Si esamini prima di tutto come aggiungere una nuova riga alla UserProfiles
tabella nel CreatedUser
gestore eventi con valori predefiniti. In seguito verrà illustrato come personalizzare l'interfaccia utente del controllo CreateUserWizard per includere campi modulo aggiuntivi per raccogliere la città principale, la home page e la firma del nuovo utente.
Aggiunta di una riga predefinita aUserProfiles
Nell'esercitazione Creazione di account utente è stato aggiunto un controllo CreateUserWizard alla CreatingUserAccounts.aspx
pagina nella Membership
cartella . Per fare in modo che il controllo CreateUserWizard aggiunga un record alla UserProfiles
tabella al momento della creazione dell'account utente, è necessario aggiornare la funzionalità del controllo CreateUserWizard. Anziché apportare queste modifiche alla CreatingUserAccounts.aspx
pagina, aggiungere invece un nuovo controllo CreateUserWizard alla EnhancedCreateUserWizard.aspx
pagina e apportare le modifiche per questa esercitazione.
Aprire la EnhancedCreateUserWizard.aspx
pagina in Visual Studio e trascinare un controllo CreateUserWizard dalla casella degli strumenti nella pagina. Impostare la proprietà del ID
controllo CreateUserWizard su NewUserWizard
. Come illustrato nell'esercitazione Creazione di account utente , l'interfaccia utente predefinita di CreateUserWizard richiede al visitatore le informazioni necessarie. Dopo aver fornito queste informazioni, il controllo crea internamente un nuovo account utente nel framework di appartenenza, senza che sia necessario scrivere una singola riga di codice.
Il controllo CreateUserWizard genera un numero di eventi durante il flusso di lavoro. Dopo che un visitatore ha fornito le informazioni sulla richiesta e ha inviato il modulo, il controllo CreateUserWizard ne genera CreatingUser
inizialmente l'evento. Se si verifica un problema durante il processo di creazione, l'eventoCreateUserError
viene generato, ma se l'utente viene creato correttamente, viene generato l'eventoCreatedUser
. Nell'esercitazione Creazione di account utente è stato creato un gestore eventi per l'evento CreatingUser
per assicurarsi che il nome utente specificato non contenga spazi iniziali o finali e che il nome utente non sia stato visualizzato in nessun punto della password.
Per aggiungere una riga nella UserProfiles
tabella per l'utente appena creato, è necessario creare un gestore eventi per l'evento CreatedUser
. Al momento dell'attivazione dell'evento CreatedUser
, l'account utente è già stato creato nel framework di appartenenza, consentendoci di recuperare il valore UserId dell'account.
Creare un gestore eventi per l'evento NewUserWizard
di CreatedUser
e aggiungere il codice seguente:
protected void NewUserWizard_CreatedUser(object sender, EventArgs e)
{
// Get the UserId of the just-added user
MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
Guid newUserId = (Guid)newUser.ProviderUserKey;
// Insert a new record into UserProfiles
string connectionString =
ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
string insertSql = "INSERT INTO UserProfiles(UserId, HomeTown, HomepageUrl,
Signature) VALUES(@UserId, @HomeTown, @HomepageUrl, @Signature)";
using (SqlConnection myConnection = new SqlConnection(connectionString))
{
myConnection.Open();
SqlCommand myCommand = new SqlCommand(insertSql, myConnection);
myCommand.Parameters.AddWithValue("@UserId", newUserId);
myCommand.Parameters.AddWithValue("@HomeTown", DBNull.Value);
myCommand.Parameters.AddWithValue("@HomepageUrl", DBNull.Value);
myCommand.Parameters.AddWithValue("@Signature", DBNull.Value);
myCommand.ExecuteNonQuery();
myConnection.Close();
}
}
Gli esseri di codice precedenti recuperano l'ID utente dell'account utente appena aggiunto. Questa operazione viene eseguita usando il Membership.GetUser(username)
metodo per restituire informazioni su un determinato utente e quindi usando la proprietà per recuperare il ProviderUserKey
relativo UserId. Il nome utente immesso dall'utente nel controllo CreateUserWizard è disponibile tramite la relativaUserName
proprietà.
Successivamente, la stringa di connessione viene recuperata da Web.config
e viene specificata l'istruzione INSERT
. Vengono create istanze degli oggetti ADO.NET necessari e il comando eseguito. Il codice assegna un'istanza DBNull
ai @HomeTown
parametri , @HomepageUrl
e @Signature
, che ha l'effetto di inserire valori di database NULL
per i HomeTown
campi , HomepageUrl
e Signature
.
Visitare la EnhancedCreateUserWizard.aspx
pagina tramite un browser e creare un nuovo account utente. Dopo aver eseguito questa operazione, tornare a Visual Studio ed esaminare il contenuto delle aspnet_Users
tabelle e UserProfiles
, come illustrato nella figura 12. Verrà visualizzato il nuovo account utente in aspnet_Users
e una riga corrispondente UserProfiles
(con NULL
i valori per HomeTown
, HomepageUrl
e Signature
).
Figura 20: Sono stati aggiunti un nuovo account utente e UserProfiles
un nuovo record (fare clic per visualizzare l'immagine a dimensione intera)
Dopo che il visitatore ha fornito le informazioni sul nuovo account e aver fatto clic sul pulsante "Crea utente", viene creato l'account utente e viene aggiunta una riga alla UserProfiles
tabella. CreateUserWizard visualizza quindi il relativo CompleteWizardStep
, che visualizza un messaggio di operazione riuscita e un pulsante Continua. Facendo clic sul pulsante Continua viene generato un postback, ma non viene eseguita alcuna azione, lasciando l'utente bloccato nella EnhancedCreateUserWizard.aspx
pagina.
È possibile specificare un URL a cui inviare l'utente quando si fa clic sul pulsante Continua tramite la proprietà del ContinueDestinationPageUrl
controllo CreateUserWizard. Impostare la ContinueDestinationPageUrl
proprietà su "~/Membership/AdditionalUserInfo.aspx". In questo modo il nuovo utente passa a AdditionalUserInfo.aspx
, dove può visualizzare e aggiornare le impostazioni.
Personalizzazione dell'interfaccia di CreateUserWizard per richiedere la home town, la home page e la firma del nuovo utente
L'interfaccia predefinita del controllo CreateUserWizard è sufficiente per scenari di creazione di account semplici in cui devono essere raccolte solo le informazioni di base sull'account utente, ad esempio nome utente, password e posta elettronica. Ma cosa succede se volevamo chiedere al visitatore di entrare nella sua città natale, nella home page e nella firma durante la creazione del suo account? È possibile personalizzare l'interfaccia del controllo CreateUserWizard per raccogliere informazioni aggiuntive all'iscrizione e queste informazioni possono essere usate nel CreatedUser
gestore eventi per inserire record aggiuntivi nel database sottostante.
Il controllo CreateUserWizard estende il controllo ASP.NET Procedura guidata, ovvero un controllo che consente a uno sviluppatore di pagine di definire una serie di elementi ordinati WizardSteps
. Il controllo Procedura guidata esegue il rendering del passaggio attivo e fornisce un'interfaccia di navigazione che consente al visitatore di spostarsi attraverso questi passaggi. Il controllo Procedura guidata è ideale per suddividere un'attività lunga in diversi passaggi brevi. Per altre informazioni sul controllo Procedura guidata, vedere Creazione di un'interfaccia utente dettagliata con il controllo procedura guidata ASP.NET 2.0.
Il markup predefinito del controllo CreateUserWizard definisce due WizardSteps
elementi: CreateUserWizardStep
e CompleteWizardStep
.
<asp:CreateUserWizard ID="NewUserWizard" runat="server"
ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
<WizardSteps>
<asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
</asp:CreateUserWizardStep>
<asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
Il primo WizardStep
, CreateUserWizardStep
esegue il rendering dell'interfaccia che richiede nome utente, password, posta elettronica e così via. Dopo che il visitatore ha fornito queste informazioni e ha fatto clic su "Crea utente", viene visualizzata la CompleteWizardStep
, che mostra il messaggio di operazione riuscita e un pulsante Continua.
Per personalizzare l'interfaccia del controllo CreateUserWizard per includere campi modulo aggiuntivi, è possibile:
Creare uno o più nuovi
WizardStep
s per contenere gli elementi aggiuntivi dell'interfaccia utente. Per aggiungere un nuovoWizardStep
oggetto a CreateUserWizard, fare clic sul collegamento "Aggiungi/RimuoviWizardSteps
" dallo smart tag per avviare l'EditorWizardStep
raccolta. Da qui è possibile aggiungere, rimuovere o riordinare i passaggi della procedura guidata. Questo è l'approccio che verrà usato per questa esercitazione.Convertire l'oggetto
CreateUserWizardStep
in un oggetto modificabileWizardStep
. In questo modo vieneCreateUserWizardStep
sostituito con un oggetto equivalenteWizardStep
il cui markup definisce un'interfaccia utente corrispondente aCreateUserWizardStep
quella di . Convertendo inCreateUserWizardStep
unWizardStep
oggetto è possibile riposizionare i controlli o aggiungere altri elementi dell'interfaccia utente a questo passaggio. Per convertire oCreateUserWizardStep
CompleteWizardStep
in un oggetto modificabileWizardStep
, fare clic sul collegamento "Personalizza crea passaggio utente" o "Personalizza passaggio completo" dallo smart tag del controllo.Usare una combinazione delle due opzioni precedenti.
Un aspetto importante da tenere presente è che il controllo CreateUserWizard esegue il processo di creazione dell'account utente quando si fa clic sul pulsante "Crea utente" dall'interno di CreateUserWizardStep
. Non importa se ci sono altri WizardStep
dopo o CreateUserWizardStep
meno.
Quando si aggiunge un oggetto personalizzato WizardStep
al controllo CreateUserWizard per raccogliere input utente aggiuntivo, è possibile posizionare l'oggetto personalizzato WizardStep
prima o dopo .CreateUserWizardStep
Se viene prima di , CreateUserWizardStep
l'input utente aggiuntivo raccolto dall'oggetto personalizzato WizardStep
è disponibile per il CreatedUser
gestore eventi. Tuttavia, se l'oggetto personalizzato WizardStep
viene eseguito dopo CreateUserWizardStep
, entro il momento in cui viene visualizzato il WizardStep
nuovo account utente è già stato creato e l'evento CreatedUser
è già stato generato.
La figura 21 mostra il flusso di lavoro quando l'oggetto aggiunto WizardStep
precede .CreateUserWizardStep
Poiché le informazioni aggiuntive sull'utente sono state raccolte dal momento in cui viene generato l'evento CreatedUser
, è necessario aggiornare il CreatedUser
gestore eventi per recuperare questi input e usarli per i INSERT
valori dei parametri dell'istruzione (anziché DBNull.Value
).
Figura 21: Flusso di lavoro CreateUserWizard quando un'altra WizardStep
precede (CreateUserWizardStep
fare clic per visualizzare l'immagine a dimensione intera)
Se l'oggetto personalizzato WizardStep
viene inserito dopo , CreateUserWizardStep
tuttavia, il processo di creazione dell'account utente viene eseguito prima che l'utente abbia avuto la possibilità di immettere la propria città, home page o firma. In tal caso, queste informazioni aggiuntive devono essere inserite nel database dopo la creazione dell'account utente, come illustrato nella figura 22.
Figura 22: Flusso di lavoro CreateUserWizard quando viene WizardStep
aggiunto dopo (CreateUserWizardStep
fare clic per visualizzare l'immagine a dimensioni intere)
Il flusso di lavoro illustrato nella figura 22 attende l'inserimento di un record nella UserProfiles
tabella fino al completamento del passaggio 2. Se il visitatore chiude il browser dopo il passaggio 1, tuttavia, verrà raggiunto uno stato in cui è stato creato un account utente, ma non è stato aggiunto alcun record a UserProfiles
. Una soluzione alternativa consiste nell'inserire un record con NULL
o valori UserProfiles
predefiniti nel gestore eventi (che viene attivato dopo il passaggio 1) e quindi aggiornare questo record al termine del CreatedUser
passaggio 2. In questo modo si garantisce che venga aggiunto un UserProfiles
record per l'account utente anche se l'utente interrompe il processo di registrazione a metà strada.
Per questa esercitazione verrà creato un nuovo WizardStep
oggetto che si verifica dopo ma CreateUserWizardStep
prima di CompleteWizardStep
. Si otterrà prima di tutto wizardStep sul posto e quindi si esaminerà il codice.
Dallo smart tag del controllo CreateUserWizard selezionare la finestra di dialogo "Aggiungi/Rimuovi WizardStep
" che consente di visualizzare la WizardStep
finestra di dialogo Editor raccolta. Aggiungere un nuovo WizardStep
oggetto , impostandone ID
su UserSettings
, su Title
"Impostazioni" e su StepType
Step
. Posizionarla in modo che venga eseguita dopo l'operazione CreateUserWizardStep
("Iscrizione per il nuovo account") e prima di CompleteWizardStep
("Completa"), come illustrato nella figura 23.
Figura 23: Aggiungere un nuovo WizardStep
al controllo CreateUserWizard (fare clic per visualizzare l'immagine a dimensione intera)
Fare clic su OK per chiudere la WizardStep
finestra di dialogo Editor raccolta. Il nuovo WizardStep
è evidenziato dal markup dichiarativo aggiornato del controllo CreateUserWizard:
<asp:CreateUserWizard ID="NewUserWizard" runat="server"
ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
<WizardSteps>
<asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
</asp:CreateUserWizardStep>
<asp:WizardStep runat="server" ID="UserSettings" StepType="Step"
Title="Your Settings">
</asp:WizardStep>
<asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
Prendere nota del nuovo <asp:WizardStep>
elemento. È necessario aggiungere l'interfaccia utente per raccogliere qui la città principale, la home page e la firma del nuovo utente. È possibile immettere questo contenuto nella sintassi dichiarativa o tramite il Designer. Per usare il Designer, selezionare il passaggio "Impostazioni" dall'elenco a discesa nello Smart Tag per visualizzare il passaggio nella Designer.
Nota
Se si seleziona un'istruzione nell'elenco a discesa di Smart Tag, viene aggiornata la proprietà del ActiveStepIndex
controllo CreateUserWizard, che specifica l'indice del passaggio iniziale. Pertanto, se si usa questo elenco a discesa per modificare il passaggio "Impostazioni" nel Designer, assicurarsi di impostarlo di nuovo su "Iscrizione per il nuovo account" in modo che questo passaggio venga visualizzato quando gli utenti visitano per la prima volta la EnhancedCreateUserWizard.aspx
pagina.
Creare un'interfaccia utente all'interno del passaggio "Impostazioni" che contiene tre controlli TextBox denominati HomeTown
, HomepageUrl
e Signature
. Dopo aver costruito questa interfaccia, il markup dichiarativo di CreateUserWizard dovrebbe essere simile al seguente:
<asp:CreateUserWizard ID="NewUserWizard" runat="server"
ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
<WizardSteps>
<asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
</asp:CreateUserWizardStep>
<asp:WizardStep runat="server" ID="UserSettings" StepType="Step"
Title="Your Settings">
<p>
<b>Home Town:</b><br />
<asp:TextBox ID="HomeTown" runat="server"></asp:TextBox>
</p>
<p>
<b>Homepage URL:</b><br />
<asp:TextBox ID="HomepageUrl" Columns="40" runat="server"></asp:TextBox>
</p>
<p>
<b>Signature:</b><br />
<asp:TextBox ID="Signature" TextMode="MultiLine" Width="95%"
Rows="5" runat="server"></asp:TextBox>
</p>
</asp:WizardStep>
<asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
Andare avanti e visitare questa pagina tramite un browser e creare un nuovo account utente, specificando i valori per la città principale, la home page e la firma. Dopo aver completato l'account CreateUserWizardStep
utente viene creato nel framework di appartenenza e il CreatedUser
gestore eventi viene eseguito, che aggiunge una nuova riga a UserProfiles
, ma con un valore di database NULL
per HomeTown
, HomepageUrl
e Signature
. I valori immessi per la città principale, la home page e la firma non vengono mai utilizzati. Il risultato netto è un nuovo account utente con un UserProfiles
record i cui HomeTown
campi , HomepageUrl
e Signature
devono ancora essere specificati.
È necessario eseguire il codice dopo il passaggio "Impostazioni" che porta alla città principale, all'honepage e ai valori della firma immessi dall'utente e aggiorna il record appropriato UserProfiles
. Ogni volta che l'utente si sposta tra i passaggi di un controllo Procedura guidata, viene generato l'evento della ActiveStepChanged
procedura guidata. È possibile creare un gestore eventi per questo evento e aggiornare la UserProfiles
tabella al termine del passaggio "Impostazioni".
Aggiungere un gestore eventi per l'evento ActiveStepChanged
CreateUserWizard e aggiungere il codice seguente:
protected void NewUserWizard_ActiveStepChanged(object sender, EventArgs e)
{
// Have we JUST reached the Complete step?
if (NewUserWizard.ActiveStep.Title == "Complete")
{
WizardStep UserSettings = NewUserWizard.FindControl("UserSettings") as
WizardStep;
// Programmatically reference the TextBox controls
TextBox HomeTown = UserSettings.FindControl("HomeTown") as TextBox;
TextBox HomepageUrl = UserSettings.FindControl("HomepageUrl") as TextBox;
TextBox Signature = UserSettings.FindControl("Signature") as TextBox;
// Update the UserProfiles record for this user
// Get the UserId of the just-added user
MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
Guid newUserId = (Guid)newUser.ProviderUserKey;
// Insert a new record into UserProfiles
string connectionString =
ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
string updateSql = "UPDATE UserProfiles SET HomeTown = @HomeTown, HomepageUrl
= @HomepageUrl, Signature = @Signature WHERE UserId = @UserId";
using (SqlConnection myConnection = new SqlConnection(connectionString))
{
myConnection.Open();
SqlCommand myCommand = new SqlCommand(updateSql, myConnection);
myCommand.Parameters.AddWithValue("@HomeTown", HomeTown.Text.Trim());
myCommand.Parameters.AddWithValue("@HomepageUrl", HomepageUrl.Text.Trim());
myCommand.Parameters.AddWithValue("@Signature", Signature.Text.Trim());
myCommand.Parameters.AddWithValue("@UserId", newUserId);
myCommand.ExecuteNonQuery();
myConnection.Close();
}
}
}
Il codice precedente inizia determinando se è stato appena raggiunto il passaggio "Completa". Poiché il passaggio "Completa" si verifica immediatamente dopo il passaggio "Impostazioni", quindi quando il visitatore raggiunge il passaggio "Completa", significa che ha appena completato il passaggio "Impostazioni".
In questo caso, è necessario fare riferimento a livello di codice ai controlli TextBox all'interno di UserSettings WizardStep
. Questa operazione viene eseguita usando innanzitutto il FindControl
metodo per fare riferimento UserSettings WizardStep
a livello di codice a , e quindi di nuovo per fare riferimento a TextBoxes dall'interno di WizardStep
. Dopo aver fatto riferimento alle caselle di testo, è possibile eseguire l'istruzione UPDATE
. L'istruzione UPDATE
ha lo stesso numero di parametri dell'istruzione INSERT
nel CreatedUser
gestore eventi, ma in questo caso vengono usati i valori della città principale, della home page e della firma forniti dall'utente.
Con questo gestore eventi sul posto, visitare la EnhancedCreateUserWizard.aspx
pagina tramite un browser e creare un nuovo account utente specificando i valori per la città principale, la home page e la firma. Dopo aver creato il nuovo account, si dovrebbe essere reindirizzati alla AdditionalUserInfo.aspx
pagina, in cui vengono visualizzate le informazioni sulla città principale, sulla home page e sulla firma appena immesse.
Nota
Il nostro sito Web ha attualmente due pagine da cui un visitatore può creare un nuovo account: CreatingUserAccounts.aspx
e EnhancedCreateUserWizard.aspx
. La pagina di accesso e la mappa del sito Web puntano alla CreatingUserAccounts.aspx
pagina, ma la CreatingUserAccounts.aspx
pagina non richiede all'utente le informazioni relative alla città principale, alla home page e alla firma e non aggiunge una riga corrispondente a UserProfiles
. Pertanto, aggiornare la CreatingUserAccounts.aspx
pagina in modo che offra questa funzionalità o aggiornare la pagina della mappa del sito e di accesso in modo che faccia riferimento EnhancedCreateUserWizard.aspx
invece di CreatingUserAccounts.aspx
. Se si sceglie quest'ultima opzione, assicurarsi di aggiornare il Membership
file della Web.config
cartella in modo da consentire agli utenti anonimi di accedere alla EnhancedCreateUserWizard.aspx
pagina.
Riepilogo
In questa esercitazione sono stati esaminati le tecniche per la modellazione dei dati correlati agli account utente all'interno del framework di appartenenza. In particolare, sono stati esaminati i modelli di entità che condividono una relazione uno-a-molti con gli account utente, nonché i dati che condividono una relazione uno-a-uno. Inoltre, è stato illustrato come visualizzare, inserire e aggiornare queste informazioni correlate, con alcuni esempi che usano il controllo SqlDataSource e altri usando ADO.NET codice.
Questa esercitazione completa l'analisi degli account utente. A partire dall'esercitazione successiva verrà rivolta l'attenzione ai ruoli. Nelle prossime esercitazioni verrà esaminato il framework Ruoli, verrà illustrato come creare nuovi ruoli, come assegnare ruoli agli utenti, come determinare i ruoli a cui appartiene un utente e come applicare l'autorizzazione basata sui ruoli.
Buon programmatori!
Altre informazioni
Per altre informazioni sugli argomenti descritti in questa esercitazione, vedere le risorse seguenti:
- Accesso e aggiornamento dei dati in ASP.NET 2.0
- Controllo procedura guidata ASP.NET 2.0
- Creazione di un'interfaccia utente dettagliata con il controllo della procedura guidata ASP.NET 2.0
- Creazione di parametri di controllo DataSource personalizzati
- Personalizzazione del controllo CreateUserWizard
- Guide introduttive ai controlli DetailsView
- Visualizzazione dei dati con il controllo ListView
- Analisi dei controlli di convalida in ASP.NET 2.0
- Modifica di inserimento ed eliminazione di dati
- Convalida dei moduli in ASP.NET
- Raccolta di informazioni di registrazione utente personalizzate
- Profili in ASP.NET 2.0
- Controllo asp:ListView
- Guida introduttiva ai profili utente
Informazioni sull'autore
Scott Mitchell, autore di più libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, lavora 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. Scott può essere raggiunto all'indirizzo mitchell@4guysfromrolla.com o tramite il suo blog all'indirizzo http://ScottOnWriting.NET.
Grazie speciale...
Questa serie di esercitazioni è stata esaminata da molti revisori utili. Si è interessati a esaminare i prossimi articoli MSDN? In tal caso, rilasciami una riga in mitchell@4GuysFromRolla.com.