Skip to content

9. Servercomponenten ASP - 3

9.1. Introduction

We zetten ons werk aan de gebruikersinterface voort door de mogelijkheden van de componenten [DataList] en [DataGrid] verder uit te breiden, met name op het gebied van het bijwerken van de gegevens die ze weergeven

9.2. Gebeurtenissen beheren die verband houden met de gegevens van componenten met gegevenskoppeling

9.2.1. Het voorbeeld

Laten we eens kijken naar de volgende pagina:

De pagina bevat drie componenten die gekoppeld zijn aan een gegevenslijst:

  • een component [DataList] met de naam [DataList1]
  • een component [DataGrid] met de naam [DataGrid1]
  • een component [Repeater] met de naam [Repeater1]

De bijbehorende gegevenslijst is de array {"nul", "één", "twee", "drie"}. Aan elk van deze gegevens is een groep van twee knoppen gekoppeld, met de namen [Infos1] en [Infos2]. De gebruiker klikt op een van de knoppen en er verschijnt een tekst met de naam van de aangeklikte knop. We willen hier laten zien hoe je een lijst met knoppen of links kunt beheren.

9.2.2. De configuratie van de componenten

De component [DataList1] is als volgt geconfigureerd:


                        <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>

We hebben alles weggelaten wat betrekking had op de opmaak van het [DataList]-bestand, zodat we ons uitsluitend op de inhoud kunnen richten:

  • de sectie <HeaderTemplate> definieert de koptekst van de [DataList] en de sectie <FooterTemplate> de voettekst.
  • de sectie <ItemTemplate> is het weergavesjabloon dat wordt gebruikt voor elk van de gegevens in de bijbehorende gegevenslijst. Hierin bevinden zich de volgende elementen:
    • de waarde van het huidige gegevenselement uit de gegevenslijst die aan de component is gekoppeld: <%# Container.DataItem %>
    • twee knoppen met respectievelijk de labels [Infos1] en [Infos2]. De klasse [Button] heeft een attribuut [CommandName] dat hier wordt gebruikt. Hiermee kunnen we bepalen welke knop de oorzaak is van een gebeurtenis in de [DataList]. Om het klikken op de knoppen te verwerken, hebben we slechts één gebeurtenishandler nodig die gekoppeld is aan de [DataList] zelf en niet aan de knoppen. Deze handler ontvangt informatie die aangeeft op welke regel van het [DataList] de klik heeft plaatsgevonden. Aan de hand van het attribuut [CommandName] kunnen we vaststellen op welke knop van die regel de klik heeft plaatsgevonden.

De component [Repeater1] is op vrijwel dezelfde manier geconfigureerd:


                        <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>

Er is simpelweg een <SeparatorTemplate>-sectie toegevoegd om de opeenvolgende gegevens die door de component worden weergegeven te scheiden met een horizontale streep.

Ten slotte is de component [DataGrid1] als volgt geconfigureerd:


                        <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>

Ook hier hebben we de stijlinformatie (kleuren, breedtes, ...) weggelaten. We bevinden ons in de modus voor automatische kolomgeneratie, wat de standaardmodus is van [DataGrid]. Dit betekent dat er evenveel kolommen zullen zijn als er in de gegevensbron staan. Hier is er één. We hebben twee andere kolommen toegevoegd, gemarkeerd met <asp:ButtonColumn>. Daarin definiëren we informatie die vergelijkbaar is met die voor de twee andere componenten, evenals het type knop, in dit geval [PushButton]. Het standaardtype is [LinkButton], c.a.d. een link. Bovendien worden de gegevens gepagineerd [AllowPaging=true] met een paginagrootte van twee elementen [PageSize=2].

9.2.3. De opmaakcode van de pagina

De opmaakcode van onze voorbeeldpagina is in een bestand [main.aspx] geplaatst:


<%@ 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>

In de bovenstaande code hebben we de opmaakcode (kleuren, lijnen, lettergroottes, ...) weggelaten

9.2.4. De besturingscode van de pagina

De besturingscode van de applicatie is in het bestand [main.aspx.vb] geplaatst:

Public Class main
    Inherits System.Web.UI.Page

     ' paginacomponenten
    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

     ' de gegevensbron
    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
             'koppelingen met gegevensbron
            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
         ' er is een fout opgetreden in een van de regels van [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
         ' er heeft zich een gebeurtenis voorgedaan op een van de regels van [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
         ' er is een gebeurtenis opgetreden op een van de regels van [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
         ' pagina gewijzigd
        With DataGrid1
            .CurrentPageIndex = e.NewPageIndex
            .DataSource = textes
            .DataBind()
        End With
    End Sub
End Class

Opmerkingen:

  • de gegevensbron [textes] is een eenvoudige tabel met tekenreeksen. Deze wordt gekoppeld aan de drie componenten op de pagina
  • deze koppeling wordt gemaakt in de procedure [Page_Load] bij de eerste aanvraag. Bij volgende aanvragen krijgen de drie componenten hun waarden terug via het mechanisme van [VIEW_STATE].
  • De drie componenten hebben een handler voor de gebeurtenis [ItemCommand]. Deze treedt op wanneer er op een knop of een link wordt geklikt in een van de rijen van de component. De handler ontvangt twee gegevens:
    • bron: de referentie van het object (knop of link) dat de gebeurtenis heeft veroorzaakt
    • a: informatie over de gebeurtenis van het type [DataListCommandEventArgs], [RepeaterCommandEventArgs] of [DataGridCommandEventArgs], afhankelijk van het geval. Het argument a bevat diverse gegevens. Hier zijn twee daarvan voor ons van belang:
      • a.Item: vertegenwoordigt de regel waarop de gebeurtenis plaatsvond, van het type [DataListItem], [DataGridItem] of [RepeaterItem]. Ongeacht het exacte type heeft het element [Item] een attribuut [ItemIndex] dat het regelnummer [Item] aangeeft van de container waartoe het behoort. We geven hier dit regelnummer weer
      • a.CommandName: dit is het attribuut [CommandName] van de knop (Button, LinkButton, ImageButton) die de gebeurtenis heeft veroorzaakt. Deze informatie, in combinatie met de vorige, stelt ons in staat te achterhalen welke knop in de container de oorzaak is van de gebeurtenis [ItemCommand]

9.3. Toepassing – beheer van een abonnementslijst

Nu we weten hoe we gebeurtenissen binnen een gegevenscontainer kunnen onderscheppen, geven we een voorbeeld dat laat zien hoe we deze kunnen beheren.

9.3.1. Inleiding

De gepresenteerde applicatie simuleert een applicatie voor abonnementen op mailinglijsten. Deze worden gedefinieerd door een object [DataTable] met drie kolommen:

naam
type
rol
id
string
primaire sleutel
thème
string
naam van het lijstthema
description
string
een beschrijving van de onderwerpen die in de lijst aan bod komen

Om ons voorbeeld niet te ingewikkeld te maken, wordt het bovenstaande object [DataTable] willekeurig via code samengesteld. In een echte toepassing zou het waarschijnlijk worden aangeleverd door een methode van een klasse voor gegevenstoegang. De lijsttabel wordt samengesteld in de procedure [Application_Start] en de resulterende tabel wordt in de toepassing geplaatst. We noemen deze tabel [dtThèmes]. De gebruiker zal zich abonneren op bepaalde onderwerpen uit deze tabel. De lijst met zijn abonnementen wordt ook daar opgeslagen in een object [DataTable], genaamd [dtAbonnements], met de volgende structuur:

naam
type
rol
id
string
primaire sleutel
thème
string
naam van het lijstthema

De applicatie met één pagina ziet er als volgt uit:

nr.
naam
type
eigenschappen
rol
1
dgThèmes
DataGrid
 
mailinglijsten waarop men zich kan abonneren
2
dlAbonnements
DataList
 
overzicht van de abonnementen van de gebruiker op de bovenstaande lijsten
3
panelInfos
paneel
 
