Skip to content

9. Componentes de servidor ASP - 3

9.1. Introduction

Continuamos nosso trabalho na interface do usuário, aprofundando as capacidades dos componentes [DataList] e [DataGrid], especialmente no que diz respeito à atualização dos dados que eles exibem

9.2. Gerenciar eventos associados aos dados dos componentes com ligação de dados

9.2.1. O exemplo

Consideremos a seguinte página:

A página inclui três componentes associados a uma lista de dados:

  • um componente [DataList] denominado [DataList1]
  • um componente [DataGrid] denominado [DataGrid1]
  • um componente [Repeater] denominado [Repeater1]

A lista de dados associada é a matriz {"zero", "um", "dois", "três"}. A cada um desses dados está associado um grupo de dois botões denominados [Infos1] e [Infos2]. O usuário clica em um dos botões e um texto exibe o nome do botão clicado. O objetivo aqui é mostrar como gerenciar uma lista de botões ou links.

9.2.2. A configuração dos componentes

O componente [DataList1] está configurado da seguinte maneira:


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

Omitimos tudo o que se referia à aparência do [DataList] para nos concentrarmos apenas em seu conteúdo:

  • a seção <HeaderTemplate> define o cabeçalho do [DataList] e a seção <FooterTemplate> define seu rodapé.
  • a seção <ItemTemplate> é o modelo de exibição utilizado para cada um dos dados da lista de dados associada. Nela encontram-se os seguintes elementos:
    • o valor do dado atual da lista de dados associada ao componente: <%# Container.DataItem %>
    • dois botões intitulados, respectivamente, [Infos1] e [Infos2]. A classe [Button] possui um atributo [CommandName] que é utilizado aqui. Ele nos permitirá determinar qual botão originou um evento no [DataList]. Para gerenciar o clique nos botões, teremos apenas um único manipulador de eventos, que estará vinculado ao próprio [DataList] e não aos botões. Esse manipulador receberá uma informação indicando em qual linha do [DataList] ocorreu o clique. O atributo [CommandName] nos permitirá saber em qual botão da linha ele ocorreu.

O componente [Repeater1] é configurado de maneira muito semelhante:


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

Simplesmente adicionamos uma seção <SeparatorTemplate> para que os dados sucessivos exibidos pelo componente sejam separados por uma barra horizontal.

Por fim, o componente [DataGrid1] está configurado da seguinte forma:


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

Nesse caso também, omitimos as informações de estilo (cores, larguras, etc.). Estamos no modo de geração automática de colunas, que é o modo padrão do [DataGrid]. Isso significa que haverá tantas colunas quantas existirem na fonte de dados. Aqui, há uma. Adicionamos outras duas colunas marcadas por <asp:ButtonColumn>. Nelas, definimos informações semelhantes às definidas para os outros dois componentes, bem como o tipo de botão, neste caso [PushButton]. O tipo padrão é [LinkButton], c.a.d. um link. Além disso, os dados serão paginados [AllowPaging=true] com um tamanho de página de dois elementos [PageSize=2].

9.2.3. O código de apresentação da página

O código de apresentação da nossa página de exemplo foi colocado em um arquivo [main.aspx]:


<%@ page codebehind="main.aspx.vb" inherits="vs.main" autoeventwireup="false" %>
<HTML>
    <HEAD>
    </HEAD>
    <body>
        <form runat="server">
            <P>Gestion d'événements de composants associés à des listes de données</P>
            <HR width="100%" SIZE="1">
            <table cellSpacing="1" cellPadding="1" bgColor="#ffcc00" border="1">
                <tr>
                    <td ...>DataList</td>
                    <td ...>DataGrid</td>
                    <td ...>Repeater</td>
                </tr>
                <tr>
                    <td ...>

                      <asp:datalist id="DataList1" ... runat="server">
                            <HeaderTemplate>
                                [début]
                            </HeaderTemplate>
                            <FooterTemplate>
                                [fin]
                            </FooterTemplate>
                            <ItemStyle ...></ItemStyle>
                            <ItemTemplate>
                                <P><%# Container.DataItem %>
                                    <asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
                                    <asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
                            </ItemTemplate>
                            <FooterStyle ...></FooterStyle>
                            <HeaderStyle ....></HeaderStyle>
                        </asp:datalist>

                        <P></P>
                    </td>
                    <td ...>

                       <asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
                            <AlternatingItemStyle ...></AlternatingItemStyle>
                            <ItemStyle ...></ItemStyle>
                            <HeaderStyle ...></HeaderStyle>
                            <FooterStyle ...></FooterStyle>
                            <Columns>
                                <asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1"> 
                              </asp:ButtonColumn>
                                <asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
                               </asp:ButtonColumn>
                            </Columns>
                            <PagerStyle ... Mode="NumericPages"></PagerStyle>
                        </asp:datagrid>

                  </td>
                    <td ...>

                       <asp:repeater id="Repeater1" runat="server">
                            <HeaderTemplate>
                                [début]<hr />
                            </HeaderTemplate>
                            <FooterTemplate>
                                <hr />
                                [fin]
                            </FooterTemplate>
                            <SeparatorTemplate>
                                <hr />
                            </SeparatorTemplate>
                            <ItemTemplate>
                                <%# Container.DataItem %>
                                <asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
                                <asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
                            </ItemTemplate>
                        </asp:repeater>

                  </td>
                </tr>
            </table>
            <P><asp:label id="lblInfo" runat="server"></asp:label></P>
            <P></P>
        </form>
    </body>
</HTML>

No código acima, omitimos o código de formatação (cores, linhas, tamanhos, etc.)

9.2.4. O código de controle da página

O código de controle do aplicativo foi colocado no arquivo [main.aspx.vb]:

Public Class main
    Inherits System.Web.UI.Page

     ' componentes da página
    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

     ' a fonte de dados
    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
             'ligações com a fonte de dados
            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
         ' ocorreu um evento em uma das linhas do [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
         ' ocorreu um evento em uma das linhas do [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
         ' ocorreu um evento em uma das linhas do [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
         ' mudança de página
        With DataGrid1
            .CurrentPageIndex = e.NewPageIndex
            .DataSource = textes
            .DataBind()
        End With
    End Sub
End Class

Comentários:

  • a fonte de dados [textes] é uma simples tabela de cadeias de caracteres. Ela será vinculada aos três componentes presentes na página
  • essa vinculação é feita no procedimento [Page_Load] durante a primeira consulta. Nas consultas seguintes, os três componentes recuperarão seus valores por meio do mecanismo do [VIEW_STATE].
  • Os três componentes possuem um manipulador para o evento [ItemCommand]. Esse evento ocorre quando um botão ou um link é clicado em uma das linhas do componente. O manipulador recebe duas informações:
    • fonte: a referência do objeto (botão ou link) que originou o evento
    • a: informações sobre o evento do tipo [DataListCommandEventArgs], [RepeaterCommandEventArgs] ou [DataGridCommandEventArgs], conforme o caso. O argumento a traz diversas informações. Aqui, duas delas nos interessam:
      • a.Item: representa a linha na qual ocorreu o evento, do tipo [DataListItem], [DataGridItem] ou [RepeaterItem]. Seja qual for o tipo exato, o elemento [Item] possui um atributo [ItemIndex] que indica o número da linha [Item] do contêiner ao qual ele pertence. Exibimos aqui esse número de linha
      • a.CommandName: é o atributo [CommandName] do botão (Button, LinkButton, ImageButton) que originou o evento. Essa informação, combinada com a anterior, nos permite saber qual botão do contêiner está na origem do evento [ItemCommand]

9.3. Aplicativo — gerenciamento de uma lista de assinaturas

Agora que sabemos como interceptar os eventos que ocorrem dentro de um contêiner de dados, apresentamos um exemplo que mostra como é possível gerenciá-los.

9.3.1. Introdução

A aplicação apresentada simula um sistema de assinaturas de listas de distribuição. Estas são definidas por um objeto [DataTable] com três colunas:

nome
tipo
função
id
string
chave primária
thème
string
nome do tema da lista
description
string
uma descrição dos assuntos abordados pela lista

Para não sobrecarregar nosso exemplo, o objeto [DataTable] acima será construído por código de forma arbitrária. Em uma aplicação real, ele provavelmente seria fornecido por um método de uma classe de acesso a dados. A construção da tabela de listas será feita no procedimento [Application_Start] e a tabela resultante será inserida na aplicação. Vamos chamá-la de tabela [dtThèmes]. O usuário se inscreverá em alguns dos temas dessa tabela. A lista de suas inscrições será mantida, mais uma vez, em um objeto [DataTable] chamado [dtAbonnements], cuja estrutura será a seguinte:

nome
tipo
função
id
string
chave primária
thème
string
nome do tema da lista

O aplicativo de página única é o seguinte:

n.º
nome
tipo
propriedades
função
1
dgThèmes
DataGrid
 
listas de discussão disponíveis para inscrição
2
dlAbonnements
DataList
 
lista das inscrições do usuário nas listas anteriores
3
panelInfos
painel
 
