9. Componenti server ASP - 3
9.1. Introduction
Continuiamo il nostro lavoro sull’interfaccia utente approfondendo le funzionalità dei componenti [DataList] e [DataGrid], in particolare per quanto riguarda l’aggiornamento dei dati che visualizzano
9.2. Gestione degli eventi associati ai dati dei componenti con collegamento dati
9.2.1. L'esempio
Consideriamo la seguente pagina:
![]() |
La pagina comprende tre componenti associati a un elenco di dati:
- un componente [DataList] denominato [DataList1]
- un componente [DataGrid] denominato [DataGrid1]
- un componente [Repeater] denominato [Repeater1]
L'elenco di dati associato è la tabella {"zero", "uno", "due", "tre"}. A ciascuno di questi dati è associato un gruppo di due pulsanti denominati [Infos1] e [Infos2]. L'utente fa clic su uno dei pulsanti e viene visualizzato un testo che riporta il nome del pulsante su cui ha fatto clic. In questa sede si intende illustrare come gestire un elenco di pulsanti o di link.
9.2.2. Configurazione dei componenti
Il componente [DataList1] è configurato come segue:
<asp:datalist id="DataList1" ... runat="server">
<SelectedItemStyle ...</SelectedItemStyle>
<HeaderTemplate>
[début]
</HeaderTemplate>
<FooterTemplate>
[fin]
</FooterTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<P><%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
<FooterStyle ...></FooterStyle>
<HeaderStyle ...></HeaderStyle>
</asp:datalist>
Abbiamo tralasciato tutto ciò che riguardava l'aspetto del [DataList] per concentrarci esclusivamente sul suo contenuto:
- la sezione <HeaderTemplate> definisce l'intestazione del [DataList] e la sezione <FooterTemplate> il suo piè di pagina.
- la sezione <ItemTemplate> è il modello di visualizzazione utilizzato per ciascuno dei dati dell’elenco associato. In essa si trovano i seguenti elementi:
- il valore del dato corrente dell’elenco dei dati associato al componente: <%# Container.DataItem %>
- due pulsanti denominati rispettivamente [Infos1] e [Infos2]. La classe [Button] ha un attributo [CommandName] che viene utilizzato in questo contesto. Ci consentirà di determinare quale pulsante sia all’origine di un evento nel [DataList]. Per gestire il clic sui pulsanti, avremo un unico gestore di eventi che sarà collegato al [DataList] stesso e non ai pulsanti. Questo gestore riceverà un'informazione che gli indicherà su quale riga del [DataList] si è verificato il clic. L'attributo [CommandName] ci permetterà di sapere su quale pulsante della riga si è verificato.
Il componente [Repeater1] è configurato in modo molto simile:
<asp:repeater id="Repeater1" runat="server">
<HeaderTemplate>
[début]<hr />
</HeaderTemplate>
<FooterTemplate>
<hr />
[fin]
</FooterTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
<ItemTemplate>
<%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
</asp:repeater>
È stata semplicemente aggiunta una sezione <SeparatorTemplate> affinché i dati successivi visualizzati dal componente siano separati da una barra orizzontale.
Infine, il componente [DataGrid1] è configurato come segue:
<asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
<SelectedItemStyle ...></SelectedItemStyle>
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ....></FooterStyle>
<Columns>
<asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1">
</asp:ButtonColumn>
<asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
</asp:ButtonColumn>
</Columns>
<PagerStyle .... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
Anche in questo caso abbiamo omesso le informazioni relative allo stile (colori, larghezze, ecc.). Ci troviamo nella modalità di generazione automatica delle colonne, che è la modalità predefinita di [DataGrid]. Ciò significa che ci saranno tante colonne quante ce ne sono nel source di dati. In questo caso, ce n’è una. Abbiamo aggiunto altre due colonne contrassegnate da <asp:ButtonColumn>. In esse definiamo informazioni analoghe a quelle definite per gli altri due componenti, oltre al tipo di pulsante, in questo caso [PushButton]. Il tipo predefinito è [LinkButton], c.a.d. un link. Inoltre, i dati saranno impaginati [AllowPaging=true] con una dimensione di pagina pari a due elementi [PageSize=2].
9.2.3. Il codice di presentazione della pagina
Il codice di presentazione della nostra pagina di esempio è stato inserito in un file [main.aspx]:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" autoeventwireup="false" %>
<HTML>
<HEAD>
</HEAD>
<body>
<form runat="server">
<P>Gestion d'événements de composants associés à des listes de données</P>
<HR width="100%" SIZE="1">
<table cellSpacing="1" cellPadding="1" bgColor="#ffcc00" border="1">
<tr>
<td ...>DataList</td>
<td ...>DataGrid</td>
<td ...>Repeater</td>
</tr>
<tr>
<td ...>
<asp:datalist id="DataList1" ... runat="server">
<HeaderTemplate>
[début]
</HeaderTemplate>
<FooterTemplate>
[fin]
</FooterTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<P><%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
<FooterStyle ...></FooterStyle>
<HeaderStyle ....></HeaderStyle>
</asp:datalist>
<P></P>
</td>
<td ...>
<asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1">
</asp:ButtonColumn>
<asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
</asp:ButtonColumn>
</Columns>
<PagerStyle ... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
</td>
<td ...>
<asp:repeater id="Repeater1" runat="server">
<HeaderTemplate>
[début]<hr />
</HeaderTemplate>
<FooterTemplate>
<hr />
[fin]
</FooterTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
<ItemTemplate>
<%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
</asp:repeater>
</td>
</tr>
</table>
<P><asp:label id="lblInfo" runat="server"></asp:label></P>
<P></P>
</form>
</body>
</HTML>
Nel codice sopra riportato abbiamo omesso il codice di formattazione (colori, linee, dimensioni, ecc.)
9.2.4. Il codice di controllo della pagina
Il codice di controllo dell'applicazione è stato inserito nel file [main.aspx.vb]:
Public Class main
Inherits System.Web.UI.Page
' componenti della pagina
Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Repeater1 As System.Web.UI.WebControls.Repeater
' la fonte dati
Protected textes() As String = {"zéro", "un", "deux", "trois"}
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Not IsPostBack Then
'collegamenti con la fonte dati
DataList1.DataSource = textes
DataGrid1.DataSource = textes
Repeater1.DataSource = textes
Page.DataBind()
End If
End Sub
Private Sub DataList1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles DataList1.ItemCommand
' si è verificato un evento su una delle righe di [datalist]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [DataList]"
End Sub
Private Sub Repeater1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs) Handles Repeater1.ItemCommand
' si è verificato un evento su una delle righe del [repeater]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [Repeater]"
End Sub
Private Sub DataGrid1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.ItemCommand
' Si è verificato un evento su una delle righe del [datagrid]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] de la page [" + DataGrid1.CurrentPageIndex.ToString() + "] du composant [DataGrid]"
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' cambio di pagina
With DataGrid1
.CurrentPageIndex = e.NewPageIndex
.DataSource = textes
.DataBind()
End With
End Sub
End Class
Commenti:
- la fonte dati [textes] è una semplice tabella di stringhe di caratteri. Verrà collegata ai tre componenti presenti nella pagina
- tale collegamento viene effettuato nella procedura [Page_Load] al momento della prima richiesta. Per le richieste successive, i tre componenti recupereranno i propri valori tramite il meccanismo di [VIEW_STATE].
- I tre componenti dispongono di un gestore per l’evento [ItemCommand]. Questo evento si verifica quando si fa clic su un pulsante o su un link in una delle righe del componente. Il gestore riceve due informazioni:
- fonte: il riferimento all’oggetto (pulsante o link) all’origine dell’evento
- a: informazioni sull’evento di tipo [DataListCommandEventArgs], [RepeaterCommandEventArgs], [DataGridCommandEventArgs] a seconda dei casi. L’argomento a porta con sé diverse informazioni. In questo caso, due di esse ci interessano:
- a.Item: rappresenta la riga in cui si è verificato l’evento, di tipo [DataListItem], [DataGridItem] o [RepeaterItem]. Indipendentemente dal tipo esatto, l’elemento [Item] possiede un attributo [ItemIndex] che indica il numero della riga [Item] del contenitore a cui appartiene. Qui visualizziamo questo numero di riga
- a.CommandName: è l’attributo [CommandName] del pulsante (Button, LinkButton, ImageButton) all’origine dell’evento. Questa informazione, unita alla precedente, ci permette di sapere quale pulsante del contenitore è all’origine dell’evento [ItemCommand]
9.3. Applicazione - gestione di un elenco di abbonamenti
Ora che sappiamo come intercettare gli eventi che si verificano all’interno di un contenitore di dati, presentiamo un esempio che mostra come gestirli.
9.3.1. Introduzione
L’applicazione presentata simula un’applicazione per l’iscrizione a mailing list. Queste sono definite da un oggetto [DataTable] a tre colonne:
nome | tipo | ruolo |
stringa | chiave primaria | |
stringa | nome del tema dell'elenco | |
stringa | una descrizione degli argomenti trattati dalla lista |
Per non appesantire il nostro esempio, l'oggetto [DataTable] sopra riportato verrà generato in modo arbitrario tramite codice. In un'applicazione reale, verrebbe probabilmente fornito da un metodo di una classe di accesso ai dati. La creazione della tabella delle liste avverrà nella procedura [Application_Start] e la tabella risultante verrà inserita nell'applicazione. La chiameremo tabella [dtThèmes]. L’utente si abbonerà ad alcuni dei temi di questa tabella. L’elenco dei suoi abbonamenti sarà conservato, anche in questo caso, in un oggetto [DataTable] denominato [dtAbonnements], la cui struttura sarà la seguente:
nome | tipo | ruolo |
stringa | chiave primaria | |
stringa | nome del tema dell'elenco |
L'applicazione a pagina singola è la seguente:
![]() |
n. | nome | tipo | proprietà | ruolo |
DataGrid | liste di distribuzione disponibili per l'iscrizione | |||
DataList | elenco delle iscrizioni dell'utente alle liste precedenti | |||
pannello | pannello informativo sull'argomento selezionato dall'utente con un link [Plus d'informations] | |||
Etichetta | fa parte di [panelInfos] | nome del tema | ||
Etichetta | fa parte di [panelInfos] | Descrizione del tema | ||
Etichetta | Messaggio informativo dell'applicazione |
Il nostro esempio mira a illustrare l'utilizzo dei componenti [DataGrid] e [DataList], in particolare la gestione degli eventi che si verificano a livello delle righe di questi contenitori di dati. Pertanto, l’applicazione non dispone di un pulsante di conferma che memorizzi, ad esempio, in un database le scelte effettuate dall’utente. Tuttavia, è realistica. Siamo vicini a un’applicazione di e-commerce in cui un utente aggiungerebbe prodotti (abbonamenti) al proprio carrello.
9.3.2. Funzionamento
La prima schermata che vede l’utente è la seguente:
![]() |
L’utente clicca sui link [Plus d'informations] per ottenere informazioni su un argomento. Queste vengono visualizzate in [panelInfos]:

Clicca sui link [S'abonner] per iscriversi a un argomento. Gli argomenti scelti vengono inseriti nel componente [dlAbonnements]:

L'utente potrebbe voler sottoscrivere un elenco a cui è già iscritto. Un messaggio lo avvisa:

Infine, può annullare l’iscrizione a uno dei temi utilizzando i pulsanti [Retirer] sopra indicati. Non viene richiesta alcuna conferma. In questo caso non è necessaria, poiché l’utente può riiscriversi facilmente. Ecco la schermata dopo l’annullamento dell’iscrizione a [thème1]:

9.3.3. Configurazione dei contenitori di dati
Il componente [dgThèmes] di tipo [DataGrid] è collegato a una fonte di tipo [DataTable]. La sua formattazione è stata effettuata tramite il collegamento [Mise en forme automatique] nel pannello delle proprietà del componente [DataGrid]. La definizione delle sue proprietà è stata invece effettuata tramite il collegamento [Générateur de proprités] dello stesso pannello. Il codice generato è il seguente (il codice di formattazione è omesso):
<asp:datagrid id="dgThèmes" AutoGenerateColumns="False" AllowPaging="True" PageSize="5"
runat="server">
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="thème" HeaderText="Thème"></asp:BoundColumn>
<asp:ButtonColumn Text="Plus d'informations" CommandName="infos"></asp:ButtonColumn>
<asp:ButtonColumn Text="S'abonner" CommandName="abonner"></asp:ButtonColumn>
</Columns>
<PagerStyle HorizontalAlign="Center" ... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
Si notino i seguenti punti:
definiamo noi stessi le colonne da visualizzare nella sezione <columns>...</columns> | |
per l'impaginazione dei dati | |
definisce la colonna [thème] (HeaderText) di [DataGrid], che sarà collegata alla colonna [thème] della fonte dati (DataField) | |
definisce due colonne di pulsanti (o link). Per distinguere i due link della stessa riga, si utilizzerà la loro proprietà [CommandName]. |
Il componente [DataGrid] non è completamente configurato. Lo sarà nel codice del controller.
Il componente [dlAbonnements] di tipo [DataList] è collegato a una fonte di tipo [DataTable]. La sua formattazione è stata effettuata tramite il collegamento [Mise en forme automatique] dal pannello delle proprietà del [DataList]. La definizione delle sue proprietà è stata invece effettuata direttamente nel codice di presentazione. Il codice è il seguente (il codice di formattazione è omesso):
<asp:DataList id="dlAbonnements" ... runat="server" >
<HeaderTemplate>
<div align="center">
Vos abonnements</div>
</HeaderTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<TABLE>
<TR>
<TD><%#Container.DataItem("tema")%></TD>
<TD>
<asp:Button id="lnkRetirer" CommandName="retirer" runat="server" Text="Retirer" /></TD>
</TR>
</TABLE>
</ItemTemplate>
<HeaderStyle ...></HeaderStyle>
</asp:DataList>
definisce il testo dell'intestazione di [DataList] | |
definisce l'elemento corrente del [DataList] - qui è stata inserita una tabella a due colonne e una riga. La prima cella conterrà il nome del tema a cui l'utente desidera iscriversi, l'altra il pulsante [Retirer] che gli consente di annullare la propria scelta. |
9.3.4. La pagina di presentazione
Il codice di presentazione [main.aspx] è il seguente:
<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
<HEAD>
<title></title>
</HEAD>
<body>
<P>Indiquez les thèmes auxquels vous voulez vous abonner :</P>
<HR width="100%" SIZE="1">
<form runat="server">
<table>
<tr>
<td vAlign="top">
<asp:datagrid id="dgThèmes" ... runat="server">
...
</asp:datagrid>
</td>
<td vAlign="top">
<asp:DataList id="dlAbonnements" ... runat="server" GridLines="Horizontal" ShowFooter="False">
....
</asp:DataList>
</td>
<td vAlign="top">
<asp:Panel ID="panelInfo" Runat="server" EnableViewState="False">
<TABLE>
<TR>
<TD bgColor="#99cccc">
<asp:Label id="lblThème" runat="server"></asp:Label></TD>
</TR>
<TR>
<TD bgColor="#ffff99">
<asp:Label id="lblDescription" runat="server"></asp:Label></TD>
</TR>
</TABLE>
</asp:Panel>
</td>
</tr>
</table>
<P>
<asp:Label id="lblInfo" runat="server" EnableViewState="False"></asp:Label></P>
</form>
</body>
</HTML>
9.3.5. I file di controllo
Il controllo è suddiviso tra i file [global.asax] e [main.aspx]. Il file [global.asax] è il seguente:
Il file associato [global.asax.vb] contiene il seguente codice:
Imports System.Web
Imports System.Web.SessionState
Imports System.Data
Imports System
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' si inizializza la fonte dati
Dim thèmes As New DataTable
' colonne
With thèmes.Columns
.Add("id", GetType(System.Int32))
.Add("thème", GetType(System.String))
.Add("description", GetType(System.String))
End With
' la colonna id sarà la chiave primaria
thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
' righe
Dim ligne As DataRow
For i As Integer = 0 To 10
ligne = thèmes.NewRow
ligne.Item("id") = i.ToString
ligne.Item("thème") = "thème" + i.ToString
ligne.Item("description") = "description du thème " + i.ToString
thèmes.Rows.Add(ligne)
Next
' si inserisce la fonte dati nell'applicazione
Application("thèmes") = thèmes
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' inizio sessione - si crea una tabella degli abbonamenti vuota
Dim dtAbonnements As New DataTable
With dtAbonnements
' le colonne
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' la chiave primaria
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
' la tabella viene inserita nella sessione
Session.Item("abonnements") = dtAbonnements
End Sub
End Class
La procedura [Application_Start], eseguita quando l'applicazione riceve la sua primissima richiesta, crea il [DataTable] dei temi a cui è possibile iscriversi. Viene creato in modo arbitrario con del codice. Ricordiamo la tecnica che abbiamo già incontrato. Si creano nell'ordine:
- un oggetto [DataTable] vuoto, senza struttura e senza dati
- la struttura della tabella definendone le colonne (nome e tipo di dati che contengono)
- le righe della tabella che rappresentano i dati utili
Qui abbiamo aggiunto una chiave primaria. È la colonna "id" a fungere da chiave primaria. Ci sono diversi modi per dirlo. In questo caso abbiamo utilizzato un vincolo. In SQL, un vincolo è una regola che i dati di una riga devono rispettare affinché questa possa essere aggiunta a una tabella. Esistono vincoli di ogni tipo. Il vincolo “Primary Key” impone alla colonna a cui è applicato di contenere valori univoci e non vuoti. Una chiave primaria può infatti essere costituita da un’espressione che coinvolge i valori di più colonne. [DataTable].Constraints è l’insieme dei vincoli di una determinata tabella. Per aggiungere un vincolo, si utilizza il metodo [DataTable.Constraints.Add]. Questo metodo presenta diverse firme. In questo caso, è stato utilizzato il metodo [Add(Byval nom as String, Byval colonne as DataColumn, Byval cléPrimaire as Boolean)]:
nome del vincolo - può essere qualsiasi | |
colonna che fungerà da chiave primaria - di tipo [DataColumn] | |
deve essere impostato su [vrai] per rendere [colonne] una chiave primaria. Se è impostato su [cléPrimaire=false], si applica solo il vincolo di valori univoci su [colonne] |
Per rendere la colonna denominata "id" la chiave primaria della tabella [dtAbonnements], si scrive quindi:
La procedura [Session_Start], eseguita quando l’applicazione riceve la prima richiesta da un cliente. Serve a creare oggetti specifici per ciascun cliente che devono persistere attraverso le sue diverse richieste. La procedura costruisce la tabella [DataTable] degli abbonamenti del cliente. Viene creata solo la sua struttura, poiché inizialmente questa tabella è vuota. Verrà popolata man mano che vengono effettuate le richieste. Anche in questo caso, la colonna "id" funge da chiave primaria. Per dichiarare questo vincolo è stata utilizzata una tecnica diversa:
è l’array delle colonne che formano la chiave primaria – in questo caso è stato dichiarato un array a elemento singolo: la colonna denominata “id” |
Quando la richiesta del cliente raggiunge il controller [main.aspx], i due oggetti [DataTable] sono disponibili nell’applicazione per la tabella dei temi e nella sessione per la tabella degli abbonamenti. Il controller [main.aspx.vb] è il seguente:
Imports System.Data
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents dgThèmes As System.Web.UI.WebControls.DataGrid
Protected WithEvents lblThème As System.Web.UI.WebControls.Label
Protected WithEvents lblDescription As System.Web.UI.WebControls.Label
Protected WithEvents dlAbonnements As System.Web.UI.WebControls.DataList
Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
Protected WithEvents panelInfo As System.Web.UI.WebControls.Panel
Protected dtThèmes As DataTable
Protected dtAbonnements As DataTable
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub liaisons()
...
End Sub
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
End Sub
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
End Sub
Private Sub infos(ByVal id As String)
...
End Sub
Private Sub abonner(ByVal id As String)
...
End Sub
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
End Sub
End Class
La procedura [Page_Load] ha come funzione principale quella di:
- recuperare le due tabelle [dtThèmes] e [dtAbonnements], che si trovano rispettivamente nell’applicazione e nella sessione, al fine di renderle disponibili a tutti i metodi della pagina
- collegare i dati di queste due fonti ai rispettivi contenitori. Ciò avviene solo durante la prima richiesta. Nelle richieste successive, il collegamento non deve essere effettuato sistematicamente e, quando è necessario, a volte è necessario attendere un evento successivo a [Page_Load] per eseguirlo.
Il codice di [Page_Load] è il seguente:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' si recuperano le fonti dei dati
dtThèmes = CType(Application("thèmes"), DataTable)
dtAbonnements = CType(Session("abonnements"), DataTable)
' collegamento dati
If Not IsPostBack Then
liaisons()
End If
' alcune informazioni vengono nascoste
panelInfo.Visible = False
End Sub
Private Sub liaisons()
'si collega la fonte dati al componente [datagrid]
With dgThèmes
.DataSource = dtThèmes
.DataKeyField = "id"
End With
' si collega la fonte dati al componente [datalist]
With dlAbonnements
.DataSource = dtAbonnements
.DataKeyField = "id"
End With
' si assegnano i dati ai componenti
Page.DataBind()
End Sub
Nella procedura [liaisons], utilizziamo la proprietà [DataKeyField] dei componenti [DataList] e [DataGrid] per definire la colonna della fonte dati che servirà a identificare in modo univoco le righe dei contenitori. Di norma, questa colonna è la chiave primaria della fonte dati, ma non è obbligatorio. È sufficiente che la colonna sia priva di duplicati e di valori vuoti. Per il contenitore [dgThèmes], sarà la colonna "id" della fonte [dtThèmes] a fungere da chiave primaria, mentre per il contenitore [dlAbonnements] sarà la colonna "id" della fonte [dtAbonnements]. Non è necessario che la colonna che funge da chiave primaria per il contenitore venga visualizzata da quest’ultimo. In questo caso, nessuno dei due contenitori visualizza la colonna della chiave primaria. Il vantaggio di un contenitore dotato di chiave primaria è che questa consente di individuare facilmente nella fonte di dati le informazioni relative alla riga del contenitore in cui si è verificato un evento. Infatti, capita spesso che, partendo dalla riga di un contenitore in cui si è verificato un evento, sia necessario intervenire sulla riga corrispondente della fonte dati ad essa collegata. La chiave primaria facilita questo lavoro.
L’impaginazione del [DataGrid] è gestita in modo classico:
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
' cambio di pagina
dgThèmes.CurrentPageIndex = e.NewPageIndex
' collegamento
liaisons()
End Sub
Le operazioni relative ai collegamenti [Plus d'informations] e [S'abonner] sono gestite dalla procedura [dgThèmes_ItemCommand]:
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
' evento su una riga del [datagrid]
Dim commande As String = e.CommandName
Select Case commande
Case "infos"
infos(dgThèmes.DataKeys(e.Item.ItemIndex))
Case "abonner"
abonner(dgThèmes.DataKeys(e.Item.ItemIndex))
End Select
' collegamento
liaisons()
End Sub
Per distinguerli, si utilizza il fatto che entrambi i link hanno un attributo [CommandName]. A seconda del valore di questo attributo, si chiama la procedura [infos] o [abonner], passando in entrambi i casi la chiave "id" associata all'elemento di [DataGrid] in cui si è verificato l'evento. Grazie a queste informazioni, la procedura [info] visualizzerà le informazioni relative al tema scelto dall’utente:
Private Sub infos(ByVal id As String)
' informazioni relative alla chiave id
' si recupera la riga di [datatable] corrispondente alla chiave
Dim ligne As DataRow
ligne = dtThèmes.Rows.Find(id)
If Not ligne Is Nothing Then
' vengono visualizzate le informazioni
lblThème.Text = CType(ligne("thème"), String)
lblDescription.Text = CType(ligne("description"), String)
panelInfo.Visible = True
End If
End Sub
Poiché la tabella [dtThèmes] ha una chiave primaria, il metodo [dtThèmes.Rows.Find("P")] consente di trovare la riga con chiave primaria P. Se viene trovata, si ottiene un oggetto [DataRow]. In questo caso, dobbiamo cercare la riga con chiave primaria [id], dove [id] viene passato come parametro. Se la riga viene trovata, si inseriscono le informazioni [thème] e [description] relative a tale riga nel pannello informativo, che viene poi reso visibile.
La procedura [abonner(id)] deve aggiungere il tema chiave [id] all’elenco degli abbonamenti. Il suo codice è il seguente:
Private Sub abonner(ByVal id As String)
' Abbonamento al tema con ID chiave
' si recupera la riga di [datatable] corrispondente alla chiave
Dim ligne As DataRow
ligne = dtThèmes.Rows.Find(id)
If Not ligne Is Nothing Then
' si verifica se non si è già abbonati
Dim abonnement As DataRow
abonnement = dtAbonnements.Rows.Find(id)
If Not abonnement Is Nothing Then
' si segnala l'errore
lblInfo.Text = "Vous êtes déjà abonné au thème [" + ligne("thème") + "]"
Else
' si aggiunge il tema agli abbonamenti
abonnement = dtAbonnements.NewRow
abonnement("id") = id
abonnement("thème") = ligne("thème")
dtAbonnements.Rows.Add(abonnement)
' si creano i collegamenti
liaisons()
End If
End If
End Sub
Nell'elenco dei temi [dtThèmes], si cerca innanzitutto la riga con la chiave [id]. Se la si trova, si verifica che quel tema non sia già presente nell’elenco degli abbonamenti per evitare di inserirlo due volte. In tal caso, viene visualizzato un messaggio di errore. Altrimenti, si aggiunge un nuovo abbonamento alla tabella [dtAbonnements] e si effettuano i collegamenti dei componenti dell’elenco dati alle rispettive fonti.
Quando l’utente fa clic su un pulsante [Retirer], è necessario eliminare un elemento dalla tabella [dtAbonnements]. Ciò avviene tramite la seguente procedura:
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
' si rimuove un abbonamento
Dim commande As String = e.CommandName
If commande = "retirer" Then
' si rimuove l'abbonamento da [datatable]
With dtAbonnements.Rows
.Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
End With
' collegamenti
liaisons()
End If
End Sub
Si verifica innanzitutto l'attributo [CommandName] dell'elemento all'origine dell'evento. In realtà è piuttosto superfluo, poiché il pulsante [Retirer] è l'unico controllo in grado di generare un evento nel componente [DataList]. Non vi è quindi alcuna ambiguità. Per eliminare una riga da un oggetto [DataTable], si utilizza il metodo [DataList.Remove(DataRow)] che rimuove dalla tabella la riga di tipo [DataRow] passata come parametro. Tale riga viene individuata dal metodo [DataList.Find], al quale è stata passata la chiave primaria della riga ricercata. Una volta eliminata la riga, si esegue il collegamento dei dati ai componenti
9.4. Gestire un [DataList] impaginato
Riprendiamo l’esempio precedente per impaginare il componente [DataList] che rappresenta l’elenco degli abbonamenti dell’utente. A differenza del componente [DataGrid], il componente [DataList] non offre alcuna funzionalità per l’impaginazione. Vedremo che la sua implementazione è complessa, il che ci farà apprezzare appieno la paginazione automatica del componente [DataGrid].
9.4.1. Funzionamento
L’unica differenza è l’impaginazione del componente [DataList]. Le pagine conterranno due abbonamenti. Se l’utente ha cinque abbonamenti, si avranno tre pagine. La prima sarà la seguente:

La seconda pagina si ottiene tramite il link [Suivant]:

La terza pagina:

Si noti che i link [Précédent] e [Suivant] sono visibili solo se, rispettivamente, c'è una pagina precedente e una pagina successiva alla pagina corrente.
9.4.2. Codice di presentazione
I link [Précédent] e [Suivant] si ottengono aggiungendo un tag <FooterTemplate> al [DataList]:
<asp:datalist id="dlAbonnements" runat="server" ...>
....
<FooterTemplate>
<asp:LinkButton id="lnkPrecedent" runat="server" CommandName="precedent">Précédent</asp:LinkButton>
<asp:LinkButton id="lnkSuivant" runat="server" CommandName="suivant">Suivant</asp:LinkButton>
</FooterTemplate>
....
</asp:datalist>
9.4.3. Codice di controllo
Il file associato [global.asax.vb] subisce le seguenti modifiche:
...
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
...
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' inizio sessione - si crea una tabella degli abbonamenti vuota
Dim dtAbonnements As New DataTable
With dtAbonnements
' le colonne
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' la chiave primaria
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
' la tabella viene inserita nella sessione
Session.Item("abonnements") = dtAbonnements
' la pagina corrente è la pagina 0
Session.Item("pAC") = 0
' il numero di abbonamenti in questa pagina è 0
Session.Item("nbAC") = 0
End Sub
Oltre alla tabella degli abbonamenti [dtAbonnements], nella sessione vengono inserite altre due informazioni:
di tipo [Integer]: si tratta del numero della pagina corrente visualizzata durante l'ultima richiesta | |
di tipo [Integer] - numero di righe visualizzate nella pagina corrente precedente |
All'inizio di una sessione, il numero della pagina corrente e il numero di righe di tale pagina sono pari a zero.
Il controller [main.aspx.vb] si evolve come segue:
....
Public Class main
Inherits System.Web.UI.Page
....
' dati dell'applicazione
Protected dtThèmes As DataTable
Protected dtAbonnements As DataTable
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub terminer()
...
End Sub
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
End Sub
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
End Sub
Private Sub infos(ByVal id As String)
...
End Sub
Private Sub abonner(ByVal id As String)
...
End Sub
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
End Sub
Private Sub changePAC()
...
End Sub
Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean)
...
End Sub
End Class
Qui vengono definiti nuovi dati relativi all'impaginazione degli abbonamenti:
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
Alcune di queste informazioni vengono memorizzate nella sessione e recuperate ad ogni richiesta nella procedura [Page_Load]:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' vengono recuperate le fonti dei dati
dtThèmes = CType(Application("thèmes"), DataTable)
dtAbonnements = CType(Session("abonnements"), DataTable)
' e le informazioni di visualizzazione degli abbonamenti
pAC = CType(Session("pAC"), Integer)
nbAC = CType(Session("nbAC"), Integer)
' collegamento dati
If Not IsPostBack Then
' visualizzazione di un elenco di abbonamenti vuoto
terminer()
End If
' si nascondono alcune informazioni
panelInfo.Visible = False
End Sub
Le informazioni recuperate [pAC] e [nbAC] riguardano la pagina degli abbonamenti visualizzata durante la richiesta precedente:
è il numero della pagina corrente visualizzata durante la richiesta precedente | |
numero di righe visualizzate nella pagina corrente |
Il metodo [terminer] associa i componenti alle rispettive fonti di dati, proprio come faceva il metodo [liaisons] nell'applicazione precedente. La novità, in questo caso, è il collegamento del [DataList] con la tabella [dtPA], che rappresenta la pagina degli abbonamenti da visualizzare:
Private Sub terminer()
' collegamento della fonte dati al componente [datagrid]
With dgThèmes
.DataSource = dtThèmes
.DataKeyField = "id"
End With
' si collega la pagina degli abbonamenti al componente [datalist] tenendo conto della pagina corrente pAC
changePAC()
' si visualizza la pagina p
With dlAbonnements
.DataSource = dtPA
.DataKeyField = "id"
End With
' si assegnano i dati ai componenti
Page.DataBind()
' gestione dei collegamenti [précédent] e [suivant] del [datalist]
Dim blprec As Boolean = pAC <> 0
Dim blsuivant As Boolean = pAC <> (dtAbonnements.Rows.Count - 1) \ nbAP
Dim nbLiensTrouvés As Integer = 0
setLiens(dlAbonnements, blprec, blsuivant, nbLiensTrouvés)
' si salvano le informazioni della pagina corrente nella sessione
Session("pAC") = pAC
Session("nbAC") = dtPA.Rows.Count
End Sub
Si prega di notare quanto segue:
- la sorgente [dtPA] dipende dal numero di pagina corrente [pAC] da visualizzare. La variabile [pAC] è una variabile globale della classe, gestita dai metodi che devono modificare tale numero di pagina corrente. È il metodo [changePAC] che si occupa di costruire la tabella [dtPA], che sarà collegata al componente [dlAbonnements].
- Il metodo [setLiens] ha il compito di visualizzare o nascondere i collegamenti [Précédent] e [Suivant] a seconda che la pagina corrente [pAC] visualizzata sia preceduta e seguita da una pagina. Ha quattro parametri:
- [dlAbonnements]: il controllo [DataList], di cui esploreremo l’albero dei controlli per individuare i due link. Infatti, sebbene questi due link si trovino in una posizione specifica, ovvero il piè di pagina del [DataList], non sembra esserci un modo semplice per farvi riferimento direttamente. In ogni caso, qui non è stato trovato.
- [blPrecedent]: valore booleano da assegnare alla proprietà [visible] del collegamento [Precedent] – è vero se la pagina corrente è diversa da 0
- [blSuivant]: valore booleano da assegnare alla proprietà [visible] del collegamento [Suivant] - è vero se la pagina corrente non è l'ultima pagina dell'elenco degli abbonamenti
- [nbLiensTrouvés]: un parametro di output che conta il numero di link trovati. Non appena tale numero è pari a due, il metodo termina.
- Le informazioni [pAC] e [nbAC] vengono salvate nella sessione per la richiesta successiva.
Il metodo [changePAC] crea la tabella [dtPA] che verrà collegata al componente [dlAbonnements]. Lo fa in base al numero [pAC] della pagina corrente da visualizzare. La tabella [dtPA] deve visualizzare alcune righe della tabella degli abbonamenti [dtAbonnements]. Si ricorda che questa tabella è memorizzata nella sessione e viene aggiornata (aumentata o diminuita) man mano che vengono effettuate le richieste. Si inizia impostando l’intervallo [premier,dernier] dei numeri di riga della tabella [dtAbonnements] che la tabella [dtPA] deve visualizzare:
Private Sub changePAC()
' imposta la pagina pAC come pagina corrente degli abbonamenti
' gestione delle pagine di [datalist]
Dim nbAbonnements = dtAbonnements.Rows.Count
Dim dernièrePage = (nbAbonnements - 1) \ nbAP
' primo e ultimo abbonamento
If pAC < 0 Then pAC = 0
If pAC > dernièrePage Then pAC = dernièrePage
Dim premier As Integer = pAC * nbAP
Dim dernier As Integer = (pAC + 1) * nbAP - 1
If dernier > nbAbonnements - 1 Then dernier = nbAbonnements - 1
Fatto ciò, è possibile creare la tabella [dtPA]. Si definisce innanzitutto la sua struttura [id,thème], quindi la si compila copiando le righe di [dtAbonnements] il cui numero rientra nell'intervallo [premier,dernier] calcolato in precedenza.
' creazione del datatable dtpa
dtPA = New DataTable
With dtPA
' le colonne
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' la chiave primaria
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
Dim abonnement As DataRow
For i As Integer = premier To dernier
abonnement = dtPA.NewRow
With abonnement
.Item("id") = dtAbonnements.Rows(i).Item("id")
.Item("thème") = dtAbonnements.Rows(i).Item("thème")
End With
dtPA.Rows.Add(abonnement)
Next
End Sub
Al termine del metodo [changePAC], la tabella [dtPA] è stata creata e può essere collegata al componente [DataList]. Ciò avviene nel metodo [terminer]. Nello stesso metodo, si utilizza la procedura [setLiens] per impostare lo stato dei collegamenti [Précédent] e [Suivant] del [DataList]. Il codice di questa procedura è il seguente:
Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean, ByRef nbLiensTrouvés As Integer)
' si cercano i collegamenti [précédent] e [suivant]
' nella struttura gerarchica dei controlli di [datalist]
' Sono stati trovati tutti i collegamenti?
If nbLiensTrouvés = 2 Then Exit Sub
' analisi dei controlli secondari
Dim c As Control
For Each c In ctl.Controls
' si lavora innanzitutto in profondità: i collegamenti si trovano in fondo all'albero
setLiens(c, blPrec, blSuivant, nbLiensTrouvés)
' collegamento [Précédent]?
If c.ID = "lnkPrecedent" Then
CType(c, LinkButton).Visible = blPrec
nbLiensTrouvés += 1
End If
' link [Suivant]?
If c.ID = "lnkSuivant" Then
CType(c, LinkButton).Visible = blSuivant
nbLiensTrouvés += 1
End If
Next
End Sub
La procedura è ricorsiva. Innanzitutto, cerca, tra i controlli figli del componente [dlAbonnements] i componenti denominati [lnkPrecedent] e [lnkSuivant], che rappresentano le identità (attributo ID) dei due link di paginazione. Li cerca innanzitutto partendo dalla base dell’albero dei controlli, poiché è lì che si trovano. Non appena viene individuato un link, il contatore [nbLiensTrouvés] viene incrementato e la proprietà [visible] del link viene popolata con un valore passato come parametro alla procedura. Non appena vengono trovati entrambi i collegamenti, l’albero dei controlli non viene più esplorato e la procedura ricorsiva termina.
Abbiamo detto che il metodo [changePAC], che imposta la fonte dati [dtPA] per il componente [dlAbonnements], operava con il numero [pAC] della pagina corrente da visualizzare. Diverse procedure modificano questo numero:
Private Sub abonner(ByVal id As String)
' abbonamento al tema con ID chiave
..
' si aggiunge il tema agli abbonamenti
..
' aggiornamento del numero della pagina corrente - ora è l'ultima pagina
pAC = (dtAbonnements.Rows.Count - 1) \ nbAP
' collegamenti dati
terminer()
End If
End Sub
Dopo aver aggiunto un abbonamento, questo viene inserito alla fine dell'elenco degli abbonamenti. Pertanto, ci si posiziona sull'ultima pagina degli abbonamenti in modo che l'utente possa vedere l'aggiunta effettuata.
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
' si rimuove un abbonamento
Dim commande As String = e.CommandName
Select Case commande
Case "retirer"
' si rimuove l'abbonamento da [datatable]
With dtAbonnements.Rows
.Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
End With
' è necessario cambiare la pagina corrente?
nbAC -= 1
If nbAC = 0 Then pAC -= 1
Case "precedent"
' cambio della pagina corrente
pAC -= 1
Case "suivant"
' cambio della pagina corrente
pAC += 1
End Select
' collegamenti dati
terminer()
End Sub
- [nbAC] è il numero di righe visualizzate nella pagina corrente prima della cancellazione di un abbonamento. Se il nuovo numero di righe della pagina è pari a 0, il numero della pagina corrente [pAC] viene decrementato di un'unità.
- Se si clicca sul link [Precedent], il numero della pagina corrente [pAC] viene decrementato di un'unità.
- Se si clicca sul link [Suivant], il numero della pagina corrente [pAC] viene incrementato di un'unità.
Le altre procedure rimangono identiche a quelle precedenti.
9.4.4. Conclusione
Con questo esempio abbiamo dimostrato che è possibile impaginare un componente [DataList]. Questa impaginazione è complessa ed è preferibile affidarsi all’impaginazione automatica del componente [DataGrid], quando possibile. Questo esempio ci ha inoltre mostrato come accedere ai componenti presenti nel piè di pagina del componente [DataList].
9.5. Classe di accesso a un database di prodotti
Ci concentriamo nuovamente sul database ACCESS [produits] già utilizzato. Ricordiamo che esso contiene un'unica tabella denominata [liste], la cui struttura è la seguente:
![]() | ![]() |
Creeremo una classe di accesso alla tabella [liste] che consentirà di leggerla e aggiornarla. Creeremo inoltre un client da console che utilizzerà la classe precedente per aggiornare la tabella. In una seconda fase, creeremo un client web per svolgere lo stesso compito.
9.5.1. La classe ExceptionProduits
La classe [Exception] dispone di un costruttore che accetta un messaggio di errore come parametro. In questo caso desideriamo disporre di una classe di eccezioni dotata di un costruttore che accetti un elenco di messaggi di errore anziché un unico messaggio di errore. Sarà la classe [ExceptionProduits] riportata di seguito:
Public Class ExceptionProduits
Inherits Exception
' messaggi di errore relativi all'eccezione
Private _erreurs As ArrayList
' costruttore
Public Sub New(ByVal erreurs As ArrayList)
Me._erreurs = erreurs
End Sub
' proprietà
Public ReadOnly Property erreurs() As ArrayList
Get
Return _erreurs
End Get
End Property
End Class
9.5.2. La struttura [sProduit]
La struttura [produit] rappresenterà un prodotto [id, nom, prix]:
' struttura sProduit
Public Structure sProduit
' i campi
Private _id As Integer
Private _nom As String
Private _prix As Double
' ID proprietà
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
_id = Value
End Set
End Property
' proprietà nome
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If IsNothing(Value) OrElse Value.Trim = String.Empty Then Throw New Exception
_nom = Value
End Set
End Property
' proprietà prezzo
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If IsNothing(Value) OrElse Value < 0 Then Throw New Exception
_prix = Value
End Set
End Property
End Structure
La struttura ammette solo dati validi per i campi [nom] e [prix].
9.5.3. La classe Prodotti
La classe [Produits] è quella che ci consentirà di aggiornare la tabella [liste] del database dei prodotti. La sua struttura è la seguente:
Public Class produits
' dati dell'istanza
Public Sub New(ByVal chaineConnexionOLEDB As String)
....
End Sub
Public Function getProduits() As DataTable
....
End Function
Public Sub ajouterProduit(ByVal produit As sProduit)
...
End Sub
Public Sub modifierProduit(ByVal produit As sProduit)
...
End Sub
Public Sub supprimerProduit(ByVal id As Integer)
...
End Sub
End Class
I dati di istanza
I dati condivisi dai diversi metodi della classe sono i seguenti:
Private connexion As OleDbConnection
Private Const selectText As String = "select id,nom,prix from liste"
Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
Private Const updateText As String = "update liste set nom=?,prix=? where id=?"
Private Const deleteText As String = "delete from liste where id=?"
Private selectCommand As New OleDbCommand
Dim insertCommand As New OleDbCommand
Dim updateCommand As New OleDbCommand
Dim deleteCommand As New OleDbCommand
Dim adaptateur As New OleDbDataAdapter
la connessione al database verrà aperta per l'esecuzione di un comando SQL e poi chiusa immediatamente dopo | |
Richiesta SQL [select] per ottenere l'intera tabella [liste] | |
query che consente l'inserimento di una riga (nome, prezzo) nella tabella [liste]. Si noti che non viene specificato il campo [id]. Infatti, questo campo viene autoincrementato dal SGBD e quindi non è necessario specificarlo. | |
query che consente l'aggiornamento dei campi (nome, prezzo) della riga della tabella [liste] con chiave [id] | |
query che consente l'eliminazione della riga della tabella [liste] con chiave [id] | |
oggetto [OleDbCommand] che esegue la query [selectText] sulla connessione [connexion] | |
oggetto [OleDbCommand] che esegue la richiesta [updateText] sulla connessione [connexion] | |
oggetto [OleDbCommand] che esegue la richiesta [insertText] sulla connessione [connexion] | |
oggetto [OleDbCommand] che esegue la richiesta [deleteText] sulla connessione [connexion] | |
oggetto che consente di recuperare il risultato dell'esecuzione di [selectCommand] in un oggetto [DataSet] |
Il costruttore
Il costruttore riceve un unico parametro, [chaineConnexionOLEDB], che è la stringa di connessione che identifica il database da utilizzare. A partire da questa, vengono preparati i quattro comandi di interrogazione e aggiornamento della tabella, nonché l’adattatore. Si tratta solo di una preparazione e non viene effettuata alcuna connessione.
Public Sub New(ByVal chaineConnexionOLEDB As String)
' si prepara la connessione
connexion = New OleDbConnection(chaineConnexionOLEDB)
' si stanno preparando i comandi di query
Dim commandes() As OleDbCommand = {selectCommand, insertCommand, updateCommand, deleteCommand}
Dim textes() As String = {selectText, insertText, updateText, deleteText}
For i As Integer = 0 To commandes.Length - 1
With commandes(i)
.CommandText = textes(i)
.Connection = connexion
End With
Next
' si sta preparando l'adattatore di accesso ai dati
adaptateur.SelectCommand = selectCommand
End Sub
Il metodo getProduits
Questo metodo consente di ottenere il contenuto della tabella [Liste] in un oggetto [DataTable]. Il codice è il seguente:
Public Function getProduits() As DataTable
' la tabella [liste] viene inserita in una [dataset]
Dim contenu As New DataSet
' si crea un oggetto DataAdapter per leggere i dati dalla fonte OLEDB
Try
With adaptateur
.FillSchema(contenu, SchemaType.Source)
.Fill(contenu)
End With
Catch e As Exception
' pb
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur d'accès à la base de données : {0}", e.Message))
Throw New ExceptionProduits(erreursCommande)
End Try
' si restituisce il risultato
Return contenu.Tables(0)
End Function
L'operazione viene eseguita tramite le due istruzioni seguenti:
With adaptateur
.FillSchema(contenu, SchemaType.Source)
.Fill(contenu)
End With
Il metodo [FillSchema] definisce la struttura (colonne, vincoli, relazioni) di [DataSet] e contenu sulla base della struttura del database a cui fa riferimento [adaptateur.Connexion]. Ciò ci consente di recuperare la struttura della tabella [liste] e, in particolare, la sua chiave primaria. L’operazione [Fill] che segue popola le tabelle [Dataset] e contenu con le righe della tabella [liste]. Con questa sola operazione avremmo ottenuto i dati e la struttura, ma non la chiave primaria. Quest’ultima ci sarà però utile per aggiornare la tabella [liste] in memoria. Qui, come negli altri metodi, gestiamo gli eventuali errori utilizzando la classe [ExceptionProduits] per ottenere un elenco (ArrayList) di errori anziché un singolo errore. Il metodo [getProduits] restituisce la tabella [liste] sotto forma di un oggetto [DataTable].
Il metodo ajouterProduits
Questo metodo consente di aggiungere una riga (id, nome, prezzo) alla tabella [liste]. Queste informazioni gli vengono fornite sotto forma di una struttura [sProduit] composta da campi [id, nom, prix]. Il codice del metodo è il seguente:
Public Sub ajouterProduit(ByVal produit As sProduit)
' si aggiunge un prodotto [nom,prix]
' si preparano i parametri dell'aggiunta
With insertCommand.Parameters
.Clear()
.Add(New OleDbParameter("nom", produit.nom))
.Add(New OleDbParameter("prix", produit.prix))
End With
' si effettua l'aggiunta
Try
' apertura connessione
connexion.Open()
' esecuzione comando
insertCommand.ExecuteNonQuery()
Catch ex As Exception
' problema
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de l'ajout : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' chiusura connessione
connexion.Close()
End Try
End Sub
I campi della struttura [produit] vengono inseriti nei parametri del comando [insertCommand]. Ricordiamo la configurazione attuale di questo comando (cfr. produttore):
Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
insertCommand.Connexion=connexion
Il testo del comando SQL [insert] contiene parametri formali ? che devono essere sostituiti con parametri effettivi. Ciò avviene tramite la collezione [Parameters] della classe [OleDbCommand]. Questa contiene elementi di tipo [OleDbParameter] che definiscono i parametri effettivi destinati a sostituire i parametri formali ?. Poiché questi ultimi non sono denominati, si utilizza l’indice dei parametri effettivi per determinare a quale parametro formale corrisponda un determinato parametro effettivo. In questo caso, il parametro effettivo n. i nella collezione [Parameters] sostituirà il parametro formale ? n. i. Per creare un parametro effettivo di tipo [OleDbParameter], qui si utilizza il costruttore [OleDbParameter (Byval nom as String, Byval valeur as Object)] che definisce il nome e il valore del parametro effettivo. Il nome può essere qualsiasi. Inoltre, in questo caso non verrà utilizzato. I due parametri dell’istruzione SQL [insert] ricevono come valori quelli dei campi [nom, prix] della struttura [produit]. Fatto ciò, l’inserimento viene effettuato dall’istruzione [insertCommand.ExecuteNonQuery].
Il metodo modifierProduits
Questo metodo consente di modificare la riga della tabella [liste]. Le informazioni necessarie gli vengono fornite nella struttura [sProduit] tramite i campi [id, nom, prix].
Public Sub modifierProduit(ByVal produit As sProduit)
' si modifica un prodotto [id,nom,prix]
' si preparano i parametri dell'aggiornamento
With updateCommand.Parameters
.Clear()
.Add(New OleDbParameter("nom", produit.nom))
.Add(New OleDbParameter("prix", produit.prix))
.Add(New OleDbParameter("id", produit.id))
End With
' si effettua la modifica
Try
' apertura della connessione
connexion.Open()
' Esecuzione del comando
Dim nbLignes As Integer = updateCommand.ExecuteNonQuery()
If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", produit.id))
Catch ex As Exception
' problema
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de la modification : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' chiusura della connessione
connexion.Close()
End Try
End Sub
Il codice è quasi identico a quello del metodo [ajouterProduits], tranne per il fatto che il comando [OleDbCommand] in questione è [updateCommand] anziché [insertCommand].
Il metodo supprimerProduits
Questo metodo consente di eliminare la riga della tabella [liste] con chiave [id] passata come parametro. Il codice è il seguente:
Public Sub supprimerProduit(ByVal id As Integer)
' elimina il prodotto della chiave [id]
' si preparano i parametri per l'eliminazione
With deleteCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", id))
End With
' si esegue l'eliminazione
Try
' apertura connessione
connexion.Open()
' esecuzione del comando
Dim nbLignes As Integer = deleteCommand.ExecuteNonQuery()
If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", id))
Catch ex As Exception
' problema
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de la suppression : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' chiusura della connessione
connexion.Close()
End Try
End Sub
Si segue la stessa procedura dei metodi precedenti.
9.5.4. Test della classe [produits]
Un programma di test [testproduits.vb] in modalità console potrebbe essere il seguente:
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Namespace st.istia.univangers.fr
' pagina di prova
Module testproduits
Dim contenu As DataTable
Sub Main(ByVal arguments() As String)
' visualizza il contenuto di una tabella dei prodotti
' la tabella si trova in un database ACCESS il cui file di pagina riceve il nome del file
Const syntaxe1 As String = "pg bdACCESS"
' verifica dei parametri del programma
If arguments.Length <> 1 Then
' messaggio di errore
Console.Error.WriteLine(syntaxe1)
' fine
Environment.Exit(1)
End If
' si prepara la stringa di connessione
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
' creazione di un oggetto prodotti
Dim objProduits As produits = New produits(chaineConnexion)
' visualizzazione di tutti i prodotti
afficheProduits(objProduits)
' si inserisce un prodotto
Dim produit As New sProduit
With produit
.nom = "xxx"
.prix = 1
End With
Try
objProduits.ajouterProduit(produit)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' visualizzazione di tutti i prodotti
afficheProduits(objProduits)
' si recupera l'ID del prodotto aggiunto
produit.id = CType(contenu.Rows(contenu.Rows.Count - 1)("id"), Integer)
' modifica il prodotto aggiunto
produit.prix = 200
Try
objProduits.modifierProduit(produit)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' visualizza tutti i prodotti
afficheProduits(objProduits)
' si elimina il prodotto aggiunto
Try
objProduits.supprimerProduit(produit.id)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' si visualizzano tutti i prodotti
afficheProduits(objProduits)
End Sub
Sub afficheProduits(ByRef objProduits As produits)
' si recupera la tabella dei prodotti in un datatable
Try
contenu = objProduits.getProduits()
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
Environment.Exit(2)
End Try
Dim lignes As DataRowCollection = contenu.Rows
For i As Integer = 0 To lignes.Count - 1
' riga i della tabella
Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
"," + lignes(i).Item("prix").ToString)
Next
' interrompe il flusso della console
Console.WriteLine("...")
Console.ReadLine()
End Sub
Sub afficheErreurs(ByRef erreurs As ArrayList)
' visualizza gli errori sulla console
If erreurs.Count <> 0 Then
Console.WriteLine("Les erreurs suivantes se sont produites :")
For i As Integer = 0 To erreurs.Count - 1
Console.WriteLine(String.Format("-- {0}", CType(erreurs(i), String)))
Next
End If
' interrompe il flusso della console
Console.WriteLine("...")
Console.ReadLine()
End Sub
End Module
End Namespace
Si compilano i due file sorgente:
dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb
dos >vbc /r:produits.dll /r:system.dll /r:system.data.dll /r:system.xml.dll testproduits.vb
dos>dir
07/04/2004 08:40 7 168 produits.dll
04/04/2004 16:38 118 784 produits.mdb
07/04/2004 08:31 6 209 produits.vb
07/04/2004 08:40 5 120 testproduits.exe
03/04/2004 19:02 3 312 testproduits.vb
poi si esegue il test:
dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,1
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,200
...
1,produit1,10
2,produit2,20
3,produit3,30
...
Si invita il lettore a confrontare l'output dello schermo sopra riportato con il codice del programma di test.
9.6. Applicazione web per l'aggiornamento della tabella dei prodotti in cache
9.6.1. Introduzione
Stiamo ora sviluppando un'applicazione web per l'aggiornamento della tabella dei prodotti (aggiunta, eliminazione, modifica). La tabella aggiornata rimarrà in memoria in un oggetto [DataTable] e sarà condivisa da tutti gli utenti. Desideriamo mettere in evidenza due punti:
- la gestione di un oggetto [DataTable]
- i problemi legati all’aggiornamento simultaneo di una tabella da parte di più utenti.
L’architettura MVC dell’applicazione sarà la seguente:
![]() |
9.6.2. Funzionamento e viste
La schermata iniziale dell’applicazione è la seguente:

Questa schermata, denominata [formulaire], consente all’utente di applicare un filtro ai prodotti e di impostare il numero di prodotti per pagina che desidera visualizzare.
n. | nome | tipo | ruolo |
LinkButton | visualizza la vista [Formulaire], utilizzata per impostare la condizione di filtro | ||
LinkButton | visualizza la vista [Produits], utilizzata per visualizzare e aggiornare la tabella dei prodotti (modifica ed eliminazione) | ||
LinkButton | visualizza la vista [Ajout], utilizzata per aggiungere un prodotto | ||
vueFormulaire | la vista [Formulaire] | ||
TextBox | la condizione di filtro | ||
TextBox | numero di prodotti per pagina | ||
RequiredFieldValidator | verifica la presenza di un valore in [txtPages] | ||
RangeValidator | verifica che txtPages rientri nell'intervallo [3,10] | ||
pulsante [submit] che visualizza la vista [produits] filtrata in base alla condizione (5) | |||
Etichetta | Testo informativo in caso di errori |
Ad esempio, se la vista [formulaire] è compilata come segue:

si ottiene il seguente risultato:
![]() |
n. | nome | tipo | ruolo |
pannello | |||
RadioButton | consente all'utente di impostare l'ordine di ordinamento desiderato quando fa clic sul titolo di una delle colonne [nom], [prix]. I due pulsanti fanno parte del gruppo [rdTri]. | ||
DataGrid | griglia di visualizzazione di una vista filtrata della tabella dei prodotti. Il filtro è quello impostato dalla vista [formulaire]. Si ha inoltre .AllowPaging=true, .AllowSorting=true | ||
DataGrid | visualizza l'intera tabella dei prodotti - consente di tenere traccia degli aggiornamenti | ||
Etichetta | testo informativo, in particolare in caso di errori | ||
DataGrid | visualizzerà i prodotti eliminati nella tabella dei prodotti | ||
DataGrid | visualizzerà i prodotti modificati nella tabella dei prodotti | ||
DataGrid | visualizzerà i prodotti aggiunti nella tabella dei prodotti |
In questa pagina sono presenti cinque contenitori di dati. Tutti visualizzano la stessa tabella [dtProduits] attraverso una vista [DataView] diversa. Una vista rappresenta un sottoinsieme delle righe della tabella di origine della vista. Questo sottoinsieme viene creato a partire dalle proprietà [RowFilter] e [RowStateFilter] della classe [DataView]:
- [RowFilter] consente di applicare un filtro alle righe, come ad esempio [prix>30] sopra riportato. Questo tipo di filtraggio verrà utilizzato da [DataGrid1].
- [RowStateFilter] consente di impostare un filtro in base allo stato della riga della tabella. Questo indica lo stato della riga rispetto allo stato originale che aveva quando è stata creata la vista sulla tabella. In questo caso, la tabella [dtProduits] proviene da un database. Inizialmente tutte le sue righe avranno uno stato pari a [Original] per indicare che si tratta delle righe originali della tabella. Questo stato può poi evolversi e assumere diversi valori, di cui riportiamo alcuni:
- [Added]: la riga è stata aggiunta – non faceva parte della tabella originale
- [Deleted]: la riga è stata eliminata – è ancora presente nella tabella ma «contrassegnata» come «da eliminare»
- [Modified]: la riga è stata modificata
[RowStateFilter] consente di visualizzare le righe della tabella con un determinato stato:
- (continua)
- [DataViewRowState.Added]: vengono visualizzate solo le righe aggiunte. Queste vengono visualizzate con i loro valori attuali.
- [DataViewRowState.ModifiedOriginal]: vengono visualizzate solo le righe modificate. Queste vengono visualizzate con i loro valori originali.
- [DataViewRowState.ModifiedCurrent]: vengono visualizzate solo le righe modificate. Queste vengono visualizzate con i loro valori attuali.
- [DataViewRowState.Deleted]: vengono visualizzate solo le righe eliminate. Vengono visualizzate con i loro valori originali.
- [DataViewRowState.CurrentRows]: vengono visualizzate le righe non eliminate. Vengono visualizzate con i loro valori attuali.
Pertanto, il filtro [RowStateFilter] avrà i seguenti valori:
nessun filtro sullo stato delle linee | ||
DataViewRowState.CurrentRows | visualizza lo stato attuale della tabella dei prodotti | |
DataViewRowState.Deleted | visualizza le righe eliminate dalla tabella dei prodotti | |
DataViewRowState.ModifiedOriginal | visualizza le righe modificate della tabella dei prodotti con i loro valori iniziali | |
DataViewRowState.Added | visualizza le righe aggiunte alla tabella dei prodotti iniziale |
I contenitori [DataGrid1-5] ci consentiranno di monitorare l'aggiornamento della tabella [dtProduits]. Il componente [DataGrid1] consente la modifica e l'eliminazione di un prodotto. Vedremo come la configurazione del componente consenta tale aggiornamento. È inoltre impaginato e ordinato. Questi due aspetti sono già stati esaminati in un esempio precedente.
Il link [Ajout] dà accesso a un modulo per l’aggiunta di un prodotto:
![]() |
n. | nome | tipo | ruolo |
pannello | |||
TextBox | nome del prodotto | ||
RequiredFieldValidator | verifica la presenza di un valore in [txtNom] | ||
TextBox | prezzo del prodotto | ||
RequiredFieldValidator | verifica la presenza di un valore in [txtPrix] | ||
CompareValidator | verifica che il prezzo sia >= 0 | ||
Pulsante | pulsante [submit] per l'aggiunta del prodotto | ||
Etichetta | Testo informativo sul risultato dell'operazione di aggiunta |
Il database [produits] potrebbe non essere disponibile all'avvio dell'applicazione. In tal caso, all'utente viene presentata la vista [erreurs]:
![]() |
n. | nome | tipo | ruolo |
pannello | |||
Ripetitore | elenco degli errori |
9.6.3. Configurazione dei contenitori di dati
I cinque contenitori [DataGrid] sono stati configurati in [WebMatrix]. Sono stati formattati (colori e bordi) tramite il collegamento [Configuration automatique] nel loro pannello delle proprietà. Il contenitore [DataGrid1] è stato configurato utilizzando il collegamento [Générateur de propriétés] presente nello stesso pannello. È stato abilitato l'ordinamento (scheda [Général]):