informatiepaneel over het door de gebruiker geselecteerde thema met een link [Plus d'informations]
4
lblThème
Label
maakt deel uit van [panelInfos]
naam van het thema
5
lblDescription
Label
maakt deel uit van [panelInfos]
beschrijving van het thema
6
lblInfo
Label
 
informatiebericht van de applicatie

Ons voorbeeld is bedoeld om het gebruik van de componenten [DataGrid] en [DataList] te illustreren, met name het beheer van gebeurtenissen die plaatsvinden op het niveau van de regels in deze gegevenscontainers. De applicatie heeft dan ook geen bevestigingsknop waarmee bijvoorbeeld de keuzes van de gebruiker in een database zouden worden opgeslagen. Toch is het een realistisch voorbeeld. We zitten dicht bij een e-commerce-applicatie waarin een gebruiker producten (abonnementen) in zijn winkelmandje zou plaatsen.

9.3.2. Werking

Het eerste scherm dat de gebruiker te zien krijgt, is het volgende:

De gebruiker klikt op de links [Plus d'informations] om informatie over een onderwerp te krijgen. Deze wordt weergegeven in [panelInfos]:

Image

Hij klikt op de links [S'abonner] om zich op een thema te abonneren. De gekozen thema’s worden weergegeven in de component [dlAbonnements]:

Image

Het kan zijn dat de gebruiker zich wil abonneren op een lijst waarop hij al geabonneerd is. Een melding wijst hem hierop:

Image

Ten slotte kan hij zijn abonnement op een van de thema’s opzeggen met de bovenstaande knoppen [Retirer]. Er wordt geen bevestiging gevraagd. Dit is hier niet nodig, omdat de gebruiker zich gemakkelijk opnieuw kan abonneren. Hieronder ziet u het scherm na het opzeggen van het abonnement op [thème1]:

Image

9.3.3. Configuratie van de gegevenscontainers

De component [dgThèmes] van het type [DataGrid] is gekoppeld aan een bron van het type [DataTable]. De opmaak is ingesteld via de koppeling [Mise en forme automatique] in het eigenschappenpaneel van [DataGrid]. De eigenschappen zijn gedefinieerd via de koppeling [Générateur de proprités] in hetzelfde venster. De gegenereerde code is als volgt (de opmaakcode is weggelaten):


                        <asp:datagrid id="dgThèmes" AutoGenerateColumns="False" AllowPaging="True" PageSize="5" 
                            runat="server">
                            <ItemStyle ...></ItemStyle>
                            <HeaderStyle ...></HeaderStyle>
                            <FooterStyle ...></FooterStyle>
                            <Columns>
                                <asp:BoundColumn DataField="th&#232;me" HeaderText="Th&#232;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>

Let op de volgende punten:

AutoGenerateColumns=false
we bepalen zelf welke kolommen moeten worden weergegeven in de sectie <columns>...</columns>
AllowPaging=true
PageSize=5
voor de paginering van de gegevens
<asp:BoundColumn>
definieert de kolom [thème] (HeaderText) van [DataGrid], die gekoppeld zal worden aan de kolom [thème] van de gegevensbron (DataField)
<asp:ButtonColumn>
definieert twee kolommen met knoppen (of links). Om de twee links in dezelfde rij van elkaar te onderscheiden, gebruiken we hun eigenschap [CommandName].

De component [DataGrid] is nog niet volledig geconfigureerd. Dit gebeurt in de code van de controller.

De component [dlAbonnements] van het type [DataList] is gekoppeld aan een bron van het type [DataTable]. De opmaak ervan is uitgevoerd via de koppeling [Mise en forme automatique] in het eigenschappenpaneel van [DataList]. De definitie van de eigenschappen is rechtstreeks in de presentatiecode vastgelegd. Deze code is als volgt (de opmaakcode is weggelaten):


                        <asp:DataList id="dlAbonnements" ... runat="server" >
                            <HeaderTemplate>
                                <div align="center">
                                    Vos abonnements</div>
                            </HeaderTemplate>
                            <ItemStyle ...></ItemStyle>
                            <ItemTemplate>
                                <TABLE>
                                    <TR>
                                        <TD><%#Container.DataItem("thema")%></TD>
                                        <TD>
                                            <asp:Button id="lnkRetirer" CommandName="retirer" runat="server" Text="Retirer" /></TD>
                                    </TR>
                                </TABLE>
                            </ItemTemplate>
                            <HeaderStyle ...></HeaderStyle>
                        </asp:DataList>
<HeaderTemplate>
definieert de koptekst van [DataList]
<ItemTemplate>
definieert het huidige element van [DataList] – hier is een tabel met twee kolommen en één rij geplaatst. De eerste cel is bedoeld voor de naam van het thema waarop de gebruiker zich wil abonneren, de andere voor de knop [Retirer] waarmee hij zijn keuze kan annuleren.

9.3.4. De weergavepagina

De weergavecode [main.aspx] is als volgt:


<%@ 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. De controleurs

De controle is verdeeld over de bestanden [global.asax] en [main.aspx]. Het bestand [global.asax] ziet er als volgt uit:

<%@ Application src="global.asax.vb" inherits="global" %>

Het bijbehorende bestand [global.asax.vb] bevat de volgende code:


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)
        ' de gegevensbron wordt geïnitialiseerd
        Dim thèmes As New DataTable
        ' kolommen
        With thèmes.Columns
            .Add("id", GetType(System.Int32))
            .Add("thème", GetType(System.String))
            .Add("description", GetType(System.String))
        End With
        ' de kolom 'id' wordt de primaire sleutel
        thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
        ' rijen
        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
        ' de gegevensbron wordt in de applicatie geladen
        Application("thèmes") = thèmes
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' begin van de sessie – we maken een lege abonnementstabel aan
        Dim dtAbonnements As New DataTable
        With dtAbonnements
            ' de kolommen
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            ' de primaire sleutel
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
        ' de tabel wordt in de sessie geplaatst
        Session.Item("abonnements") = dtAbonnements
    End Sub

End Class

De procedure [Application_Start], die wordt uitgevoerd wanneer de applicatie haar allereerste verzoek ontvangt, genereert de [DataTable] met de thema's waarop men zich kan abonneren. Deze wordt willekeurig met code samengesteld. Laten we de techniek die we al eerder hebben gezien nog eens in herinnering brengen. We genereren in deze volgorde:

  • een leeg [DataTable]-object, zonder structuur en zonder gegevens
  • de structuur van de tabel door de kolommen te definiëren (naam en het type gegevens dat ze bevatten)
  • de rijen van de tabel die de bruikbare gegevens vertegenwoordigen

We hebben hier een primaire sleutel toegevoegd. De kolom "id" fungeert als primaire sleutel. Er zijn verschillende manieren om dit aan te geven. Hier hebben we een constraint gebruikt. In SQL is een constraint een regel waaraan de gegevens van een rij moeten voldoen, zodat deze aan een tabel kan worden toegevoegd. Er zijn allerlei soorten constraints mogelijk. De constraint "Primary Key" dwingt de kolom waarop deze is toegepast om unieke en niet-lege waarden te bevatten. Een primaire sleutel kan in feite bestaan uit een uitdrukking waarin waarden uit meerdere kolommen worden gebruikt. [DataTable].Constraints is de verzameling van constraints van een bepaalde tabel. Om een beperking toe te voegen, gebruikt men de methode [DataTable.Constraints.Add]. Deze heeft meerdere signaturen. Hier is de methode [Add(Byval nom as String, Byval colonne as DataColumn, Byval cléPrimaire as Boolean)] gebruikt:

nom
naam van de beperking – kan willekeurig zijn
colonne
kolom die de primaire sleutel wordt – van het type [DataColumn]
cléPrimaire
moet [vrai] zijn om van [colonne] een primaire sleutel te maken. Als het [cléPrimaire=false] is, geldt alleen de beperking voor unieke waarden op [colonne]

Om de kolom met de naam "id" tot primaire sleutel van de tabel [dtAbonnements] te maken, schrijven we dus:

dtAbonnements.Constraints.Add("xxx",dtAbonnements.Columns("id"),true)

De procedure [Session_Start] wordt uitgevoerd wanneer de applicatie het eerste verzoek van een klant ontvangt. Deze procedure dient om voor elke klant specifieke objecten aan te maken die tijdens de verschillende verzoeken van die klant behouden moeten blijven. De procedure bouwt de tabel [DataTable] op met de abonnementen van de klant. Alleen de structuur ervan wordt opgebouwd, aangezien deze tabel aanvankelijk leeg is. De tabel wordt in de loop van de verzoeken gevuld. Ook hier dient de kolom „id” als primaire sleutel. Er is een andere techniek gebruikt om deze beperking te definiëren:

[DataTable].PrimaryKey
is de array van kolommen die de primaire sleutel vormen – hier is een array met één element gedefinieerd: de kolom met de naam "id"

Wanneer de verzoek van de klant de controller [main.aspx] bereikt, zijn de twee objecten [DataTable] beschikbaar in de applicatie voor de thematabel en in de sessie voor de abonnemententabel. De controller [main.aspx.vb] ziet er als volgt uit:


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

De procedure [Page_Load] heeft als belangrijkste functie:

  • de twee tabellen [dtThèmes] en [dtAbonnements] op te halen, die zich respectievelijk in de applicatie en de sessie bevinden, om ze beschikbaar te maken voor alle methoden van de pagina
  • de gegevens uit deze twee bronnen te koppelen aan hun respectievelijke containers. Dit gebeurt alleen bij de eerste aanvraag. Bij volgende aanvragen hoeft de koppeling niet systematisch te worden uitgevoerd en wanneer dit wel nodig is, moet soms worden gewacht op een gebeurtenis die plaatsvindt na [Page_Load] om dit te doen.

De code van [Page_Load] is als volgt:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' de gegevensbronnen worden opgehaald
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' gegevenskoppeling
        If Not IsPostBack Then
            liaisons()
        End If
        ' bepaalde informatie wordt verborgen
        panelInfo.Visible = False
    End Sub

    Private Sub liaisons()
        'de gegevensbron wordt gekoppeld aan de component [datagrid]
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        ' de gegevensbron wordt gekoppeld aan de component [datalist]
        With dlAbonnements
            .DataSource = dtAbonnements
            .DataKeyField = "id"
        End With
        ' de gegevens worden aan de componenten toegewezen
        Page.DataBind()
    End Sub

In de procedure [liaisons] gebruiken we de eigenschap [DataKeyField] van de componenten [DataList] en [DataGrid] om de kolom in de gegevensbron te definiëren die zal dienen om de rijen van de containers op unieke wijze te identificeren. Meestal is deze kolom de primaire sleutel van de gegevensbron, maar dat is niet verplicht. Het volstaat dat de kolom geen dubbele waarden en geen lege waarden bevat. Voor de container [dgThèmes] is de kolom „id” van de bron [dtThèmes] de primaire sleutel, en voor de container [dlAbonnements] is dat de kolom „id” van de bron [dtAbonnements]. Het is niet noodzakelijk dat de kolom die als primaire sleutel voor de container dient, door de container zelf wordt weergegeven. In dit geval geeft geen van beide containers de primaire-sleutelkolom weer. Het voordeel van een primaire sleutel in een container is dat hiermee in de gegevensbron gemakkelijk de informatie kan worden teruggevonden die verband houdt met de rij in de container waarop een gebeurtenis heeft plaatsgevonden. Het komt namelijk vaak voor dat men, uitgaande van de rij in een container waarin een gebeurtenis heeft plaatsgevonden, actie moet ondernemen op de overeenkomstige rij in de daaraan gekoppelde gegevensbron. De primaire sleutel vergemakkelijkt dit werk.

De paginering van [DataGrid] wordt op de gebruikelijke manier beheerd:


    Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
        ' pagina wisselen
        dgThèmes.CurrentPageIndex = e.NewPageIndex
        ' koppeling
        liaisons()
    End Sub

De bewerkingen op de koppelingen [Plus d'informations] en [S'abonner] worden beheerd door de procedure [dgThèmes_ItemCommand]:


    Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
        ' gebeurtenis op een regel van [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
        ' koppeling
        liaisons()
    End Sub

We maken gebruik van het feit dat beide links het attribuut [CommandName] hebben om ze van elkaar te onderscheiden. Afhankelijk van de waarde van dit attribuut wordt de procedure [infos] of [abonner] aangeroepen, waarbij in beide gevallen de sleutel "id" wordt doorgegeven die gekoppeld is aan het element van [DataGrid] waar de gebeurtenis plaatsvond. Met deze informatie geeft de procedure [info] de informatie weer van het door de gebruiker gekozen thema:


    Private Sub infos(ByVal id As String)
        ' informatie over het sleutelthema id
        ' de rij uit [datatable] die overeenkomt met de sleutel wordt opgehaald
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
            ' de informatie wordt weergegeven
            lblThème.Text = CType(ligne("thème"), String)
            lblDescription.Text = CType(ligne("description"), String)
            panelInfo.Visible = True
        End If
    End Sub

Aangezien de tabel [dtThèmes] een primaire sleutel heeft, kan met de methode [dtThèmes.Rows.Find("P")] de rij met de primaire sleutel P worden gevonden. Als deze wordt gevonden, krijgen we een object [DataRow]. Hier moeten we zoeken naar de rij met de primaire sleutel [id], waarbij [id] als parameter wordt doorgegeven. Als de rij wordt gevonden, worden de gegevens [thème] en [description] van deze rij in het informatiepaneel geplaatst, dat vervolgens zichtbaar wordt gemaakt.

De procedure [abonner(id)] moet het sleutelthema [id] toevoegen aan de lijst met abonnementen. De code ervan is als volgt:


    Private Sub abonner(ByVal id As String)
         ' abonnement op het thema met sleutel-id
         ' we halen de regel op van [datatable] die overeenkomt met de sleutel
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
             ' er wordt gecontroleerd of men nog niet is geabonneerd
            Dim abonnement As DataRow
            abonnement = dtAbonnements.Rows.Find(id)
            If Not abonnement Is Nothing Then
                 ' er wordt een foutmelding weergegeven
                lblInfo.Text = "Vous êtes déjà abonné au thème [" + ligne("thème") + "]"
            Else
                 ' het thema wordt toegevoegd aan de abonnementen
                abonnement = dtAbonnements.NewRow
                abonnement("id") = id
                abonnement("thème") = ligne("thème")
                dtAbonnements.Rows.Add(abonnement)
                 ' er worden koppelingen gemaakt
                liaisons()
            End If
        End If
    End Sub

In de lijst met thema's [dtThèmes] wordt eerst gezocht naar de sleutelregel [id]. Als deze wordt gevonden, wordt gecontroleerd of dit thema niet al in de lijst met abonnementen voorkomt, om te voorkomen dat het dubbel wordt geregistreerd. Als dat het geval is, wordt er een foutmelding weergegeven. Anders wordt er een nieuw abonnement toegevoegd aan de tabel [dtAbonnements] en worden de koppelingen van de componenten van de gegevenslijst naar hun respectievelijke bronnen gemaakt.

Wanneer de gebruiker op een knop [Retirer] klikt, moet een element uit de tabel [dtAbonnements] worden verwijderd. Dit gebeurt via de volgende procedure:


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' een abonnement wordt verwijderd
        Dim commande As String = e.CommandName
        If commande = "retirer" Then
            ' het abonnement op [datatable] wordt verwijderd
            With dtAbonnements.Rows
                .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
            End With
            ' koppelingen
            liaisons()
        End If
    End Sub

Eerst wordt het attribuut [CommandName] van het element dat de gebeurtenis veroorzaakt, gecontroleerd. Dit is eigenlijk vrij zinloos, aangezien de knop [Retirer] het enige besturingselement is dat een gebeurtenis kan genereren in de component [DataList]. Er is dus geen onduidelijkheid. Om een rij uit een object [DataTable] te verwijderen, gebruiken we de methode [DataList.Remove(DataRow)], die de als parameter doorgegeven rij van het type [DataRow] uit de tabel verwijdert. Deze rij wordt gevonden door de methode [DataList.Find], waaraan de primaire sleutel van de gezochte rij is doorgegeven. Zodra de rij is verwijderd, worden de gegevens aan de componenten gekoppeld

9.4. Een gepagineerde [DataList] beheren

We nemen het vorige voorbeeld opnieuw ter hand om de component [DataList], die de lijst met abonnementen van de gebruiker weergeeft, te pagineren. In tegenstelling tot de component [DataGrid] biedt de component [DataList] geen mogelijkheden voor paginering. We zullen zien dat het implementeren hiervan complex is, waardoor we de automatische paginering van de [DataGrid] des te meer zullen waarderen.

9.4.1. Werking

Het enige verschil is de paginering van de [DataList]. De pagina’s bevatten twee abonnementen. Als de gebruiker vijf abonnementen heeft, zijn er drie pagina’s. De eerste ziet er als volgt uit:

Image

De tweede pagina wordt verkregen via de link [Suivant]:

Image

De derde pagina:

Image

Merk op dat de links [Précédent] en [Suivant] alleen zichtbaar zijn als er respectievelijk een voorgaande en een volgende pagina is ten opzichte van de huidige pagina.

9.4.2. Opmaakcode

De links [Précédent] en [Suivant] worden verkregen door een tag <FooterTemplate> toe te voegen aan [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. Controlecode

Het bijbehorende bestand [global.asax.vb] verandert als volgt:

...

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)
         ' sessie starten - een lege abonnemententabel aanmaken
        Dim dtAbonnements As New DataTable
        With dtAbonnements
             ' de kolommen
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
             ' de primaire sleutel
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
         ' de tabel wordt in de sessie geplaatst
        Session.Item("abonnements") = dtAbonnements
         ' de huidige pagina is pagina 0
        Session.Item("pAC") = 0
         ' het aantal abonnementen op deze pagina is 0
        Session.Item("nbAC") = 0
    End Sub

Naast de abonnementstabel [dtAbonnements] worden er nog twee andere gegevens in de sessie opgenomen:

pAC
van het type [Integer] – dit is het nummer van de huidige pagina die bij de laatste aanvraag werd weergegeven
nbAC
van het type [Integer] – het aantal regels dat op de vorige huidige pagina werd weergegeven

Aan het begin van een sessie zijn zowel het nummer van de huidige pagina als het aantal regels op die pagina nul.

De controller [main.aspx.vb] ontwikkelt zich als volgt:

....

Public Class main
    Inherits System.Web.UI.Page

....

     ' gegevens van de applicatie
    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

Hier worden nieuwe gegevens gedefinieerd met betrekking tot de paginering van de abonnementen:


    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

Een deel van deze informatie wordt in de sessie opgeslagen en bij elke aanvraag opgehaald in de procedure [Page_Load]:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' de gegevensbronnen worden opgehaald
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' en de weergave-informatie van de abonnementen
        pAC = CType(Session("pAC"), Integer)
        nbAC = CType(Session("nbAC"), Integer)
        ' gegevenskoppeling
        If Not IsPostBack Then
            ' weergave van een lege lijst met abonnementen
            terminer()
        End If
        ' bepaalde informatie wordt in de cache opgeslagen
        panelInfo.Visible = False
    End Sub

De opgehaalde gegevens [pAC] en [nbAC] zijn gegevens over de abonnementspagina die bij de vorige aanvraag werd weergegeven:

pAC
dit is het nummer van de huidige pagina die bij de vorige aanvraag werd weergegeven
nbAC
aantal regels dat op deze huidige pagina wordt weergegeven

De methode [terminer] koppelt de componenten aan hun gegevensbronnen, net zoals de methode [liaisons] dat deed in de vorige toepassing. Nieuw is hier de koppeling van [DataList] met de tabel [dtPA], de te tonen abonnementspagina:


    Private Sub terminer()
        ' de gegevensbron wordt gekoppeld aan de component [datagrid]
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        ' de abonnementenpagina wordt gekoppeld aan de component [datalist], rekening houdend met de huidige pagina pAC
        changePAC()
        ' wordt de pagina p weergegeven
        With dlAbonnements
            .DataSource = dtPA
            .DataKeyField = "id"
        End With
        ' de gegevens worden toegewezen aan de componenten
        Page.DataBind()
        ' beheer van de koppelingen [précédent] en [suivant] van [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)
        ' de gegevens van de huidige pagina worden opgeslagen in de sessie
        Session("pAC") = pAC
        Session("nbAC") = dtPA.Rows.Count
    End Sub

Let op de volgende punten:

  • de bron [dtPA] is afhankelijk van het huidige paginanummer [pAC] dat moet worden weergegeven. De variabele [pAC] is een globale variabele van de klasse, die wordt bewerkt door de methoden die dit huidige paginanummer moeten wijzigen. De methode [changePAC] is verantwoordelijk voor het opbouwen van de tabel [dtPA], die gekoppeld zal worden aan de component [dlAbonnements].
  • De methode [setLiens] heeft als taak de links [Précédent] en [Suivant] weer te geven of te verbergen, afhankelijk van of de weergegeven huidige pagina [pAC] wordt voorafgegaan en gevolgd door een pagina. De methode heeft vier parameters:
    • [dlAbonnements]: het besturingselement [DataList], waarvan we de structuur gaan verkennen om de twee links te vinden. Hoewel deze twee links zich op een specifieke plek bevinden, namelijk in de voettekst van [DataList], lijkt er namelijk geen eenvoudige manier te zijn om er rechtstreeks naar te verwijzen. In ieder geval is die hier niet gevonden.
    • [blPrecedent]: booleaanse waarde die moet worden toegewezen aan de eigenschap [visible] van de link [Precedent] – is waar als de huidige pagina niet gelijk is aan 0
    • [blSuivant]: booleaanse waarde die moet worden toegewezen aan de eigenschap [visible] van de link [Suivant] – is waar als de huidige pagina niet de laatste pagina is in de lijst met abonnementen
    • [nbLiensTrouvés]: een uitvoerparameter die het aantal gevonden links telt. Zodra dit aantal gelijk is aan twee, wordt de methode beëindigd.
  • De gegevens [pAC] en [nbAC] worden in de sessie opgeslagen voor de volgende aanvraag.

De methode [changePAC] maakt de tabel [dtPA] aan, die wordt gekoppeld aan de component [dlAbonnements]. Dit gebeurt op basis van het nummer [pAC] van de huidige pagina die moet worden weergegeven. De tabel [dtPA] moet bepaalde rijen uit de abonnemententabel [dtAbonnements] weergeven. Ter herinnering: deze tabel wordt in de sessie opgeslagen en bij elke aanvraag bijgewerkt (uitgebreid of ingekort). We beginnen met het instellen van het interval [premier,dernier] van de regelnummers uit de tabel [dtAbonnements] die de tabel [dtPA] moet weergeven:


    Private Sub changePAC()
        ' maakt van de pagina pAC de huidige pagina voor abonnementen
        ' pagina's van de [datalist] beheren
        Dim nbAbonnements = dtAbonnements.Rows.Count
        Dim dernièrePage = (nbAbonnements - 1) \ nbAP
        ' eerste en laatste abonnement
        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

Zodra dit is gebeurd, kunnen we de tabel [dtPA] aanmaken. Eerst definiëren we de structuur [id,thème] en vullen we deze vervolgens door de rijen uit [dtAbonnements] te kopiëren waarvan het nummer binnen het eerder berekende interval [premier,dernier] valt.


         ' aanmaken van de datatable dtpa
        dtPA = New DataTable
        With dtPA
            ' de kolommen
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            ' de primaire sleutel
            .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

Aan het einde van de methode [changePAC] is de tabel [dtPA] aangemaakt en kan deze worden gekoppeld aan de component [DataList]. Dit gebeurt in de methode [terminer]. In dezelfde methode wordt de procedure [setLiens] gebruikt om de status van de koppelingen [Précédent] en [Suivant] van [DataList] vast te leggen. De code van deze procedure is als volgt:


    Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean, ByRef nbLiensTrouvés As Integer)
        ' we zoeken naar de koppelingen [précédent] en [suivant]
        ' in de controleboomstructuur van [datalist]
        ' Zijn alle koppelingen gevonden?
        If nbLiensTrouvés = 2 Then Exit Sub
        ' controle van de onderliggende controles
        Dim c As Control
        For Each c In ctl.Controls
            ' we werken eerst grondig door – de koppelingen bevinden zich onderaan de boomstructuur
            setLiens(c, blPrec, blSuivant, nbLiensTrouvés)
            ' link [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

De procedure is recursief. Ze zoekt eerst, onder de onderliggende besturingselementen van de component [dlAbonnements] naar de componenten met de namen [lnkPrecedent] en [lnkSuivant], die de identiteiten (attribuut ID) zijn van de twee paginatiekoppelingen. De procedure zoekt ze eerst vanaf de onderkant van de controleboom, omdat ze zich daar bevinden. Zodra een link is gevonden, wordt de teller [nbLiensTrouvés] verhoogd en wordt de eigenschap [visible] van de link gevuld met een waarde die als parameter aan de procedure is doorgegeven. Zodra beide links zijn gevonden, wordt de controleboom niet meer doorzocht en wordt de recursieve procedure beëindigd.

We hebben gezegd dat de methode [changePAC], die de gegevensbron [dtPA] voor de component [dlAbonnements] instelt, werkt met het nummer [pAC] van de huidige pagina die moet worden weergegeven. Verschillende procedures wijzigen dit nummer:


    Private Sub abonner(ByVal id As String)
        ' abonnement op het thema met sleutel-id
..
            ' het thema wordt toegevoegd aan de abonnementen
..
             ' het nummer van de huidige pagina wordt bijgewerkt – dit is nu de laatste pagina
            pAC = (dtAbonnements.Rows.Count - 1) \ nbAP
             ' gegevenskoppelingen
            terminer()
        End If
    End Sub

Nadat een abonnement is toegevoegd, staat dit onderaan de lijst met abonnementen. Daarom wordt de gebruiker naar de laatste pagina van de abonnementen geleid, zodat hij de toegevoegde abonnements kan zien.


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' een abonnement wordt verwijderd
        Dim commande As String = e.CommandName
        Select Case commande
            Case "retirer"
                ' het abonnement van [datatable] wordt verwijderd
                With dtAbonnements.Rows
                    .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
                End With
                ' moet de huidige pagina worden gewijzigd?
                nbAC -= 1
                If nbAC = 0 Then pAC -= 1
            Case "precedent"
                ' huidige pagina wijzigen
                pAC -= 1
            Case "suivant"
                ' huidige pagina wijzigen
                pAC += 1
        End Select
        ' gegevenskoppelingen
        terminer()
    End Sub
  • [nbAC] is het aantal regels dat op de huidige pagina wordt weergegeven voordat een abonnement wordt opgezegd. Als het nieuwe aantal regels op de pagina gelijk is aan 0, wordt het huidige paginanummer [pAC] met één verlaagd.
  • Als er op de link [Precedent] wordt geklikt, wordt het huidige paginanummer [pAC] met één verlaagd.
  • Als er op de link [Suivant] wordt geklikt, wordt het huidige paginanummer [pAC] met één verhoogd.

De overige procedures blijven ongewijzigd ten opzichte van voorheen.

9.4.4. Conclusie

Aan de hand van dit voorbeeld hebben we laten zien dat we een component [DataList] kunnen pagineren. Deze paginering is lastig en het verdient de voorkeur om, waar mogelijk, gebruik te maken van de automatische paginering van de component [DataGrid]. Dit voorbeeld heeft ons ook laten zien hoe we de componenten in de voettekst van de component [DataList] kunnen bereiken.

9.5. Toegangsklasse voor een productdatabase

We richten ons opnieuw op de reeds gebruikte database ACCESS [produits]. Ter herinnering: deze bevat één enkele tabel met de naam [liste], waarvan de structuur als volgt is:

We gaan een klasse bouwen voor toegang tot de tabel [liste], waarmee we deze kunnen lezen en bijwerken. We zullen ook een console-client bouwen die gebruikmaakt van de vorige klasse om de tabel bij te werken. In een tweede stap zullen we een webclient bouwen om hetzelfde werk te doen.

9.5.1. De klasse ExceptionProduits

De klasse [Exception] heeft een constructor die een foutmelding als parameter accepteert. We willen hier echter beschikken over een uitzonderingsklasse met een constructor die een lijst met foutmeldingen accepteert in plaats van één enkele foutmelding. Dit wordt de onderstaande klasse [ExceptionProduits]:


    Public Class ExceptionProduits
        Inherits Exception

        ' foutmeldingen met betrekking tot de uitzondering
        Private _erreurs As ArrayList

        ' constructor
        Public Sub New(ByVal erreurs As ArrayList)
            Me._erreurs = erreurs
        End Sub

        ' eigenschap
        Public ReadOnly Property erreurs() As ArrayList
            Get
                Return _erreurs
            End Get
        End Property
    End Class

9.5.2. De structuur [sProduit]

De structuur [produit] vertegenwoordigt een product [id, nom, prix]:


     ' structuur sProduit
    Public Structure sProduit
         ' de velden
        Private _id As Integer
        Private _nom As String
        Private _prix As Double

         ' eigenschap id
        Public Property id() As Integer
            Get
                Return _id
            End Get
            Set(ByVal Value As Integer)
                _id = Value
            End Set
        End Property

        ' naam-eigenschap
        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

        ' prijs-eigenschap
        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

De structuur staat alleen geldige gegevens toe voor de velden [nom] en [prix].

9.5.3. De klasse Producten

De klasse [Produits] is de klasse waarmee we de tabel [liste] in de productdatabase kunnen bijwerken. De structuur ervan is als volgt:

    Public Class produits
         ' instantiegegevens

        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

De instantiegegevens

De gegevens die door de verschillende methoden van de klasse worden gedeeld, zijn de volgende:


        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
connexion
de verbinding met de database wordt geopend voor de uitvoering van een opdracht SQL en wordt onmiddellijk daarna weer gesloten
selectText
query SQL [select] waarmee de volledige tabel wordt opgehaald [liste]
insertText
query waarmee een rij (naam, prijs) in de tabel kan worden ingevoegd [liste]. Merk op dat het veld [id] niet wordt gespecificeerd. Dit veld wordt namelijk automatisch opgehoogd door SGBD en hoeft daarom niet te worden opgegeven.
updateText
query waarmee de velden (naam, prijs) kunnen worden bijgewerkt van de rij in de tabel [liste] met de sleutel [id]
deleteText
query waarmee de rij uit de tabel [liste] met sleutel [id] kan worden verwijderd
selectCommand
object [OleDbCommand] dat de query [selectText] uitvoert op de verbinding [connexion]
updateCommand
object [OleDbCommand] voert de query [updateText] uit op de verbinding [connexion]
insertCommand
object [OleDbCommand] voert de query [insertText] uit op de verbinding [connexion]
deleteCommand
object [OleDbCommand] voert de query [deleteText] uit op de verbinding [connexion]
adaptateur
object waarmee het resultaat van de uitvoering van [selectCommand] kan worden opgehaald in een object [DataSet]

De constructor

De constructor ontvangt één enkele parameter, [chaineConnexionOLEDB], namelijk de verbindingsstring die de te gebruiken database aangeeft. Op basis hiervan worden de vier opdrachten voor het opvragen en bijwerken van de tabel, evenals de adapter, voorbereid. Dit is slechts een voorbereiding; er wordt geen verbinding tot stand gebracht.


        Public Sub New(ByVal chaineConnexionOLEDB As String)
            ' de verbinding wordt voorbereid
            connexion = New OleDbConnection(chaineConnexionOLEDB)
            ' query-opdrachten worden voorbereid
            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
            ' de gegevensadapter wordt voorbereid
            adaptateur.SelectCommand = selectCommand
        End Sub

De methode getProduits

Met deze methode kan de inhoud van de tabel [Liste] in een object [DataTable] worden opgeslagen. De code ervan is als volgt:


        Public Function getProduits() As DataTable
            ' de tabel [liste] wordt in een [dataset] geplaatst
            Dim contenu As New DataSet
            ' er wordt een object DataAdapter aangemaakt om de gegevens uit de bron OLEDB te lezen
            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
            ' het resultaat wordt weergegeven
            Return contenu.Tables(0)
        End Function

Het werk wordt uitgevoerd door de volgende twee instructies:


                With adaptateur
                    .FillSchema(contenu, SchemaType.Source)
                    .Fill(contenu)
                End With

De methode [FillSchema] bepaalt de structuur (kolommen, beperkingen, relaties) van de tabellen [DataSet] en contenu op basis van de structuur van de database waarnaar wordt verwezen door [adaptateur.Connexion]. Hierdoor kunnen we de structuur van de tabel [liste] ophalen, en met name de primaire sleutel ervan. De daaropvolgende bewerking [Fill] vult de tabellen [Dataset] en contenu met de rijen uit de tabel [liste]. Met deze ene bewerking zouden we wel de gegevens en de structuur hebben gehad, maar niet de primaire sleutel. Deze hebben we echter nodig om de tabel [liste] in het geheugen bij te werken. Net als bij de andere methoden behandelen we hier eventuele fouten met behulp van de klasse [ExceptionProduits], zodat we een lijst (ArrayList) met fouten krijgen in plaats van één enkele fout. De methode [getProduits] retourneert de tabel [liste] in de vorm van een object [DataTable].

De methode ajouterProduits

Met deze methode kan een rij (id, naam, prijs) worden toegevoegd aan de tabel [liste]. Deze informatie wordt aan de methode doorgegeven in de vorm van een structuur [sProduit] met velden [id, nom, prix]. De code van de methode is als volgt:


        Public Sub ajouterProduit(ByVal produit As sProduit)
            ' er wordt een product toegevoegd: [nom,prix]
            ' we stellen de parameters voor de toevoeging in
            With insertCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
            End With
            ' toevoeging
            Try
                ' verbinding geopend
                connexion.Open()
                ' opdracht wordt uitgevoerd
                insertCommand.ExecuteNonQuery()
            Catch ex As Exception
                ' probleem
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de l'ajout : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' verbinding beëindigd
                connexion.Close()
            End Try
        End Sub

De velden van de structuur [produit] worden in de parameters van de opdracht [insertCommand] ingevoerd. Ter herinnering: de huidige configuratie van deze opdracht (zie fabrikant) is als volgt:


 Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
 insertCommand.Connexion=connexion

Bevat de tekst van de opdracht SQL [insert] formele parameters ? die moeten worden vervangen door daadwerkelijke parameters. Dit gebeurt met de verzameling [Parameters] van de klasse [OleDbCommand]. Deze bevat elementen van het type [OleDbParameter] die de daadwerkelijke parameters definiëren die de formele parameters ? moeten vervangen. Aangezien deze geen naam hebben, wordt de index van de effectieve parameters gebruikt om te bepalen aan welke formele parameter een bepaalde effectieve parameter overeenkomt. Hier zal de effectieve parameter nr. i in de verzameling [Parameters] de formele parameter ? nr. i vervangen. Om een effectieve parameter van het type [OleDbParameter] aan te maken, wordt hier de constructor [OleDbParameter (Byval nom as String, Byval valeur as Object)] gebruikt, die de naam en de waarde van de effectieve parameter definieert. De naam kan willekeurig zijn. Bovendien wordt deze hier niet gebruikt. De twee parameters van de instructie SQL [insert] krijgen als waarden die van de velden [nom, prix] van de structuur [produit]. Zodra dit is gebeurd, wordt de invoeging uitgevoerd door de instructie [insertCommand.ExecuteNonQuery].

De methode modifierProduits

Met deze methode kan de rij in de tabel [liste] worden gewijzigd. De benodigde informatie wordt aangeleverd via de structuur [sProduit] met velden [id, nom, prix].


        Public Sub modifierProduit(ByVal produit As sProduit)
            ' een product wordt gewijzigd [id,nom,prix]
            ' de parameters voor de update worden voorbereid
            With updateCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
                .Add(New OleDbParameter("id", produit.id))
            End With
            ' de wijziging wordt doorgevoerd
            Try
                ' verbinding openen
                connexion.Open()
                ' opdracht wordt uitgevoerd
                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
                ' probleem
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la modification : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' verbinding wordt verbroken
                connexion.Close()
            End Try
        End Sub

De code is vrijwel identiek aan die van de methode [ajouterProduits], behalve dat de betreffende opdracht [OleDbCommand] [updateCommand] is in plaats van [insertCommand].

De methode supprimerProduits

Met deze methode kan de rij uit de tabel [liste] worden verwijderd waarvan de sleutel [id] als parameter is doorgegeven. De code is als volgt:


        Public Sub supprimerProduit(ByVal id As Integer)
            ' verwijdert het sleutelproduct [id]
            ' de parameters voor het verwijderen worden voorbereid
            With deleteCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("id", id))
            End With
            ' het product wordt verwijderd
            Try
                ' verbinding openen
                connexion.Open()
                ' commando wordt uitgevoerd
                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
                ' probleem
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la suppression : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' verbinding wordt verbroken
                connexion.Close()
            End Try
        End Sub