painel de informações sobre o tema selecionado pelo usuário com um link [Plus d'informations]
4
lblThème
Rótulo
faz parte de [panelInfos]
nome do tema
5
lblDescription
Etiqueta
faz parte de [panelInfos]
descrição do tema
6
lblInfo
Rótulo
 
mensagem informativa do aplicativo

Nosso exemplo tem como objetivo ilustrar o uso dos componentes [DataGrid] e [DataList], especialmente o gerenciamento dos eventos que ocorrem nas linhas desses contêineres de dados. Portanto, o aplicativo não possui um botão de confirmação que gravaria em um banco de dados, por exemplo, as escolhas feitas pelo usuário. No entanto, ele é realista. Estamos próximos de um aplicativo de comércio eletrônico em que um usuário colocaria produtos (assinaturas) em seu carrinho.

9.3.2. Funcionamento

A primeira tela que o usuário vê é a seguinte:

O usuário clica nos links [Plus d'informations] para obter informações sobre um tema. Essas informações são exibidas em [panelInfos]:

Image

Ele clica nos links [S'abonner] para se inscrever em um tema. Os temas escolhidos são registrados no componente [dlAbonnements]:

Image

O usuário pode querer se inscrever em uma lista na qual já está inscrito. Uma mensagem o avisa sobre isso:

Image

Por fim, ele pode cancelar a assinatura de um dos temas usando os botões [Retirer] acima. Não é solicitada nenhuma confirmação. Isso não é necessário neste caso, pois o usuário pode se inscrever novamente com facilidade. Veja a tela após o cancelamento da assinatura de [thème1]:

Image

9.3.3. Configuração dos contêineres de dados

O componente [dgThèmes], do tipo [DataGrid], está vinculado a uma fonte do tipo [DataTable]. Sua formatação foi definida por meio do link [Mise en forme automatique] no painel de propriedades do [DataGrid]. A definição de suas propriedades, por sua vez, foi feita por meio do link [Générateur de proprités] no mesmo painel. O código gerado é o seguinte (o código de formatação foi omitido):


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

Destacam-se os seguintes pontos:

AutoGenerateColumns=false
nós mesmos definimos as colunas a serem exibidas na seção <columns>...</columns>
AllowPaging=true
PageSize=5
para a paginação dos dados
<asp:BoundColumn>
define a coluna [thème] (HeaderText) do [DataGrid], que será vinculada à coluna [thème] da fonte de dados (DataField)
<asp:ButtonColumn>
define duas colunas de botões (ou links). Para diferenciar os dois links de uma mesma linha, utilizar-se-á sua propriedade [CommandName].

O componente [DataGrid] não está totalmente configurado. Ele será configurado no código do controlador.

O componente [dlAbonnements], do tipo [DataList], está vinculado a uma fonte do tipo [DataTable]. Sua formatação foi feita por meio do link [Mise en forme automatique] no painel de propriedades do [DataList]. A definição de suas propriedades, por sua vez, foi feita diretamente no código de apresentação. Esse código é o seguinte (o código de formatação foi omitido):


                        <asp:DataList id="dlAbonnements" ... runat="server" >
                            <HeaderTemplate>
                                <div align="center">
                                    Vos abonnements</div>
                            </HeaderTemplate>
                            <ItemStyle ...></ItemStyle>
                            <ItemTemplate>
                                <TABLE>
                                    <TR>
                                        <TD><%#Container.DataItem("tema")%></TD>
                                        <TD>
                                            <asp:Button id="lnkRetirer" CommandName="retirer" runat="server" Text="Retirer" /></TD>
                                    </TR>
                                </TABLE>
                            </ItemTemplate>
                            <HeaderStyle ...></HeaderStyle>
                        </asp:DataList>
<HeaderTemplate>
define o texto do cabeçalho do [DataList]
<ItemTemplate>
define o elemento atual do [DataList] — aqui foi inserida uma tabela com duas colunas e uma linha. A primeira célula servirá para conter o nome do tema ao qual o usuário deseja se inscrever, e a outra, o botão [Retirer], que permite que ele cancele sua escolha.

9.3.4. A página de apresentação

O código de apresentação [main.aspx] é o seguinte:


<%@ 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. Os controladores

A verificação é dividida entre os arquivos [global.asax] e [main.aspx]. O arquivo [global.asax] é o seguinte:

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

O arquivo associado [global.asax.vb] contém o seguinte código:


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)
        ' inicializando a fonte de dados
        Dim thèmes As New DataTable
        ' colunas
        With thèmes.Columns
            .Add("id", GetType(System.Int32))
            .Add("thème", GetType(System.String))
            .Add("description", GetType(System.String))
        End With
        ' a coluna id será a chave primária
        thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
        ' linhas
        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
        ' insere-se a fonte de dados no aplicativo
        Application("thèmes") = thèmes
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' início da sessão — cria-se uma tabela de assinaturas vazia
        Dim dtAbonnements As New DataTable
        With dtAbonnements
            ' as colunas
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            ' a chave primária
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
        ' a tabela é inserida na sessão
        Session.Item("abonnements") = dtAbonnements
    End Sub

End Class

O procedimento [Application_Start], executado quando o aplicativo recebe sua primeira solicitação, cria o [DataTable] dos temas aos quais é possível se inscrever. Ele é criado arbitrariamente com código. Vale lembrar a técnica que já abordamos. Cria-se, na seguinte ordem:

  • um objeto [DataTable] vazio, sem estrutura e sem dados
  • a estrutura da tabela, definindo suas colunas (nome e tipo de dados que contém)
  • as linhas da tabela que representam os dados úteis

Adicionamos aqui uma chave primária. A coluna “id” serve como chave primária. Há várias maneiras de expressar isso. Neste caso, utilizamos uma restrição. No SQL, uma restrição é uma regra que os dados de uma linha devem respeitar para que ela possa ser adicionada a uma tabela. Existem todos os tipos de restrições possíveis. A restrição “Primary Key” obriga a coluna à qual é aplicada a ter valores únicos e não vazios. Uma chave primária pode, na verdade, ser constituída por uma expressão que envolva valores de várias colunas. [DataTable].Constraints é a coleção de restrições de uma determinada tabela. Para adicionar uma restrição, utiliza-se o método [DataTable.Constraints.Add]. Esse método possui várias assinaturas. Aqui, utilizamos o método [Add(Byval nom as String, Byval colonne as DataColumn, Byval cléPrimaire as Boolean)]:

nom
nome da restrição — pode ser qualquer um
colonne
coluna que será a chave primária — do tipo [DataColumn]
cléPrimaire
deve ser [vrai] para tornar [colonne] uma chave primária. Se for [cléPrimaire=false], teremos apenas a restrição de valores únicos em [colonne]

Para tornar a coluna chamada “id” a chave primária da tabela [dtAbonnements], escreve-se, portanto:

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

O procedimento [Session_Start] é executado quando o aplicativo recebe a primeira solicitação de um cliente. Ele serve para criar objetos específicos para cada cliente, que devem persistir ao longo das diferentes solicitações desse cliente. O procedimento constrói a tabela [DataTable] com as assinaturas do cliente. Apenas sua estrutura é criada, já que, inicialmente, essa tabela está vazia. Ela será preenchida à medida que as solicitações forem ocorrendo. Nesse caso também, a coluna “id” serve como chave primária. Utilizou-se uma técnica diferente para declarar essa restrição:

[DataTable].PrimaryKey
é a matriz das colunas que formam a chave primária — aqui, declaramos uma matriz com um único elemento: a coluna chamada “id”

Quando a solicitação do cliente chega ao controlador [main.aspx], os dois objetos [DataTable] estão disponíveis na aplicação para a tabela de temas e na sessão para a tabela de assinaturas. O controlador [main.aspx.vb] é o seguinte:


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

A função principal do procedimento [Page_Load] é:

  • recuperar as duas tabelas [dtThèmes] e [dtAbonnements], que se encontram, respectivamente, no aplicativo e na sessão, a fim de disponibilizá-las a todos os métodos da página
  • vincular os dados dessas duas fontes aos seus respectivos contêineres. Isso é feito apenas na primeira consulta. Nas consultas seguintes, a vinculação não precisa ser feita sistematicamente e, quando for necessário fazê-la, às vezes é preciso aguardar um evento posterior ao [Page_Load] para realizá-la.

O código de [Page_Load] é o seguinte:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' recuperam-se as fontes de dados
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' ligação de dados
        If Not IsPostBack Then
            liaisons()
        End If
        ' algumas informações são ocultadas
        panelInfo.Visible = False
    End Sub

    Private Sub liaisons()
        'vincula-se a fonte de dados ao componente [datagrid]
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        ' vincula-se a fonte de dados ao componente [datalist]
        With dlAbonnements
            .DataSource = dtAbonnements
            .DataKeyField = "id"
        End With
        ' atribui-se os dados aos componentes
        Page.DataBind()
    End Sub

No procedimento [liaisons], utilizamos a propriedade [DataKeyField] dos componentes [DataList] e [DataGrid] para definir a coluna da fonte de dados que servirá para identificar de forma exclusiva as linhas dos contêineres. Normalmente, essa coluna é a chave primária da fonte de dados, mas isso não é obrigatório. Basta que a coluna não contenha duplicatas nem valores vazios. Para o contêiner [dgThèmes], é a coluna “id” da fonte [dtThèmes] que servirá como chave primária e, para o contêiner [dlAbonnements], será a coluna “id” da fonte [dtAbonnements]. Não é necessário que a coluna que serve como chave primária do contêiner seja exibida por ele. Neste caso, nenhum dos dois contêineres exibe a coluna da chave primária. A vantagem de um contêiner ter uma chave primária é que ela permite localizar facilmente, na fonte de dados, as informações relacionadas à linha do contêiner na qual ocorreu um evento. De fato, é comum que, a partir da linha de um contêiner na qual ocorreu um evento, seja necessário agir sobre a linha correspondente na fonte de dados a ela vinculada. A chave primária facilita esse trabalho.

A paginação do [DataGrid] é gerenciada de maneira convencional:


    Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
        ' mudança de página
        dgThèmes.CurrentPageIndex = e.NewPageIndex
        ' vinculação
        liaisons()
    End Sub

As ações nos links [Plus d'informations] e [S'abonner] são gerenciadas pelo procedimento [dgThèmes_ItemCommand]:


    Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
        ' evento em uma linha do [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
        ' ligação
        liaisons()
    End Sub

Utiliza-se o fato de que os dois links possuem o atributo [CommandName] para diferenciá-los. Dependendo do valor desse atributo, chama-se a procedimento [infos] ou [abonner], passando, em ambos os casos, a chave “id” associada ao elemento do [DataGrid] onde ocorreu o evento. Com essas informações, o procedimento [info] exibirá as informações do tema escolhido pelo usuário:


    Private Sub infos(ByVal id As String)
        ' informações sobre o tema da chave id
        ' recupera-se a linha do [datatable] correspondente à chave
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
            ' exibe-se a informação
            lblThème.Text = CType(ligne("thème"), String)
            lblDescription.Text = CType(ligne("description"), String)
            panelInfo.Visible = True
        End If
    End Sub

Como a tabela [dtThèmes] possui uma chave primária, o método [dtThèmes.Rows.Find("P")] permite localizar a linha com a chave primária P. Se ela for encontrada, obtém-se um objeto [DataRow]. Aqui, precisamos procurar a linha com a chave primária [id], onde [id] é passado como parâmetro. Se a linha for encontrada, as informações [thème] e [description] dessa linha são inseridas no painel de informações, que é então exibido.

O procedimento [abonner(id)] deve adicionar o tema-chave [id] à lista de assinaturas. Seu código é o seguinte:


    Private Sub abonner(ByVal id As String)
         ' assinatura do tema da chave id
         ' recupera-se a linha de [datatable] correspondente à chave
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
             ' verifica-se se já não se está inscrito
            Dim abonnement As DataRow
            abonnement = dtAbonnements.Rows.Find(id)
            If Not abonnement Is Nothing Then
                 ' é sinalizado o erro
                lblInfo.Text = "Vous êtes déjà abonné au thème [" + ligne("thème") + "]"
            Else
                 ' adiciona-se o tema às assinaturas
                abonnement = dtAbonnements.NewRow
                abonnement("id") = id
                abonnement("thème") = ligne("thème")
                dtAbonnements.Rows.Add(abonnement)
                 ' estabelece-se as conexões
                liaisons()
            End If
        End If
    End Sub

Na lista de temas [dtThèmes], procura-se primeiro a linha de chave [id]. Se ela for encontrada, verifica-se se esse tema já não está presente na lista de assinaturas para evitar registrá-lo duas vezes. Se for o caso, exibe-se uma mensagem de erro. Caso contrário, adiciona-se uma nova assinatura à tabela [dtAbonnements] e estabelecem-se as ligações dos componentes da lista de dados às respectivas fontes.

Quando o usuário clica em um botão [Retirer], é necessário excluir um elemento da tabela [dtAbonnements]. Isso é feito pelo seguinte procedimento:


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' cancela-se uma assinatura
        Dim commande As String = e.CommandName
        If commande = "retirer" Then
            ' removendo a assinatura de [datatable]
            With dtAbonnements.Rows
                .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
            End With
            ' vínculos
            liaisons()
        End If
    End Sub

Primeiramente, verifica-se o atributo [CommandName] do elemento que originou o evento. Na verdade, isso é bastante desnecessário, já que o botão [Retirer] é o único controle capaz de gerar um evento no componente [DataList]. Portanto, não há ambiguidade. Para excluir uma linha de um objeto [DataTable], utiliza-se o método [DataList.Remove(DataRow)], que remove da tabela a linha do tipo [DataRow] passada como parâmetro. Essa linha é localizada pelo método [DataList.Find], ao qual foi passada a chave primária da linha procurada. Após a exclusão da linha, realiza-se a vinculação dos dados aos componentes

9.4. Gerenciar um [DataList] paginado

Retomamos o exemplo anterior para paginar o componente [DataList], que representa a lista de assinaturas do usuário. Ao contrário do componente [DataGrid], o componente [DataList] não oferece nenhum recurso para paginação. Veremos que a implementação dessa funcionalidade é complexa, o que nos fará valorizar ainda mais a paginação automática do [DataGrid].

9.4.1. Funcionamento

A única diferença é a paginação do [DataList]. As páginas terão duas assinaturas. Se o usuário tiver cinco assinaturas, teremos três páginas. A primeira será a seguinte:

Image

A segunda página é acessada pelo link [Suivant]:

Image

A terceira página:

Image

Observe-se que os links [Précédent] e [Suivant] só ficam visíveis se houver, respectivamente, uma página anterior e uma página posterior à página atual.

9.4.2. Código de apresentação

Os links [Précédent] e [Suivant] são obtidos adicionando-se uma tag <FooterTemplate> ao [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. Código de controle

O arquivo associado [global.asax.vb] sofre as seguintes alterações:

...

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)
         ' início da sessão  cria-se uma tabela de assinaturas vazia
        Dim dtAbonnements As New DataTable
        With dtAbonnements
             ' as colunas
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
             ' a chave primária
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
         ' a tabela é inserida na sessão
        Session.Item("abonnements") = dtAbonnements
         ' a página atual é a página 0
        Session.Item("pAC") = 0
         ' o número de assinaturas nesta página é 0
        Session.Item("nbAC") = 0
    End Sub

Além da tabela de assinaturas [dtAbonnements], são inseridas na sessão mais duas informações:

pAC
do tipo [Integer] — trata-se do número da página atual exibida na última consulta
nbAC
do tipo [Integer] — número de linhas exibidas na página atual anterior

No início de uma sessão, o número da página atual e o número de linhas dessa página são nulos.

O controlador [main.aspx.vb] evolui da seguinte forma:

....

Public Class main
    Inherits System.Web.UI.Page

....

     ' dados do aplicativo
    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

Nele são definidos novos dados relacionados à paginação das assinaturas:


    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

Algumas dessas informações são armazenadas na sessão e são recuperadas a cada solicitação no procedimento [Page_Load]:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' as fontes de dados são recuperadas
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' e as informações de exibição das assinaturas
        pAC = CType(Session("pAC"), Integer)
        nbAC = CType(Session("nbAC"), Integer)
        ' ligação de dados
        If Not IsPostBack Then
            ' exibição de uma lista vazia de assinaturas
            terminer()
        End If
        ' ocultando determinadas informações
        panelInfo.Visible = False
    End Sub

As informações recuperadas [pAC] e [nbAC] são informações sobre a página de assinaturas exibida na consulta anterior:

pAC
este é o número da página atual exibida na solicitação anterior
nbAC
número de linhas exibidas nesta página atual

O método [terminer] estabelece as ligações entre os componentes e suas fontes de dados, da mesma forma que o método [liaisons] fazia na aplicação anterior. A novidade, neste caso, é a ligação do [DataList] à tabela [dtPA], que é a página de assinaturas a ser exibida:


    Private Sub terminer()
        ' vinculação da fonte de dados ao componente [datagrid]
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        ' vincula-se a página de assinaturas ao componente [datalist], levando em consideração a página atual pAC
        changePAC()
        ' visualiza-se a página p
        With dlAbonnements
            .DataSource = dtPA
            .DataKeyField = "id"
        End With
        ' atribui-se os dados aos componentes
        Page.DataBind()
        ' gerenciamento dos links [précédent] e [suivant] do [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)
        ' as informações da página atual são salvas na sessão
        Session("pAC") = pAC
        Session("nbAC") = dtPA.Rows.Count
    End Sub

Os seguintes pontos devem ser observados:

  • a fonte [dtPA] depende do número da página atual [pAC] a ser exibida. A variável [pAC] é uma variável global da classe, manipulada pelos métodos responsáveis por alterar esse número da página atual. É o método [changePAC] que realiza a construção da tabela [dtPA], que será vinculada ao componente [dlAbonnements].
  • O método [setLiens] tem a função de exibir ou ocultar os links [Précédent] e [Suivant], dependendo se a página atual [pAC] exibida é precedida e seguida por uma página. Ela possui quatro parâmetros:
    • [dlAbonnements]: o controle [DataList], cuja árvore de controles será explorada para localizar os dois links. De fato, embora esses dois links estejam em um local específico, que é o rodapé do [DataList], não parece haver uma maneira simples de referenciá-los diretamente. De qualquer forma, isso não foi encontrado aqui.
    • [blPrecedent]: valor booleano a ser atribuído à propriedade [visible] do link [Precedent] — é verdadeiro se a página atual for diferente de 0
    • [blSuivant]: valor booleano a ser atribuído à propriedade [visible] do link [Suivant] — é verdadeiro se a página atual não for a última página da lista de assinaturas
    • [nbLiensTrouvés]: um parâmetro de saída que conta o número de links encontrados. Assim que esse número for igual a dois, o método é encerrado.
  • As informações [pAC] e [nbAC] são salvas na sessão para a próxima consulta.

O método [changePAC] cria a tabela [dtPA], que será vinculada ao componente [dlAbonnements]. Isso é feito com base no número [pAC] da página atual a ser exibida. A tabela [dtPA] deve exibir determinadas linhas da tabela de assinaturas [dtAbonnements]. Vale lembrar que essa tabela é armazenada na sessão e atualizada (aumentada ou reduzida) à medida que as consultas são realizadas. Começamos definindo o intervalo [premier,dernier] dos números das linhas da tabela [dtAbonnements] que a tabela [dtPA] deve exibir:


    Private Sub changePAC()
        ' define a página pAC como a página atual das assinaturas
        ' gerenciamento das páginas do [datalist]
        Dim nbAbonnements = dtAbonnements.Rows.Count
        Dim dernièrePage = (nbAbonnements - 1) \ nbAP
        ' primeira e última assinatura
        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

Feito isso, podemos criar a tabela [dtPA]. Primeiramente, define-se sua estrutura [id,thème] e, em seguida, preenche-se a tabela copiando as linhas de [dtAbonnements] cujos números estão no intervalo [premier,dernier], calculado anteriormente.


         ' criação da tabela de dados dtpa
        dtPA = New DataTable
        With dtPA
            ' as colunas
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            ' a chave primária
            .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

Ao final do método [changePAC], a tabela [dtPA] foi criada e pode ser vinculada ao componente [DataList]. Isso é feito no método [terminer]. Nesse mesmo método, utiliza-se o procedimento [setLiens] para definir o status das ligações [Précédent] e [Suivant] do [DataList]. O código desse procedimento é o seguinte:


    Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean, ByRef nbLiensTrouvés As Integer)
        ' procurando os links [précédent] e [suivant]
        ' na árvore de controles do [datalist]
        ' Encontramos todos os links?
        If nbLiensTrouvés = 2 Then Exit Sub
        ' análise dos controles filhos
        Dim c As Control
        For Each c In ctl.Controls
            ' primeiro, trabalhamos em profundidade — os links estão na parte inferior da árvore
            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

O procedimento é recursivo. Ele procura, em primeiro lugar, entre os controles filhos do componente [dlAbonnements], os componentes denominados [lnkPrecedent] e [lnkSuivant], que correspondem às identidades (atributo ID) dos dois links de paginação. Ela os procura inicialmente a partir da base da árvore de controles, pois é lá que eles se encontram. Assim que um link é encontrado, o contador [nbLiensTrouvés] é incrementado e a propriedade [visible] do link é preenchida com um valor passado como parâmetro do procedimento. Assim que os dois links forem encontrados, a árvore de controles não é mais explorada e o procedimento recursivo é encerrado.

Mencionamos que o método [changePAC], que define a fonte de dados [dtPA] para o componente [dlAbonnements], trabalhava com o número [pAC] da página atual a ser exibida. Vários procedimentos modificam esse número:


    Private Sub abonner(ByVal id As String)
        ' assinatura do tema com ID de chave
..
            ' adicionamos o tema às assinaturas
..
             ' atualização do número da página atual — agora é a última página
            pAC = (dtAbonnements.Rows.Count - 1) \ nbAP
             ' vínculos de dados
            terminer()
        End If
    End Sub

Após a adição de uma assinatura, ela aparece no final da lista de assinaturas. Por isso, o usuário é direcionado para a última página das assinaturas, para que possa ver a adição que foi realizada.


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' remoção de uma assinatura
        Dim commande As String = e.CommandName
        Select Case commande
            Case "retirer"
                ' removendo a assinatura do [datatable]
                With dtAbonnements.Rows
                    .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
                End With
                ' é necessário mudar a página atual?
                nbAC -= 1
                If nbAC = 0 Then pAC -= 1
            Case "precedent"
                ' mudança da página atual
                pAC -= 1
            Case "suivant"
                ' mudança da página atual
                pAC += 1
        End Select
        ' ligações de dados
        terminer()
    End Sub
  • [nbAC] é o número de linhas exibidas na página atual antes do cancelamento de uma assinatura. Se o novo número de linhas da página for igual a 0, o número da página atual [pAC] é diminuído em uma unidade.
  • Ao clicar no link [Precedent], o número da página atual [pAC] é diminuído em uma unidade.
  • Ao clicar no link [Suivant], o número da página atual [pAC] é incrementado em uma unidade.

Os demais procedimentos permanecem idênticos aos anteriores.

9.4.4. Conclusão

Mostramos neste exemplo que é possível paginar um componente [DataList]. Essa paginação é complexa e é preferível recorrer à paginação automática do componente [DataGrid] sempre que possível. Este exemplo também nos mostrou como acessar os componentes presentes no rodapé do componente [DataList].

9.5. Classe de acesso a uma base de produtos

Voltamos a nos concentrar no banco de dados ACCESS [produits] já utilizado. Vale lembrar que ele possui uma única tabela chamada [liste], cuja estrutura é a seguinte:

Vamos criar uma classe de acesso à tabela [liste] que permitirá lê-la e atualizá-la. Também criaremos um cliente de console que utilizará a classe anterior para atualizar a tabela. Em uma segunda etapa, criaremos um cliente web para realizar essa mesma tarefa.

9.5.1. A classe ExceptionProduits

A classe [Exception] possui um construtor que aceita uma mensagem de erro como parâmetro. Aqui, queremos dispor de uma classe de exceção com um construtor que aceite uma lista de mensagens de erro, em vez de uma única mensagem de erro. Essa será a classe [ExceptionProduits], apresentada a seguir:


    Public Class ExceptionProduits
        Inherits Exception

        ' mensagens de erro relacionadas à exceção
        Private _erreurs As ArrayList

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

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

9.5.2. A estrutura [sProduit]

A estrutura [produit] representará um produto [id, nom, prix]:


     ' estrutura sProduit
    Public Structure sProduit
         ' os campos
        Private _id As Integer
        Private _nom As String
        Private _prix As Double

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

        ' propriedade nome
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                If IsNothing(Value) OrElse Value.Trim = String.Empty Then Throw New Exception
                _nom = Value
            End Set
        End Property

        ' propriedade preço
        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

A estrutura aceita apenas dados válidos para os campos [nom] e [prix].

9.5.3. A classe Produtos

A classe [Produits] é a classe que nos permitirá atualizar a tabela [liste] do banco de dados de produtos. Sua estrutura é a seguinte:

    Public Class produits
         ' dados de instância

        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

Dados de instância

Os dados compartilhados pelos diferentes métodos da classe são os seguintes:


        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
a conexão com o banco de dados — será aberta para a execução de um comando SQL e fechada imediatamente em seguida
selectText
consulta SQL [select] recuperando toda a tabela [liste]
insertText
consulta que permite a inserção de uma linha (nome, preço) na tabela [liste]. Observe-se que não se especifica o campo [id]. De fato, esse campo é autoincrementado pelo SGBD e, portanto, não precisamos especificá-lo.
updateText
consulta que permite a atualização dos campos (nome, preço) da linha da tabela [liste] com a chave [id]
deleteText
consulta que permite a exclusão da linha da tabela [liste] com a chave [id]
selectCommand
objeto [OleDbCommand] executando a consulta [selectText] na conexão [connexion]
updateCommand
objeto [OleDbCommand] executando a consulta [updateText] na conexão [connexion]
insertCommand
objeto [OleDbCommand] executando a consulta [insertText] na conexão [connexion]
deleteCommand
objeto [OleDbCommand] executando a consulta [deleteText] na conexão [connexion]
adaptateur
objeto que permite recuperar o resultado da execução de [selectCommand] em um objeto [DataSet]

O construtor

O construtor recebe um único parâmetro, [chaineConnexionOLEDB], que é a string de conexão que designa o banco de dados a ser utilizado. A partir dela, são preparadas as quatro instruções de consulta e atualização da tabela, bem como o adaptador. Trata-se apenas de uma preparação, e nenhuma conexão é estabelecida.


        Public Sub New(ByVal chaineConnexionOLEDB As String)
            ' preparando a conexão
            connexion = New OleDbConnection(chaineConnexionOLEDB)
            ' preparando os comandos de consulta
            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
            ' preparando o adaptador de acesso aos dados
            adaptateur.SelectCommand = selectCommand
        End Sub

O método getProduits

Este método permite obter o conteúdo da tabela [Liste] em um objeto [DataTable]. Seu código é o seguinte:


        Public Function getProduits() As DataTable
            ' a tabela [liste] está sendo inserida em [dataset]
            Dim contenu As New DataSet
            ' cria-se um objeto DataAdapter para ler os dados da fonte OLEDB
            Try
                With adaptateur
                    .FillSchema(contenu, SchemaType.Source)
                    .Fill(contenu)
                End With
            Catch e As Exception
                ' pb
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur d'accès à la base de données : {0}", e.Message))
                Throw New ExceptionProduits(erreursCommande)
            End Try
            ' retornamos o resultado
            Return contenu.Tables(0)
        End Function

O trabalho é realizado pelas duas instruções a seguir:


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

O método [FillSchema] define a estrutura (colunas, restrições, relações) do [DataSet] e do contenu com base na estrutura do banco de dados referenciado pelo [adaptateur.Connexion]. Isso nos permite recuperar a estrutura da tabela [liste] e, em especial, sua chave primária. A operação [Fill] a seguir preenche as tabelas [Dataset] e contenu com as linhas da tabela [liste]. Com essa única operação, teríamos obtido os dados e a estrutura, mas não a chave primária. No entanto, ela será útil para atualizar a tabela [liste] na memória. Aqui, assim como nos outros métodos, tratamos os possíveis erros com a ajuda da classe [ExceptionProduits] para obter uma lista (ArrayList) de erros, em vez de um único erro. O método [getProduits] retorna a tabela [liste] na forma de um objeto [DataTable].

O método ajouterProduits

Este método permite adicionar uma linha (id, nome, preço) à tabela [liste]. Essas informações são fornecidas na forma de uma estrutura [sProduit] composta por campos [id, nom, prix]. O código do método é o seguinte:


        Public Sub ajouterProduit(ByVal produit As sProduit)
            ' adiciona-se um produto [nom,prix]
            ' preparamos os parâmetros da adição
            With insertCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
            End With
            ' fazemos a adição
            Try
                ' início da conexão
                connexion.Open()
                ' execução do comando
                insertCommand.ExecuteNonQuery()
            Catch ex As Exception
                ' problema
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de l'ajout : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' encerramento da conexão
                connexion.Close()
            End Try
        End Sub

Os campos da estrutura [produit] são inseridos nos parâmetros do comando [insertCommand]. Vale lembrar a configuração atual desse comando (consulte o fabricante):


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

O texto do comando SQL [insert] contém parâmetros formais que devem ser substituídos por parâmetros efetivos. Isso é feito com a coleção [Parameters] da classe [OleDbCommand]. Essa coleção contém elementos do tipo [OleDbParameter] que definem os parâmetros efetivos que devem substituir os parâmetros formais ?. Como estes últimos não são nomeados, utiliza-se o índice dos parâmetros efetivos para determinar a qual parâmetro formal corresponde determinado parâmetro efetivo. Aqui, o parâmetro efetivo nº i na coleção [Parameters] substituirá o parâmetro formal ? nº i. Para criar um parâmetro efetivo do tipo [OleDbParameter], utiliza-se aqui o construtor [OleDbParameter (Byval nom as String, Byval valeur as Object)], que define o nome e o valor do parâmetro efetivo. O nome pode ser qualquer um. Além disso, neste caso, ele não será utilizado. Os dois parâmetros da instrução SQL [insert] recebem como valores os dos campos [nom, prix] da estrutura [produit]. Feito isso, a inserção é realizada pela instrução [insertCommand.ExecuteNonQuery].

O método modifierProduits

Este método permite modificar a linha da tabela [liste]. As informações necessárias são fornecidas na estrutura [sProduit], nos campos [id, nom, prix].


        Public Sub modifierProduit(ByVal produit As sProduit)
            ' modificação de um produto [id,nom,prix]
            ' preparação dos parâmetros da atualização
            With updateCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
                .Add(New OleDbParameter("id", produit.id))
            End With
            ' realização da modificação
            Try
                ' abertura da conexão
                connexion.Open()
                ' execução do comando
                Dim nbLignes As Integer = updateCommand.ExecuteNonQuery()
                If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", produit.id))
            Catch ex As Exception
                ' problema
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la modification : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' encerramento da conexão
                connexion.Close()
            End Try
        End Sub

O código é praticamente idêntico ao do método [ajouterProduits], com a única diferença de que o comando [OleDbCommand] em questão é [updateCommand], em vez de [insertCommand].

O método supprimerProduits

Este método permite excluir a linha da tabela [liste] cuja chave [id] foi passada como parâmetro. O código é o seguinte:


        Public Sub supprimerProduit(ByVal id As Integer)
            ' exclui o produto da chave [id]
            ' prepara-se os parâmetros para a exclusão
            With deleteCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("id", id))
            End With
            ' realiza a exclusão
            Try
                ' abertura da conexão
                connexion.Open()
                ' execução do comando
                Dim nbLignes As Integer = deleteCommand.ExecuteNonQuery()
                If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", id))
            Catch ex As Exception
                ' problema
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la suppression : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' encerramento da conexão
                connexion.Close()
            End Try
        End Sub

