Tutorial ASP.NET 2.0 - Parte quinta: modificare dati
a cura di Antonio Catucci e Mario De Ghetto (requisiti: conoscenza di ADO.Net e SQL)Premessa
Nel precedente articolo abbiamo visto come è semplice e veloce modificare dei dati con i nuovi controlli data di ASP.NET 2.0 come la GridView.
Per quanto potente, però, la GridView non sempre si presta ai nostri scopi. Per esempio, è poco usabile quando i campi da modificare sono molti come nel caso di un'anagrafica clienti.
Per venire incontro a questa esigenza sono stati introdotti due nuovi controlli, simili alla GridView per funzionalità, ma diversi nell'aspetto: la DetailsView e la FormView.Nota: si invita a scaricare nuovamente il codice sorgente a corredo dell'ultimo articolo, dato che sono state corrette alcune imperfezioni.
Il controllo DetailsView
La DetailsView è un controllo data che visualizza i dati "a record", anziché in maniera tabellare. Per capirci, i dati vengono visualizzati a schede in maniera del tutto simile a una form, fornendo molte delle funzionalità della GridView, come la paginazione e l'editing in line automatico.Vediamo come utilizzare questo controllo per modificare l'anagrafica di un cliente.
Creiamo una nuova pagina chiamata DetailsView.aspx nella nuova cartella "5_DetailsView" e inseriamo un controllo ObjectDataSource e una DetailsView.
Configuriamo l'ObjectDataSource come segue:<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" TypeName="VBTT.Tutorial.BLL.Customer" SelectMethod="GetCustomers" UpdateMethod="UpdateCustomer" </asp:ObjectDataSource>Ora agganciamo la DetailsView all'ObjectDataSource mediante il designer raggiungibile con lo SmartTag e selezioniamo l'opzione "Enable Paging":
Il markup generato è il seguente:
<asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" AutoGenerateRows="False" DataSourceID="odsCustomers" Height="50px"> <Fields> <asp:BoundField DataField="CustomerID" HeaderText="CustomerID" SortExpression="CustomerID" /> <asp:BoundField DataField="CompanyName" HeaderText="Company Name" SortExpression="CompanyName" /> <asp:BoundField DataField="ContactName" HeaderText="Contact Name" SortExpression="ContactName" /> <asp:BoundField DataField="ContactTitle" HeaderText="Contact Title" SortExpression="ContactTitle" /> <asp:BoundField DataField="Address" HeaderText="Address" SortExpression="Address" /> <asp:BoundField DataField="City" HeaderText="City" SortExpression="City" /> <asp:BoundField DataField="PostalCode" HeaderText="Postal Code" SortExpression="PostalCode" /> <asp:BoundField DataField="Region" HeaderText="Region" SortExpression="Region" /> <asp:BoundField DataField="Country" HeaderText="Country" SortExpression="Country" /> <asp:BoundField DataField="Phone" HeaderText="Phone" SortExpression="Phone" /> <asp:BoundField DataField="Fax" HeaderText="Fax" SortExpression="Fax" /> </Fields> </asp:DetailsView>A parte le proprietà generali del controllo, la parte più importante è l'elenco dei campi da visualizzare racchiusi tra i tag <Fields>.
La definizione di ciascun campo è del tutto simile a quella vista per la GridView, con il nome della proprietà dell'oggetto (DataField) ed il nome dell'etichetta (HeaderText).
Eseguendo la pagina otteniamo questo risultato:
Con pochi clic abbiamo ottenuto una pagina che ci permette di "sfogliare" l'anagrafica dei clienti in maniera del tutto simile ad un'applicazione WinForm.
Naturalmente è possibile personalizzare il layout grafico del controllo utilizzando i temi. Ad esempio questo è lo skin per il tema Default che aggiungiamo al file default.skin:<asp:DetailsView runat="server"> <HeaderStyle CssClass="gridHeader" /> <FieldHeaderStyle Font-Bold="true" Width="150px" BackColor="#dce4f9" /> <RowStyle Height="20px" /> </asp:DetailsView>Modificare i dati
La DetailsView non offre solo la possibilità di visualizzare i dati a "Scheda", ma offre anche il supporto per la modifica aggiungendo un elemento <CommandField>.Impostiamo la proprietà ShowEditButton a True, così da visualizzare un pulsante di Edit che gestisce l'operazione di "editing in line" del record corrente, esattamente come avviene per la GridView. Inoltre aggiungiamo la proprietà DataKeyNames valorizzandola con la proprietà CustomerID:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="CustomerID" AllowPaging="True" DataSourceID="odsCustomers" HeaderText="Scheda Cliente" Height="50px" Width="400px" GridLines="Horizontal"> <Fields> <asp:CommandField ShowEditButton="True" /> <%--...--%> </Fields> </asp:DetailsView>Un'altra operazione da fare è quella di definire il campo CustomerID a sola lettura impostandone la proprietà ReadOnly a True perché, come spiegato nell'articolo precedente, questo campo è a sola lettura nella classe Customer.
Eseguendo la pagina DetailsView.aspx, oltre a vedere applicato il layout definito del tema, vediamo il link "Modifica" in alto che, se premuto, trasforma tutti i campi (ad eccezione di CustomerID) in TextBox, per consentirne la modifica, e aggiunge i pulsanti per la conferma o l'annullo dell'operazione:
Se proviamo a confermare la modifica otteniamo lo stesso errore che abbiamo ottenuto nel precedente articolo, dovuto al fatto che non è possibile valorizzare la proprietà CustomerID.
La soluzione implementata in quel contesto è stata la creazione di un overload per il metodo UpdateCustomer() della classe Customer con l'utilizzo dei soli valori gestiti nella GridView. Nel nostro caso, però, abbiamo bisogno di modificare tutti i campi dell'oggetto Customer e non avrebbe molto senso aggiungere un ulteriore overload con tutti i i campi da aggiornare come parametri (benchè la cosa sia lecita).
La soluzione che adotteremo in questo caso non riguarderà, dunque, il Business Layer, ma la pagina stessa, sfruttando gli eventi messi a disposizione dal controllo.Gli eventi del DetailsView
La DetailsView, come anche gli altri controlli data, espone numerosi eventi che permettono un controllo quasi totale dell'intero processo di modifica dei dati.
I più interessanti per noi sono:
ItemUpdating ItemInserting ItemDeleting ItemUpdated ItemInserted ItemDeletedCome si può vedere, esiste una coppia di eventi per ciascuna operazione possibile, ovvero Inserimento, Modifica e Cancellazione di un record. Ogni coppia si riferisce al momento immediatamente precedente (Xxxxing) ed immediatamente dopo (Xxxxed) l'operazione.
Nel nostro esempio, l'evento utile al nostro scopo è ItemUpdating, che viene scatenato prima di passare il controllo all'ObjectDataSource per l'aggiornamento effettivo dei dati. Infatti, è in questo momento che la DetailsView prepara l'oggetto Customer da passare all'ObjectDataSource con i dati inseriti dall'utente e, dato che non vogliamo che ciò avvenga, saremo noi a fare l'aggiornamento:
Protected Sub dvCustomer_ItemUpdating(ByVal sender As Object, _ ByVal e As DetailsViewUpdateEventArgs) _ Handles dvCustomer.ItemUpdating ' Carica il cliente specificato dalla chiave corrente Dim Cust As Customer = Customer.GetCustomer(e.Keys("CustomerID")) With Cust .CompanyName = e.NewValues("CompanyName") .ContactTitle = e.NewValues("ContactTitle") .ContactName = e.NewValues("ContactName") .Address = e.NewValues("Address") .City = e.NewValues("City") .PostalCode = e.NewValues("PostalCode") .Country = e.NewValues("Country") .Region = e.NewValues("Region") .Fax = e.NewValues("Fax") .Update() End With ' Annulla l'operazione di modifica evitando di chiamare l'ObjectDataSource e.Cancel = True dvCustomer.ChangeMode(DetailsViewMode.ReadOnly) End SubIl codice è molto semplice. Viene recuperato il cliente corrente attraverso la chiave del record corrente memorizzata nell'array Keys(), poi si aggiorna l'oggetto con i valori inseriti dall'utente che troviamo nella collection NewValues() (di tipo Dictionary) e infine si salvano le modifiche invocando il metodo Update().
Le ultime due istruzioni sono importanti:
- e.Cancel = True:
interrompe il processo corrente evitando così che venga richiamato l'ObjectDataSource per la modifica del database;- dvCustomer.ChangeMode(DetailsViewMode.ReadOnly):
cambia la modalità di visualizzazione corrente (EditMode) in ReadOnly, cioè sola lettura. L'uso in questo contesto è obbligato, perché interrompendo l'operazione con l'istruzione precedente il controllo rimane in modalità di edit.Naturalmente, così facendo, non è più necessario mappare il metodo UpdateCustomer nell'ObjectDataSource, visto che viene fatto tutto manualmente nell'evento ItemUpdating.
Inserimento di un nuovo record
Vediamo come inserire un nuovo record nella tabella Customer. L'oggetto Customer espone il metodo InsertCustomer(), che accetta l'elenco dei campi da inserire nella tabella, quindi occorre modificare l'ObjectDataSource nel modo seguente:<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" TypeName="VBTT.Tutorial.BLL.Customer" SelectMethod="GetCustomers" OldValuesParameterFormatString="original_{0}" InsertMethod="InsertCustomer"> <InsertParameters> <asp:Parameter Name="companyName" Type="String" /> <asp:Parameter Name="contactName" Type="String" /> <asp:Parameter Name="contactTitle" Type="String" /> <asp:Parameter Name="address" Type="String" /> <asp:Parameter Name="city" Type="String" /> <asp:Parameter Name="country" Type="String" /> <asp:Parameter Name="region" Type="String" /> <asp:Parameter Name="postalCode" Type="String" /> <asp:Parameter Name="phone" Type="String" /> <asp:Parameter Name="fax" Type="String" /> </InsertParameters> </asp:ObjectDataSource>Poi è sufficiente visualizzare il pulsante per l'inserimento di un nuovo record impostando la proprietà ShowInsertButton a True all'interno del tag <CommandField>. La gestione è del tutto simile alla modifica.
Validazione dell'input
Sebbene l'operazione di inserimento sia molto semplice, esiste un problema: la validazione dei dati. E' buona norma, infatti, che durante l'inserimento dati da parte dell'utente venga sempre fatto un controllo sulla validità, per evitare che vengano immessi dati non corretti e, soprattutto, per evitare attacchi di SQL injection molto diffusi in applicazioni web che accedono a database. Vediamo, dunque, come è possibile validare i dati prima che questi vengano salvati.
Nel nostro esempio, l'inserimento di un nuovo cliente deve soddisfare questi requisiti:
- specificare un codice alfanumerico univoco di lunghezza massima di cinque caratteri;
- specificare il CompanyName
Come per la modifica, possiamo utilizzare uno degli eventi del controllo scatenati durante l'operazione di inserimento, ItemInserting, che viene sollevato prima dell'invio dei dati al database.
Implementiamo il seguente codice di controllo:Protected Sub dvCustomer_ItemInserting(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls.DetailsViewInsertEventArgs) _ Handles dvCustomer.ItemInserting If String.IsNullOrEmpty(e.Values("CustomerID")) Then ShowMessage("Specificare un codice cliente!") e.Cancel = True Return End If If e.Values("CustomerID").Length > 5 Then ShowMessage("Specificare un codice non più lungo di 5 caratteri!") e.Cancel = True Return End If If String.IsNullOrEmpty(e.Values("CompanyName")) Then ShowMessage("Specificare il nome del cliente!") e.Cancel = True Return End If End SubL'evento espone una collection contenente tutti i valori dei campi definiti nel controllo che l'utente ha inserito e noi utilizzeremo proprio questi valori per verificare che sia il CustomerID che il CompanyName vengano inseriti. In caso contrario viene bloccata l'operazione impostando la proprietà Cancel a True e visualizzando un messaggio di errore con la funzione ShowMessage().
Esiste, però, un altro modo per validare i dati.
Validare l'input con i Validators Control
L'uso dei validator per il controllo dei dati è molto comune in applicazioni ASP.NET e possono tornare utili anche nel nostro caso. Ma come possiamo utilizzarli, se non abbiamo un accesso diretto ai controlli contenenti i dati (i controlli TextBox per intenderci)?La soluzione è molto semplice: i TemplateField. I controlli data, infatti, consentono la personalizzazione del layout mediante l'utilizzo di Template che possono essere applicati all'intero controllo oppure a parti di esso. La DetailsView permette di trasformare ciascun campo visualizzato in template in maniera molto semplice.
Attiviamo il designer del controllo attraverso lo SmartTag selezionando Edit Fields... Ora selezioniamo il campo da trasformare in template dall'elenco SelectedField e selezioniamo l'opzione "Convert this field into a TemplateField" come mostrato nella figura seguente:
Premendo OK il markup del nostro controllo diventa come segue:
<asp:DetailsView ID="DetailsView1" runat="server"> <Fields> <asp:CommandField ShowEditButton="True" ShowInsertButton="True" /> <asp:TemplateField HeaderText="CustomerID" SortExpression="CustomerID"> <EditItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Eval("CustomerID") %>'/> </EditItemTemplate> <InsertItemTemplate> <asp:TextBox ID="txtCustomerId" runat="server" Text='<%# Bind("CustomerID") %>'/> </InsertItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("CustomerID") %>'/> </ItemTemplate> </asp:TemplateField> <%--[altri campi omessi]--%> </Fields> </asp:DetailsView>La colonna relativa al campo CustomeID è stata trasformata in un <TemplateField> con all'interno tre template diversi, uno per ciascuno stato gestito, ovvero EditItemTemplate, InsertItemTemplate e ItemTemplate rispettivamente per la modifica, l'inserimento e la visualizzazione. In ciascun template possiamo inserire tutto quello che vogliamo!
Di default, Visual Studio inserisce un controllo idoneo in base al tipo di dato ed allo stato da gestire. Infatti nell'InsertItemTemplate è stata inserita una TextBox, mentre negli altri una Label (ricordate che CustomerID non è modificabile?).
Una piccola spiegazione merita il codice in-line inserito da Visual Studio. Si tratta di due espressioni di binding molto comuni quando si utilizzano controlli collegati a dati e sono contraddistinte dal fatto che sono sempre incluse tra i tag <%# ... %>.
Il metodo Eval() esiste sin dalla versione 1.0 di ASP.NET (prima della 2.0 era conosciuta come DataBinder.Eval()) mentre Bind() è stato introdotto con la 2.0. Entrambi hanno la funzione di estrarre il valore della proprietà specificata dal DataSource sottostante durante un'operazione di binding.
La differenza tra le due funzioni è che, mentre Eval() può solo leggere un valore Bind() può anche modificarlo; cioè è in grado di aggiornare la proprietà a cui si riferisce con il valore specificato nel controllo in cui è utilizzato.
Nel nostro esempio Bind() è utilizzato nel template di inserimento di un nuovo record, consentendo di inserire il valore della TextBox in una collection quando la TextBox viene letta.Trasformiamo in TemplateField anche il campo CompanyName.
A questo punto possiamo inserire un controllo validator di tipo RequireFieldValidator nell'InsertItemTemplate per entrambi i campi template creati:<InsertItemTemplate> <asp:TextBox ID="txtCustomerId" runat="server" Text='<%# Bind("CustomerID") %>'></asp:TextBox> <asp:RequiredFieldValidator ID="rfvCustomerID" runat="server" ControlToValidate="txtCustomerId" ErrorMessage="Specificare il codice cliente!" Display="None" SetFocusOnError="true" Text="*" /> </InsertItemTemplate> <InsertItemTemplate> <asp:TextBox ID="txtCompanyName" runat="server" Text='<%# Bind("CompanyName") %>'></asp:TextBox> <asp:RequiredFieldValidator ID="rfvCompanyName" runat="server" ControlToValidate="txtCompanyName" ErrorMessage="Specificare il nome del cliente!" Display="None" SetFocusOnError="true" Text="*" /> </InsertItemTemplate>La proprietà Display è impostata su None, perché utilizzeremo il controllo ValidationSummary per mostrare tutti i messaggi di errore in un punto della pagina, anziché a fianco al controllo TextBox, dunque inseriamo un controllo ValidationSummary subito dopo il titolo nella pagina .aspx:
<h2>Anagrafico Clienti</h2> <%--[...]--%> <asp:ValidationSummary ID="vErrors" DisplayMode="BulletList" ShowSummary="true" runat="server" />Se proviamo ad inserire un nuovo cliente senza specificare il codice o il nome, avremo una situazione come questa:
La differenza, rispetto al metodo visto con l'uso dell'evento ItemInserting, è che in quest'ultimo caso non c'è postback per la validazione. L'uso dei validator è molto comodo soprattutto per le potenzialità offerte, ma questo non deve sollevarci dal controllare i dati anche lato server perché, lavorando solo lato client, i validator possono essere manipolati, annullandone l'efficacia. Per questo motivo noi lasceremo entrambi i controlli.
Cancellazione di un record
Concludiamo l'articolo aggiungendo il supporto per la cancellazione di una scheda cliente.
L'operazione da fare è simile a quella vista per l'abilitazione dell'editing, cioè basta spuntare l'opzione Enable Deleting nello SmartTag del controllo per visualizzare il pulsante di Delete.
Se vogliamo aggiungere un messaggio di conferma prima della cancellazione del record dobbiamo convertire anche il <CommandField> in un TemplateField ottendo il seguente codice di markup:<asp:TemplateField ShowHeader="False"> <EditItemTemplate> <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True" CommandName="Update" Text="Salva"/> <asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False" CommandName="Cancel" Text="Annulla" /> </EditItemTemplate> <InsertItemTemplate> <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True" CommandName="Update" Text="Nuovo"/> <asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False" CommandName="Cancel" Text="Annulla"/> </InsertItemTemplate> <ItemTemplate> <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False" CommandName="Edit" Text="Modifica"/> <asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False" CommandName="New" Text="Nuovo"/> <asp:LinkButton ID="LinkButton3" runat="server" CausesValidation="False" CommandName="Delete" Text="Elimina" OnClientClick="javascript:return confirm('Cancellare il cliente?');"/> </ItemTemplate> </asp:TemplateField>Notate il codice javascript inserito nel LinkButton3 Delete posto nell'ItemTemplate, che visualizza una messagebox di conferma quando l'utente clicca sul link. Negli altri due stati (modifica ed inserimento) il comando non esiste, ovviamente.
Conclusioni
Abbiamo visto come il nuovo controllo DetailsView rende molto più semplice la visualizzazione in stile "Form" dei dati senza dover scrivere quel noioso codice per riempire i controlli TextBox con i dati letti dal database e viceversa, in quanto viene gestito tutto automaticamente dal controllo stesso.
Tutto il codice relativo a questo esempio è scaricabile dall'area download.
In merito a questo articolo, potete scrivere agli autori Antonio Catucci e Mario De Ghetto.