We volgen dezelfde aanpak als bij de voorgaande methoden.

9.5.4. Tests van de klasse [produits]

Een testprogramma [testproduits.vb] in de vorm van een consoleprogramma zou er als volgt uit kunnen zien:

Option Explicit On 
Option Strict On

' naamruimten
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections

Namespace st.istia.univangers.fr

     ' testpagina
    Module testproduits
        Dim contenu As DataTable

        Sub Main(ByVal arguments() As String)
             ' toont de inhoud van een producttabel
             ' de tabel bevindt zich in een database ACCESS waarvan de pg de bestandsnaam krijgt
            Const syntaxe1 As String = "pg bdACCESS"

             ' controle van de programma-instellingen
            If arguments.Length <> 1 Then
                 ' foutmelding
                Console.Error.WriteLine(syntaxe1)
                 ' einde
                Environment.Exit(1)
            End If
             ' de verbindingsstring wordt voorbereid
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
             ' productobject aanmaken
            Dim objProduits As produits = New produits(chaineConnexion)
             ' alle producten worden weergegeven
            afficheProduits(objProduits)
             ' een product wordt toegevoegd
            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
             ' alle producten weergeven
            afficheProduits(objProduits)
             ' de id van het toegevoegde product ophalen
            produit.id = CType(contenu.Rows(contenu.Rows.Count - 1)("id"), Integer)
             ' het toegevoegde product wijzigen
            produit.prix = 200
            Try
                objProduits.modifierProduit(produit)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
             ' alle producten weergeven
            afficheProduits(objProduits)
             ' het toegevoegde product verwijderen
            Try
                objProduits.supprimerProduit(produit.id)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
             ' alle producten weergeven
            afficheProduits(objProduits)
        End Sub

        Sub afficheProduits(ByRef objProduits As produits)
             ' de producttabel wordt opgehaald in een 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
                 ' rij i van de tabel
                Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
                "," + lignes(i).Item("prix").ToString)
            Next
             ' de console-uitvoer wordt gestopt
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub

        Sub afficheErreurs(ByRef erreurs As ArrayList)
             ' geeft fouten weer op de 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
             ' stopt de console-uitvoer
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub
    End Module