Segue-se o mesmo procedimento utilizado nos métodos anteriores.

9.5.4. Testes da classe [produits]

Um programa de testes [testproduits.vb] do tipo console poderia ser o seguinte:

Option Explicit On 
Option Strict On

' espaços de nomes
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections

Namespace st.istia.univangers.fr

     ' página de teste
    Module testproduits
        Dim contenu As DataTable

        Sub Main(ByVal arguments() As String)
             ' exibe o conteúdo de uma tabela de produtos
             ' a tabela está em um banco de dados ACCESS, cuja página recebe o nome do arquivo
            Const syntaxe1 As String = "pg bdACCESS"

             ' verificação dos parâmetros do programa
            If arguments.Length <> 1 Then
                 ' mensagem de erro
                Console.Error.WriteLine(syntaxe1)
                 ' fim
                Environment.Exit(1)
            End If
             ' preparação da cadeia de conexão
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
             ' criação de um objeto de produtos
            Dim objProduits As produits = New produits(chaineConnexion)
             ' exibindo todos os produtos
            afficheProduits(objProduits)
             ' inserindo um produto
            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
             ' exibindo todos os produtos
            afficheProduits(objProduits)
             ' recuperando o ID do produto adicionado
            produit.id = CType(contenu.Rows(contenu.Rows.Count - 1)("id"), Integer)
             ' modifica-se o produto adicionado
            produit.prix = 200
            Try
                objProduits.modifierProduit(produit)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
             ' exibe todos os produtos
            afficheProduits(objProduits)
             ' exclui o produto adicionado
            Try
                objProduits.supprimerProduit(produit.id)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
             ' exibe todos os produtos
            afficheProduits(objProduits)
        End Sub

        Sub afficheProduits(ByRef objProduits As produits)
             ' recupera-se a tabela de produtos em um 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
                 ' linha i da tabela
                Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
                "," + lignes(i).Item("prix").ToString)
            Next
             ' interrompe o fluxo do console
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub

        Sub afficheErreurs(ByRef erreurs As ArrayList)
             ' exibe os erros no console
            If erreurs.Count <> 0 Then
                Console.WriteLine("Les erreurs suivantes se sont produites :")
                For i As Integer = 0 To erreurs.Count - 1
                    Console.WriteLine(String.Format("-- {0}", CType(erreurs(i), String)))
                Next
            End If
             ' interrompe o fluxo do console
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub
    End Module