Le colonne non vengono generate automaticamente, a differenza degli altri quattro contenitori. Sono state definite manualmente tramite la procedura guidata:

In primo luogo sono state create due colonne di tipo [Colonne connexe] denominate [nom] e [prix]. Sono state associate rispettivamente ai campi [nom] e [prix] della fonte dati che i contenitori visualizzeranno. Ecco, ad esempio, la configurazione della colonna [nom]:

L’espressione di ordinamento è quella che dovrà essere inserita dopo la clausola [order by] dell’istruzione SQL [select], che verrà eseguita quando l’utente cliccherà sul nome della colonna di intestazione [nom] associata al campo [nom] del [DataGrid]. Qui abbiamo inserito [nom] in modo che la clausola di ordinamento sia [order by nom]. Vedremo che la modificheremo in modo che diventi [order by nom asc] o [order by nom desc] a seconda dell’ordine di ordinamento scelto dall’utente.
Abbiamo inoltre creato due colonne di pulsanti:

La colonna [Modifier, Mettre à jour, Annuler] ci consentirà di modificare un prodotto, mentre la colonna [Supprimer] di eliminarlo. Ciascuna di queste colonne può essere configurata. La colonna [Modifier, Mettre à jour, Annuler] offre la seguente configurazione:

Si nota che è possibile modificare i testi dei pulsanti. Per quanto riguarda i pulsanti, è possibile scegliere tra link e pulsanti (elenco a discesa in alto). In questo caso sono stati scelti i link. La configurazione della colonna [Supprimer] è analoga. Oltre a questa procedura guidata di configurazione, abbiamo utilizzato direttamente la finestra delle proprietà di [DataGrid] per impostare la proprietà [DataKeyField], che indica con quale campo della fonte dati vengono indicizzate le righe di [DataGrid]. In questo caso viene utilizzata la chiave primaria della tabella dei prodotti:

Alla fine, questa configurazione genera il seguente codice di presentazione:
<asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4" AllowPaging="True" AutoGenerateColumns="False" DataKeyField="id">
<SelectedItemStyle ...></SelectedItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom"></asp:BoundColumn>
<asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix"></asp:BoundColumn>
<asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre à jour" CancelText="Annuler" EditText="Modifier"></asp:EditCommandColumn>
<asp:ButtonColumn Text="Supprimer" CommandName="Delete"></asp:ButtonColumn>
</Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" ...></PagerStyle>
</asp:DataGrid>
Come sempre, una volta acquisita una certa esperienza, è possibile scrivere direttamente tutto o parte del codice sopra riportato.
Gli altri contenitori [DataGrid] presentano la configurazione predefinita ottenuta tramite la generazione automatica delle colonne del [DataGrid] a partire da quelle della fonte dati a cui è associato.
Il contenitore [Repeater] serve a visualizzare un elenco di errori. La sua configurazione viene effettuata direttamente nel codice di presentazione:
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
Ogni riga del componente visualizza il valore [Container.DataItem], c.a.d, corrispondente all'elenco dei dati. Quest'ultimo sarà di tipo [ArrayList] e rappresenterà un elenco di errori.
9.6.4. Il codice di presentazione dell'applicazione
Questo è contenuto nel file [main.aspx]:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
</HEAD>
<body>
<form id="Form1" runat="server">
<P>
<table>
<tr>
<td><FONT size="6">Options :</FONT></td>
<td>
<asp:linkbutton id="lnkFiltre" runat="server" CausesValidation="False">
Filtrage
</asp:linkbutton>
</td>
<td>
<asp:linkbutton id="lnkMisajour" runat="server" CausesValidation="False">
Mise à jour
</asp:linkbutton>
</td>
<td>
<asp:linkbutton id="lnkAjout" runat="server" CausesValidation="False">
Ajout
</asp:linkbutton>
</td>
</tr>
</table>
</P>
<HR width="100%" SIZE="1">
<table>
<tr>
<td>
<asp:panel id="vueFormulaire" runat="server">
<P>Condition de filtrage sur la table LISTE. Exemple : prix<100 and
prix>50</P>
<P>
<asp:TextBox id="txtFiltre" runat="server" Columns="60"></asp:TextBox></P>
<P>Nombre de lignes par page :
<asp:TextBox id="txtPages" runat="server" Columns="3">5</asp:TextBox>
<asp:RequiredFieldValidator id="rfvLignes" runat="server" Display="Dynamic"
ControlToValidate="txtPages" ErrorMessage="Indiquez le nombre de lignes par page"
EnableClientScript="False">
</asp:RequiredFieldValidator></P>
<P>
<asp:RangeValidator id="rvLignes" runat="server" Display="Dynamic"
ControlToValidate="txtPages" ErrorMessage="Vous devez indiquer un nombre entre 3 et 10"
EnableClientScript="False" MaximumValue="10" MinimumValue="3" Type="Integer"
EnableViewState="False">
</asp:RangeValidator></P>
<P>
<asp:Label id="lblinfo1" runat="server"></asp:Label></P>
<P>
<asp:Button id="btnExécuter" runat="server" CausesValidation="False"
EnableViewState="False" Text="Exécuter">
</asp:Button></P>
</asp:panel>
<asp:panel id="vueProduits" runat="server">
<TABLE>
<TR>
<TD align="center" bgColor="#ff9966">
<P>Tri
<asp:RadioButton id="rdCroissant" runat="server" Text="croissant"
GroupName="rdTri" Checked="True">
</asp:RadioButton>
<asp:RadioButton id="rdDécroissant" runat="server" Text="décroissant"
GroupName="rdTri">
</asp:RadioButton></P>
</TD>
<TD align="center" bgColor="#ff9966">Tous les produits
</TD>
<TD align="center" bgColor="#ff9966">Suppressions
</TD>
<TD align="center" bgColor="#ff9966">Modifications
</TD>
<TD align="center" bgColor="#ff9966">Ajouts
</TD>
<TR>
<TD vAlign="top">
<P>
<asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4"
AllowPaging="True" .... AutoGenerateColumns="False" DataKeyField="id">
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom">
</asp:BoundColumn>
<asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix">
</asp:BoundColumn>
<asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre à jour"
CancelText="Annuler" EditText="Modifier">
</asp:EditCommandColumn>
<asp:ButtonColumn Text="Supprimer" CommandName="Delete">
</asp:ButtonColumn>
</Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" ....>
</PagerStyle>
</asp:DataGrid>
</P>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid2" runat="server" ...>
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ....></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ... Mode="NumericPages"></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid3" runat="server" ...>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ...></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid4" runat="server" ...>
<ItemStyle ....></ItemStyle>
<HeaderStyle ....></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle .... Mode="NumericPages"></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid5" runat="server....>
<AlternatingItemStyle ...></AlternatingItemStyle>
<HeaderStyle ..></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ....></PagerStyle>
</asp:DataGrid>
</TD>
</TR>
</TABLE>
<P></P>
<P>
<asp:Label id="lblInfo2" runat="server"></asp:Label></P>
</asp:panel>
<asp:panel id="vueErreurs" runat="server">
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</asp:panel>
<asp:panel id="vueAjout" EnableViewState="False" Runat="server">
<P>Ajout d'un produit</P>
<P>
<TABLE ... border="1">
<TR>
<TD>nom</TD>
<TD>
<asp:TextBox id="txtNom" runat="server" Columns="30"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvNom" runat="server" ControlToValidate="txtNom"
ErrorMessage="Vous devez indiquer un nom">
</asp:RequiredFieldValidator>
</TD>
</TR>
<TR>
<TD>prix</TD>
<TD>
<asp:TextBox id="txtPrix" runat="server" Columns="10"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvPrix" runat="server" ControlToValidate="txtPrix"
ErrorMessage="Vous devez indiquer un prix">
</asp:RequiredFieldValidator>
<asp:CompareValidator id="cvPrix" runat="server" ControlToValidate="txtPrix"
ErrorMessage="CompareValidator" Type="Double" Operator="GreaterThanEqual"
ValueToCompare="0">
</asp:CompareValidator>
</TD>
</TR>
</TABLE>
</P>
<P>
<asp:Button id="btnAjouter" runat="server" CausesValidation="False"
Text="Ajouter">
</asp:Button></P>
<P>
<asp:Label id="lblInfo3" runat="server"></asp:Label></P>
</asp:panel>
</td>
</tr>
</table>
</form>
</body>
</HTML>
Si notino i seguenti punti:
- la pagina è composta da quattro contenitori (panel) [vueFormulaire, vueProduits, vueAjout, vueErreurs] che costituiranno le quattro viste dell'applicazione.
- i pulsanti o i link di tipo [submit] hanno la proprietà [CausesValidation=false]. La proprietà [causesValidation=true] fa sì che vengano eseguiti tutti i controlli di validità della pagina. Tuttavia, in questo caso, non tutti i controlli di validità devono essere effettuati contemporaneamente. Quando si effettua un'aggiunta, ad esempio, non si desidera che vengano eseguiti i controlli relativi al numero di righe per pagina. Specificheremo quindi noi stessi quali controlli di validità devono essere effettuati.
9.6.5. Il codice di controllo [global.asax]
Il controller [global.asax] è il seguente:
Il codice associato [global.asax.vb]:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' si recuperano le informazioni di configurazione
Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
Dim erreurs As New ArrayList
If IsNothing(chaînedeConnexion) Then erreurs.Add("Le paramètre [OLEDBStringConnection] n'a pas été initialisé")
If IsNothing(defaultProduitsPage) Then
erreurs.Add("Le paramètre [defaultProduitsPage] n'a pas été initialisé")
Else
Try
Dim defProduitsPage As Integer = CType(defaultProduitsPage, Integer)
If defProduitsPage <= 0 Then Throw New Exception
Catch ex As Exception
erreurs.Add("Le paramètre [defaultProduitsPage] a une valeur incorrecte")
End Try
End If
' Ci sono errori di configurazione?
If erreurs.Count <> 0 Then
' si annotano gli errori
Application("erreurs") = erreurs
' si esce
Exit Sub
End If
' qui non ci sono errori di configurazione
' si crea un oggetto prodotti
Dim dtProduits As DataTable
Try
dtProduits = New produits(chaînedeConnexion).getProduits
Catch ex As ExceptionProduits
'si è verificato un errore di accesso ai prodotti, lo si registra nell'applicazione
Application("erreurs") = ex.erreurs
Exit Sub
Catch ex As Exception
' errore non gestito
erreurs.Add(ex.Message)
Application("erreurs") = erreurs
' uscita dalla subroutine
End Try
' qui non ci sono errori di inizializzazione
' si memorizza il numero di prodotti per pagina
Application("defaultProduitsPage") = defaultProduitsPage
' si memorizza la tabella dei prodotti
Application("dtProduits") = dtProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' inizializzazione delle variabili di sessione
If IsNothing(Application("erreurs")) Then
' visualizzazione della tabella dei prodotti
Session("dvProduits") = CType(Application("dtProduits"), DataTable).DefaultView
' numero di prodotti per pagina
Session("nbProduitsPage") = Application("defaultProduitsPage")
' pagina corrente visualizzata
Session("pageCourante") = 0
End If
End Sub
End Class
In [Application_Start], si inizia recuperando due informazioni dal file di configurazione [web.config] dell'applicazione:
- OLEDBStringConnection: la stringa OLEDB per la connessione al database dei prodotti
- defaultProduitsPage: il numero predefinito di prodotti per pagina visualizzata
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="OLEDBStringConnection" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\devel\aspnet\poly\webforms3\vs\majproduits1\produits.mdb" />
<add key="defaultProduitsPage" value="5" />
</appSettings>
</configuration>
Se una di queste due informazioni è assente, viene generato un elenco di errori e inserito nell'applicazione. Lo stesso vale se il parametro [defaultProduitsPage] esiste ma non è corretto. Se entrambi i parametri previsti sono presenti e corretti, viene creata una tabella [dtProduits] e inserita nell’applicazione. È questa tabella che verrà utilizzata e aggiornata dai vari clienti. Il database, invece, rimarrà invariato. Ci occuperemo del suo aggiornamento in una prossima applicazione. Questa tabella viene creata a partire da un'istanza della classe [produits], già esaminata in precedenza, e dal relativo metodo [getProduits]. L'ottenimento della tabella [dtProduits] potrebbe non andare a buon fine. In tal caso, è noto che la classe [produits] genera un'eccezione di tipo [ExceptionProduits]. Qui viene intercettata e l'elenco degli errori associato viene inserito nell'applicazione associata alla chiave [erreurs]. La presenza di questa chiave nelle informazioni registrate nell’applicazione verrà verificata ad ogni elaborazione della richiesta. Se viene rilevata, la vista [erreurs] verrà inviata al cliente.
Sebbene la tabella [dtProduits] sia condivisa da tutti i client web, ciascuno di essi disporrà comunque di una propria vista [dvProduits] su di essa. Infatti, ogni client web ha la possibilità di impostare un filtro sulla tabella [dtProduits] e un ordine di ordinamento. Queste informazioni specifiche di ciascun client web vengono memorizzate nella vista del client. Pertanto, essa viene creata in [Session_Start] per essere inserita nella sessione specifica di ciascun client. Si utilizza l’attributo [DefaultView] della tabella [dtProduits] per ottenere una vista predefinita della tabella. Inizialmente non sono presenti né filtri né ordini di ordinamento. Inoltre, nella sessione vengono inserite anche due informazioni:
- il numero di prodotti per pagina con chiave [nbProduitsPage]. All'avvio della sessione, questo numero è pari al valore predefinito definito nel file di configurazione.
- il numero della pagina corrente dei prodotti. Inizialmente, si tratta della prima pagina.
9.6.6. Il codice del controller [main.aspx.vb]
Lo scheletro del controller è il seguente:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Imports System.Web.UI.WebControls
Public Class main
Inherits System.Web.UI.Page
' componenti della pagina
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtFiltre As System.Web.UI.WebControls.TextBox
Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
Protected WithEvents lblinfo1 As System.Web.UI.WebControls.Label
Protected WithEvents lblErreurs As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid2 As System.Web.UI.WebControls.DataGrid
Protected WithEvents vueProduits As System.Web.UI.WebControls.Panel
Protected WithEvents vueAjout As System.Web.UI.WebControls.Panel
Protected WithEvents lnkMisajour As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkFiltre As System.Web.UI.WebControls.LinkButton
Protected WithEvents txtNom As System.Web.UI.WebControls.TextBox
Protected WithEvents txtPrix As System.Web.UI.WebControls.TextBox
Protected WithEvents btnAjouter As System.Web.UI.WebControls.Button
Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label
Protected WithEvents rfvLignes As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rvLignes As System.Web.UI.WebControls.RangeValidator
Protected WithEvents rfvNom As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rfvPrix As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents cvPrix As System.Web.UI.WebControls.CompareValidator
Protected WithEvents lnkAjout As System.Web.UI.WebControls.LinkButton
Protected WithEvents DataGrid3 As System.Web.UI.WebControls.DataGrid
Protected WithEvents DataGrid4 As System.Web.UI.WebControls.DataGrid
Protected WithEvents DataGrid5 As System.Web.UI.WebControls.DataGrid
' dati della pagina
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected defaultProduitsPage As Integer
Protected erreur As Boolean = False
' caricamento della pagina
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheErreurs()
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
....
End Sub
Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
...
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
...
End Sub
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
....
End Sub
Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
....
End Sub
Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
...
End Sub
Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
...
End Sub
Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
...
End Sub
Private Sub supprimerProduit(ByVal idProduit As Integer)
...
End Sub
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
....
End Sub
Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
...
End Sub
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
...
End Sub
Private Sub afficheAjout()
...
End Sub
Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
....
End Sub
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
....
End Sub
End Class
9.6.7. Dati di istanza
La classe [main] utilizza i seguenti dati di istanza:
Public Class main
Inherits System.Web.UI.Page
' componenti della pagina
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
...
' dati della pagina
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected defaultProduitsPage As Integer
la tabella [DataTable] dei prodotti - comune a tutti i clienti | |
la vista [DataView] dei prodotti - specifica per ogni cliente | |
numero di prodotti per pagina proposto di default |
9.6.8. La procedura [Page_Load] per il caricamento della pagina
Il codice della procedura [page_Load] è il seguente:
' caricamento pagina
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' si verifica se l'applicazione presenta un errore
If Not IsNothing(Application("erreurs")) Then
' l'applicazione non si è inizializzata correttamente
afficheErreurs(CType(Application("erreurs"), ArrayList))
Exit Sub
End If
' si recupera un riferimento dalla tabella dei prodotti
dtProduits = CType(Application("dtProduits"), DataTable)
' si recupera la vista dei prodotti
dvProduits = CType(Session("dvProduits"), DataView)
' si recupera il numero di prodotti per pagina
defaultProduitsPage = CType(Application("defaultProduitsPage"), Integer)
' si annulla un eventuale aggiornamento in corso
DataGrid1.EditItemIndex = -1
'prima richiesta
If Not IsPostBack Then
' viene visualizzato il modulo iniziale
txtPages.Text = defaultProduitsPage.ToString
afficheFormulaire()
End If
End Sub
- Si inizia innanzitutto verificando se la tabella dei prodotti è stata caricata all'avvio dell'applicazione. In caso contrario, viene visualizzata la vista [erreurs] con i messaggi di errore appropriati, quindi si esce dalla procedura dopo aver impostato il valore booleano [erreur] su vero. Questo indicatore verrà verificato da alcune procedure.
- Si recupera la tabella dei prodotti [dtProduits] dall’applicazione e la si assegna alla variabile di istanza [dtProduits] affinché sia condivisa da tutti i metodi della pagina.
- Si procede allo stesso modo con la vista [dvProduits], recuperata dalla sessione, e con il numero predefinito di prodotti per pagina, recuperato dall’applicazione.
- Se si tratta della prima richiesta del cliente, gli viene presentato il modulo per la definizione delle condizioni di filtro e di paginazione con una paginazione predefinita di [defaultProduitsPage] prodotti per pagina.
- Si annulla la modalità di modifica del componente [dataGrid1]. Questo componente ha un attributo [EditItemIndex] che rappresenta l’indice dell’elemento attualmente in fase di modifica. Se [EditItemIndex]=-1, allora non vi è alcun elemento in fase di modifica. Se [EditItemIndex]=i, allora l’elemento n. i del componente [DataGrid] è in fase di modifica. Viene quindi visualizzato in modo diverso dagli altri elementi del [dataGrid]:

Nell’esempio sopra riportato, l’elemento [Produit8, 80] si trova in modalità [édition]. Come si vede sopra, l’utente può confermare l’aggiornamento tramite il link [Mettre à jour] e annullarlo tramite il link [Annuler]. Può inoltre utilizzare altri link che non hanno nulla a che vedere con l’elemento in fase di modifica. Inserendo [DataGrid1.EditItemIndex=-1] ad ogni caricamento della pagina, ci si assicura di annullare sistematicamente la modalità di modifica di [DataGrid1]. Gli verrà assegnato un valore diverso solo se è stato cliccato un link [Modifier]. Ciò avverrà nella procedura che gestisce tale evento. Si verificheranno le seguenti situazioni:
- è stato cliccato il link [Modifier] dell’elemento n. 8 di [DataGrid1]. [Page_Load] imposta innanzitutto -1 in [DataGrid1.EditItemIndex]. Successivamente, la procedura che gestisce l’evento [Modifier] inserirà 8 in [DataGrid1.EditItemIndex]. Alla fine, quando la pagina verrà rinviata al cliente, l’elemento n. 8 sarà effettivamente in modalità [édition] e apparirà come mostrato sopra.
- L'utente modifica il prodotto che si trova in modalità [édition] e conferma la modifica tramite il link [Mettre à jour]. [Page_Load] inserisce -1 in [DataGrid1.EditItemIndex]. Successivamente verrà eseguita la procedura che gestisce l’evento [Mettre à jour] e l’elemento n. 8 di [dtProduits] verrà aggiornato. Quando la pagina verrà rinviata al cliente, nessun elemento di [DataGrid1] sarà in modalità di modifica (DataGrid.EditItemIndex=-1).
- Se un utente ha avviato l'aggiornamento di un prodotto, sarebbe logico che interrompesse tale aggiornamento tramite [Annuler]. Tuttavia, nulla gli impedisce di cliccare su un altro link. Dobbiamo quindi prevedere questo caso. In questo caso, come in quelli precedenti, [Page_Load] inizia inserendo -1 in [DataGrid1.EditItemIndex], dopodiché verrà eseguita la procedura che gestisce l’evento verificatosi. Non modificherà la proprietà [DataGrid1.EditItemIndex], che rimarrà quindi con il valore -1. Quando la pagina verrà rinviata al cliente, nessun elemento di [DataGrid1] sarà in modalità di modifica.
Si nota che, impostando [EditItemIndex] a -1 al momento del caricamento della pagina, si evita di doversi preoccupare se l’utente fosse o meno in modalità di aggiornamento quando ha cliccato su un link.
9.6.9. Visualizzazione delle viste [erreurs], [formulaire] e [ajout]
La visualizzazione della vista [erreurs] viene richiesta al caricamento della pagina [Page_Load] se si rileva che l'applicazione non si è inizializzata correttamente. La visualizzazione avviene associando il componente dati [rptErreurs] all’elenco degli errori passato come parametro e rendendo visibile il contenitore appropriato. Infine, i tre link [Filtre, Mise à jour, Ajout] vengono nascosti poiché, in caso di errore, l’applicazione non può essere utilizzata.
Private Sub afficheErreurs(ByVal erreurs As ArrayList)
' si associa l'elenco degli errori al ripetitore rptErreurs
With rptErreurs
.DataSource = erreurs
.DataBind()
End With
'si disabilitano le opzioni
lnkAjout.Visible = False
lnkMisajour.Visible = False
lnkFiltre.Visible = False
' viene visualizzata la vista [erreurs]
vueErreurs.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueAjout.Visible = False
End Sub
Le altre visualizzazioni vengono richieste in base alle opzioni proposte all'utente:

La visualizzazione della vista [Ajout] viene richiesta quando l'utente clicca sul link [Ajout] nella pagina:
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
' si visualizza la vista [ajout]
afficheAjout()
End Sub
Private Sub afficheAjout()
' visualizza la vista "Aggiungi"
vueAjout.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueErreurs.Visible = False
End Sub
La visualizzazione della vista [Formulaire] viene richiesta quando l'utente clicca sul link [Filtrage] nella pagina. A quel punto deve essere visualizzata la vista che consente di definire
- il filtro sulla tabella dei prodotti
- il numero di prodotti visualizzati in ogni pagina web
Viene visualizzato un modulo che riprende i valori memorizzati nella sessione:
Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
' definire le condizioni di filtro
txtFiltre.Text = dvProduits.RowFilter
' definire l'impaginazione
txtPages.Text = CType(Session("nbProduitsPage"), String)
' viene visualizzato il modulo di filtro
afficheFormulaire()
End Sub
La condizione di filtro di una vista è definita nel suo attributo [RowFilter]. Ricordiamo che la vista filtrata e impaginata si chiama [dvProduits] ed è stata recuperata nella sessione al momento del caricamento della pagina [Page_Load]. La condizione di filtro viene quindi recuperata in [dvProduits.RowFilter]. Anche il numero di prodotti per pagina viene recuperato dalla sessione. Se l’utente non ha mai definito questa informazione, tale numero è pari al numero predefinito di prodotti per pagina. Fatto ciò, viene visualizzato il modulo che consente di definire queste due informazioni:
Private Sub afficheFormulaire()
' viene visualizzata la vista [formulaire]
vueFormulaire.Visible = True
vueErreurs.Visible = False
vueProduits.Visible = False
vueAjout.Visible = False
End Sub

9.6.10. Convalida della vista [formulaire]
Il clic sul pulsante [Exécuter] qui sopra viene gestito dalla seguente procedura:
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
' pagina valida?
rfvLignes.Validate()
rvLignes.Validate()
If Not rfvLignes.IsValid Or Not rvLignes.IsValid Then
afficheFormulaire()
Exit Sub
End If
' allegato dei dati filtrati alla griglia
Try
dvProduits.RowFilter = txtFiltre.Text.Trim
Catch ex As Exception
lblinfo1.Text = "Erreur de filtrage (" + ex.Message + ")"
afficheFormulaire()
Exit Sub
End Try
' si memorizza il numero di prodotti per pagina
Session("nbProduitsPage") = txtPages.Text
'e la pagina corrente
Session("pageCourante") = 0
' tutto a posto - si visualizzano i dati
afficheProduits(0, CType(txtPages.Text, Integer))
End Sub
Ricordiamo innanzitutto che la pagina presenta due componenti di convalida:
n. | nome | tipo | ruolo |
RequiredFieldValidator | verifica la presenza di un valore in [txtPages] | ||
RangeValidator | verifica che txtPages rientri nell'intervallo [3,10] |
La procedura inizia eseguendo il codice di controllo dei due componenti sopra indicati tramite il loro metodo [Validate], quindi verifica il valore del loro attributo [IsValid]. Quest'ultimo assume il valore [true] solo se il dato verificato è risultato valido. Se i test di validità di uno dei due componenti hanno dato esito negativo, viene nuovamente visualizzata la vista [formulaire] affinché l'utente possa correggere il proprio errore o i propri errori. La condizione di filtro viene applicata all’attributo [dvProduits.RowFilter]. In questo caso può verificarsi un’eccezione se l’utente ha inserito un criterio di filtro errato, come mostrato di seguito:

In questo caso, viene nuovamente visualizzata la vista [formulaire]. Se entrambe le informazioni inserite sono corrette, nella sessione vengono inseriti due dati:
- il numero di prodotti per pagina scelto dall’utente
- il numero della pagina che verrà visualizzata – inizialmente la pagina 0
Queste due informazioni vengono utilizzate ogni volta che viene visualizzata la vista [produits].
9.6.11. Visualizzazione della vista [produits]
La vista [produits] è la seguente:

Abbiamo 5 componenti [DataGrid], ciascuno dei quali espone una vista specifica della tabella dei prodotti [dtProduits]. Partendo da sinistra:
nome | ruolo |
griglia di visualizzazione di una vista filtrata della tabella dei prodotti. Il filtro è quello impostato dalla vista [formulaire]. Abbiamo inoltre .AllowPaging=true, .AllowSorting=true | |
visualizza l'intera tabella dei prodotti - consente di tenere traccia degli aggiornamenti | |
visualizzerà i prodotti eliminati nella tabella dei prodotti | |
visualizzerà i prodotti modificati nella tabella dei prodotti | |
visualizzerà i prodotti aggiunti nella tabella dei prodotti |
La procedura [afficheProduits] è incaricata di visualizzare la vista precedente:
Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
' si associano i dati ai due componenti [DataGrid]
Application.Lock()
With DataGrid1
.DataSource = dvProduits
.PageSize = taillePage
.CurrentPageIndex = page
.DataBind()
End With
Dim dvCurrent As New DataView(dtProduits)
dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
With DataGrid2
.DataSource = dvCurrent
.DataBind()
End With
Dim dvSupp As DataView = New DataView(dtProduits)
dvSupp.RowStateFilter = DataViewRowState.Deleted
With DataGrid3
.DataSource = dvSupp
.DataBind()
End With
Dim dvModif As DataView = New DataView(dtProduits)
dvModif.RowStateFilter = DataViewRowState.ModifiedOriginal
With DataGrid4
.DataSource = dvModif
.DataBind()
End With
Dim dvAjout As DataView = New DataView(dtProduits)
dvAjout.RowStateFilter = DataViewRowState.Added
With DataGrid5
.DataSource = dvAjout
.DataBind()
End With
Application.UnLock()
' viene visualizzata la vista [produits]
vueProduits.Visible = True
vueFormulaire.Visible = False
vueErreurs.Visible = False
vueAjout.Visible = False
' si salva la pagina corrente
Session("pageCourante") = page
End Sub
La procedura accetta due parametri:
- il numero di pagina [page] da visualizzare in [DataGrid1]
- il numero di prodotti [taillePage] per pagina
Il collegamento di [DataGrid] alla tabella dei prodotti avviene tramite 5 diverse viste.
- [DataGrid1] è collegata alla vista paginata e ordinata [dvProduits].
With DataGrid1
.DataSource = dvProduits
.PageSize = taillePage
.CurrentPageIndex = page
.DataBind()
End With
È proprio durante questo collegamento di [DataGrid1] alla sua fonte dati che servono le due informazioni passate come parametri alla procedura.
- [DataGrid2] è collegato a una vista che mostra tutti gli elementi attuali della tabella [dtProduits]. Come [DataGrid1], presenta una vista aggiornata della tabella [dtProduits], ma senza impaginazione né ordinamento.
Dim dvCurrent As New DataView(dtProduits)
dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
With DataGrid2
.DataSource = dvCurrent
.DataBind()
End With
Qui utilizziamo l'attributo [RowStateFilter] della classe [DataView]. Una riga di una tabella o, in questo caso, di una vista possiede un attributo [RowState] che indica lo stato della riga. Eccone alcuni:
- (continua)
- [Added]: la riga è stata aggiunta
- [Modified]: la riga è stata modificata
- [Deleted]: la riga è stata eliminata
- [Unchanged]: la riga non è cambiata
Quando la tabella [dtProduits] è stata creata inizialmente in [Application_Start], tutte le sue righe si trovavano nello stato [Unchanged]. Questo stato cambierà man mano che i client web effettueranno gli aggiornamenti:
- (continua)
- quando un cliente crea un nuovo prodotto, viene aggiunta una riga alla tabella [dtProduits]. Essa si troverà nello stato [Added].
- quando un prodotto viene modificato, il suo stato passa da [Unchanged] a [Modified].
- Quando un prodotto viene eliminato, la riga non viene eliminata fisicamente. Viene invece contrassegnata come da eliminare e il suo stato passa a [Deleted]. È possibile annullare tale eliminazione.
L’attributo [RowStateFilter] della classe [DataView] consente di filtrare una vista in base allo stato [RowState] delle sue righe. I suoi valori possibili sono quelli dell’enumerazione [DataRowViewState]:
- (continua)
- DataRowViewState.CurrentRows: le righe non eliminate vengono visualizzate con i loro valori attuali
- DataRowViewState.Added: le righe aggiunte vengono visualizzate con i loro valori attuali
- DataRowViewState.Deleted: le righe eliminate vengono visualizzate con i loro valori originali
- DataRowViewState.ModifiedOriginal: le righe modificate vengono visualizzate con i loro valori originali
- DataRowViewState.ModifiedCurrent: le righe modificate vengono visualizzate con i loro valori attuali
Una riga modificata presenta due valori: il valore originale, ovvero quello che la riga aveva prima della prima modifica, e il valore attuale, ottenuto dopo una o più modifiche. Questi due valori vengono conservati contemporaneamente.
Le righe da 2 a 5 di DataGrid mostrano una vista della tabella [dtProduits] filtrata in base all’attributo [RowStateFilter]
DataGrid | Filtro |
RowStateFilter= DataRowViewState.CurrentRows | |
RowStateFilter= DataRowViewState.Deleted | |
RowStateFilter= DataRowViewState.ModifiedOriginal | |
RowStateFilter= DataRowViewState.Added |
La tabella [dtPoduits] viene aggiornata contemporaneamente da diversi client web, il che può causare conflitti di accesso alla tabella. Quando un client visualizza le proprie viste della tabella [dtProduits], si vuole evitare che lo faccia mentre la tabella si trova in uno stato instabile perché è in fase di modifica da parte di un altro client web. Pertanto, ogni volta che un client necessita della tabella [dtProduits] in lettura come sopra descritto o in scrittura per aggiungere, modificare ed eliminare prodotti, si sincronizzerà con gli altri client tramite la seguente sequenza:
Nel codice dell’applicazione possono esserci diverse sezioni critiche:
Il meccanismo è il seguente:
- uno o più client raggiungono l'istruzione [Application.Lock] della sezione critica 1. Si tratta di un distributore di token di ingresso unico. Un solo client ottiene questo token. Chiamiamolo C1.
- Finché il client C1 non ha restituito il token tramite [Application.Unlock], nessun altro client è autorizzato ad accedere a una sezione critica controllata da [Application.Lock]. Nell’esempio sopra riportato, nessun client può quindi accedere alle sezioni critiche 1 e 2.
- Il client C1 esegue [Application.Unlock] e restituisce quindi il token di accesso. Quest’ultimo può quindi essere assegnato a un altro client. I passaggi da 1 a 3 vengono ripetuti.
Con questo meccanismo, garantiremo che un solo client abbia accesso alla tabella [dtProduits], sia in lettura che in scrittura.
Il collegamento [Mise à jour] visualizza anche la vista [Produits]:

La procedura associata è la seguente:
Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
' visualizzazione della vista prodotti
afficheProduits(CType(Session("pageCourante"), Integer), CType(Session("nbProduitsPage"), Integer))
End Sub
È necessario visualizzare la vista [produits]. Ciò avviene tramite la procedura [afficheProduits], che accetta due parametri: il numero della pagina corrente da visualizzare e il numero di prodotti per pagina da visualizzare. Queste due informazioni sono presenti nella sessione, eventualmente con i loro valori iniziali se l'utente non li ha mai definiti autonomamente.
9.6.12. Impaginazione e ordinamento di [DataGrid1]
Abbiamo già visto le procedure che gestiscono l’impaginazione e l’ordinamento di una [DataGrid] in un altro esempio. In caso di cambio di pagina, si visualizza la vista [produits] passando il numero della nuova pagina come parametro alla procedura [afficheProduits].
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' cambio di pagina
afficheProduits(e.NewPageIndex, DataGrid1.PageSize)
End Sub
La procedura [DataGrid1_SortCommand] viene eseguita quando l’utente fa clic sull’intestazione di una delle colonne di [DataGrid1]. È quindi necessario assegnare l'espressione di ordinamento all'attributo [Sort] della vista [dvProduits] visualizzata da [DataGrid1]. Quest’ultima è sintatticamente equivalente all’espressione di ordinamento che segue la clausola [order by] dell’istruzione SQL SELECT. L'argomento [e] della procedura possiede un attributo [SortExpression] che fornisce l'espressione di ordinamento associata alla colonna la cui intestazione è stata selezionata. Al momento della creazione del componente [DataGrid1], tale espressione di ordinamento era già stata definita. Di seguito è riportata quella definita per la colonna [nom] di [DataGrid1]:

Se l’espressione di ordinamento non è stata definita durante la progettazione di [DataGrid], viene utilizzato il nome del campo dati associato alla colonna di [DataGrid]. L'ordine di ordinamento (crescente, decrescente) viene qui impostato dall'utente tramite i pulsanti di opzione [rdCroissant, rd Décroissant]:

La procedura di ordinamento è la seguente:
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
' si ordina la vista dati
With dvProduits
.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
End With
' visualizzazione
afficheProduits(0, DataGrid1.PageSize)
End Sub
9.6.13. Eliminazione di un prodotto
Per eliminare un prodotto, l'utente utilizza il link [Supprimer] presente nella riga del prodotto:

Viene quindi eseguita la procedura [DataGrid1_DeleteCommand]:
Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
' eliminazione di un prodotto
' chiave del prodotto da eliminare
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
' eliminazione del prodotto
Dim erreur As Boolean = False
Try
supprimerProduit(idProduit)
Catch ex As Exception
' problema
lblInfo2.Text = ex.Message
erreur = True
End Try
' cambio di pagina?
Dim page As Integer = DataGrid1.CurrentPageIndex
If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
page = DataGrid1.CurrentPageIndex - 1
If page < 0 Then page = 0
End If
' visualizzazione prodotti
afficheProduits(page, DataGrid1.PageSize)
End Sub
L'aggiornamento dei prodotti avverrà tramite la chiave [id] del prodotto. Infatti, la colonna [id] della tabella [dtProduits] è la chiave primaria e, grazie ad essa, possiamo individuare nella tabella [dtProduits] la riga del prodotto che viene aggiornata nel componente [DataGrid1]. La procedura inizia quindi recuperando la chiave del prodotto il cui link [Supprimer] è stato cliccato:
' codice del prodotto da eliminare
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
Sappiamo che [e.Item] è l’elemento di [DataGrid1] all’origine dell’evento. In sostanza, questo elemento è una riga di [DataGrid]. [e.Item.ItemIndex] è il numero della riga all'origine dell'evento. Questo indice è relativo alla pagina attualmente visualizzata. Pertanto, la prima riga della pagina visualizzata ha la proprietà [ItemIndex=0], anche se nella tabella dei prodotti ha il numero 17. [DataGrid1.DataKeys] è l’elenco delle chiavi di [DataGrid]. Poiché in fase di progettazione abbiamo inserito [DataGrid1.DataKey=id], le chiavi di [DataGrid1] sono costituite dai valori della colonna [id] della tabella [dtProduits], che è al contempo la chiave primaria. Pertanto, [DataGrid1.DataKeys(e.Item.ItemIndex)] è la chiave [id] del prodotto da eliminare. Una volta ottenuto questo dato, se ne richiede l’eliminazione tramite la procedura [supprimerProduit]:
' eliminazione del prodotto
Dim erreur As Boolean = False
Try
supprimerProduit(idProduit)
Catch ex As Exception
' pb
lblInfo2.Text = ex.Message
erreur = True
End Try
In alcuni casi questa operazione di eliminazione potrebbe non andare a buon fine. Vedremo perché. In tal caso viene generata un'eccezione, che viene gestita in questa sede. In caso di errore, il codice [DataGrid1] non deve essere modificato. Viene aggiunto solo un messaggio di errore alla vista [produits]. Se non si verifica alcun errore e l’utente ha appena eliminato l’unico prodotto dalla pagina corrente, viene visualizzata la pagina precedente.
' cambio di pagina?
Dim page As Integer = DataGrid1.CurrentPageIndex
If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
page = DataGrid1.CurrentPageIndex - 1
If page < 0 Then page = 0
End If
In ogni caso, la vista [produits] viene visualizzata nuovamente con la procedura [afficheProduits]:
La procedura [supprimerProduit] è la seguente:
Private Sub supprimerProduit(ByVal idProduit As Integer)
Dim erreur As String
Try
' sincronizzazione
Application.Lock()
' ricerca della riga da eliminare
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' eliminazione della riga
ligne.Delete()
End If
Catch ex As Exception
erreur = String.Format("Erreur de suppression : {0}", ex.Message)
Finally
' fine sincronizzazione
Application.UnLock()
End Try
' in caso di errore viene generata un'eccezione
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
La procedura riceve come parametro la chiave del prodotto da eliminare. Se ci fosse un solo cliente da aggiornare, tale eliminazione potrebbe avvenire come segue:
Quando più clienti effettuano gli aggiornamenti contemporaneamente, la situazione si complica. Consideriamo infatti la seguente sequenza temporale:
![]() |
Tempo | Azione |
T1 | il client A legge la tabella [dtProduits] – è presente un prodotto con chiave 20 |
T2 | il cliente B legge la tabella [dtProduits] - c'è un prodotto con chiave 20 |
T3 | il cliente A elimina il prodotto con chiave 20 |
T4 | il cliente B elimina il prodotto con chiave 20 |
Quando i clienti A e B leggono la tabella dei prodotti e ne visualizzano il contenuto in una pagina web, la tabella contiene il prodotto con chiave 20. Potrebbero quindi voler eseguire un'azione su questo prodotto, ad esempio eliminarlo. Inevitabilmente uno dei due lo farà per primo. Chi arriverà dopo vorrà quindi eliminare un prodotto che non esiste più. Questo caso viene gestito dalla procedura [supprimerProduits] in diversi modi:
- innanzitutto avviene la sincronizzazione con [Application.Lock]. Ciò significa che quando un cliente supera questa barriera, nessun altro può modificare o leggere la tabella dei prodotti. Infatti, tutte queste operazioni sono sincronizzate in questo modo. Lo abbiamo già visto per la lettura.
- Si verifica quindi se il prodotto che si desidera eliminare esiste. In caso affermativo, viene eliminato; in caso contrario, viene generato un messaggio di errore.
' ricerca della riga da eliminare
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' eliminazione della riga
ligne.Delete()
End If
- Si esce dalla sequenza critica tramite [Application.Unlock] per consentire agli altri client di eseguire i propri aggiornamenti.
- Se la riga non è stata eliminata, la procedura genera un'eccezione associata a un messaggio di errore.
Vediamo un esempio. Avviamo un client [Mozilla] e selezioniamo immediatamente l’opzione [Mise à jour] (vista parziale):

Facciamo lo stesso con un client [Internet Explorer] (vista parziale):

Con il cliente [Mozilla] eliminiamo il prodotto [produit2]. Otteniamo la seguente nuova pagina:

L'operazione di eliminazione è andata a buon fine. Il prodotto eliminato compare correttamente in [DataGrid] tra i prodotti eliminati e non compare più nell'elenco dei prodotti dei componenti [DataGrid1] e [DataGrid2] che mostrano i prodotti attualmente presenti nella tabella. Procediamo allo stesso modo con [Interbet Explorer]. Eliminiamo l'elemento [produit2]:

La risposta ottenuta è la seguente:

Un messaggio di errore segnala all’utente che il prodotto con chiave [2] non esiste. La risposta restituisce al client una nuova vista che riflette lo stato attuale della tabella. Si nota che, effettivamente, il prodotto con chiave [2] è presente nell’elenco dei prodotti eliminati. La vista [produits] riflette quindi gli aggiornamenti di tutti i clienti e non di uno solo.
9.6.14. Aggiunta di un prodotto
Per aggiungere un prodotto, l’utente utilizza il link [Ajout] presente tra le opzioni. Questo si limita a visualizzare la vista [Ajout]:


Le due procedure coinvolte in questa azione sono le seguenti:
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
' viene visualizzata la vista [ajout]
afficheAjout()
End Sub
Private Sub afficheAjout()
' visualizza la vista "Aggiungi"
vueAjout.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueErreurs.Visible = False
End Sub
Si ricorda che la vista [Ajout] presenta i seguenti componenti di convalida:
nome | tipo | ruolo |
RequiredFieldValidator | verifica la presenza di un valore in [txtNom] | |
RequiredFieldValidator | verifica la presenza di un valore in [txtPrix] | |
CompareValidator | verifica che il prezzo sia maggiore o uguale a 0 |
La procedura di gestione del clic sul pulsante [Ajouter] è la seguente:
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
' aggiunta di un nuovo elemento alla tabella dei prodotti
' innanzitutto i dati devono essere validi
rfvNom.Validate()
rfvPrix.Validate()
cvPrix.Validate()
If Not rfvNom.IsValid Or Not rfvPrix.IsValid Or Not cvPrix.IsValid Then
' di nuovo il modulo di inserimento
afficheAjout()
Exit Sub
End If
' si crea una riga
Dim produit As DataRow = dtProduits.NewRow
produit("nom") = txtNom.Text.Trim
produit("prix") = txtPrix.Text.Trim
' si aggiunge la riga alla tabella
Application.Lock()
Try
dtProduits.Rows.Add(produit)
lblInfo3.Text = "Ajout réussi"
' pulizia
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Application.UnLock()
End Sub
Innanzitutto, vengono eseguiti i controlli di validità dei tre componenti [rfvNom, rfvPrix, cvPrix]. Se uno dei controlli fallisce, viene nuovamente visualizzata la vista [Ajout] con i messaggi di errore relativi ai componenti di validazione. Ecco un esempio:

Se i dati sono validi, si prepara la riga da inserire nella tabella [dtProduits].
' si crea una riga
Dim produit As DataRow = dtProduits.NewRow
produit("nom") = txtNom.Text.Trim
produit("prix") = txtPrix.Text.Trim
Per prima cosa si crea una nuova riga della tabella [dtProduits] utilizzando il metodo [DataTable.NewRow]. Questa riga conterrà le tre colonne [id, nom, prix] della tabella [dtProduits]. Le colonne [nom, prix] vengono compilate con i valori inseriti nel modulo di aggiunta. La colonna [id] non viene invece compilata. Questa colonna è di tipo [AutoIncrement], c.a.d. Il valore SGBD fungerà da chiave per un nuovo prodotto, ovvero la chiave massima esistente aumentata di 1. La riga [produit] qui creata è separata dalla tabella [dtProduits]. Ora dobbiamo inserirla in questa tabella. Poiché aggiorneremo la tabella [dtProduits], attiviamo la sincronizzazione tra clienti:
Fatto ciò, proviamo ad aggiungere la riga alla tabella [dtProduits]. Se l’operazione va a buon fine, visualizziamo un messaggio di conferma, altrimenti un messaggio di errore.
Try
dtProduits.Rows.Add(produit)
lblInfo3.Text = "Ajout réussi"
' pulizia
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Normalmente non sono previste eccezioni. Tuttavia, per precauzione, è stata implementata una gestione delle eccezioni. Una volta effettuata l'aggiunta, si esce dalla sezione critica tramite [Application.Unlock].
9.6.15. Modifica di un prodotto
Per modificare un prodotto, l’utente utilizza il link [Modifier] presente nella riga del prodotto:

Si passa quindi alla modalità di modifica:

Grazie al componente [DataGrid], questo risultato si ottiene con pochissimo codice:
Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
' si imposta l'elemento corrente in modalità di modifica
DataGrid1.EditItemIndex = e.Item.ItemIndex
' si visualizzano nuovamente i prodotti
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
Facendo clic sul link [Modifier] si avvia l'esecuzione, lato server, della procedura [DataGrid1_EditCommand]. Il componente [DataGrid1] presenta un campo denominato [EditItemIndex]. La riga con indice pari al valore di [EditItemIndex] viene messa in modalità di modifica. Ogni valore della riga così aggiornata può essere modificato in una casella di immissione, come mostrato nella schermata sopra. Recuperiamo quindi l’indice del prodotto su cui è stato effettuato il clic sul link [Modifier] e lo assegniamo all’attributo [EditItemIndex] del componente [DataGrid1]. Non resta che visualizzare nuovamente la vista [produits]. Apparirà identica a prima, ma con una riga in modalità di modifica.
Il link [Annuler] del prodotto in fase di modifica consente all’utente di annullare l’aggiornamento. La procedura associata a questo link è la seguente:
Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
' si visualizzano nuovamente i prodotti
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
Si limita a visualizzare nuovamente la vista [produits]. Si potrebbe essere tentati di inserire -1 in [DataGrid1.EditItemIndex] per annullare la modalità di aggiornamento. In realtà, sappiamo che la procedura [Page_Load] lo fa sistematicamente. Non è quindi necessario ripeterlo.
La modifica viene confermata tramite il link [Mettre à jour] della riga in fase di modifica. Viene quindi eseguita la procedura seguente:
Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
' chiave del prodotto da modificare
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
' elementi modificati
Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim
' Modifiche valide?
lblInfo2.Text = ""
If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
If prix = String.Empty Then
lblInfo2.Text += "[Indiquez un prix]"
Else
Dim nouveauPrix As Double
Try
nouveauPrix = CType(prix, Double)
If nouveauPrix < 0 Then Throw New Exception
Catch ex As Exception
lblInfo2.Text += "[prix invalide]"
End Try
End If
' in caso di errore, si visualizza nuovamente la pagina di aggiornamento
If lblInfo2.Text <> String.Empty Then
' si riporta la riga in modalità aggiornamento
DataGrid1.EditItemIndex = e.Item.ItemIndex
' visualizzazione prodotti
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
' fine
Exit Sub
End If
' se non ci sono errori, si modifica la tabella
Try
modifierProduit(idProduit, e.Item)
Catch ex As Exception
' problema
lblInfo2.Text = ex.Message
End Try
' visualizzazione prodotti
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
Come per l'eliminazione, dobbiamo recuperare la chiave del prodotto da modificare per individuare la relativa riga nella tabella dei prodotti [dtProduits]:
' chiave del prodotto da modificare
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
Successivamente dobbiamo recuperare i nuovi valori da assegnare alla riga. Questi si trovano nel componente [DataGrid1]. L'argomento [e] della procedura ci aiuta in questo. [e.Item] rappresenta la riga di [DataGrid1] da cui proviene l’evento. Si tratta quindi della riga in fase di aggiornamento, poiché il collegamento [Mettre à jour] esiste solo su questa riga. Questa riga contiene colonne designate dalla collezione [Cells] della riga. Pertanto, [e.Item.Cells(0)] rappresenta la colonna 0 della riga aggiornata. Sappiamo che i nuovi valori si trovano nei campi di immissione. La collezione [e.Item.Cells(i).Controls] rappresenta la collezione dei controlli della colonna i della riga [e.Item]. Le due istruzioni seguenti recuperano i valori delle caselle di immissione della riga aggiornata in [DataGrid1]:
' elementi modificati
Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim
Abbiamo quindi i nuovi valori della riga modificata sotto forma di stringhe di caratteri. Verifichiamo quindi se questi dati sono validi. Il nome non deve essere vuoto e il prezzo deve essere un numero positivo o zero:
' modifiche valide?
lblInfo2.Text = ""
If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
If prix = String.Empty Then
lblInfo2.Text += "[Indiquez un prix]"
Else
Dim nouveauPrix As Double
Try
nouveauPrix = CType(prix, Double)
If nouveauPrix < 0 Then Throw New Exception
Catch ex As Exception
lblInfo2.Text += "[prix invalide]"
End Try
End If
In caso di errore, l'etichetta [lblInfo2] visualizzerà un messaggio di errore e si limiterà a visualizzare nuovamente la stessa pagina:
' in caso di errore, si visualizza nuovamente la pagina di aggiornamento
If lblInfo2.Text <> String.Empty Then
' si riporta la riga in modalità aggiornamento
DataGrid1.EditItemIndex = e.Item.ItemIndex
' visualizzazione prodotti
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
' fine
Exit Sub
End If
Ciò che il codice sopra riportato non mostra è che i valori inseriti vanno persi. Infatti, [DataGrid1] è collegato ai dati della tabella [dtProduits], che contiene i valori originali della riga modificata. Ecco un esempio.

La risposta ottenuta è la seguente:

Si nota che i valori inseriti inizialmente sono andati persi. In un’applicazione professionale, ciò probabilmente non sarebbe accettabile. Qui si raggiungono alcuni limiti della modalità standard di aggiornamento del componente [DataGrid]. Sarebbe preferibile disporre di una vista [Modification] analoga alla vista [Ajout].
Se i dati sono validi, vengono utilizzati per aggiornare la tabella [dtProduits]:
' se non ci sono errori, si modifica la tabella
Try
modifierProduit(idProduit, e.Item)
Catch ex As Exception
' problema
lblInfo2.Text = ex.Message
End Try
' visualizzazione prodotti
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
Per lo stesso motivo indicato in caso di eliminazione di un prodotto, la modifica potrebbe non andare a buon fine. Infatti, tra il momento in cui il cliente ha letto la tabella [dtProduits] e quello in cui intende modificare uno dei propri prodotti, quest'ultimo potrebbe essere stato eliminato da un altro cliente. La procedura [modifierProduit] gestisce questo caso generando un'eccezione. Quest'ultima viene gestita in questa sede. A seguito del successo o del fallimento dell'aggiornamento, l'applicazione restituisce al cliente la vista [produits]. Resta da vedere come la procedura [modifierProduit] esegua l’aggiornamento:
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
Dim erreur As String
Try
' sincronizzazione
Application.Lock()
' ricerca della riga da modificare
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' si modifica la riga
With ligne
.Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
.Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
End With
End If
Catch ex As Exception
erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
Finally
' fine della sincronizzazione
Application.UnLock()
End Try
' in caso di errore viene generata un'eccezione
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
Non entreremo nei dettagli di questa procedura, il cui codice è analogo a quello della procedura [supprimerProduit], che è stata spiegata ampiamente. Vediamo semplicemente due esempi. Innanzitutto modifichiamo il prezzo del prodotto [produit1]:

L'utilizzo del link [Mettre à jour] sopra riportato porta alla seguente risposta:

La modifica è effettivamente presente nei componenti [DataGrid] 1 e 2, che riflettono lo stato attuale della tabella [dtProduits]. È presente anche nel componente [DataGrid4], che è una vista delle righe modificate, visualizzate con i loro valori originali. Ora esaminiamo un caso di conflitto di accesso. Come nell’esempio di cancellazione, utilizzeremo due client web diversi. Un client [Mozilla] legge la tabella [dtProduits] e passa alla modalità di modifica del prodotto [produit1]:

Un cliente [Internet Explorer] sta per eliminare il prodotto [produit1]:

Il cliente [Internet Explorer] elimina [produit1]:

Si noti che [produit1] non figura più nella tabella delle righe modificate, ma in quella delle righe eliminate. Il client [Mozilla], dal canto suo, convalida il proprio aggiornamento:

Il client [Mozilla] riceve la seguente risposta al proprio aggiornamento:

Può constatare che [produit1] è stato eliminato poiché figura nell’elenco dei prodotti eliminati.
9.7. Applicazione web per l’aggiornamento della tabella fisica dei prodotti
9.7.1. Soluzioni proposte
L'applicazione precedente era più che altro un caso di studio volto a illustrare la gestione di un oggetto [DataTable] nella cache piuttosto che un caso reale. Infatti, prima o poi è necessario aggiornare la fonte effettiva dei dati. Si possono adottare due strategie diverse:
- si utilizza la cache [dtProduits] in memoria per aggiornare la fonte dei dati. Si può prevedere una pagina situata nella struttura ad albero web dell’applicazione precedente per poter accedere alla cache [dtProduits] di quest’ultima. Tale pagina offrirebbe a un amministratore la possibilità di replicare sulla fonte fisica dei dati le modifiche presenti nella cache in [dtProduits]. A tal fine, si potrebbe aggiungere un nuovo metodo alla classe di accesso [produits] che accetti come parametro la cache [dtProduits] e che, utilizzando tale cache, aggiorni la fonte fisica dei dati.
- La fonte fisica dei dati viene aggiornata contemporaneamente alla cache.
La strategia n. 1 consente di aprire una sola connessione con la fonte fisica dei dati. La strategia n. 2 richiede una connessione ad ogni aggiornamento. A seconda della disponibilità delle connessioni, si potrà preferire l’una o l’altra strategia. Poiché disponiamo degli strumenti per implementarla (la classe [produits]), scegliamo la strategia n. 2.
9.7.2. Soluzione 1
Per motivi di continuità con l’applicazione precedente, scegliamo la seguente strategia:
- la fonte fisica viene aggiornata contemporaneamente alla cache
- la cache viene costruita una sola volta all’avvio dell’applicazione in [global.asax.vb]. Ciò significa che se la fonte fisica dei dati viene aggiornata da clienti diversi da quelli web, questi ultimi non vedono tali modifiche. Vedono solo quelle che essi stessi apportano alla tabella nella cache.
Per aggiornare la fonte fisica dei dati, abbiamo bisogno di un'istanza della classe di accesso ai prodotti. Ogni client potrebbe averne una propria. È anche possibile condividere un'unica istanza che verrebbe creata dall'applicazione al suo avvio. È questa la soluzione che scegliamo in questo caso. Il codice di controllo [global.asax.vb] viene modificato come segue:
Imports System
Imports System.Web
...
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' si recuperano le informazioni di configurazione
Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
Dim erreurs As New ArrayList
...
' qui non ci sono errori di configurazione
' si crea un oggetto prodotti
Dim objProduits As New produits(chaînedeConnexion)
Dim dtProduits As DataTable
Try
dtProduits = objProduits.getProduits
Catch ex As ExceptionProduits
'si è verificato un errore di accesso ai prodotti, lo si registra nell'applicazione
Application("erreurs") = ex.erreurs
Exit Sub
Catch ex As Exception
' errore non gestito
erreurs.Add(ex.Message)
Application("erreurs") = erreurs
' uscita dalla subroutine
End Try
' qui non si verificano errori di inizializzazione
...
' si memorizza l'istanza di accesso ai dati
Application("objProduits") = objProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
...
End Sub
End Class
Nell'applicazione è stata memorizzata un'istanza della classe di accesso ai dati, associata alla chiave [objProduits]. Ogni cliente utilizzerà questa istanza per accedere alla fonte fisica dei dati. Essa verrà recuperata nella procedura [Page_Load] di [main.aspx.vb]:
Protected objProduits As produits
....
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' si recupera l'istanza di accesso ai prodotti
objProduits = CType(Application("objProduits"), produits)
...
End Sub
L'istanza di accesso ai dati [objProduits] è accessibile a tutti i metodi della pagina. Verrà utilizzata per le tre operazioni di aggiornamento: aggiunta, eliminazione e modifica.
La procedura di inserimento viene modificata come segue:
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
...
' si aggiunge la riga
Application.Lock()
Try
' si aggiunge la riga alla tabella nella cache
dtProduits.Rows.Add(produit)
' si aggiunge la riga alla fonte fisica
Dim nouveauProduit As sProduit
With nouveauProduit
.nom = CType(produit("nom"), String)
.prix = CType(produit("prix"), Double)
End With
objProduits.ajouterProduit(nouveauProduit)
' monitoraggio
lblInfo3.Text = "Ajout réussi"
' pulizia
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
' errore
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Application.UnLock()
End Sub
La procedura di modifica viene modificata come segue:
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
Dim erreur As String
Try
' sincronizzazione
Application.Lock()
' ricerca della riga da modificare
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' si modifica la riga nella cache
With ligne
.Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
.Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
End With
' modifica della riga nella fonte fisica
Dim nouveauProduit As sProduit
With nouveauProduit
.id = idProduit
.nom = CType(ligne.Item("nom"), String)
.prix = CType(ligne.Item("prix"), Double)
End With
objProduits.modifierProduit(nouveauProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
Finally
' fine della sincronizzazione
Application.UnLock()
End Try
' in caso di errore viene generata un'eccezione
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
La procedura di cancellazione viene modificata come segue:
Private Sub supprimerProduit(ByVal idProduit As Integer)
Dim erreur As String
Try
' sincronizzazione
Application.Lock()
' ricerca della riga da eliminare
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' eliminazione della riga dalla cache
ligne.Delete()
' eliminazione della riga nella fonte fisica
objProduits.supprimerProduit(idProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur de suppression : {0}", ex.Message)
Finally
' fine della sincronizzazione
Application.UnLock()
End Try
' in caso di errore viene generata un'eccezione
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
9.7.3. Test
Partiamo dalla seguente tabella di dati contenuta nel file ACCESS:

Viene avviato un client web:

Aggiungiamo un prodotto:

Eliminiamo [produit1]:

Modifichiamo il prezzo di [produit2]:

Dopo queste modifiche, esaminiamo il contenuto della tabella [liste] nel database ACCESS:

Le tre modifiche sono state correttamente applicate alla tabella fisica. Esaminiamo ora un caso di conflitto. Eliminiamo, direttamente sotto ACCESS, la riga relativa a [produit2]:

Torniamo al nostro client web. Quest’ultimo non vede l’eliminazione che è stata effettuata e vuole a sua volta eliminare [produit2]:

Riceve la seguente risposta:

La riga [produit2] è stata effettivamente rimossa dalla cache, come indicato dall’elenco dei prodotti eliminati. L’eliminazione di [produit2] nella fonte fisica, invece, non è andata a buon fine, come indicato dal messaggio di errore.
9.7.4. Soluzione 2
Nella soluzione precedente, i client web aggiornano simultaneamente la fonte dati fisica, ma non vedono le modifiche apportate dagli altri client. Vedono solo le proprie. Ora vorremmo che un client potesse vedere la fonte dati fisica così com’è attualmente e non com’era all’avvio dell’applicazione. A tal fine, offriremo al client una nuova opzione:

Con l’opzione [Rafraîchir], il client forza una nuova lettura della fonte dati fisica. Affinché ciò non influisca sugli altri client, è necessario che la tabella risultante da questa lettura appartenga al client che esegue l’aggiornamento e non sia condivisa con gli altri client. Questa è la prima differenza rispetto all’applicazione precedente. La cache [dtProduits] della fonte dati verrà creata da ciascun client e non dall’applicazione stessa. La modifica viene apportata in [global.asax.vb]:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' si recuperano le informazioni di configurazione
...
' qui non ci sono errori di configurazione
' si crea un oggetto prodotti
Dim objProduits As New produits(chaînedeConnexion)
' si memorizza il numero di prodotti per pagina
Application("defaultProduitsPage") = defaultProduitsPage
' si memorizza l'istanza di accesso ai dati
Application("objProduits") = objProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' inizializzazione delle variabili di sessione
If IsNothing(Application("erreurs")) Then
' si memorizza nella cache la fonte dei dati
Dim dtProduits As DataTable
Try
Application.Lock()
dtProduits = CType(Application("objProduits"), produits).getProduits
Catch ex As ExceptionProduits
'si è verificato un errore nell'accesso ai prodotti, lo si registra nella sessione
Session("erreurs") = ex.erreurs
Exit Sub
Finally
Application.UnLock()
End Try
' si inserisce la cache [dtProduits] nella sessione
Session("dtProduits") = dtProduits
' visualizzazione della tabella dei prodotti
Session("dvProduits") = dtProduits.DefaultView
' numero di prodotti per pagina
Session("nbProduitsPage") = Application("defaultProduitsPage")
' pagina corrente visualizzata
Session("pageCourante") = 0
End If
End Sub
End Class
Le informazioni relative alla sessione vengono recuperate ad ogni richiesta nella procedura [Page_Load]:
' dati della pagina
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected objProduits As produits
Protected nbProduitsPage As Integer
Protected pageCourante As Integer
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' si verifica se l'applicazione presenta un errore
If Not IsNothing(Application("erreurs")) Then
' l'applicazione non si è inizializzata correttamente
afficheErreurs(CType(Application("erreurs"), ArrayList))
Exit Sub
End If
' si verifica se la sessione presenta un errore
If Not IsNothing(Session("erreurs")) Then
' la sessione non si è inizializzata correttamente
afficheErreurs(CType(Session("erreurs"), ArrayList))
Exit Sub
End If
' si recupera un riferimento dalla tabella dei prodotti
dtProduits = CType(Session("dtProduits"), DataTable)
' si recupera la vista dei prodotti
dvProduits = CType(Session("dvProduits"), DataView)
' si recupera il numero di prodotti per pagina
nbProduitsPage = CType(Session("nbProduitsPage"), Integer)
' si recupera la pagina corrente
pageCourante = CType(Session("pageCourante"), Integer)
' si recupera l'istanza di accesso ai prodotti
objProduits = CType(Application("objProduits"), produits)
' si annulla un eventuale aggiornamento in corso
DataGrid1.EditItemIndex = -1
'prima richiesta
If Not IsPostBack Then
' viene visualizzato il modulo iniziale
txtPages.Text = nbProduitsPage.ToString
afficheFormulaire()
End If
End Sub
Le informazioni recuperate durante la sessione verranno salvate alla fine della stessa sessione al termine di ogni richiesta:
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
' si memorizzano alcune informazioni nella sessione
Session("dtProduits") = dtProduits
Session("dvProduits") = dvProduits
Session("nbProduitsPage") = nbProduitsPage
Session("pageCourante") = pageCourante
End Sub
L'evento [PreRender] segnala che la risposta sta per essere inviata al cliente. Ne approfittiamo per salvare nella sessione tutti i dati da conservare. Si tratta di un'operazione eccessiva, poiché molto spesso solo alcuni di essi hanno cambiato valore. Questo salvataggio sistematico ha il vantaggio di sollevarci dalla gestione della sessione negli altri metodi della pagina.
L'operazione di aggiornamento della cache è gestita dalla seguente procedura:
Private Sub lnkRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkRefresh.Click
' è necessario aggiornare la cache [dtProduits] con la fonte dati fisica
' inizio sincronizzazione
Application.Lock()
Try
' la tabella dei prodotti
dtProduits = CType(Application("objProduits"), produits).getProduits
' si memorizza il filtro attuale
Dim filtre As String = dvProduits.RowFilter
' si crea la nuova vista filtrata
dvProduits = New DataView(dtProduits)
' si ripristina il filtro
dvProduits.RowFilter = filtre
Catch ex As ExceptionProduits
'si è verificato un errore di accesso ai prodotti, lo si registra nella sessione
Session("erreurs") = ex.erreurs
' si visualizza la vista [erreurs]
afficheErreurs(ex.erreurs)
' finito
Exit Sub
Finally
' sincronizzazione completata
Application.UnLock()
End Try
' tutto andato bene - si visualizzano i prodotti a partire dalla prima pagina
afficheProduits(0, nbProduitsPage)
End Sub
La procedura rigenera nuovi valori per la cache [dtProduits] e la vista [dvProduits]. Questi valori saranno inseriti nella sessione dalla procedura [Page_PreRender] descritta in precedenza. Una volta ricostruita la cache [dtProduits], vengono visualizzati i prodotti a partire dalla prima pagina.
Ecco un esempio di esecuzione. Viene avviato un client [mozilla] che visualizza i prodotti:

Anche un client [Internet Explorer] fa lo stesso:

Il client [Mozilla] elimina [produit1], modifica [produit2] e aggiunge un nuovo prodotto. Si ottiene la seguente nuova pagina:

Il cliente [Internet Explorer] desidera eliminare [produit1].

Ottiene la seguente risposta:

Gli è stato segnalato che [produit1] non esiste più. L'utente decide quindi di aggiornare la cache utilizzando il link [Rafraîchir] sopra indicato. Ottiene la seguente risposta:

Ora dispone della stessa fonte di dati del client [Mozilla].
9.8. Conclusion
In questo capitolo abbiamo dedicato molto tempo ai contenitori di dati e al loro collegamento con le fonti di dati. Abbiamo concluso mostrando come aggiornare una fonte di dati utilizzando un componente [DataGrid]. Abbiamo utilizzato come fonte di dati una tabella di un database. Anche se il componente [DataGrid] semplifica leggermente la rappresentazione dei dati, la vera difficoltà non risiede nella parte di presentazione, ma nella gestione degli aggiornamenti della fonte di dati effettuati dai diversi clienti. Possono verificarsi conflitti di accesso che devono essere gestiti. In questo caso, li abbiamo gestiti nel controller utilizzando il componente [Application.Lock]. Probabilmente sarebbe più opportuno sincronizzare gli accessi alla fonte di dati nella classe di accesso, in modo che il controller non debba occuparsi di tali dettagli che non rientrano nelle sue competenze.
In pratica, le tabelle di un database sono collegate tra loro da relazioni e il loro aggiornamento deve tenerne conto. Ciò ha conseguenze innanzitutto sulla classe di accesso ai dati, che diventa più complessa di quella necessaria per una tabella indipendente. In genere ha anche conseguenze sulla parte di presentazione dell’applicazione web, poiché spesso è necessario visualizzare non una singola tabella, ma tabelle collegate tra loro da relazioni.
Questo capitolo ha inoltre permesso di presentare diverse strutture di dati, come ad esempio [DataTable, DataView].