End Namespace

We compileren de twee bronbestanden:

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

en vervolgens testen we:

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
...

De lezer wordt verzocht de bovenstaande schermuitvoer te vergelijken met de code van het testprogramma.

9.6. Webapplicatie voor het bijwerken van de cache van de producttabel

9.6.1. Inleiding

We gaan nu een webapplicatie schrijven voor het bijwerken van de producttabel (toevoegen, verwijderen, wijzigen). De bijgewerkte tabel blijft in het geheugen opgeslagen in een object [DataTable] en wordt door alle gebruikers gedeeld. We willen twee punten benadrukken:

  • het beheer van een object [DataTable]
  • de problemen bij het gelijktijdig bijwerken van een tabel door meerdere gebruikers.

De MVC-architectuur van de applicatie ziet er als volgt uit:

9.6.2. Werking en weergaven

Het startscherm van de applicatie ziet er als volgt uit:

Image

 

Deze weergave, genaamd [formulaire], stelt de gebruiker in staat om een filtervoorwaarde toe te passen op de producten en het gewenste aantal producten per pagina in te stellen.

nr.
naam
type
rol
1
lnkFiltre
LinkButton
toont de weergave [Formulaire], die wordt gebruikt om de filtervoorwaarde vast te stellen
2
lnkMisaJour
LinkButton
toont de weergave [Produits], die dient om de producttabel te bekijken en bij te werken (wijzigen en verwijderen)
3
lnkAjout
LinkButton
toont het scherm [Ajout], dat wordt gebruikt om een product toe te voegen
4
panel
vueFormulaire
de weergave [Formulaire]
5
txtFiltre
TextBox
de filtervoorwaarde
6
txtPages
TextBox
aantal producten per pagina
7
rfvLignes
RequiredFieldValidator
controleert of er een waarde aanwezig is in [txtPages]
8
rvLignes
RangeValidator
controleert of txtPages binnen het interval [3,10] valt
9
btnExécuter
 
knop [submit] die de weergave [produits] weergeeft, gefilterd op voorwaarde (5)
10
lblInfo1
Label
Informatietekst bij fouten

Als de weergave [formulaire] bijvoorbeeld als volgt is ingevuld:

Image

krijgt men het volgende resultaat:

nr.
naam
type
rol
1
vueProduits
paneel
 
2
rdCroissant
rdDécroissant
RadioButton
stelt de gebruiker in staat de gewenste sorteervolgorde in te stellen wanneer hij op de titel van een van de kolommen [nom], [prix] klikt. Beide knoppen maken deel uit van de groep [rdTri].
3
DataGrid1
DataGrid
weergaverooster van een gefilterde weergave van de producttabel. Het filter is het filter dat is ingesteld door de weergave [formulaire]. Daarnaast geldt ook .AllowPaging=true, .AllowSorting=true
4
DataGrid2
DataGrid
toont de volledige producttabel – maakt het mogelijk om updates bij te houden
5
LblInfo2
Label
informatieve tekst, met name in geval van fouten
6
DataGrid3
DataGrid
toont de verwijderde producten in de producttabel
7
DataGrid4
DataGrid
geeft de gewijzigde producten weer in de producttabel
8
DataGrid5
DataGrid
toont de toegevoegde producten in de producttabel

Er zijn vijf gegevenscontainers op deze pagina. Ze geven allemaal dezelfde tabel [dtProduits] weer via een verschillende weergave [DataView]. Een weergave vertegenwoordigt een subset van de rijen uit de brontabel van de weergave. Deze subset wordt aangemaakt op basis van de eigenschappen [RowFilter] en [RowStateFilter] van de klasse [DataView]:

  • Met [RowFilter] kan een filter op de rijen worden ingesteld, zoals bijvoorbeeld hierboven bij [prix>30]. Dit type filtering wordt gebruikt door [DataGrid1].
  • Met [RowStateFilter] kan een filter worden ingesteld op basis van de status van de rij in de tabel. Dit geeft de status van de rij aan ten opzichte van de oorspronkelijke status die deze had toen de weergave van de tabel werd aangemaakt. In dit geval is de tabel [dtProduits] afkomstig uit een database. Aanvankelijk hebben alle rijen een status die gelijk is aan [Original], om aan te geven dat het om de oorspronkelijke rijen van de tabel gaat. Deze status kan vervolgens veranderen en verschillende waarden aannemen, waarvan hier enkele worden genoemd:
    • [Added]: de rij is toegevoegd – deze maakte geen deel uit van de oorspronkelijke tabel
    • [Deleted]: de rij is verwijderd – deze staat nog steeds in de tabel, maar is ‘gemarkeerd’ als ‘te verwijderen’
    • [Modified]: de rij is gewijzigd

[RowStateFilter] maakt het mogelijk om de rijen in de tabel weer te geven die een bepaalde status hebben:

  • (vervolg)
    • [DataViewRowState.Added]: alleen de toegevoegde rijen worden weergegeven. Deze worden weergegeven met hun huidige waarden.
    • [DataViewRowState.ModifiedOriginal]: alleen de gewijzigde rijen worden weergegeven. Ze worden weergegeven met hun oorspronkelijke waarden.
    • [DataViewRowState.ModifiedCurrent]: alleen de gewijzigde rijen worden weergegeven. Deze worden weergegeven met hun huidige waarden.
    • [DataViewRowState.Deleted]: alleen de verwijderde rijen worden weergegeven. Deze worden weergegeven met hun oorspronkelijke waarden.
    • [DataViewRowState.CurrentRows]: de niet-verwijderde rijen worden weergegeven. Ze worden weergegeven met hun huidige waarden.

Het filter [RowStateFilter] zal dus de volgende waarden hebben:

DataGrid1
geen filter op de toestand van de lijnen
 
DataGrid2
DataViewRowState.CurrentRows
toont de huidige status van de producttabel
DataGrid3
DataViewRowState.Deleted
geeft de verwijderde rijen uit de producttabel weer
DataGrid4
DataViewRowState.ModifiedOriginal
toont de gewijzigde rijen uit de producttabel met hun oorspronkelijke waarden
DataGrid5
DataViewRowState.Added
geeft de rijen weer die aan de oorspronkelijke producttabel zijn toegevoegd

Met de containers [DataGrid1-5] kunnen we de update van de tabel [dtProduits] volgen. Met de component [DataGrid1] kunnen we een product wijzigen en verwijderen. We zullen zien hoe de configuratie van de component deze update mogelijk maakt. De component is ook gepagineerd en gesorteerd. Deze twee punten zijn al in een eerder voorbeeld behandeld.

De link [Ajout] geeft toegang tot een formulier voor het toevoegen van een product:

nr.
naam
type
rol
1
vueAjout
paneel
 
2
txtNom
TextBox
productnaam
3
rfvNom
RequiredFieldValidator
controleert of er een waarde aanwezig is in [txtNom]
4
txtPrix
TextBox
productprijs
5
rfvPrix
RequiredFieldValidator
controleert of er een waarde aanwezig is in [txtPrix]
6
cvPrix
CompareValidator
controleert of prijs >= 0 is
7
btnAjouter
Knop
knop [submit] om het product toe te voegen
8
lblInfo3
Label
informatietekst over het resultaat van de toevoegactie

De database [produits] is mogelijk niet beschikbaar bij het opstarten van de applicatie. In dat geval wordt de weergave [erreurs] aan de gebruiker getoond:

nr.
naam
type
rol
1
vueErreurs
paneel
 
2
rptErreurs
Repeater
foutenlijst

9.6.3. Configuratie van de gegevenscontainers

De vijf containers [DataGrid] zijn geconfigureerd onder [WebMatrix]. Ze zijn opgemaakt (kleuren en randen) via de link [Configuration automatique] in hun eigenschappenpaneel. De container [DataGrid1] is geconfigureerd met behulp van de link [Générateur de propriétés] in datzelfde paneel. Sorteren is toegestaan (tabblad [Général]):

Image

De kolommen worden niet automatisch gegenereerd, in tegenstelling tot de vier andere containers. Ze zijn handmatig gedefinieerd met behulp van de wizard:

Image

Eerst zijn twee kolommen van het type [Colonne connexe] aangemaakt, genaamd [nom] en [prix]. Deze zijn respectievelijk gekoppeld aan de velden [nom] en [prix] van de gegevensbron die de containers zullen weergeven. Hieronder volgt bijvoorbeeld de configuratie van de kolom [nom]:

Image

De sorteeruitdrukking is de uitdrukking die achter de clausule [order by] van de instructie SQL [select] moet worden geplaatst, die wordt uitgevoerd wanneer dede gebruiker op de naam van de kopkolom [nom] klikt die gekoppeld is aan het veld [nom] van [DataGrid]. Hier hebben we [nom] ingevoerd, zodat de sorteerclausule [order by nom] wordt. We zullen zien dat we deze zullen aanpassen zodat deze [order by nom asc] of [order by nom desc] wordt, afhankelijk van de door de gebruiker gekozen sorteervolgorde.

Daarnaast hebben we twee kolommen met knoppen aangemaakt:

Image

Met de kolom [Modifier, Mettre à jour, Annuler] kunnen we een product wijzigen en met de kolom [Supprimer] kunnen we het verwijderen. Elk van deze kolommen kan worden geconfigureerd. De kolom [Modifier, Mettre à jour, Annuler] biedt de volgende configuratie:

Image

We zien dat we de teksten van de knoppen kunnen aanpassen. Wat de knoppen betreft, hebben we de keuze tussen links en knoppen (uitklapmenu hierboven). Hier is gekozen voor links. De configuratie van de kolom [Supprimer] is vergelijkbaar. Naast deze configuratiewizard hebben we rechtstreeks het eigenschappenvenster van [DataGrid] gebruikt om de eigenschap [DataKeyField] in te vullen, die aangeeft met welk veld uit de gegevensbron de rijen van [DataGrid] worden geïndexeerd. Hier wordt de primaire sleutel van de producttabel gebruikt:

Image

Uiteindelijk genereert deze configuratie de volgende weergavecode:


<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 &#224; jour" CancelText="Annuler" EditText="Modifier"></asp:EditCommandColumn>
          <asp:ButtonColumn Text="Supprimer" CommandName="Delete"></asp:ButtonColumn>
  </Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;dent" ...></PagerStyle>
</asp:DataGrid>

Zoals altijd geldt dat je, zodra je enige ervaring hebt opgedaan, de bovenstaande code geheel of gedeeltelijk rechtstreeks kunt schrijven.

De overige [DataGrid]-containers hebben de standaardconfiguratie die wordt verkregen door de kolommen van de [DataGrid] automatisch te genereren op basis van die van de gegevensbron waaraan deze is gekoppeld.

De container [Repeater] wordt gebruikt om een lijst met fouten weer te geven. De configuratie ervan gebeurt rechtstreeks in de presentatiecode:


                            <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>

Elke regel van de component geeft de waarde [Container.DataItem], c.a.d weer. De bijbehorende waarde uit de gegevenslijst. Deze heeft het type [ArrayList] en vertegenwoordigt een foutenlijst.

9.6.4. De weergavecode van de applicatie