End Namespace

Compilamos os dois arquivos-fonte:

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

e então testamos:

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

Solicita-se ao leitor que compare a saída de tela acima com o código do programa de teste.

9.6. Aplicativo web para atualização da tabela de produtos em cache

9.6.1. Introdução

Estamos desenvolvendo agora um aplicativo web para atualização da tabela de produtos (adição, exclusão, modificação). A tabela atualizada permanecerá na memória em um objeto [DataTable] e será compartilhada por todos os usuários. Gostaríamos de destacar dois pontos:

  • o gerenciamento de um objeto [DataTable]
  • os problemas relacionados à atualização simultânea de uma tabela por vários usuários.

A arquitetura MVC do aplicativo será a seguinte:

9.6.2. Funcionamento e visualizações

A tela inicial do aplicativo é a seguinte:

Image

 

Essa tela, denominada [formulaire], permite que o usuário defina um critério de filtragem para os produtos e especifique o número de produtos por página que deseja visualizar.

n.º
nome
tipo
função
1
lnkFiltre
LinkButton
exibe a visão [Formulaire], que serve para definir a condição de filtragem
2
lnkMisaJour
LinkButton
exibe a visão [Produits], que serve para visualizar e atualizar a tabela de produtos (alteração e exclusão)
3
lnkAjout
LinkButton
exibe a tela [Ajout], que serve para adicionar um produto
4
panel
vueFormulaire
a visão [Formulaire]
5
txtFiltre
TextBox
a condição de filtragem
6
txtPages
TextBox
número de produtos por página
7
rfvLignes
RequiredFieldValidator
verifica se há um valor em [txtPages]
8
rvLignes
RangeValidator
verifica se txtPages está no intervalo [3,10]
9
btnExécuter
 
botão [submit] que exibe a visualização [produits] filtrada pela condição (5)
10
lblInfo1
Rótulo
Texto informativo em caso de erros

Por exemplo, se a visualização [formulaire] for preenchida da seguinte forma:

Image

obtém-se o seguinte resultado:

n.º
nome
tipo
função
1
vueProduits
painel
 
2
rdCroissant
rdDécroissant
RadioButton
permite que o usuário defina a ordem de classificação desejada ao clicar no título de uma das colunas [nom], [prix]. Os dois botões fazem parte do grupo [rdTri].
3
DataGrid1
DataGrid
grade de exibição de uma visualização filtrada da tabela de produtos. O filtro é aquele definido pela visualização [formulaire]. Também temos .AllowPaging=true, .AllowSorting=true
4
DataGrid2
DataGrid
exibe toda a tabela de produtos — permite acompanhar as atualizações
5
LblInfo2
Rótulo
texto informativo, especialmente em caso de erros
6
DataGrid3
DataGrid
exibirá os produtos excluídos na tabela de produtos
7
DataGrid4
DataGrid
exibirá os produtos modificados na tabela de produtos
8
DataGrid5
DataGrid
exibirá os produtos adicionados na tabela de produtos

Há cinco contêineres de dados nesta página. Todos eles exibem a mesma tabela [dtProduits] por meio de uma visualização [DataView] diferente. Uma visualização representa um subconjunto das linhas da tabela de origem da visualização. Esse subconjunto é criado a partir das propriedades [RowFilter] e [RowStateFilter] da classe [DataView]:

  • [RowFilter] permite aplicar um filtro às linhas, como, por exemplo, o [prix>30] acima. Esse tipo de filtragem será utilizado pelo [DataGrid1].
  • [RowStateFilter] permite definir um filtro de acordo com o estado da linha da tabela. Esse filtro indica o estado da linha em relação ao estado original que ela possuía quando a visualização da tabela foi criada. Aqui, a tabela [dtProduits] provém de um banco de dados. Inicialmente, todas as suas linhas terão um estado igual a [Original] para indicar que se trata das linhas originais da tabela. Esse estado pode, em seguida, mudar e assumir diferentes valores, dos quais apresentamos alguns a seguir:
    • [Added]: a linha foi adicionada — ela não fazia parte da tabela original
    • [Deleted]: a linha foi excluída — ela ainda está na tabela, mas está “marcada” como “a ser excluída”
    • [Modified]: a linha foi modificada

[RowStateFilter] permite exibir as linhas da tabela com um determinado status:

  • (continuação)
    • [DataViewRowState.Added]: apenas as linhas adicionadas são exibidas. Elas são exibidas com seus valores atuais.
    • [DataViewRowState.ModifiedOriginal]: apenas as linhas modificadas são exibidas. Elas são exibidas com seus valores originais.
    • [DataViewRowState.ModifiedCurrent]: apenas as linhas modificadas são exibidas. Elas são exibidas com seus valores atuais.
    • [DataViewRowState.Deleted]: apenas as linhas excluídas são exibidas. Elas são exibidas com seus valores originais.
    • [DataViewRowState.CurrentRows]: as linhas não excluídas são exibidas. Elas são exibidas com seus valores atuais.

Assim, o filtro [RowStateFilter] terá os seguintes valores:

DataGrid1
sem filtro para o estado das linhas
 
DataGrid2
DataViewRowState.CurrentRows
exibe o estado atual da tabela de produtos
DataGrid3
DataViewRowState.Deleted
exibe as linhas excluídas da tabela de produtos
DataGrid4
DataViewRowState.ModifiedOriginal
exibe as linhas modificadas da tabela de produtos com seus valores iniciais
DataGrid5
DataViewRowState.Added
exibe as linhas adicionadas à tabela de produtos inicial

Os contêineres [DataGrid1-5] nos permitirão acompanhar a atualização da tabela [dtProduits]. O componente [DataGrid1] permite a modificação e a exclusão de um produto. Veremos como a configuração do componente permite essa atualização. Ele também é paginado e classificado. Esses dois pontos já foram abordados em um exemplo anterior.

O link [Ajout] dá acesso a um formulário para adição de produto:

n.º
nome
tipo
função
1
vueAjout
painel
 
2
txtNom
TextBox
nome do produto
3
rfvNom
RequiredFieldValidator
verifica se há um valor em [txtNom]
4
txtPrix
TextBox
preço do produto
5
rfvPrix
RequiredFieldValidator
verifica se há um valor em [txtPrix]
6
cvPrix
CompareValidator
verifica se preço >= 0
7
btnAjouter
Botão
botão [submit] para adicionar o produto
8
lblInfo3
Rótulo
Texto informativo sobre o resultado da operação de adição

O banco de dados [produits] pode estar indisponível ao iniciar o aplicativo. Nesse caso, a visualização [erreurs] é exibida ao usuário:

n.º
nome
tipo
função
1
vueErreurs
painel
 
2
rptErreurs
Repetidor
lista de erros

9.6.3. Configuração dos contêineres de dados

Os cinco contêineres [DataGrid] foram configurados em [WebMatrix]. Eles foram formatados (cores e bordas) por meio do link [Configuration automatique] em seu painel de propriedades. O contêiner [DataGrid1] foi configurado por meio do link [Générateur de propriétés] nesse mesmo painel. A classificação foi habilitada (guia [Général]):

Image

As colunas não são geradas automaticamente, ao contrário dos outros quatro contêineres. Elas foram definidas manualmente por meio do assistente:

Image

Primeiramente, criamos duas colunas do tipo [Colonne connexe], denominadas [nom] e [prix]. Elas foram associadas, respectivamente, aos campos [nom] e [prix] da fonte de dados que os contêineres exibirão. Veja, por exemplo, a configuração da coluna [nom]:

Image

A expressão de ordenação é a expressão que deverá ser colocada após a cláusula [order by] da instrução SQL [select], que será executada quando ousuário clicar no nome da coluna de cabeçalho [nom] associada ao campo [nom] do [DataGrid]. Aqui, definimos [nom], de modo que a cláusula de ordenação será [order by nom]. Veremos que modificaremos essa cláusula para que seja [order by nom asc] ou [order by nom desc], dependendo da ordem de classificação escolhida pelo usuário.

Além disso, criamos duas colunas de botões:

Image