Deze staat in het bestand [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&nbsp;la table LISTE.&nbsp;Exemple : prix&lt;100 and 
                                prix&gt;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 &#224; jour"
                                                         CancelText="Annuler" EditText="Modifier">
                                       </asp:EditCommandColumn>
                                                    <asp:ButtonColumn Text="Supprimer" CommandName="Delete">
                                     </asp:ButtonColumn>
                                                </Columns>
                                                <PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;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>

Let op de volgende punten:

  • de pagina bestaat uit vier containers (panelen) [vueFormulaire, vueProduits, vueAjout, vueErreurs] die de vier weergaven van de applicatie vormen.
  • De knoppen of links van het type [submit] hebben de eigenschap [CausesValidation=false]. De eigenschap [causesValidation=true] zorgt ervoor dat alle validatiecontroles van de pagina worden uitgevoerd. Hier hoeven echter niet alle validatiecontroles tegelijkertijd te worden uitgevoerd. Wanneer we bijvoorbeeld iets toevoegen, willen we niet dat de controles met betrekking tot het aantal regels per pagina worden uitgevoerd. We zullen dus zelf aangeven welke validatiecontroles moeten worden uitgevoerd.

9.6.5. De controlecode [global.asax]

De controlecode [global.asax] is als volgt:

<%@ Application src="global.asax.vb" inherits="Global" %>

De bijbehorende code [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)
        ' de configuratiegegevens worden opgehaald
        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
        ' zijn er configuratiefouten?
        If erreurs.Count <> 0 Then
            ' de fouten worden genoteerd
            Application("erreurs") = erreurs
            ' we sluiten af
            Exit Sub
        End If
        ' hier geen configuratiefouten
        ' er wordt een productobject aangemaakt
        Dim dtProduits As DataTable
        Try
            dtProduits = New produits(chaînedeConnexion).getProduits
        Catch ex As ExceptionProduits
            'er is een fout opgetreden bij het openen van de producten; dit wordt genoteerd in de applicatie
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            ' niet-afgehandelde fout
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            ' exit sub
        End Try
        ' hier geen initialisatiefouten
        ' het aantal producten per pagina wordt opgeslagen
        Application("defaultProduitsPage") = defaultProduitsPage
        ' de producttabel wordt opgeslagen
        Application("dtProduits") = dtProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' sessievariabelen initialiseren
        If IsNothing(Application("erreurs")) Then
            ' weergave van de producttabel
            Session("dvProduits") = CType(Application("dtProduits"), DataTable).DefaultView
            ' aantal producten per pagina
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' huidige weergegeven pagina
            Session("pageCourante") = 0
        End If
    End Sub
End Class

In [Application_Start] worden eerst twee gegevens uit het configuratiebestand [web.config] van de applicatie opgehaald:

  • OLEDBStringConnection: de tekenreeks OLEDB voor de verbinding met de productdatabase
  • defaultProduitsPage: het standaard aantal producten per weergegeven pagina

<?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>

Als een van deze twee gegevens ontbreekt, wordt er een foutenlijst aangevuld en in de applicatie geplaatst. Hetzelfde geldt als de parameter [defaultProduitsPage] wel bestaat, maar onjuist is. Als beide verwachte parameters aanwezig en correct zijn, wordt een tabel [dtProduits] aangemaakt en in de applicatie geplaatst. Deze tabel zal door de verschillende klanten worden gebruikt en bijgewerkt. De database zelf blijft ongewijzigd. In een volgende applicatie zullen we ons richten op het bijwerken van de database. Deze tabel wordt aangemaakt op basis van een instantie van de eerder besproken klasse [produits] en de bijbehorende methode [getProduits]. Het ophalen van de tabel [dtProduits] kan mislukken. In dat geval is bekend dat de klasse [produits] een uitzondering van het type [ExceptionProduits] genereert. Deze wordt hier opgevangen en de bijbehorende foutenlijst wordt opgeslagen in de applicatie die gekoppeld is aan de sleutel [erreurs]. Bij elke verwerking van een verzoek wordt gecontroleerd of deze sleutel aanwezig is in de informatie die in de applicatie is opgeslagen. Als deze wordt gevonden, wordt de weergave [erreurs] naar de klant verzonden.

Hoewel de tabel [dtProduits] door alle webclients wordt gedeeld, heeft elke client niettemin zijn eigen weergave [dvProduits] van deze tabel. Elke webclient heeft namelijk de mogelijkheid om een filter in te stellen op de tabel [dtProduits] en een sorteervolgorde te kiezen. Deze informatie, die specifiek is voor elke webclient, wordt opgeslagen in de weergave van de client. Daarom wordt deze weergave aangemaakt in [Session_Start], zodat deze in de sessie van elke afzonderlijke client kan worden geplaatst. We gebruiken het attribuut [DefaultView] van de tabel [dtProduits] om een standaardweergave van de tabel te krijgen. In eerste instantie zijn er noch een filter, noch een sorteervolgorde. Daarnaast worden er ook twee gegevens in de sessie opgeslagen:

  • het aantal producten per pagina met sleutel [nbProduitsPage]. Bij het starten van de sessie is dit aantal gelijk aan het standaardgetal dat in het configuratiebestand is gedefinieerd.
  • het nummer van de huidige productpagina. In eerste instantie is dit de eerste pagina.

9.6.6. De controlecode [main.aspx.vb]

Het skelet van de controller ziet er als volgt uit:


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

    ' paginacomponenten
    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

    ' pagina-gegevens
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
    Protected erreur As Boolean = False

    ' pagina wordt geladen
    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. De instantiegegevens

De klasse [main] maakt gebruik van de volgende instantiegegevens:

Public Class main
    Inherits System.Web.UI.Page

     ' paginaonderdelen
    Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
...

     ' pagina-gegevens
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
dtProduits
de producttabel [DataTable] – gemeenschappelijk voor alle klanten
dvProduits
de weergave [DataView] van de producten – specifiek voor elke klant
defaultProduitsPage
standaard aantal producten per pagina

9.6.8. De procedure [Page_Load] voor het laden van de pagina

De code van de procedure [page_Load] is als volgt:


    ' pagina laden
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' wordt gecontroleerd of de applicatie een fout vertoont
        If Not IsNothing(Application("erreurs")) Then
            ' de applicatie is niet correct geïnitialiseerd
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        ' er wordt een referentie opgehaald uit de producttabel
        dtProduits = CType(Application("dtProduits"), DataTable)
         ' de productweergave wordt opgehaald
        dvProduits = CType(Session("dvProduits"), DataView)
         ' het aantal producten per pagina wordt opgehaald
        defaultProduitsPage = CType(Application("defaultProduitsPage"), Integer)
         ' een eventuele lopende update wordt geannuleerd
        DataGrid1.EditItemIndex = -1
        'Eerste verzoek
        If Not IsPostBack Then
            ' het oorspronkelijke formulier wordt weergegeven
            txtPages.Text = defaultProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub
  • Eerst wordt gecontroleerd of de producttabel bij het opstarten van de applicatie kon worden geladen. Als dat niet het geval is, wordt de weergave [erreurs] met de bijbehorende foutmeldingen weergegeven, waarna de procedure wordt beëindigd nadat de booleaanse variabele [erreur] op 'waar' is gezet. Deze indicator wordt door bepaalde procedures getest.
  • We halen de producttabel [dtProduits] op uit de applicatie en slaan deze op in de instantievariabele [dtProduits], zodat deze door alle methoden van de pagina kan worden gedeeld.
  • We doen hetzelfde met de weergave [dvProduits], die uit de sessie wordt opgehaald, en het standaard aantal producten per pagina, dat uit de applicatie wordt opgehaald.
  • Als dit het eerste verzoek van de klant is, tonen we hem het formulier voor het instellen van de filter- en pagineringvoorwaarden met een standaardpaginering van [defaultProduitsPage] producten per pagina.
  • De bewerkingsmodus van de component [dataGrid1] wordt geannuleerd. Deze component heeft een attribuut [EditItemIndex] dat de index is van het element dat momenteel wordt bewerkt. Als [EditItemIndex]=-1 is, wordt er geen element bewerkt. Als [EditItemIndex]=i is, dan wordt element nr. i van de component [DataGrid] bewerkt. Dit element wordt dan anders weergegeven dan de andere elementen van de [dataGrid]:

Image

Hierboven bevindt het element [Produit8, 80] zich in de modus [édition]. Hierboven is te zien dat de gebruiker zijn wijziging kan bevestigen via de link [Mettre à jour] en deze kan annuleren via de link [Annuler]. Hij kan ook andere links gebruiken die niets te maken hebben met het element dat momenteel wordt bewerkt. Door bij elke keer dat de pagina wordt geladen [DataGrid1.EditItemIndex=-1] in te voeren, zorgen we ervoor dat de bewerkingsmodus van [DataGrid1] systematisch wordt geannuleerd. Er wordt alleen een andere waarde toegewezen als er op een link [Modifier] is geklikt. Dit gebeurt in de procedure die deze gebeurtenis afhandelt. Er zijn dan de volgende situaties:

  1. er is op de link [Modifier] van element nr. 8 van [DataGrid1] geklikt. [Page_Load] zet eerst -1 in [DataGrid1.EditItemIndex]. Vervolgens zal de procedure die de gebeurtenis [Modifier] verwerkt, de waarde 8 in [DataGrid1.EditItemIndex] plaatsen. Uiteindelijk, wanneer de pagina naar de klant wordt teruggestuurd, zal element nr. 8 zich inderdaad in de modus [édition] bevinden en zal dit worden weergegeven zoals hierboven getoond.
  2. De gebruiker wijzigt het product dat zich in de modus [édition] bevindt en bevestigt zijn wijziging via de link [Mettre à jour]. [Page_Load] zet -1 in [DataGrid1.EditItemIndex]. Vervolgens wordt de procedure uitgevoerd die de gebeurtenis [Mettre à jour] verwerkt en wordt element nr. 8 van [dtProduits] bijgewerkt. Wanneer de pagina naar de klant wordt teruggestuurd, zal geen enkel element van [DataGrid1] in de bewerkingsmodus staan (DataGrid.EditItemIndex=-1).
  3. Als een gebruiker de bewerkingsmodus voor een product heeft geopend, zou het logisch zijn dat hij deze bewerking afbreekt via [Annuler]. Niets weerhoudt hem er echter van om op een andere link te klikken. We moeten dus rekening houden met dit scenario. In dit geval, net als in de voorgaande gevallen, begint [Page_Load] met het instellen van -1 in [DataGrid1.EditItemIndex], waarna de procedure die de opgetreden gebeurtenis afhandelt, wordt uitgevoerd. De eigenschap [DataGrid1.EditItemIndex] wordt hierdoor niet gewijzigd en behoudt dus de waarde -1. Wanneer de pagina naar de klant wordt teruggestuurd, zal geen enkel element van [DataGrid1] in de bewerkingsmodus staan.

We zien dat door [EditItemIndex] bij het laden van de pagina op -1 te zetten, we ons geen zorgen hoeven te maken of de gebruiker al dan niet bezig was met een bewerking toen hij op een link klikte.

9.6.9. Weergave van de weergaven [erreurs], [formulaire] en [ajout]

De weergave van de weergave [erreurs] wordt aangevraagd bij het laden van de pagina [Page_Load] als blijkt dat de applicatie niet correct is geïnitialiseerd. Dit gebeurt door de gegevenscomponent [rptErreurs] te koppelen aan de foutenlijst die als parameter wordt doorgegeven en door de betreffende container zichtbaar te maken. Ten slotte worden de drie links [Filtre, Mise à jour, Ajout] verborgen, omdat de applicatie bij een fout niet kan worden gebruikt.


    Private Sub afficheErreurs(ByVal erreurs As ArrayList)
        ' de foutenlijst wordt gekoppeld aan de repeater rptErreurs
        With rptErreurs
            .DataSource = erreurs
            .DataBind()
        End With
        'de opties worden uitgeschakeld
        lnkAjout.Visible = False
        lnkMisajour.Visible = False
        lnkFiltre.Visible = False
        ' de weergave wordt weergegeven [erreurs]
        vueErreurs.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

De overige weergaven worden opgevraagd via de opties die aan de gebruiker worden aangeboden:

Image

De weergave van de weergave [Ajout] wordt opgevraagd wanneer de gebruiker op de link [Ajout] op de pagina klikt:


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        ' de weergave wordt weergegeven [ajout]
        afficheAjout()
    End Sub

    Private Sub afficheAjout()
        ' toont de weergave 'Toevoegen'
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

De weergave van de weergave [Formulaire] wordt opgevraagd wanneer de gebruiker op de link [Filtrage] op de pagina klikt. Vervolgens moet de weergave worden getoond waarmee

  • het filter voor de producttabel
  • het aantal producten dat op elke webpagina wordt weergegeven

Er wordt een formulier weergegeven met de waarden die in de sessie zijn opgeslagen:


    Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
        ' filtervoorwaarden instellen
        txtFiltre.Text = dvProduits.RowFilter
        ' paginering instellen
        txtPages.Text = CType(Session("nbProduitsPage"), String)
        ' het filterformulier wordt weergegeven
        afficheFormulaire()
    End Sub

De filtervoorwaarde van een weergave wordt gedefinieerd in het bijbehorende attribuut [RowFilter]. Ter herinnering: de gefilterde en gepagineerde weergave heet [dvProduits] en is bij het laden van de pagina [Page_Load] in de sessie opgeslagen. De filtervoorwaarde wordt dus opgehaald uit [dvProduits.RowFilter]. Het aantal producten per pagina wordt eveneens uit de sessie opgehaald. Als de gebruiker deze informatie nog nooit heeft ingesteld, is dit aantal gelijk aan het standaard aantal producten per pagina. Vervolgens wordt het formulier weergegeven waarmee deze twee gegevens kunnen worden ingesteld:


    Private Sub afficheFormulaire()
        ' de weergave [formulaire] wordt weergegeven
        vueFormulaire.Visible = True
        vueErreurs.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

Image

9.6.10. Validatie van de weergave [formulaire]

Als u op de bovenstaande knop [Exécuter] klikt, wordt dit verwerkt door de volgende procedure:


    Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
        ' is de pagina geldig?
        rfvLignes.Validate()
        rvLignes.Validate()
        If Not rfvLignes.IsValid Or Not rvLignes.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If
        ' de gefilterde gegevens aan het raster koppelen
        Try
            dvProduits.RowFilter = txtFiltre.Text.Trim
        Catch ex As Exception
            lblinfo1.Text = "Erreur de filtrage (" + ex.Message + ")"
            afficheFormulaire()
            Exit Sub
        End Try
        ' het aantal producten per pagina wordt opgeslagen
        Session("nbProduitsPage") = txtPages.Text
        'en de huidige pagina
        Session("pageCourante") = 0
        ' alles is in orde – de gegevens worden weergegeven
        afficheProduits(0, CType(txtPages.Text, Integer))
    End Sub

Laten we er allereerst op wijzen dat de pagina twee validatiecomponenten bevat:

nr.
naam
type
rol
7
rfvLignes
RequiredFieldValidator
controleert of er een waarde aanwezig is in [txtPages]
8
rvLignes
RangeValidator
controleert of txtPages binnen het interval [3,10] ligt

De procedure begint met het uitvoeren van de controlecode van de twee bovenstaande componenten met behulp van hun methode [Validate] en controleert vervolgens de waarde van hun attribuut [IsValid]. Deze waarde is alleen [true] als de gecontroleerde gegevens geldig zijn bevonden. Als de geldigheidstests van een van de twee componenten zijn mislukt, wordt de weergave [formulaire] opnieuw weergegeven, zodat de gebruiker zijn of haar fouten kan corrigeren. De filtervoorwaarde wordt toegepast op het attribuut [dvProduits.RowFilter]. Hier kan een uitzondering optreden als de gebruiker een onjuist filtercriterium heeft ingevoerd, zoals hieronder wordt weergegeven:

Image

In dat geval wordt de weergave [formulaire] opnieuw weergegeven. Als beide ingevoerde gegevens correct zijn, worden er twee gegevens in de sessie geplaatst:

  • het aantal producten per pagina dat de gebruiker heeft gekozen
  • het paginanummer dat wordt weergegeven – in eerste instantie pagina 0

Deze twee gegevens worden telkens gebruikt wanneer de weergave [produits] wordt opgevraagd.

9.6.11. Weergave van de weergave [produits]

De weergave [produits] ziet er als volgt uit:

Image

We hebben 5 componenten [DataGrid] die elk een specifieke weergave van de producttabel [dtProduits] weergeven. Van links naar rechts:

naam
rol
DataGrid1
weergaverooster van een gefilterd overzicht van de producttabel. Het filter is dat wat is ingesteld door de weergave [formulaire]. We hebben ook .AllowPaging=true, .AllowSorting=true
DataGrid2
geeft de volledige producttabel weer – maakt het mogelijk om updates bij te houden
DataGrid3
toont de verwijderde producten in de producttabel
DataGrid4
toont de gewijzigde producten in de producttabel
DataGrid5
toont de toegevoegde producten in de producttabel

De procedure [afficheProduits] is verantwoordelijk voor het weergeven van de vorige weergave:


    Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
        ' de gegevens worden aan beide componenten gekoppeld [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()
        ' de weergave wordt weergegeven [produits]
        vueProduits.Visible = True
        vueFormulaire.Visible = False
        vueErreurs.Visible = False
        vueAjout.Visible = False
        ' de huidige pagina wordt opgeslagen
        Session("pageCourante") = page
    End Sub

De procedure accepteert twee parameters:

  • het paginanummer [page] dat moet worden weergegeven in [DataGrid1]
  • het aantal producten [taillePage] per pagina

De koppeling van [DataGrid] aan de producttabel gebeurt via 5 verschillende weergaven.

  • [DataGrid1] is gekoppeld aan de gepagineerde en gesorteerde weergave [dvProduits].

        With DataGrid1
            .DataSource = dvProduits
            .PageSize = taillePage
            .CurrentPageIndex = page
            .DataBind()
        End With

Bij het koppelen van [DataGrid1] aan de gegevensbron zijn de twee gegevens nodig die als parameters aan de procedure worden doorgegeven.

  • [DataGrid2] is gekoppeld aan een weergave die alle huidige elementen van de tabel [dtProduits] toont. Deze biedt, net als [DataGrid1], een bijgewerkte weergave van de tabel [dtProduits], maar dan zonder paginering of sortering.

        Dim dvCurrent As New DataView(dtProduits)
        dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
        With DataGrid2
            .DataSource = dvCurrent
            .DataBind()
        End With

We gebruiken hier het attribuut [RowStateFilter] van de klasse [DataView]. Een rij in een tabel of, in dit geval, in een weergave heeft een attribuut [RowState] dat de status van de rij aangeeft. Hier volgen enkele voorbeelden:

  • (vervolg)
    • [Added]: de rij is toegevoegd
    • [Modified]: de rij is gewijzigd
    • [Deleted]: de rij is verwijderd
    • [Unchanged]: de rij is niet gewijzigd

Toen de tabel [dtProduits] aanvankelijk werd aangemaakt in [Application_Start], bevonden alle rijen zich in de status [Unchanged]. Deze status zal veranderen naarmate de webclients updates uitvoeren:

  • (vervolg)
    • wanneer een klant een nieuw product aanmaakt, wordt er een rij toegevoegd aan de tabel [dtProduits]. Deze krijgt de status [Added].
    • wanneer een product wordt gewijzigd, verandert de status ervan van [Unchanged] naar [Modified].
    • Wanneer een product wordt verwijderd, wordt de regel niet fysiek verwijderd. In plaats daarvan wordt deze gemarkeerd als te verwijderen en verandert de status in [Deleted]. Het is mogelijk om deze verwijdering ongedaan te maken.

Met het attribuut [RowStateFilter] van de klasse [DataView] kan een weergave worden gefilterd op basis van de status [RowState] van de rijen. De mogelijke waarden zijn die van de opsomming [DataRowViewState]:

  • (vervolg)
    • DataRowViewState.CurrentRows: de niet-verwijderde rijen worden weergegeven met hun huidige waarden
    • DataRowViewState.Added: de toegevoegde rijen worden weergegeven met hun huidige waarden
    • DataRowViewState.Deleted: de verwijderde rijen worden weergegeven met hun oorspronkelijke waarden
    • DataRowViewState.ModifiedOriginal: de gewijzigde regels worden weergegeven met hun oorspronkelijke waarden
    • DataRowViewState.ModifiedCurrent: gewijzigde rijen worden weergegeven met hun huidige waarden

Een gewijzigde rij heeft twee waarden: de oorspronkelijke waarde, die de rij had vóór de eerste wijziging, en de huidige waarde, die is verkregen na één of meerdere wijzigingen. Deze twee waarden worden tegelijkertijd bewaard.

De DataGrid 2 tot en met 5 geven een weergave van de tabel [dtProduits] weer, gefilterd op het kenmerk [RowStateFilter]

DataGrid
Filter
DataGrid2
RowStateFilter= DataRowViewState.CurrentRows
DataGrid3
RowStateFilter= DataRowViewState.Deleted
DataGrid4
RowStateFilter= DataRowViewState.ModifiedOriginal
DataGrid5
RowStateFilter= DataRowViewState.Added

De tabel [dtPoduits] wordt gelijktijdig door verschillende webclients bijgewerkt, wat kan leiden tot conflicten bij de toegang tot de tabel. Wanneer een client zijn weergaven van de tabel [dtProduits] weergeeft, willen we voorkomen dat hij dit doet terwijl de tabel zich in een onstabiele toestand bevindt omdat deze op dat moment door een andere webclient wordt gewijzigd. Ook telkens wanneer een client de tabel [dtProduits] nodig heeft voor lezen, zoals hierboven beschreven, of voor schrijven wanneer hij producten gaat toevoegen, wijzigen of verwijderen, zal hij zich met de andere clients synchroniseren aan de hand van de volgende volgorde:

Application.Lock
... section critique 1
Application.Unlock

Er kunnen meerdere kritieke secties in de applicatiecode voorkomen:

Application.Lock
... section critique 2
Application.Unlock

De werking is als volgt:

  1. een of meer clients komen bij de instructie [Application.Lock] in kritiek sectie 1. Dit is een tokenverdelingspunt met één ingang. Slechts één client krijgt dit token. Laten we deze client C1 noemen.
  2. Zolang de client C1 het token niet heeft teruggegeven via [Application.Unlock], mag geen enkele andere client een kritieke sectie betreden die wordt gecontroleerd door [Application.Lock]. In het bovenstaande voorbeeld kan dus geen enkele client de kritieke secties 1 en 2 betreden.
  3. De client C1 voert [Application.Unlock] uit en geeft daarmee het toegangstoken terug. Dit token kan vervolgens aan een andere client worden toegewezen. De stappen 1 tot en met 3 worden herhaald.

Met dit mechanisme zorgen we ervoor dat slechts één klant toegang heeft tot de tabel [dtProduits], zowel voor lezen als voor schrijven.

De link [Mise à jour] geeft ook de weergave [Produits] weer:

Image

De bijbehorende procedure is als volgt:


    Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
        ' weergave productoverzicht
        afficheProduits(CType(Session("pageCourante"), Integer), CType(Session("nbProduitsPage"), Integer))
    End Sub

De weergave [produits] moet worden weergegeven. Dit gebeurt met behulp van de procedure [afficheProduits], die twee parameters accepteert: het nummer van de huidige pagina die moet worden weergegeven en het aantal producten per pagina dat moet worden weergegeven. Deze twee gegevens bevinden zich in de sessie, eventueel met hun standaardwaarden als de gebruiker ze nog nooit zelf heeft ingesteld.

9.6.12. Paginering en sortering van [DataGrid1]

We hebben de procedures voor paginering en sortering van een [DataGrid] al in een ander voorbeeld gezien. Bij het wisselen van pagina wordt de weergave [produits] weergegeven door het nummer van de nieuwe pagina als parameter door te geven aan de procedure [afficheProduits].


    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
        ' pagina wisselen
        afficheProduits(e.NewPageIndex, DataGrid1.PageSize)
    End Sub

De procedure [DataGrid1_SortCommand] wordt uitgevoerd wanneer de gebruiker op de koptekst van een van de kolommen van [DataGrid1] klikt. De sorteeruitdrukking moet dan worden toegewezen aan het attribuut [Sort] van de weergave [dvProduits] die wordt weergegeven door [DataGrid1]. Deze is syntactisch gelijk aan de sorteeruitdrukking die achter de clausule [order by] van de instructie SQL SELECT staat. Het argument [e] van de procedure heeft een attribuut [SortExpression] dat ons de sorteeruitdrukking geeft die hoort bij de kolom waarvan de koptekst is aangeklikt. Toen de component [DataGrid1] werd aangemaakt, was deze sorteeruitdrukking al gedefinieerd. Hieronder staat de sorteringsuitdrukking die is gedefinieerd voor de kolom [nom] van [DataGrid1]:

Image

Als de sorteeruitdrukking niet is gedefinieerd bij het ontwerpen van [DataGrid], wordt de naam van het gegevensveld gebruikt dat is gekoppeld aan de kolom van [DataGrid]. De sorteervolgorde (oplopend, aflopend) wordt hier door de gebruiker ingesteld met behulp van de keuzerondjes [rdCroissant, rd Décroissant]:

Image

De sorteerprocedure is als volgt:


    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
        ' de dataview wordt gesorteerd
        With dvProduits
            .Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        End With
        ' we geven deze weer
        afficheProduits(0, DataGrid1.PageSize)
    End Sub

9.6.13. Een product verwijderen

Om een product te verwijderen, gebruikt de gebruiker de link [Supprimer] in de productregel:

Image

Vervolgens wordt de procedure [DataGrid1_DeleteCommand] uitgevoerd:


    Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
        ' een product verwijderen
        ' ID van het te verwijderen product
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' het product verwijderen
        Dim erreur As Boolean = False
        Try
            supprimerProduit(idProduit)
        Catch ex As Exception
            ' probleem
            lblInfo2.Text = ex.Message
            erreur = True
        End Try
        ' pagina wisselen?
        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
        ' producten weergeven
        afficheProduits(page, DataGrid1.PageSize)
    End Sub

De producten worden bijgewerkt via de productcode [id]. De kolom [id] in de tabel [dtProduits] is namelijk de primaire sleutel en dankzij deze sleutel kunnen we in de tabel [dtProduits] de productregel terugvinden die wordt bijgewerkt in de component [DataGrid1]. De procedure begint dus met het ophalen van de sleutel van het product waarvan de link [Supprimer] is aangeklikt:


         ' sleutel van het te verwijderen product
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

We weten dat [e.Item] het element is van [DataGrid1] dat aan de basis ligt van de gebeurtenis. Grofweg is dit element een regel van [DataGrid]. [e.Item.ItemIndex] is het nummer van de regel die de gebeurtenis heeft veroorzaakt. Deze index heeft betrekking op de huidige weergegeven pagina. Zo is de eerste regel van de weergegeven pagina de eigenschap [ItemIndex=0], ook al heeft deze nummer 17 in de producttabel. [DataGrid1.DataKeys] is de lijst met sleutels van [DataGrid]. Omdat we bij het ontwerp [DataGrid1.DataKey=id] hebben opgegeven, bestaan de sleutels van [DataGrid1] uit de waarden van de kolom [id] van de tabel [dtProduits], die tegelijkertijd de primaire sleutel is. [DataGrid1.DataKeys(e.Item.ItemIndex)] is dus de sleutel [id] van het te verwijderen product. Zodra we dit hebben vastgesteld, vragen we de verwijdering aan met behulp van de procedure [supprimerProduit]:


         ' verwijdering van het product
        Dim erreur As Boolean = False
        Try
            supprimerProduit(idProduit)
        Catch ex As Exception
            ' pb
            lblInfo2.Text = ex.Message
            erreur = True
        End Try

Het verwijderen kan in sommige gevallen mislukken. We zullen zien waarom. Er wordt dan een uitzondering gegenereerd. Deze wordt hier afgehandeld. Als er een fout optreedt, hoeft [DataGrid1] niet te worden gewijzigd. Er wordt alleen een foutmelding toegevoegd aan de weergave [produits]. Als er geen fout is en de gebruiker zojuist het enige product van de huidige pagina heeft verwijderd, wordt de vorige pagina weergegeven.


         ' pagina wisselen?
        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 alle gevallen wordt de weergave [produits] opnieuw weergegeven met de procedure [afficheProduits]:

         ' productweergave
        afficheProduits(page, DataGrid1.PageSize)

De procedure [supprimerProduit] is als volgt:


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            ' synchronisatie
            Application.Lock()
            ' zoeken naar de te verwijderen regel
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' regel verwijderen
                ligne.Delete()
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            ' einde van de synchronisatie
            Application.UnLock()
        End Try
        ' er wordt een uitzondering gegenereerd bij een fout
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

De procedure ontvangt als parameter de sleutel van het product dat moet worden verwijderd. Als er slechts één klant was die de updates zou uitvoeren, zou deze verwijdering als volgt kunnen plaatsvinden:

             ' verwijdering van de regel [idProduit]
            dtProduits.Rows.Find(idProduit).Delete()

Als meerdere klanten tegelijkertijd updates uitvoeren, is het ingewikkelder. Laten we namelijk eens de volgende tijdsvolgorde bekijken:

Tijd
Actie
T1
klant A leest de tabel [dtProduits] – er is een product met sleutel 20
T2
klant B leest de tabel [dtProduits] - er is een product met sleutel 20
T3
klant A verwijdert het product met sleutel 20
T4
klant B verwijdert het product met sleutel 20

Wanneer klanten A en B de producttabel lezen en de inhoud ervan op een webpagina weergeven, bevat de tabel het product met sleutel 20. Ze willen dan wellicht een actie op dit product uitvoeren, bijvoorbeeld het verwijderen ervan. Een van de twee zal dit onvermijdelijk als eerste doen. Degene die daarna komt, zal dan een product willen verwijderen dat niet meer bestaat. Dit scenario wordt door de procedure [supprimerProduits] op verschillende manieren afgehandeld:

  • allereerst vindt er synchronisatie plaats met [Application.Lock]. Dit betekent dat wanneer een klant deze barrière passeert, er geen andere is die de producttabel kan wijzigen of lezen. Deze bewerkingen worden namelijk allemaal op deze manier gesynchroniseerd. We hebben dit al gezien bij het lezen.
  • Vervolgens wordt gecontroleerd of het product dat men wil verwijderen, bestaat. Zo ja, dan wordt het verwijderd; zo niet, dan wordt er een foutmelding gegenereerd.

             ' zoeken naar de te verwijderen regel
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                 ' verwijdering van de regel
                ligne.Delete()
            End If
  • we verlaten de kritieke reeks via [Application.Unlock], zodat andere clients hun updates kunnen uitvoeren.
  • Als de regel niet kon worden verwijderd, genereert de procedure een uitzondering in combinatie met een foutmelding.

Laten we een voorbeeld bekijken. We starten een client [Mozilla] en kiezen onmiddellijk de optie [Mise à jour] (gedeeltelijk overzicht):

Image

We doen hetzelfde met een client [Internet Explorer] (gedeeltelijke weergave):

Image

Bij klant [Mozilla] verwijderen we het product [produit2]. We krijgen dan de volgende nieuwe pagina te zien:

Image

Het verwijderen is goed verlopen. Het verwijderde product verschijnt inderdaad in [DataGrid] onder de verwijderde producten en komt niet meer voor in de productlijst van de componenten [DataGrid1] en [DataGrid2], die de producten weergeven die momenteel in de tabel aanwezig zijn. Laten we hetzelfde doen met [Interbet Explorer]. We verwijderen het element [produit2]:

Image

Het verkregen antwoord is als volgt:

Image

Een foutmelding geeft de gebruiker aan dat het product met sleutel [2] niet bestaat. Het antwoord stuurt de klant een nieuwe weergave terug die de huidige status van de tabel weergeeft. We zien dat het product met sleutel [2] inderdaad in de lijst met verwijderde producten staat. De weergave [produits] geeft dus de updates van alle klanten weer, en niet die van één enkele klant.

9.6.14. Een product toevoegen

Om een product toe te voegen, gebruikt de gebruiker de link [Ajout] in de opties. Deze link geeft alleen de weergave [Ajout] weer:

Image

Image

De twee procedures die bij deze actie betrokken zijn, zijn de volgende:


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        ' de weergave [ajout] wordt weergegeven
        afficheAjout()
    End Sub

    Private Sub afficheAjout()
        ' de weergave ‘toevoegen’ wordt weergegeven
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

Ter herinnering: de weergave [Ajout] bevat validatiecomponenten:

naam
type
rol
rfvNom
RequiredFieldValidator
controleert of er een waarde aanwezig is in [txtNom]
rfvPrix
RequiredFieldValidator
controleert of er een waarde aanwezig is in [txtPrix]
cvPrix
CompareValidator
controleert of prijs >= 0 is

De procedure voor het verwerken van een klik op de knop [Ajouter] is als volgt:


    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
        ' een nieuw item toevoegen aan de producttabel
        ' de gegevens moeten eerst geldig zijn
        rfvNom.Validate()
        rfvPrix.Validate()
        cvPrix.Validate()
        If Not rfvNom.IsValid Or Not rfvPrix.IsValid Or Not cvPrix.IsValid Then
            ' opnieuw het invoerformulier
            afficheAjout()
            Exit Sub
        End If
        ' we maken een rij aan
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim
        ' de rij wordt aan de tabel toegevoegd
        Application.Lock()
        Try
            dtProduits.Rows.Add(produit)
            lblInfo3.Text = "Ajout réussi"
            ' opschonen
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try
        Application.UnLock()
    End Sub

Allereerst worden de validatiecontroles van de drie componenten [rfvNom, rfvPrix, cvPrix] uitgevoerd. Als een van de controles mislukt, wordt de weergave [Ajout] opnieuw weergegeven met de foutmeldingen van de validatiecomponenten. Hier volgt een voorbeeld:

Image

Als de gegevens geldig zijn, wordt de rij voorbereid om te worden ingevoegd in de tabel [dtProduits].


         ' er wordt een rij aangemaakt
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim

Eerst wordt er een nieuwe rij aangemaakt in de tabel [dtProduits] met behulp van de methode [DataTable.NewRow]. Deze rij bevat de drie kolommen [id, nom, prix] uit de tabel [dtProduits]. De kolommen [nom, prix] worden gevuld met de waarden die in het toevoegformulier zijn ingevoerd. De kolom [id] wordt niet ingevuld. Deze kolom is van het type [AutoIncrement], c.a.d. De waarde SGBD zal als sleutel voor een nieuw product dienen, waarbij de bestaande maximale sleutel met 1 wordt verhoogd. De hier aangemaakte rij [produit] staat los van de tabel [dtProduits]. We moeten deze nu in deze tabel invoegen. Omdat we de tabel [dtProduits] gaan bijwerken, activeren we de synchronisatie tussen klanten:

        Application.Lock()

Zodra dit is gebeurd, proberen we de rij toe te voegen aan de tabel [dtProduits]. Als dit lukt, geven we een succesmelding weer; anders een foutmelding.

        Try
            dtProduits.Rows.Add(produit)
            lblInfo3.Text = "Ajout réussi"
             ' opschonen
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try

Normaal gesproken zijn er geen uitzonderingen mogelijk. Uit voorzorg is er niettemin een uitzonderingsbeheer ingesteld. Nadat de toevoeging is voltooid, verlaat men de kritieke sectie via [Application.Unlock].

9.6.15. Een product wijzigen

Om een product te wijzigen, gebruikt de gebruiker de link [Modifier] in de productregel:

Image

Vervolgens gaat men naar de bewerkingsmodus:

Image

Dankzij de component [DataGrid] wordt dit resultaat met zeer weinig code bereikt:


    Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
        ' het huidige element wordt in de bewerkingsmodus gezet
        DataGrid1.EditItemIndex = e.Item.ItemIndex
        ' de producten worden opnieuw weergegeven
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Als u op de link [Modifier] klikt, wordt de procedure [DataGrid1_EditCommand] aan de serverzijde uitgevoerd. De component [DataGrid1] heeft een veld met de naam [EditItemIndex]. De regel met de indexwaarde van [EditItemIndex] wordt in de bewerkingsmodus gezet. Elke waarde van de aldus bijgewerkte regel kan worden gewijzigd in een invoervak, zoals te zien is op de bovenstaande schermafbeelding. We halen dus de index op van het product waarop op de link [Modifier] is geklikt en wijzen deze toe aan het attribuut [EditItemIndex] van de component [DataGrid1]. Nu hoeven we alleen nog maar de weergave [produits] opnieuw weer te geven. Deze ziet er hetzelfde uit als voorheen, maar met een regel in de bewerkingsmodus.

Via de link [Annuler] van het product dat momenteel wordt bewerkt, kan de gebruiker de wijzigingen ongedaan maken. De procedure die aan deze link is gekoppeld, is als volgt:


    Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
        ' de producten worden opnieuw weergegeven
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Deze geeft gewoon de weergave [produits] opnieuw weer. Men zou in de verleiding kunnen komen om -1 in te voeren in [DataGrid1.EditItemIndex] om de bijwerkingsmodus ongedaan te maken. We weten echter dat de procedure [Page_Load] dit automatisch doet. Het is dus niet nodig om dit nogmaals te doen.

De wijziging wordt bevestigd via de link [Mettre à jour] van de regel die momenteel wordt bewerkt. Vervolgens wordt de volgende procedure uitgevoerd:


    Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
        ' sleutel van het te wijzigen product
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' gewijzigde elementen
        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
        ' zijn de wijzigingen geldig?
        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
        '  bij een fout wordt de bijwerkpagina opnieuw weergegeven
        If lblInfo2.Text <> String.Empty Then
            ' de regel wordt weer in de bijwerkmodus gezet
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            ' producten weergeven
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
            ' einde
            Exit Sub
        End If
        ' als er geen fout is, wordt de tabel aangepast
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' probleem
            lblInfo2.Text = ex.Message
        End Try
        ' producten weergeven
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Net als bij het verwijderen moeten we de productcode van het te wijzigen product ophalen om de bijbehorende regel in de producttabel [dtProduits] te vinden:


         ' sleutel van het te wijzigen product
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

Vervolgens moeten we de nieuwe waarden ophalen die aan de regel moeten worden toegewezen. Deze bevinden zich in de component [DataGrid1]. Het argument [e] van de procedure is bedoeld om ons daarbij te helpen. [e.Item] vertegenwoordigt de regel van [DataGrid1] die de bron van de gebeurtenis vormt. Dit is dus de regel die momenteel wordt bijgewerkt, aangezien de koppeling [Mettre à jour] alleen op deze regel bestaat. Deze regel bevat kolommen die worden aangeduid door de verzameling [Cells] van de regel. [e.Item.Cells(0)] vertegenwoordigt dus kolom 0 van de bijgewerkte regel. We weten dat de nieuwe waarden zich in invoervelden bevinden. De verzameling [e.Item.Cells(i).Controls] vertegenwoordigt de verzameling van de besturingselementen van kolom i van de regel [e.Item]. De volgende twee instructies halen de waarden op uit de invoervelden van de bijgewerkte regel in [DataGrid1]:


         ' gewijzigde elementen
        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

We beschikken dus over de nieuwe waarden van de gewijzigde regel in de vorm van tekenreeksen. Vervolgens controleren we of deze gegevens geldig zijn. De naam mag niet leeg zijn en de prijs moet een positief getal of nul zijn:


         ' zijn de wijzigingen geldig?
        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 geval van een fout geeft het label [lblInfo2] een foutmelding weer en wordt gewoon dezelfde pagina opnieuw weergegeven:


          ' bij fout wordt de bijwerkpagina opnieuw weergegeven
        If lblInfo2.Text <> String.Empty Then
            ' de regel wordt weer in de update-modus gezet
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            ' producten weergeven
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
             ' einde
            Exit Sub
        End If

Wat de bovenstaande code niet laat zien, is dat de ingevoerde waarden verloren gaan. [DataGrid1] is namelijk gekoppeld aan de gegevens in de tabel [dtProduits], die de oorspronkelijke waarden van de gewijzigde regel bevat. Hier volgt een voorbeeld.

Image

Het verkregen antwoord is als volgt:

Image

We zien dat de oorspronkelijk ingevoerde waarden verloren zijn gegaan. In een professionele toepassing zou dit waarschijnlijk onaanvaardbaar zijn. Hier stuit men op bepaalde beperkingen van de standaardwijze van bijwerken van de component [DataGrid]. Het zou beter zijn om een weergave [Modification] te hebben, analoog aan de weergave [Ajout].

Als de gegevens geldig zijn, worden ze gebruikt om de tabel [dtProduits] bij te werken:


         ' als er geen fout is, wordt de tabel gewijzigd
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' probleem
            lblInfo2.Text = ex.Message
        End Try
        ' productweergave
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)

Om dezelfde reden die bij het verwijderen van een product werd genoemd, kan de wijziging mislukken. Tussen het moment waarop de klant de tabel [dtProduits] heeft gelezen en het moment waarop hij een van zijn producten gaat wijzigen, kan dit product namelijk door een andere klant zijn verwijderd. De procedure [modifierProduit] behandelt dit geval door een uitzondering te genereren. Deze wordt hier afgehandeld. Nadat de update is geslaagd of mislukt, stuurt de applicatie de weergave [produits] terug naar de klant. Nu moeten we nog bekijken hoe de procedure [modifierProduit] de update uitvoert:


    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
        Dim erreur As String
        Try
            ' synchronisatie
            Application.Lock()
            ' zoeken naar de te wijzigen regel
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' de regel wordt gewijzigd
                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
            ' einde van de synchronisatie
            Application.UnLock()
        End Try
        ' er wordt een uitzondering gegenereerd bij een fout
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

We zullen niet in detail treden over deze procedure, waarvan de code vergelijkbaar is met die van de procedure [supprimerProduit], die al uitgebreid is uitgelegd. Laten we gewoon twee voorbeelden bekijken. Allereerst passen we de prijs van het product [produit1] aan:

Image

Als we de bovenstaande link [Mettre à jour] gebruiken, krijgen we het volgende antwoord:

Image

De wijziging is duidelijk zichtbaar in de componenten [DataGrid] 1 en 2, die de huidige status van de tabel [dtProduits] weergeven. De wijziging is ook aanwezig in de component [DataGrid4], die een overzicht biedt van de gewijzigde rijen, waarbij deze worden weergegeven met hun oorspronkelijke waarden. Laten we nu eens kijken naar een geval van een toegangsconflict. Net als bij het voorbeeld van het verwijderen, gebruiken we twee verschillende webclients. Een client [Mozilla] leest de tabel [dtProduits] en gaat over tot het wijzigen van het product [produit1]:

Image

Een klant met nummer [Internet Explorer] staat op het punt het product [produit1] te verwijderen:

Image

De klant [Internet Explorer] verwijdert [produit1]:

Image

Merk op dat [produit1] niet meer in de tabel met gewijzigde regels staat, maar in die met verwijderde regels. De client [Mozilla] bevestigt zijn update:

Image

De client [Mozilla] ontvangt het volgende antwoord op zijn update:

Image

Hij kan zien dat [produit1] is verwijderd, aangezien het in de lijst met verwijderde producten staat.

9.7. Webapplicatie voor het bijwerken van de fysieke producttabel

9.7.1. Voorgestelde oplossingen

De vorige applicatie was meer een schoolvoorbeeld om het beheer van een [DataTable]-object in de cache te illustreren dan een veelvoorkomend geval. Op een bepaald moment moet de daadwerkelijke gegevensbron immers worden bijgewerkt. Er kan worden gekozen uit twee verschillende strategieën:

  1. men gebruikt de cache [dtProduits] in het geheugen om de gegevensbron bij te werken. Men kan een pagina in de webstructuur van de vorige applicatie opzetten om toegang te krijgen tot de cache [dtProduits] daarvan. Deze pagina zou een beheerder de mogelijkheid bieden om de wijzigingen in de cache in [dtProduits] door te voeren naar de fysieke gegevensbron. Hiervoor zou men een nieuwe methode kunnen toevoegen aan de toegangsklasse [produits], die de cache [dtProduits] als parameter zou accepteren en die met behulp van deze cache de fysieke gegevensbron zou bijwerken.
  2. De fysieke gegevensbron wordt tegelijk met de cache bijgewerkt.