A coluna [Modifier, Mettre à jour, Annuler] nos permitirá modificar um produto, e a coluna [Supprimer], excluí-lo. Cada uma dessas colunas pode ser configurada. A coluna [Modifier, Mettre à jour, Annuler] oferece a seguinte configuração:

Image

Vemos que é possível alterar os textos dos botões. No que diz respeito aos botões, podemos escolher entre links e botões (lista suspensa acima). Aqui, foram escolhidos os links. A configuração da coluna [Supprimer] é semelhante. Além desse assistente de configuração, utilizamos diretamente a janela de propriedades do [DataGrid] para preencher a propriedade [DataKeyField], que indica com qual campo da fonte de dados as linhas do [DataGrid] são indexadas. Nesse caso, é utilizada a chave primária da tabela de produtos:

Image

No final, essa configuração gera o seguinte código de apresentação:


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

Como sempre, depois de adquirir alguma experiência, é possível escrever todo ou parte do código acima diretamente.

Os demais contêineres [DataGrid] possuem a configuração padrão obtida pela geração automática das colunas do [DataGrid] a partir das colunas da fonte de dados à qual ele está associado.

O contêiner [Repeater] serve para exibir uma lista de erros. Sua configuração é feita diretamente no código de apresentação:


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

Cada linha do componente exibe o valor [Container.DataItem], c.a.d. O valor correspondente da lista de dados será do tipo [ArrayList] e representará uma lista de erros.

9.6.4. O código de apresentação do aplicativo

Este está localizado no arquivo [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>

Observe os seguintes pontos:

  • a página é composta por quatro contêineres (painéis) [vueFormulaire, vueProduits, vueAjout, vueErreurs], que formarão as quatro visualizações do aplicativo.
  • Os botões ou links do tipo [submit] possuem a propriedade [CausesValidation=false]. A propriedade [causesValidation=true] faz com que todas as verificações de validade da página sejam executadas. No entanto, neste caso, nem todas as verificações de validade precisam ser realizadas ao mesmo tempo. Por exemplo, ao fazer uma adição, não queremos que as verificações relativas ao número de linhas por página sejam executadas. Portanto, especificaremos nós mesmos quais verificações de validade devem ser realizadas.

9.6.5. O código de controle [global.asax]

O controlador [global.asax] é o seguinte:

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

O código associado [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)
        ' recuperamos as informações de configuração
        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
        ' há erros de configuração?
        If erreurs.Count <> 0 Then
            ' registram-se os erros
            Application("erreurs") = erreurs
            ' sai
            Exit Sub
        End If
        ' aqui não há erros de configuração
        ' cria-se um objeto de produtos
        Dim dtProduits As DataTable
        Try
            dtProduits = New produits(chaînedeConnexion).getProduits
        Catch ex As ExceptionProduits
            'ocorreu um erro de acesso aos produtos; isso é registrado no aplicativo
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            ' erro não tratado
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            ' saída da subrotina
        End Try
        ' aqui não há erros de inicialização
        ' armazenamos o número de produtos por página
        Application("defaultProduitsPage") = defaultProduitsPage
        ' armazenando a tabela de produtos
        Application("dtProduits") = dtProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' inicialização das variáveis de sessão
        If IsNothing(Application("erreurs")) Then
            ' visualização da tabela de produtos
            Session("dvProduits") = CType(Application("dtProduits"), DataTable).DefaultView
            ' número de produtos por página
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' página atual exibida
            Session("pageCourante") = 0
        End If
    End Sub
End Class

No [Application_Start], começa-se por recuperar duas informações do arquivo de configuração [web.config] do aplicativo:

  • OLEDBStringConnection: a string OLEDB de conexão com o banco de dados de produtos
  • defaultProduitsPage: o número padrão de produtos por página exibida

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="OLEDBStringConnection" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\devel\aspnet\poly\webforms3\vs\majproduits1\produits.mdb" />
        <add key="defaultProduitsPage" value="5" />
    </appSettings>
</configuration>

Se uma dessas duas informações estiver ausente, uma lista de erros é gerada e inserida no aplicativo. O mesmo ocorre se o parâmetro [defaultProduitsPage] existir, mas estiver incorreto. Se os dois parâmetros esperados estiverem presentes e corretos, uma tabela [dtProduits] é criada e inserida no aplicativo. É essa tabela que será utilizada e atualizada pelos diversos clientes. O banco de dados, por sua vez, permanecerá inalterado. Abordaremos a atualização deste em um próximo aplicativo. Essa tabela é criada a partir de uma instância da classe [produits], analisada anteriormente, e do método [getProduits] dessa classe. A obtenção da tabela [dtProduits] pode falhar. Nesse caso, sabe-se que a classe [produits] lança uma exceção do tipo [ExceptionProduits]. Ela é interceptada aqui e a lista de erros associada é inserida na aplicação associada à chave [erreurs]. A presença dessa chave nas informações registradas no aplicativo será verificada a cada processamento de solicitação. Se for encontrada, a visualização [erreurs] será enviada ao cliente.

Embora a tabela [dtProduits] seja compartilhada por todos os clientes da web, cada um deles terá, no entanto, sua própria visualização [dvProduits] sobre ela. De fato, cada cliente web tem a possibilidade de definir um filtro na tabela [dtProduits], bem como uma ordem de classificação. Essas informações específicas de cada cliente web são armazenadas na visão do cliente. Por isso, ela é criada na tabela [Session_Start] para ser colocada na sessão específica de cada cliente. Utiliza-se o atributo [DefaultView] da tabela [dtProduits] para obter uma visualização padrão da tabela. Inicialmente, não há filtro nem ordem de classificação. Além disso, duas informações também são inseridas na sessão:

  • o número de produtos por página da chave [nbProduitsPage]. Ao iniciar a sessão, esse número é igual ao número padrão definido no arquivo de configuração.
  • o número da página atual dos produtos. Inicialmente, é a primeira página.

9.6.6. O código de controle [main.aspx.vb]

A estrutura do controlador é a seguinte:


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

    ' componentes da página
    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

    ' dados da página
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
    Protected erreur As Boolean = False

    ' carregamento da página
    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. Dados de instância

A classe [main] utiliza os seguintes dados de instância:

Public Class main
    Inherits System.Web.UI.Page

     ' componentes da página
    Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
...

     ' dados da página
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
dtProduits
a tabela [DataTable] de produtos — comum a todos os clientes
dvProduits
a visão [DataView] dos produtos — específica para cada cliente
defaultProduitsPage
número de produtos por página proposto por padrão

9.6.8. O procedimento [Page_Load] para carregamento da página

O código do procedimento [page_Load] é o seguinte:


    ' carregamento da página
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' verificando se o aplicativo está com erro
        If Not IsNothing(Application("erreurs")) Then
            ' o aplicativo não foi inicializado corretamente
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        ' recuperando uma referência da tabela de produtos
        dtProduits = CType(Application("dtProduits"), DataTable)
         ' recuperando a visualização dos produtos
        dvProduits = CType(Session("dvProduits"), DataView)
         ' recuperando o número de produtos por página
        defaultProduitsPage = CType(Application("defaultProduitsPage"), Integer)
         ' cancelando uma possível atualização em andamento
        DataGrid1.EditItemIndex = -1
        '1ª consulta
        If Not IsPostBack Then
            ' exibe-se o formulário inicial
            txtPages.Text = defaultProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub
  • Primeiramente, verifica-se se a tabela de produtos foi carregada ao iniciar o aplicativo. Caso contrário, exibe-se a visualização [erreurs] com as mensagens de erro apropriadas e, em seguida, encerra-se o procedimento após definir a variável booleana [erreur] como verdadeira. Esse indicador será testado por alguns procedimentos.
  • Recupera-se a tabela de produtos [dtProduits] na aplicação e ela é armazenada na variável de instância [dtProduits] para que seja compartilhada por todos os métodos da página.
  • Fazemos o mesmo com a visualização [dvProduits], recuperada da sessão, e com o número padrão de produtos por página, recuperado do aplicativo.
  • Se for a primeira solicitação do cliente, exibimos o formulário de definição das condições de filtragem e paginação com uma paginação padrão de [defaultProduitsPage] produtos por página.
  • Cancela-se o modo de edição do componente [dataGrid1]. Esse componente possui um atributo [EditItemIndex], que é o índice do elemento que está sendo editado. Se [EditItemIndex]=-1, então não há nenhum elemento sendo editado. Se [EditItemIndex]=i, então o elemento nº i do componente [DataGrid] está sendo editado. Ele é, então, apresentado de maneira diferente dos demais elementos do [dataGrid]:

Image

Acima, o elemento [Produit8, 80] está no modo [édition]. Vê-se acima que o usuário pode confirmar sua atualização pelo link [Mettre à jour] e cancelá-la pelo link [Annuler]. Ele também pode utilizar outros links que não tenham nada a ver com o elemento que está sendo editado. Ao inserir [DataGrid1.EditItemIndex=-1] a cada carregamento da página, garantimos que o modo de edição de [DataGrid1] seja cancelado sistematicamente. Só se atribuirá a ele um valor diferente se um link [Modifier] tiver sido clicado. Isso será feito no procedimento que trata desse evento. Teremos as seguintes situações:

  1. o link [Modifier] do elemento nº 8 de [DataGrid1] foi clicado. [Page_Load], em primeiro lugar, atribui -1 a [DataGrid1.EditItemIndex]. Em seguida, o procedimento que processa o evento [Modifier] inserirá 8 em [DataGrid1.EditItemIndex]. Por fim, quando a página for reenviada ao cliente, o elemento nº 8 estará efetivamente no modo [édition] e aparecerá conforme mostrado acima.
  2. O usuário altera o produto que está no modo [édition] e confirma a alteração por meio do link [Mettre à jour]. O [Page_Load] insere -1 no [DataGrid1.EditItemIndex]. Em seguida, o procedimento que processa o evento [Mettre à jour] será executado e o elemento nº 8 de [dtProduits] será atualizado. Quando a página for reenviada ao cliente, nenhum elemento de [DataGrid1] estará no modo de edição (DataGrid.EditItemIndex=-1).
  3. Se um usuário tiver iniciado a atualização de um produto, seria lógico que ele cancelasse essa atualização por meio de [Annuler]. No entanto, nada o impede de clicar em outro link. Portanto, precisamos prever esse caso. Nesse caso, assim como nos anteriores, o [Page_Load] começa inserindo -1 no [DataGrid1.EditItemIndex]; em seguida, o procedimento que trata do evento ocorrido será executado. Ela não alterará a propriedade [DataGrid1.EditItemIndex], que permanecerá, portanto, com o valor -1. Quando a página for reenviada ao cliente, nenhum elemento de [DataGrid1] estará no modo de edição.

Vemos que, ao definir [EditItemIndex] como -1 ao carregar a página, evitamos ter que nos preocupar se o usuário estava ou não em modo de atualização quando clicou em um link.

9.6.9. Exibição das visualizações [erreurs], [formulaire] e [ajout]

A exibição da visualização [erreurs] é solicitada ao carregar a página [Page_Load], caso se constate que o aplicativo não foi inicializado corretamente. Essa exibição é feita associando o componente de dados [rptErreurs] à lista de erros passada como parâmetro e tornando visível o contêiner apropriado. Por fim, os três links [Filtre, Mise à jour, Ajout] são ocultados, pois, em caso de erro, o aplicativo não pode ser utilizado.


    Private Sub afficheErreurs(ByVal erreurs As ArrayList)
        ' associa-se a lista de erros ao repetidor rptErreurs
        With rptErreurs
            .DataSource = erreurs
            .DataBind()
        End With
        ': as opções são desativadas
        lnkAjout.Visible = False
        lnkMisajour.Visible = False
        lnkFiltre.Visible = False
        ' exibe-se a visualização [erreurs]
        vueErreurs.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

As demais visualizações são solicitadas a partir das opções oferecidas ao usuário:

Image

A exibição da visualização [Ajout] é solicitada quando o usuário clica no link [Ajout] da página:


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        ' exibe a visualização [ajout]
        afficheAjout()
    End Sub

    Private Sub afficheAjout()
        ' exibe a visualização de adição
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

A exibição da visão [Formulaire] é solicitada quando o usuário clica no link [Filtrage] da página. Nesse momento, deve-se apresentar a visão que permite definir

  • o filtro na tabela de produtos
  • o número de produtos exibidos em cada página da web

É exibido um formulário que retoma os valores armazenados na sessão:


    Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
        ' definir as condições de filtragem
        txtFiltre.Text = dvProduits.RowFilter
        ' definir a paginação
        txtPages.Text = CType(Session("nbProduitsPage"), String)
        ' exibe o formulário de filtragem
        afficheFormulaire()
    End Sub

A condição de filtragem de uma visualização é definida em seu atributo [RowFilter]. Vale lembrar que a visualização filtrada e paginada se chama [dvProduits] e foi recuperada na sessão ao carregar a página [Page_Load]. A condição de filtragem é, portanto, recuperada em [dvProduits.RowFilter]. O número de produtos por página também é recuperado da sessão. Se o usuário nunca tiver definido essa informação, esse número será igual ao número padrão de produtos por página. Feito isso, é exibido o formulário que permite definir essas duas informações:


    Private Sub afficheFormulaire()
        ' exibe a visualização [formulaire]
        vueFormulaire.Visible = True
        vueErreurs.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

Image

9.6.10. Validação da visualização [formulaire]

O clique no botão [Exécuter] acima é processado pelo seguinte procedimento:


    Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
        ' página válida?
        rfvLignes.Validate()
        rvLignes.Validate()
        If Not rfvLignes.IsValid Or Not rvLignes.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If
        ' anexação dos dados filtrados à tabela
        Try
            dvProduits.RowFilter = txtFiltre.Text.Trim
        Catch ex As Exception
            lblinfo1.Text = "Erreur de filtrage (" + ex.Message + ")"
            afficheFormulaire()
            Exit Sub
        End Try
        ' armazenamento do número de produtos por página
        Session("nbProduitsPage") = txtPages.Text
        'e a página atual
        Session("pageCourante") = 0
        ' tudo certo — exibe-se os dados
        afficheProduits(0, CType(txtPages.Text, Integer))
    End Sub

Vale lembrar, antes de mais nada, que a página possui dois componentes de validação:

n.º
nome
tipo
função
7
rfvLignes
RequiredFieldValidator
verifica se há um valor em [txtPages]
8
rvLignes
RangeValidator
verifica se txtPages está no intervalo [3,10]

O procedimento começa executando o código de verificação dos dois componentes acima por meio de seu método [Validate] e, em seguida, verifica o valor de seu atributo [IsValid]. Esse valor será [true] somente se os dados verificados forem considerados válidos. Se os testes de validade de um dos dois componentes falharem, a visualização [formulaire] é exibida novamente para que o usuário corrija seu(s) erro(s). A condição de filtragem é aplicada ao atributo [dvProduits.RowFilter]. Aqui, pode ocorrer uma exceção se o usuário tiver definido um critério de filtragem incorreto, conforme mostrado abaixo:

Image

Nesse caso, a visualização [formulaire] é exibida novamente. Se as duas informações inseridas estiverem corretas, dois dados são armazenados na sessão:

  • o número de produtos por página escolhido pelo usuário
  • o número da página que será exibida — inicialmente, a página 0

Essas duas informações são utilizadas sempre que a visualização [produits] é exibida.

9.6.11. Exibição da visualização [produits]

A visualização [produits] é a seguinte:

Image

Temos 5 componentes [DataGrid], cada um exibindo uma visão específica da tabela de produtos [dtProduits]. Começando pela esquerda:

nome
função
DataGrid1
grade de exibição de uma visualização filtrada da tabela de produtos. O filtro é aquele definido pela visualização [formulaire]. Também temos .AllowPaging=true, .AllowSorting=true
DataGrid2
exibe a tabela completa de produtos — permite acompanhar as atualizações
DataGrid3
exibirá os produtos excluídos na tabela de produtos
DataGrid4
exibirá os produtos modificados na tabela de produtos
DataGrid5
exibirá os produtos adicionados na tabela de produtos

O procedimento [afficheProduits] é responsável por exibir a visualização anterior:


    Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
        ' os dados são anexados aos dois componentes [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()
        ' a exibição [produits] é exibida
        vueProduits.Visible = True
        vueFormulaire.Visible = False
        vueErreurs.Visible = False
        vueAjout.Visible = False
        ' a página atual é salva
        Session("pageCourante") = page
    End Sub

A procedimento aceita dois parâmetros:

  • o número da página [page] a ser exibida em [DataGrid1]
  • o número de produtos [taillePage] por página

A ligação do [DataGrid] à tabela de produtos é feita por meio de 5 visualizações diferentes.

  • [DataGrid1] está vinculada à visualização paginada e ordenada [dvProduits].

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

É durante essa ligação do [DataGrid1] à sua fonte de dados que são necessárias as duas informações passadas como parâmetros para a procedimento.

  • [DataGrid2] está vinculado a uma visualização que exibe todos os elementos atuais da tabela [dtProduits]. Assim como [DataGrid1], ela apresenta uma visualização atualizada da tabela [dtProduits], mas sem paginação nem ordenação.

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

Utilizamos aqui o atributo [RowStateFilter] da classe [DataView]. Uma linha de uma tabela ou, neste caso, de uma visualização, possui um atributo [RowState] que indica o status da linha. Aqui estão alguns exemplos:

  • (continuação)
    • [Added]: a linha foi adicionada
    • [Modified]: a linha foi modificada
    • [Deleted]: a linha foi excluída
    • [Unchanged]: a linha não sofreu alterações

Quando a tabela [dtProduits] foi criada inicialmente em [Application_Start], todas as suas linhas estavam no estado [Unchanged]. Esse estado irá evoluir à medida que os clientes da web forem atualizados:

  • (continuação)
    • quando um cliente cria um novo produto, uma linha é adicionada à tabela [dtProduits]. Ela ficará no estado [Added].
    • quando um produto é modificado, seu estado passa de [Unchanged] para [Modified].
    • Quando um produto é excluído, a linha não é excluída fisicamente. Em vez disso, ela é marcada como a ser excluída e seu estado passa para [Deleted]. É possível cancelar essa exclusão.

O atributo [RowStateFilter] da classe [DataView] permite filtrar uma visualização de acordo com o status [RowState] de suas linhas. Seus valores possíveis são os da enumeração [DataRowViewState]:

  • (continuação)
    • DataRowViewState.CurrentRows: as linhas não excluídas são exibidas com seus valores atuais
    • DataRowViewState.Added: as linhas adicionadas são exibidas com seus valores atuais
    • DataRowViewState.Deleted: as linhas excluídas são exibidas com seus valores originais
    • DataRowViewState.ModifiedOriginal: as linhas modificadas são exibidas com seus valores originais
    • DataRowViewState.ModifiedCurrent: as linhas modificadas são exibidas com seus valores atuais

Uma linha modificada possui dois valores: o valor original, que é aquele que a linha possuía antes da primeira modificação, e o valor atual, obtido após uma ou mais modificações. Esses dois valores são mantidos simultaneamente.

Os DataGrid 2 a 5 exibem uma visão da tabela [dtProduits] filtrada pelo atributo [RowStateFilter]

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

A tabela [dtPoduits] é atualizada simultaneamente por diferentes clientes da web, o que pode causar conflitos de acesso à tabela. Quando um cliente exibe suas visualizações da tabela [dtProduits], queremos evitar que ele faça isso enquanto a tabela estiver em um estado instável, pois está sendo modificada por outro cliente web. Portanto, sempre que um cliente precisar da tabela [dtProduits] para leitura, como descrito acima, ou para gravação — ao adicionar, modificar ou excluir produtos —, ele se sincronizará com os outros clientes por meio da seguinte sequência:

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

Podem existir várias seções críticas no código do aplicativo:

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

O mecanismo funciona da seguinte forma:

  1. um ou mais clientes chegam à instrução [Application.Lock] da seção crítica 1. Trata-se de um distribuidor de token de entrada único. Apenas um cliente recebe esse token. Vamos chamá-lo de C1.
  2. Enquanto o cliente C1 não devolver o token por meio de [Application.Unlock], nenhum outro cliente tem permissão para entrar em uma seção crítica controlada por [Application.Lock]. No exemplo acima, nenhum cliente pode, portanto, entrar nas seções críticas 1 e 2.
  3. O cliente C1 executa [Application.Unlock] e, portanto, devolve o token de entrada. Este pode então ser concedido a outro cliente. As etapas 1 a 3 são repetidas.

Com esse mecanismo, garantiremos que apenas um cliente tenha acesso à tabela [dtProduits], seja para leitura ou gravação.

O link [Mise à jour] também exibe a visualização [Produits]:

Image

O procedimento associado é o seguinte:


    Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
        ' exibição da visualização de produtos
        afficheProduits(CType(Session("pageCourante"), Integer), CType(Session("nbProduitsPage"), Integer))
    End Sub

É necessário exibir a visualização [produits]. Isso é feito por meio do procedimento [afficheProduits], que aceita dois parâmetros: o número da página atual a ser exibida e o número de produtos por página a serem exibidos. Essas duas informações estão na sessão, eventualmente com seus valores iniciais, caso o usuário nunca as tenha definido por conta própria.

9.6.12. Paginação e ordenação do [DataGrid1]

Já abordamos as rotinas que gerenciam a paginação e a ordenação de um [DataGrid] em outro exemplo. No caso de uma mudança de página, exibimos a visão [produits] passando o número da nova página como parâmetro para o procedimento [afficheProduits].


    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
        ' mudança de página
        afficheProduits(e.NewPageIndex, DataGrid1.PageSize)
    End Sub

A procedimento [DataGrid1_SortCommand] é executada quando o usuário clica no cabeçalho de uma das colunas da [DataGrid1]. É necessário, então, atribuir a expressão de ordenação ao atributo [Sort] da visualização [dvProduits] exibida por [DataGrid1]. Essa expressão é sintaticamente equivalente à expressão de ordenação colocada após a cláusula [order by] da instrução SQL SELECT. O argumento [e] do procedimento possui um atributo [SortExpression] que nos fornece a expressão de classificação associada à coluna cujo cabeçalho foi clicado. Quando o componente [DataGrid1] foi criado, essa expressão de classificação já havia sido definida. Abaixo, a expressão definida para a coluna [nom] de [DataGrid1]:

Image

Se a expressão de classificação não tiver sido definida durante o projeto do [DataGrid], será utilizado o nome do campo de dados associado à coluna do [DataGrid]. A ordem de classificação (crescente, decrescente) é definida aqui pelo usuário por meio dos botões de opção [rdCroissant, rd Décroissant]:

Image

O procedimento de ordenação é o seguinte:


    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
        ' ordena-se a visualização de dados
        With dvProduits
            .Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        End With
        ' exibição
        afficheProduits(0, DataGrid1.PageSize)
    End Sub

9.6.13. Exclusão de um produto

Para excluir um produto, o usuário utiliza o link [Supprimer] na linha do produto:

Image

O procedimento [DataGrid1_DeleteCommand] é então executado:


    Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
        ' exclusão de um produto
        ' chave do produto a ser excluído
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' exclusão do produto
        Dim erreur As Boolean = False
        Try
            supprimerProduit(idProduit)
        Catch ex As Exception
            ' problema
            lblInfo2.Text = ex.Message
            erreur = True
        End Try
        ' mudança de página?
        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
        ' exibição de produtos
        afficheProduits(page, DataGrid1.PageSize)
    End Sub

A atualização dos produtos será feita por meio da chave [id] do produto. De fato, a coluna [id] da tabela [dtProduits] é a chave primária e, graças a ela, podemos localizar na tabela [dtProduits] a linha do produto que está sendo atualizada no componente [DataGrid1]. O procedimento começa, portanto, recuperando a chave do produto cujo link [Supprimer] foi clicado:


         ' chave do produto a ser excluído
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

Sabemos que [e.Item] é o elemento de [DataGrid1] que originou o evento. Em termos gerais, esse elemento é uma linha do [DataGrid]. [e.Item.ItemIndex] é o número da linha que originou o evento. Esse índice é relativo à página atual exibida. Assim, a primeira linha da página exibida tem a propriedade [ItemIndex=0], mesmo que tenha o número 17 na tabela de produtos. [DataGrid1.DataKeys] é a lista de chaves do [DataGrid]. Como, na concepção, definimos [DataGrid1.DataKey=id], as chaves de [DataGrid1] são constituídas pelos valores da coluna [id] da tabela [dtProduits], que é, ao mesmo tempo, a chave primária. Assim, [DataGrid1.DataKeys(e.Item.ItemIndex)] é a chave [id] do produto a ser excluído. Obtida essa informação, solicitamos sua exclusão por meio do procedimento [supprimerProduit]:


         ' exclusão do produto
        Dim erreur As Boolean = False
        Try
            supprimerProduit(idProduit)
        Catch ex As Exception
            ' pb
            lblInfo2.Text = ex.Message
            erreur = True
        End Try

Essa exclusão pode falhar em alguns casos. Veremos por quê. Nesse caso, é lançada uma exceção, que é tratada aqui. Se houver erro, o [DataGrid1] não precisa ser alterado. Apenas uma mensagem de erro é adicionada à visualização [produits]. Se não houver erro e se o usuário acabou de excluir o único produto da página atual, exibe-se a página anterior.


         ' mudança de página?
        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

Em todos os casos, a visualização [produits] é exibida novamente com o procedimento [afficheProduits]:

         ' exibição de produtos
        afficheProduits(page, DataGrid1.PageSize)

O procedimento [supprimerProduit] é o seguinte:


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            ' sincronização
            Application.Lock()
            ' busca da linha a ser excluída
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' exclusão da linha
                ligne.Delete()
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            ' fim da sincronização
            Application.UnLock()
        End Try
        ' lança-se uma exceção em caso de erro
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

O procedimento recebe como parâmetro a chave do produto a ser excluído. Se houvesse apenas um cliente para realizar as atualizações, essa exclusão poderia ser feita da seguinte forma:

             ' exclusão da linha [idProduit]
            dtProduits.Rows.Find(idProduit).Delete()

Com vários clientes realizando as atualizações ao mesmo tempo, a situação fica mais complicada. De fato, consideremos a seguinte sequência temporal:

Tempo
Ação
T1
o cliente A lê a tabela [dtProduits] — há um registro com chave 20
T2
O cliente B consulta a tabela [dtProduits] — há um produto com a chave 20
T3
o cliente A exclui o produto com a chave 20
T4
o cliente B exclui o produto com chave 20

Quando os clientes A e B leem a tabela de produtos e exibem seu conteúdo em uma página da web, a tabela contém o produto com chave 20. Portanto, eles podem querer realizar uma ação nesse produto, por exemplo, excluí-lo. Inevitavelmente, um dos dois fará isso primeiro. Aquele que acessar em seguida tentará excluir um produto que já não existe mais. Esse caso é tratado pelo procedimento [supprimerProduits] de várias maneiras:

  • primeiramente, há uma sincronização com o [Application.Lock]. Isso significa que, quando um cliente ultrapassa essa barreira, não há outro que possa modificar ou ler a tabela de produtos. De fato, todas essas operações são sincronizadas dessa forma. Já vimos isso no caso da leitura.
  • Em seguida, verifica-se se o produto que se deseja excluir existe. Se sim, ele é excluído; caso contrário, é gerada uma mensagem de erro.

             ' busca da linha a ser excluída
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                 ' exclusão da linha
                ligne.Delete()
            End If
  • saímos da sequência crítica por meio de [Application.Unlock] para permitir que os demais clientes realizem suas atualizações.
  • Se a linha não puder ser excluída, o procedimento lança uma exceção associada a uma mensagem de erro.

Vejamos um exemplo. Iniciamos um cliente [Mozilla] e selecionamos imediatamente a opção [Mise à jour] (visualização parcial):

Image

Fazemos o mesmo com um cliente [Internet Explorer] (visualização parcial):

Image

Com o cliente [Mozilla], excluímos o produto [produit2]. Obtemos a seguinte nova página:

Image

A operação de exclusão foi concluída com sucesso. O produto excluído aparece corretamente no [DataGrid], na lista de produtos excluídos, e não aparece mais na lista de produtos dos componentes [DataGrid1] e [DataGrid2], que exibem os produtos atualmente presentes na tabela. Vamos fazer o mesmo com o [Interbet Explorer]. Eliminamos o elemento [produit2]:

Image

A resposta obtida é a seguinte:

Image

Uma mensagem de erro informa ao usuário que o produto com chave [2] não existe. A resposta retorna ao cliente uma nova visualização que reflete o estado atual da tabela. Percebe-se que, de fato, o produto com chave [2] está na lista de produtos excluídos. A visualização [produits] reflete, portanto, as atualizações de todos os clientes e não de apenas um.

9.6.14. Adicionar um produto

Para adicionar um produto, o usuário utiliza o link [Ajout] nas opções. Esse link simplesmente exibe a visualização [Ajout]:

Image

Image

Os dois procedimentos envolvidos nessa ação são os seguintes:


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        ' exibe a visualização [ajout]
        afficheAjout()
    End Sub

    Private Sub afficheAjout()
        ' exibe a visualização de adição
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

Vale lembrar que a visualização [Ajout] possui componentes de validação:

nome
tipo
função
rfvNom
RequiredFieldValidator
verifica se há um valor em [txtNom]
rfvPrix
RequiredFieldValidator
verifica se há um valor em [txtPrix]
cvPrix
CompareValidator
verifica se preço >= 0

O procedimento de gerenciamento do clique no botão [Ajouter] é o seguinte:


    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
        ' adição de um novo item à tabela de produtos
        ' primeiro, os dados devem ser válidos
        rfvNom.Validate()
        rfvPrix.Validate()
        cvPrix.Validate()
        If Not rfvNom.IsValid Or Not rfvPrix.IsValid Or Not cvPrix.IsValid Then
            ' novamente o formulário de preenchimento
            afficheAjout()
            Exit Sub
        End If
        ' criamos uma linha
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim
        ' adicionamos a linha à tabela
        Application.Lock()
        Try
            dtProduits.Rows.Add(produit)
            lblInfo3.Text = "Ajout réussi"
            ' limpeza
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try
        Application.UnLock()
    End Sub

Primeiramente, são executadas as verificações de validade dos três componentes [rfvNom, rfvPrix, cvPrix]. Se alguma das verificações falhar, a visualização [Ajout] é exibida novamente com as mensagens de erro dos componentes de validação. Veja um exemplo:

Image

Se os dados forem válidos, prepara-se a linha a ser inserida na tabela [dtProduits].


         ' cria-se uma linha
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim

Primeiramente, cria-se uma nova linha na tabela [dtProduits] utilizando o método [DataTable.NewRow]. Essa linha conterá as três colunas [id, nom, prix] da tabela [dtProduits]. As colunas [nom, prix] são preenchidas com os valores inseridos no formulário de adição. A coluna [id], por sua vez, não é preenchida. Essa coluna é do tipo [AutoIncrement], c.a.d. O SGBD servirá como chave para um novo produto, sendo o valor máximo existente mais 1. A linha [produit] criada aqui está separada da tabela [dtProduits]. Agora precisamos inseri-la nessa tabela. Como vamos atualizar a tabela [dtProduits], ativamos a sincronização entre clientes:

        Application.Lock()

Feito isso, tentamos adicionar a linha à tabela [dtProduits]. Se for bem-sucedido, exibimos uma mensagem de sucesso; caso contrário, uma mensagem de erro.

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

Normalmente, não há nenhuma exceção possível. No entanto, por precaução, implementamos um gerenciamento de exceções. Após a adição, saímos da seção crítica por meio de [Application.Unlock].

9.6.15. Modificação de um produto

Para modificar um produto, o usuário utiliza o link [Modifier] na linha do produto:

Image

Em seguida, entra-se no modo de edição:

Image

Graças ao componente [DataGrid], esse resultado é obtido com muito pouco código:


    Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
        ' coloca-se o elemento atual no modo de edição
        DataGrid1.EditItemIndex = e.Item.ItemIndex
        ' reexibe os produtos
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Ao clicar no link [Modifier], o procedimento [DataGrid1_EditCommand] é executado no servidor. O componente [DataGrid1] possui um campo [EditItemIndex]. A linha cujo índice corresponde ao valor de [EditItemIndex] é colocada no modo de edição. Cada valor da linha assim atualizada pode ser modificado em um campo de entrada, conforme mostra a captura de tela acima. Recuperamos, portanto, o índice do produto no qual ocorreu o clique no link [Modifier] e o atribuímos ao atributo [EditItemIndex] do componente [DataGrid1]. Resta apenas exibir novamente a visualização [produits]. Ela aparecerá idêntica ao que era, mas com uma linha no modo de edição.

O link [Annuler] do produto que está sendo editado permite que o usuário cancele sua atualização. O procedimento associado a esse link é o seguinte:


    Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
        ' reexibe os produtos
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Ela simplesmente exibe novamente a visualização [produits]. Pode-se ficar tentado a inserir -1 em [DataGrid1.EditItemIndex] para cancelar o modo de atualização. Na verdade, sabemos que o procedimento [Page_Load] faz isso sistematicamente. Portanto, não há necessidade de repeti-lo.

A modificação é validada pelo link [Mettre à jour] da linha que está sendo editada. Em seguida, é executado o procedimento:


    Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
        ' chave do produto a ser modificado
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' elementos modificados
        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
        ' as alterações são válidas?
        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
        '  se houver erro, exibe-se novamente a página de atualização
        If lblInfo2.Text <> String.Empty Then
            ' coloca-se a linha novamente no modo de atualização
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            ' exibição de produtos
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
            ' fim
            Exit Sub
        End If
        ' caso não haja erro — altera-se a tabela
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' problema
            lblInfo2.Text = ex.Message
        End Try
        ' exibição de produtos
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Assim como na exclusão, precisamos recuperar a chave do produto a ser modificado para localizar sua linha na tabela de produtos [dtProduits]:


         ' chave do produto a ser alterado
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

Em seguida, precisamos recuperar os novos valores a serem atribuídos à linha. Esses valores estão no componente [DataGrid1]. O argumento [e] do procedimento serve para nos ajudar. [e.Item] representa a linha de [DataGrid1], que é a origem do evento. Portanto, essa é a linha que está sendo atualizada, já que o link [Mettre à jour] existe apenas nessa linha. Essa linha contém colunas designadas pela coleção [Cells] da linha. Assim, [e.Item.Cells(0)] representa a coluna 0 da linha que está sendo atualizada. Sabemos que os novos valores estão nos campos de entrada. A coleção [e.Item.Cells(i).Controls] representa a coleção de controles da coluna i da linha [e.Item]. As duas instruções a seguir recuperam os valores dos campos de entrada da linha atualizada em [DataGrid1]:


         ' elementos alterados
        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

Portanto, temos os novos valores da linha modificada na forma de cadeias de caracteres. Em seguida, verificamos se esses dados são válidos. O nome não deve estar vazio e o preço deve ser um número positivo ou zero:


         ' as alterações são válidas?
        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

Em caso de erro, o tag [lblInfo2] exibirá uma mensagem de erro e a página será simplesmente reexibida:


          ' se houver erro, exibe-se novamente a página de atualização
        If lblInfo2.Text <> String.Empty Then
            ' coloca-se a linha no modo de atualização
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            ' exibição de produtos
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
             ' fim
            Exit Sub
        End If

O que o código acima não mostra é que os valores inseridos são perdidos. De fato, [DataGrid1] está vinculado aos dados da tabela [dtProduits], que contém os valores originais da linha modificada. Veja um exemplo.

Image

A resposta obtida é a seguinte:

Image

Percebe-se que os valores inseridos inicialmente foram perdidos. Em uma aplicação profissional, isso provavelmente não seria aceitável. Aqui, atingimos certos limites do modo padrão de atualização do componente [DataGrid]. Seria preferível ter uma visão [Modification] análoga à visão [Ajout].

Se os dados forem válidos, eles são utilizados para atualizar a tabela [dtProduits]:


         ' caso não haja erro — modifica-se a tabela
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' problema
            lblInfo2.Text = ex.Message
        End Try
        ' exibição de produtos
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)