Met strategie nr. 1 hoeft er slechts één verbinding met de fysieke gegevensbron te worden geopend. Strategie nr. 2 vereist een verbinding bij elke update. Afhankelijk van de beschikbaarheid van verbindingen kan de ene of de andere strategie de voorkeur genieten. Omdat we over de tools beschikken om deze te implementeren (de klasse [produits]), kiezen we voor strategie nr. 2.

9.7.2. Oplossing 1

Omwille van de continuïteit met de vorige applicatie kiezen we voor de volgende strategie:

  • de fysieke bron wordt tegelijk met de cache bijgewerkt
  • de cache wordt slechts één keer opgebouwd bij het opstarten van de applicatie in [global.asax.vb]. Dit betekent dat als de fysieke gegevensbron wordt bijgewerkt door andere clients dan de webclients, deze laatste die wijzigingen niet zien. Zij zien alleen de wijzigingen die zij zelf aanbrengen in de tabel in de cache.

Om de fysieke gegevensbron bij te werken, hebben we een instantie van de producttoegangsklasse nodig. Elke client zou zijn eigen instantie kunnen hebben. We kunnen ook één enkele instantie delen die door de applicatie bij het opstarten wordt aangemaakt. Dat is de oplossing waarvoor we hier kiezen. De controlecode [global.asax.vb] wordt als volgt aangepast:


Imports System
Imports System.Web
...

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' configuratiegegevens ophalen
        Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
        Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
        Dim erreurs As New ArrayList
...
        ' hier zijn er geen configuratiefouten
        ' er wordt een productobject aangemaakt
        Dim objProduits As New produits(chaînedeConnexion)
        Dim dtProduits As DataTable
        Try
            dtProduits = objProduits.getProduits
        Catch ex As ExceptionProduits
            'er is een fout opgetreden bij het ophalen van de producten; dit wordt in de applicatie genoteerd
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            ' niet-afgehandelde fout
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            ' exit sub
        End Try
        ' hier geen initialisatiefouten
...
        ' de instantie voor de toegang tot de gegevens wordt opgeslagen
        Application("objProduits") = objProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
...
    End Sub
End Class

Er is een instantie van de klasse voor gegevenstoegang opgeslagen in de applicatie, gekoppeld aan de sleutel [objProduits]. Elke client zal deze instantie gebruiken om toegang te krijgen tot de fysieke gegevensbron. Deze wordt opgehaald in de procedure [Page_Load] van [main.aspx.vb]:


    Protected objProduits As produits
....
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
        ' de producttoegangsinstantie wordt opgehaald
        objProduits = CType(Application("objProduits"), produits)
...
    End Sub

De gegevenstoegangsinstantie [objProduits] is toegankelijk voor alle methoden van de pagina. Deze wordt gebruikt voor de drie bewerkingen: toevoegen, verwijderen en wijzigen.

De procedure voor het toevoegen wordt als volgt gewijzigd:


    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
...
        ' de rij wordt toegevoegd
        Application.Lock()
        Try
            ' de rij wordt toegevoegd aan de cache-tabel
            dtProduits.Rows.Add(produit)
             ' de rij wordt toegevoegd aan de fysieke bron
            Dim nouveauProduit As sProduit
            With nouveauProduit
                .nom = CType(produit("nom"), String)
                .prix = CType(produit("prix"), Double)
            End With
            objProduits.ajouterProduit(nouveauProduit)
            ' opvolging
            lblInfo3.Text = "Ajout réussi"
            ' opschoning
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            ' fout
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try
        Application.UnLock()
    End Sub

De wijzigingsprocedure wordt als volgt gewijzigd:


    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
        Dim erreur As String
        Try
            ' synchronisatie
            Application.Lock()
            ' zoeken naar de te wijzigen regel
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' de regel wordt gewijzigd in de 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
                 ' de regel wordt gewijzigd in de fysieke bron
                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
            ' einde van de synchronisatie
            Application.UnLock()
        End Try
        ' er wordt een uitzondering gegenereerd bij een fout
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

De verwijderingsprocedure wordt als volgt gewijzigd:


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            ' synchronisatie
            Application.Lock()
            ' de te verwijderen regel wordt opgezocht
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' verwijdering van de regel uit de cache
                ligne.Delete()
                 ' verwijdering van de regel in de fysieke bron
                objProduits.supprimerProduit(idProduit)
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            ' einde van de synchronisatie
            Application.UnLock()
        End Try
        ' er wordt een uitzondering gegenereerd bij een fout
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

9.7.3. Tests

We gaan uit van de volgende gegevenstabel in een bestand ACCESS:

Image

Er wordt een webclient gestart:

Image

We voegen een product toe:

Image

We verwijderen [produit1]:

Image

We wijzigen de prijs van [produit2]:

Image

Na deze wijzigingen bekijken we de inhoud van de tabel [liste] in de database ACCESS:

Image

De drie wijzigingen zijn correct verwerkt in de fysieke tabel. Laten we nu eens kijken naar een conflict. We verwijderen, direct onder ACCESS, de regel van [produit2]:

Image

We gaan terug naar onze webklant. Deze ziet niet dat het item al is verwijderd en wil op zijn beurt [produit2] verwijderen:

Image

Hij krijgt het volgende antwoord:

Image

De regel [produit2] is inderdaad uit de cache verwijderd, zoals blijkt uit de lijst met verwijderde producten. Het verwijderen van [produit2] in de fysieke bron is echter mislukt, zoals blijkt uit de foutmelding.

9.7.4. Oplossing 2

In de vorige oplossing werken de webclients de fysieke gegevensbron gelijktijdig bij, maar zien ze de wijzigingen die door andere clients zijn aangebracht niet. Ze zien alleen hun eigen wijzigingen. We willen nu dat een client de fysieke gegevensbron kan zien zoals deze op dit moment is, en niet zoals deze was bij het starten van de applicatie. Hiervoor bieden we de client een nieuwe optie aan:

Image

Met de optie [Rafraîchir] dwingt de client een herlezing van de fysieke gegevensbron af. Om te voorkomen dat dit gevolgen heeft voor de andere clients, moet de tabel die uit deze herlezing voortkomt, toebehoren aan de client die de verversing uitvoert en mag deze niet worden gedeeld met de andere clients. Dit is het eerste verschil met de vorige applicatie. De cache [dtProduits] van de gegevensbron wordt door elke client opgebouwd en niet door de applicatie zelf. De wijziging wordt aangebracht 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)
        ' configuratiegegevens worden opgehaald
...
        ' hier geen configuratiefouten
        ' er wordt een productobject aangemaakt
        Dim objProduits As New produits(chaînedeConnexion)
        ' het aantal producten per pagina wordt opgeslagen
        Application("defaultProduitsPage") = defaultProduitsPage
        ' de gegevensinstantie wordt opgeslagen
        Application("objProduits") = objProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' sessievariabelen initialiseren
        If IsNothing(Application("erreurs")) Then
            ' de gegevensbron wordt in de cache opgeslagen
            Dim dtProduits As DataTable
            Try
                Application.Lock()
                dtProduits = CType(Application("objProduits"), produits).getProduits
            Catch ex As ExceptionProduits
                'er is een fout opgetreden bij het ophalen van de producten; dit wordt in de sessie genoteerd
                Session("erreurs") = ex.erreurs
                Exit Sub
            Finally
                Application.UnLock()
            End Try
            ' de cache [dtProduits] wordt in de sessie opgeslagen
            Session("dtProduits") = dtProduits
            ' weergave van de producttabel
            Session("dvProduits") = dtProduits.DefaultView
            ' aantal producten per pagina
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' huidige weergegeven pagina
            Session("pageCourante") = 0
        End If
    End Sub

End Class

De sessiegegevens worden bij elk verzoek opgehaald in de procedure [Page_Load]:


     ' pagina-gegevens
    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
        ' wordt gecontroleerd of de applicatie een fout vertoont
        If Not IsNothing(Application("erreurs")) Then
            ' de applicatie is niet correct geïnitialiseerd
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        ' er wordt gecontroleerd of er een fout is opgetreden in de sessie
        If Not IsNothing(Session("erreurs")) Then
            ' de sessie is niet correct geïnitialiseerd
            afficheErreurs(CType(Session("erreurs"), ArrayList))
            Exit Sub
        End If
        ' er wordt een referentie opgehaald uit de producttabel
        dtProduits = CType(Session("dtProduits"), DataTable)
        ' de weergave van de producten wordt opgehaald
        dvProduits = CType(Session("dvProduits"), DataView)
        ' het aantal producten per pagina wordt opgehaald
        nbProduitsPage = CType(Session("nbProduitsPage"), Integer)
        ' de huidige pagina wordt opgehaald
        pageCourante = CType(Session("pageCourante"), Integer)
        ' de toegangsinstantie voor de producten wordt opgehaald
        objProduits = CType(Application("objProduits"), produits)
        ' een eventuele lopende update wordt geannuleerd
        DataGrid1.EditItemIndex = -1
        'Eerste verzoek
        If Not IsPostBack Then
            ' het startformulier wordt weergegeven
            txtPages.Text = nbProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub

De gegevens die tijdens de sessie zijn verzameld, worden aan het einde van elke verzoekcyclus in deze sessie opgeslagen:


    Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
        ' bepaalde gegevens worden in de sessie opgeslagen
        Session("dtProduits") = dtProduits
        Session("dvProduits") = dvProduits
        Session("nbProduitsPage") = nbProduitsPage
        Session("pageCourante") = pageCourante
    End Sub

Het evenement [PreRender] geeft aan dat het antwoord naar de klant wordt verzonden. We maken van de gelegenheid gebruik om alle gegevens die bewaard moeten blijven in de sessie op te slaan. Dit is wat overdreven, aangezien vaak slechts enkele gegevens van waarde zijn veranderd. Deze systematische opslag heeft als voordeel dat we in de andere methoden van de pagina niet meer met het sessiebeheer bezig hoeven te zijn.

Het vernieuwen van de cache gebeurt via de volgende procedure:


    Private Sub lnkRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkRefresh.Click
        ' de cache [dtProduits] moet worden vernieuwd met de fysieke gegevensbron
        ' synchronisatie start
        Application.Lock()
        Try
            ' de producttabel
            dtProduits = CType(Application("objProduits"), produits).getProduits
            ' het huidige filter wordt opgeslagen
            Dim filtre As String = dvProduits.RowFilter
            ' de nieuwe gefilterde weergave wordt aangemaakt
            dvProduits = New DataView(dtProduits)
            ' het filter wordt opnieuw toegepast
            dvProduits.RowFilter = filtre
        Catch ex As ExceptionProduits
            'er is een fout opgetreden bij het ophalen van de producten; dit wordt in de sessie genoteerd
            Session("erreurs") = ex.erreurs
            ' de weergave wordt weergegeven [erreurs]
            afficheErreurs(ex.erreurs)
            ' klaar
            Exit Sub
        Finally
            ' synchronisatie voltooid
            Application.UnLock()
        End Try
        ' het is goed gegaan – we geven de producten weer vanaf de eerste pagina
        afficheProduits(0, nbProduitsPage)
    End Sub

De procedure genereert nieuwe waarden voor de cache [dtProduits] en de weergave [dvProduits]. Deze worden in de sessie geplaatst door de hierboven beschreven procedure [Page_PreRender]. Zodra de cache [dtProduits] is hersteld, worden de producten vanaf de eerste pagina weergegeven.

Hier volgt een voorbeeld van de uitvoering. Een client [mozilla] wordt gestart en geeft de producten weer:

Image

Een client [Internet Explorer] doet hetzelfde:

Image

De client [Mozilla] verwijdert [produit1], wijzigt [produit2] en voegt een nieuw product toe. Hij krijgt de volgende nieuwe pagina te zien:

Image

De klant [Internet Explorer] wil [produit1] verwijderen.

Image

Hij krijgt het volgende antwoord:

Image

Er is hem gemeld dat [produit1] niet meer bestaat. De gebruiker besluit vervolgens zijn cache te vernieuwen met de bovenstaande link [Rafraîchir]. Hij krijgt het volgende antwoord:

Image

Hij beschikt nu over dezelfde gegevensbron als de client [Mozilla].

9.8. Conclusion

In dit hoofdstuk hebben we uitgebreid aandacht besteed aan gegevenscontainers en hun koppelingen met gegevensbronnen. We hebben afgesloten met een demonstratie van hoe een gegevensbron kan worden bijgewerkt met behulp van een [DataGrid]-component. We hebben een databasetabel als gegevensbron gebruikt. Hoewel de component [DataGrid] de weergave van de gegevens enigszins vergemakkelijkt, ligt de echte uitdaging niet in de presentatie, maar wel in het beheer van de updates van de gegevensbron die door de verschillende clients worden uitgevoerd. Er kunnen toegangskonflicten ontstaan die moeten worden afgehandeld. Hier hebben we deze in de controller afgehandeld met behulp van [Application.Lock]. Het zou waarschijnlijk verstandiger zijn om de toegang tot de gegevensbron in de toegangsklasse te synchroniseren, zodat de controller zich niet hoeft bezig te houden met dergelijke details die niet onder zijn verantwoordelijkheid vallen.

In de praktijk zijn de tabellen van een database onderling gekoppeld via relaties en moet bij het bijwerken ervan rekening worden gehouden met deze relaties. Dit heeft in de eerste plaats gevolgen voor de datatoegangsklasse, die complexer wordt dan nodig is voor een op zichzelf staande tabel. Over het algemeen heeft dit ook gevolgen voor het presentatiegedeelte van de webapplicatie, omdat vaak niet één enkele tabel moet worden weergegeven, maar tabellen die via relaties met elkaar zijn verbonden.

In dit hoofdstuk zijn bovendien verschillende gegevensstructuren gepresenteerd, zoals [DataTable, DataView].