Pelo mesmo motivo mencionado durante a exclusão de um produto, a modificação pode falhar. De fato, entre o momento em que o cliente consultou a tabela [dtProduits] e aquele em que ele vai modificar um de seus produtos, este pode ter sido excluído por outro cliente. O procedimento [modifierProduit] lida com esse caso lançando uma exceção. Essa exceção é tratada aqui. Após o sucesso ou a falha da atualização, o aplicativo retorna a visualização [produits] ao cliente. Resta-nos ver como o procedimento [modifierProduit] realiza a atualização:


    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
        Dim erreur As String
        Try
            ' sincronização
            Application.Lock()
            ' busca da linha a ser modificada
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' modifica-se a linha
                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
            ' fim da sincronização
            Application.UnLock()
        End Try
        ' lança-se uma exceção em caso de erro
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

Não entraremos em detalhes sobre esse procedimento, cujo código é semelhante ao do procedimento [supprimerProduit], que já foi explicado detalhadamente. Vejamos apenas dois exemplos. Primeiro, alteramos o preço do produto [produit1]:

Image

Ao utilizar o link [Mettre à jour] acima, obtemos a seguinte resposta:

Image

A alteração está presente nos componentes [DataGrid] 1 e 2, que refletem o estado atual da tabela [dtProduits]. Ela também está presente no componente [DataGrid4], que é uma visualização das linhas modificadas, as quais são exibidas com seus valores originais. Agora, vamos examinar um caso de conflito de acesso. Assim como no exemplo de exclusão, utilizaremos dois clientes web diferentes. Um cliente [Mozilla] lê a tabela [dtProduits] e entra no modo de modificação do produto [produit1]:

Image

Um cliente [Internet Explorer] está prestes a excluir o produto [produit1]:

Image

O cliente [Internet Explorer] exclui o [produit1]:

Image

Observe-se que o [produit1] não consta mais na tabela de linhas modificadas, mas sim na de linhas excluídas. O cliente [Mozilla], por sua vez, valida sua atualização:

Image

O cliente [Mozilla] recebe a seguinte resposta à sua atualização:

Image

Ele pode constatar que o [produit1] foi excluído, uma vez que consta na lista de produtos excluídos.

9.7. Aplicativo web para atualização da tabela física de produtos

9.7.1. Soluções propostas

A aplicação anterior era mais um caso teórico destinado a demonstrar o gerenciamento de um objeto [DataTable] em cache do que um caso comum. De fato, em algum momento, é necessário atualizar a fonte real dos dados. É possível optar por duas estratégias diferentes:

  1. utiliza-se o cache [dtProduits] na memória para atualizar a fonte dos dados. Pode-se criar uma página localizada na árvore de navegação da aplicação anterior para ter acesso ao cache [dtProduits] dessa aplicação. Essa página ofereceria ao administrador a possibilidade de refletir na fonte física dos dados as alterações realizadas no cache [dtProduits]. Para isso, seria possível adicionar um novo método à classe de acesso [produits], que aceitasse como parâmetro o cache [dtProduits] e, com esse cache, atualizasse a fonte física dos dados.
  2. A fonte física de dados é atualizada ao mesmo tempo que o cache.

A estratégia nº 1 permite abrir apenas uma conexão com a fonte física de dados. A estratégia nº 2 requer uma conexão a cada atualização. Dependendo da disponibilidade das conexões, poderá-se optar por uma ou outra estratégia. Como dispomos das ferramentas para implementá-la (a classe [produits]), escolhemos a estratégia nº 2.

9.7.2. Solução 1

Por uma questão de continuidade com o aplicativo anterior, optamos pela seguinte estratégia:

  • a fonte física é atualizada ao mesmo tempo que o cache
  • o cache é construído apenas uma vez, na inicialização do aplicativo na classe [global.asax.vb]. Isso significa que, se a fonte física de dados for atualizada por outros clientes que não sejam os clientes da web, estes não verão essas alterações. Eles verão apenas as alterações que eles próprios fizerem na tabela em cache.

Para atualizar a fonte física dos dados, precisamos de uma instância da classe de acesso aos produtos. Cada cliente poderia ter a sua própria. Também é possível compartilhar uma única instância, que seria criada pela aplicação ao iniciar. Essa é a solução que escolhemos aqui. O código de controle [global.asax.vb] é modificado da seguinte forma:


Imports System
Imports System.Web
...

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' recuperação das informações de configuração
        Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
        Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
        Dim erreurs As New ArrayList
...
        ' aqui não há erros de configuração
        ' cria-se um objeto de produtos
        Dim objProduits As New produits(chaînedeConnexion)
        Dim dtProduits As DataTable
        Try
            dtProduits = objProduits.getProduits
        Catch ex As ExceptionProduits
            'ocorreu um erro ao acessar os produtos; isso é registrado no aplicativo
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            ' erro não tratado
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            ' saída da subrotina
        End Try
        ' aqui não há erros de inicialização
...
        ' a instância de acesso aos dados é armazenada
        Application("objProduits") = objProduits
    End Sub

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

Uma instância da classe de acesso aos dados foi armazenada no aplicativo, associada à chave [objProduits]. Cada cliente utilizará essa instância para acessar a fonte física dos dados. Ela será recuperada no procedimento [Page_Load] de [main.aspx.vb]:


    Protected objProduits As produits
....
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
        ' recuperando a instância de acesso aos produtos
        objProduits = CType(Application("objProduits"), produits)
...
    End Sub

A instância de acesso aos dados [objProduits] está disponível para todos os métodos da página. Ela será utilizada para as três operações de atualização: adição, exclusão e modificação.

O procedimento de adição é modificado da seguinte forma:


    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
...
        ' adiciona-se a linha
        Application.Lock()
        Try
            ' adiciona-se a linha à tabela em cache
            dtProduits.Rows.Add(produit)
             ' adiciona-se a linha à fonte física
            Dim nouveauProduit As sProduit
            With nouveauProduit
                .nom = CType(produit("nom"), String)
                .prix = CType(produit("prix"), Double)
            End With
            objProduits.ajouterProduit(nouveauProduit)
            ' acompanhamento
            lblInfo3.Text = "Ajout réussi"
            ' limpeza
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            ' erro
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try
        Application.UnLock()
    End Sub

O procedimento de modificação é alterado da seguinte forma:


    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
        Dim erreur As String
        Try
            ' sincronização
            Application.Lock()
            ' busca pela linha a ser modificada
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' modificação da linha no cache
                With ligne
                    .Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
                    .Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
                End With
                 ' modificação da linha na fonte física
                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
            ' fim da sincronização
            Application.UnLock()
        End Try
        ' lança-se uma exceção em caso de erro
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

O procedimento de exclusão é alterado da seguinte forma:


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            ' sincronização
            Application.Lock()
            ' busca da linha a ser excluída
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' exclusão da linha no cache
                ligne.Delete()
                 ' exclusão da linha na fonte física
                objProduits.supprimerProduit(idProduit)
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            ' fim da sincronização
            Application.UnLock()
        End Try
        ' lança-se uma exceção em caso de erro
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

9.7.3. Testes

Partimos da seguinte tabela de dados em um arquivo ACCESS:

Image

Um cliente web é iniciado:

Image

Adicionamos um produto:

Image

Excluímos [produit1]:

Image

Alteramos o preço de [produit2]:

Image

Após essas alterações, verificamos o conteúdo da tabela [liste] no banco de dados ACCESS:

Image

As três alterações foram corretamente refletidas na tabela física. Vamos agora examinar um caso de conflito. Excluímos, diretamente abaixo de ACCESS, a linha de [produit2]:

Image

Voltamos ao nosso cliente web. Ele não vê a exclusão que foi feita e, por sua vez, deseja excluir o [produit2]:

Image

Ele recebe a seguinte resposta:

Image

A linha [produit2] foi efetivamente removida do cache, conforme indicado na lista de produtos excluídos. A exclusão de [produit2] na fonte física, por sua vez, falhou, conforme indicado na mensagem de erro.

9.7.4. Solução 2

Na solução anterior, os clientes da web atualizam simultaneamente a fonte de dados física, mas não visualizam as alterações feitas pelos outros clientes. Eles visualizam apenas as suas próprias alterações. Agora, gostaríamos que um cliente pudesse visualizar a fonte de dados física como ela está atualmente, e não como estava no momento da inicialização do aplicativo. Para isso, ofereceremos ao cliente uma nova opção:

Image

Com a opção [Rafraîchir], o cliente força uma nova leitura da fonte de dados física. Para que isso não afete os outros clientes, é necessário que a tabela resultante dessa leitura pertença ao cliente que está realizando a atualização e não seja compartilhada com os outros clientes. Essa é a primeira diferença em relação ao aplicativo anterior. O cache [dtProduits] da fonte de dados será criado por cada cliente e não pela própria aplicação. A modificação é feita em [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)
        ' recuperação das informações de configuração
...
        ' aqui não há erros de configuração
        ' cria-se um objeto de produtos
        Dim objProduits As New produits(chaînedeConnexion)
        ' armazenamos o número de produtos por página
        Application("defaultProduitsPage") = defaultProduitsPage
        ' armazenamos a instância de acesso aos dados
        Application("objProduits") = objProduits
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' inicialização das variáveis de sessão
        If IsNothing(Application("erreurs")) Then
            ' a fonte de dados é armazenada em cache
            Dim dtProduits As DataTable
            Try
                Application.Lock()
                dtProduits = CType(Application("objProduits"), produits).getProduits
            Catch ex As ExceptionProduits
                'ocorreu um erro ao acessar os produtos; isso é registrado na sessão
                Session("erreurs") = ex.erreurs
                Exit Sub
            Finally
                Application.UnLock()
            End Try
            ' o cache [dtProduits] é armazenado na sessão
            Session("dtProduits") = dtProduits
            ' visualização da tabela de produtos
            Session("dvProduits") = dtProduits.DefaultView
            ' número de produtos por página
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' página atual exibida
            Session("pageCourante") = 0
        End If
    End Sub

End Class

As informações da sessão são recuperadas a cada solicitação no procedimento [Page_Load]:


     ' dados da página
    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
        ' verifica se o aplicativo está com erro
        If Not IsNothing(Application("erreurs")) Then
            ' o aplicativo não foi inicializado corretamente
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        ' verifica se a sessão está com erro
        If Not IsNothing(Session("erreurs")) Then
            ' a sessão não foi inicializada corretamente
            afficheErreurs(CType(Session("erreurs"), ArrayList))
            Exit Sub
        End If
        ' recupera-se uma referência da tabela de produtos
        dtProduits = CType(Session("dtProduits"), DataTable)
        ' recuperando a visualização dos produtos
        dvProduits = CType(Session("dvProduits"), DataView)
        ' recupera-se o número de produtos por página
        nbProduitsPage = CType(Session("nbProduitsPage"), Integer)
        ' recuperando a página atual
        pageCourante = CType(Session("pageCourante"), Integer)
        ' recupera-se a instância de acesso aos produtos
        objProduits = CType(Application("objProduits"), produits)
        ' cancela uma eventual atualização em andamento
        DataGrid1.EditItemIndex = -1
        '1ª consulta
        If Not IsPostBack Then
            ' exibe-se o formulário inicial
            txtPages.Text = nbProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub

As informações recuperadas durante a sessão serão salvas no final dessa mesma sessão, após cada consulta:


    Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
        ' armazenam-se algumas informações na sessão
        Session("dtProduits") = dtProduits
        Session("dvProduits") = dvProduits
        Session("nbProduitsPage") = nbProduitsPage
        Session("pageCourante") = pageCourante
    End Sub

O evento [PreRender] é aquele que sinaliza que a resposta será enviada ao cliente. Aproveitamos esse momento para salvar na sessão todos os dados que devem ser mantidos. Isso é um pouco exagerado, já que, com bastante frequência, apenas alguns deles tiveram seus valores alterados. Esse salvamento sistemático tem a vantagem de nos livrar da gestão da sessão nos demais métodos da página.

A operação de atualização do cache é realizada pelo procedimento a seguir:


    Private Sub lnkRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkRefresh.Click
        ' é necessário atualizar o cache [dtProduits] com a fonte de dados física
        ' início da sincronização
        Application.Lock()
        Try
            ' a tabela de produtos
            dtProduits = CType(Application("objProduits"), produits).getProduits
            ' armazenamos o filtro atual
            Dim filtre As String = dvProduits.RowFilter
            ' cria-se a nova visualização filtrada
            dvProduits = New DataView(dtProduits)
            ' reaplicando o filtro
            dvProduits.RowFilter = filtre
        Catch ex As ExceptionProduits
            'ocorreu um erro de acesso aos produtos; isso é registrado na sessão
            Session("erreurs") = ex.erreurs
            ' exibe-se a visualização [erreurs]
            afficheErreurs(ex.erreurs)
            ' concluído
            Exit Sub
        Finally
            ' fim da sincronização
            Application.UnLock()
        End Try
        ' tudo correu bem — exibimos os produtos a partir da primeira página
        afficheProduits(0, nbProduitsPage)
    End Sub

O procedimento gera novos valores para o cache [dtProduits] e para a visualização [dvProduits]. Esses valores serão inseridos na sessão pelo procedimento [Page_PreRender] descrito acima. Assim que o cache [dtProduits] for reconstruído, os produtos são exibidos a partir da primeira página.

Segue um exemplo de execução. Um cliente [mozilla] é iniciado e exibe os produtos:

Image

Um cliente [Internet Explorer] faz o mesmo:

Image

O cliente [Mozilla] exclui o [produit1], modifica o [produit2] e adiciona um novo produto. Ele obtém a seguinte nova página:

Image

O cliente [Internet Explorer] deseja excluir o [produit1].

Image

Ele recebe a seguinte resposta:

Image

Foi informado de que [produit1] não existia mais. O usuário decide então atualizar seu cache com o link [Rafraîchir] acima. Ele recebe a seguinte resposta:

Image

Agora ele possui a mesma fonte de dados que o cliente [Mozilla].

9.8. Conclusion

Neste capítulo, dedicamos bastante tempo aos contêineres de dados e suas ligações com fontes de dados. Concluímos mostrando como é possível atualizar uma fonte de dados usando um componente [DataGrid]. Utilizamos uma tabela de banco de dados como fonte de dados. Embora o componente [DataGrid] facilite um pouco a representação dos dados, a verdadeira dificuldade não está na parte da apresentação, mas sim no gerenciamento das atualizações da fonte de dados realizadas pelos diferentes clientes. Podem surgir conflitos de acesso, que precisam ser gerenciados. Neste caso, nós os gerenciamos no controlador utilizando o [Application.Lock]. Provavelmente seria mais sensato sincronizar os acessos à fonte de dados na classe de acesso a ela, para que o controlador não precise se preocupar com tais detalhes que não são de sua competência.

Na prática, as tabelas de um banco de dados estão interligadas por relações, e sua atualização deve levar isso em consideração. Isso tem consequências, em primeiro lugar, na classe de acesso aos dados, que se torna mais complexa do que seria necessário para uma tabela independente. Geralmente, isso também tem consequências na parte de apresentação da aplicação web, pois muitas vezes é preciso exibir não apenas uma única tabela, mas as tabelas interligadas por relações.

Além disso, este capítulo permitiu apresentar diferentes estruturas de dados, como [DataTable, DataView].