Skip to content

9. Componentes de servidor ASP - 3

9.1. Introdução

Continuamos o nosso trabalho na interface do utilizador explorando as capacidades dos componentes [DataList] e [DataGrid], particularmente na área da atualização dos dados que apresentam

9.2. Tratamento de eventos associados a dados em componentes ligados a dados

9.2.1. O Exemplo

Considere 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"}. Cada um destes pontos de dados está associado a um grupo de dois botões rotulados [Info1] e [Info2]. Quando o utilizador clica num dos botões, o texto exibe o nome do botão clicado. O objetivo aqui é demonstrar como gerir uma lista de botões ou links.

9.2.2. Configuração do componente

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


                        <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 está relacionado com a aparência da [DataList] para nos concentrarmos exclusivamente no seu conteúdo:

  • a secção <HeaderTemplate> define o cabeçalho do [DataList] e a secção <FooterTemplate> define o seu rodapé.
  • A secção <ItemTemplate> é o modelo de apresentação utilizado para cada item na lista de dados associada. Contém os seguintes elementos:
    • o valor do item de dados atual na lista de dados associada ao componente: <%# Container.DataItem %>
    • dois botões rotulados [Info1] e [Info2], respetivamente. A classe [Button] possui um atributo [CommandName] que é utilizado aqui. Isto permitir-nos-á determinar qual o botão que desencadeou um evento na [DataList]. Para lidar com os cliques nos botões, teremos um único manipulador de eventos ligado à própria [DataList], e não aos botões. Este manipulador receberá informações indicando em que linha da [DataList] ocorreu o clique. O atributo [CommandName] permitir-nos-á saber qual o botão nessa linha que desencadeou o clique.

O componente [Repeater1] é configurado de forma 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 adicionámos uma secção <SeparatorTemplate> para que os dados sucessivos apresentados pelo componente sejam separados por uma barra horizontal.

Por fim, o componente [DataGrid1] é 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>

Aqui também omitimos as informações de estilo (cores, larguras, etc.). Estamos no modo de geração automática de colunas, que é o modo predefinido para o [DataGrid]. Isto significa que haverá tantas colunas quantas existirem na fonte de dados. Aqui, existe uma. Adicionámos duas outras colunas marcadas com <asp:ButtonColumn>. Definimos informações semelhantes às definidas para os outros dois componentes, bem como o tipo de botão, que aqui é [PushButton]. O tipo predefinido é [LinkButton], ou seja, um link. Além disso, os dados serão paginados [AllowPaging=true] com um tamanho de página de dois itens [PageSize=2].

9.2.3. O código de layout da página

O código de apresentação da nossa página de exemplo foi colocado num ficheiro chamado [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 controlo da página

O código de controlo da aplicação foi colocado no ficheiro [main.aspx.vb]:

Public Class main
    Inherits System.Web.UI.Page

    ' components page
    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

    ' the data source
    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
            'data source links
            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
        ' an event has occurred on one of the [datalist] lines
        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
        ' an event has occurred on one of the [repeater] lines
        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
        ' an event has occurred on one of the [datagrid] lines
        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
        ' change page
        With DataGrid1
            .CurrentPageIndex = e.NewPageIndex
            .DataSource = textes
            .DataBind()
        End With
    End Sub
End Class

Comentários:

  • A fonte de dados [textes] é uma matriz simples de cadeias de caracteres. Será ligada aos três componentes da página
  • Esta ligação é estabelecida no procedimento [Page_Load] durante o primeiro pedido. Para pedidos subsequentes, os três componentes irão recuperar os seus valores através do mecanismo [VIEW_STATE].
  • Os três componentes possuem um manipulador para o evento [ItemCommand]. Este evento ocorre quando se clica num botão ou num link numa das linhas do componente. O manipulador recebe duas informações:
    • source: a referência ao objeto (botão ou link) que desencadeou o evento
    • a: informações sobre o evento do tipo [DataListCommandEventArgs], [RepeaterCommandEventArgs] ou [DataGridCommandEventArgs], conforme apropriado. O argumento a contém várias informações. Aqui, duas delas são do nosso interesse:
      • a.Item: representa a linha onde o evento ocorreu, do tipo [DataListItem], [DataGridItem] ou [RepeaterItem]. Independentemente do tipo exato, o elemento [Item] possui um atributo [ItemIndex] que indica o número da linha do [Item] no contentor ao qual pertence. Aqui, exibimos esse número de linha
      • a.CommandName: é o atributo [CommandName] do botão (Button, LinkButton, ImageButton) que desencadeou o evento. Esta informação, combinada com a anterior, permite-nos determinar qual o botão no contentor que desencadeou o evento [ItemCommand]

9.3. Aplicação - Gestão de uma lista de subscrições

Agora que sabemos como interceptar eventos que ocorrem dentro de um contentor de dados, apresentamos um exemplo que mostra como lidar com eles.

9.3.1. Introdução

A aplicação aqui apresentada simula uma aplicação de subscrição de uma lista de correio. Estas são definidas por um objeto [DataTable] de três colunas:

nome
tipo
função
id
string
chave primária
tema
string
lista nome do tema
descrição
cadeia
uma descrição dos tópicos abordados pela lista

Para manter o nosso exemplo simples, o objeto [DataTable] acima será construído programaticamente de forma arbitrária. Numa aplicação real, seria provavelmente fornecido por um método de uma classe de acesso a dados. A tabela da lista será construída no procedimento [Application_Start], e a tabela resultante será armazenada na aplicação. Chamaremos-lhe a tabela [dtThèmes]. O utilizador subscreverá determinados tópicos desta tabela. A lista das suas subscrições será armazenada num objeto [DataTable] chamado [dtAbonnements], que terá a seguinte estrutura:

nome
tipo
função
id
string
chave primária
tema
string
lista nome do tema

A aplicação de página única é a seguinte:

 
N.º
nome
tipo
propriedades
função
1
dgThemes
DataGrid
 
listas de correio disponíveis para subscrição
2
dlSubscriptions
DataList
 
lista das subscrições do utilizador às listas anteriores
3
panelInfo
painel
 
painel de informações sobre o tópico selecionado pelo utilizador com um link [Mais informações]
4
lblTema
Label
parte de [panelInfos]
nome do tema
5
lblDescrição
Rótulo
parte de [panelInfos]
descrição do tema
6
lblInfo
Rótulo
 
mensagem de informação da aplicação

O nosso exemplo visa ilustrar a utilização dos componentes [DataGrid] e [DataList], em particular o tratamento de eventos que ocorrem ao nível das linhas destes contentores de dados. Por conseguinte, a aplicação não possui um botão de envio que guardaria as seleções do utilizador numa base de dados, por exemplo. No entanto, é realista. Estamos próximos de uma aplicação de comércio eletrónico em que um utilizador adicionaria produtos (assinaturas) ao seu carrinho.

9.3.2. Funcionalidade

A primeira vista que o utilizador vê é a seguinte:

O utilizador clica nos links [Mais Informações] para obter detalhes sobre um tópico. Estes são apresentados em [panelInfos]:

Image

Clicam nos links [Subscrever] para subscrever um tópico. Os tópicos selecionados são adicionados ao componente [dlSubscriptions]:

Image

O utilizador pode querer subscrever uma lista à qual já está subscrito. Uma mensagem alerta-o para isso:

Image

Por fim, pode cancelar a subscrição de qualquer um dos tópicos utilizando os botões [Cancelar subscrição] acima. Não é necessária qualquer confirmação. Isto não é necessário aqui porque o utilizador pode facilmente voltar a subscrever. Aqui está a visualização após cancelar a subscrição de [topic1]:

Image

9.3.3. Configurar contentores de dados

O componente [dgThèmes] do tipo [DataGrid] está associado a uma fonte do tipo [DataTable]. A sua formatação foi realizada através do link [Auto Format] no painel de propriedades do [DataGrid]. As suas propriedades foram definidas através do link [Property Generator] 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>

Tenha em atenção os seguintes pontos:

AutoGenerateColumns=false
Definimos nós próprios as colunas a apresentar na secção <columns>...</columns>
AllowPaging=true
PageSize=5
para paginação de dados
<asp:BoundColumn>
define a coluna [theme] (HeaderText) do [DataGrid] que será ligada à coluna [theme] da fonte de dados (DataField)
<asp:ButtonColumn>
define duas colunas de botões (ou links). Para distinguir entre os dois links na mesma linha, utilizaremos a sua propriedade [CommandName].

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

O componente [dlAbonnements] do tipo [DataList] está ligado a uma fonte do tipo [DataTable]. A sua formatação foi feita utilizando o link [AutoFormat] no painel de propriedades do [DataList]. As suas propriedades foram definidas diretamente no código de apresentação. Este 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("thème")%></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 da [DataList]
<ItemTemplate>
define o item atual na [DataList] — aqui colocámos uma tabela com duas colunas e uma linha. A primeira célula conterá o nome do tema ao qual o utilizador pretende subscrever-se, e a outra conterá o botão [Cancelar subscrição], que permite ao utilizador cancelar a sua seleção.

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

A lógica de controlo está distribuída entre os ficheiros [global.asax] e [main.aspx]. O ficheiro [global.asax] é o seguinte:

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

O ficheiro [global.asax.vb] associado 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)
        ' initialize the data source
        Dim thèmes As New DataTable
        ' columns
        With thèmes.Columns
            .Add("id", GetType(System.Int32))
            .Add("thème", GetType(System.String))
            .Add("description", GetType(System.String))
        End With
        ' column id will be primary key
        thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
        ' lines
        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
        ' put the data source in the application
        Application("thèmes") = thèmes
    End Sub
 
    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' start of session - create an empty subscriptions table
        Dim dtAbonnements As New DataTable
        With dtAbonnements
            ' the columns
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            ' the primary key
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
        ' the table is placed in the session
        Session.Item("abonnements") = dtAbonnements
    End Sub
 
End Class

O procedimento [Application_Start], executado quando a aplicação recebe o seu primeiro pedido, constrói a [DataTable] de tópicos que podem ser subscritos. É construída arbitrariamente utilizando código. Recorde a técnica que já abordámos. Construímos na seguinte ordem:

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

Aqui adicionámos uma chave primária. A coluna «id» serve como chave primária. Existem várias formas de expressar isto. Aqui, utilizámos uma restrição. Em SQL, uma restrição é uma regra que os dados numa linha devem seguir para que essa linha seja adicionada a uma tabela. Existem todo o tipo 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, consistir numa expressão que envolva valores de várias colunas. [DataTable].Constraints é a coleção de restrições para uma determinada tabela. Para adicionar uma restrição, usamos o método [DataTable.Constraints.Add]. Este método tem várias assinaturas. Aqui, usámos o método [Add(Byval name as String, Byval column as DataColumn, Byval primaryKey as Boolean)]:

name
nome da restrição - pode ser qualquer coisa
column
coluna que será a chave primária - do tipo [DataColumn]
primaryKey
deve ser definido como [true] para tornar [coluna] uma chave primária. Se [primaryKey=false], apenas a restrição de unicidade se aplica a [coluna]

Para tornar a coluna denominada "id" a chave primária da tabela [dtAbonnements], escrevemos:

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

O procedimento [Session_Start], executado quando a aplicação recebe o primeiro pedido de um cliente. É utilizado para criar objetos específicos para cada cliente que devem persistir ao longo dos vários pedidos do cliente. O procedimento constrói a [DataTable] das subscrições do cliente. Apenas a sua estrutura é construída, uma vez que esta tabela está inicialmente vazia. Será preenchida à medida que forem feitos pedidos. Também aqui, a coluna "id" serve como chave primária. Utilizámos uma técnica diferente para declarar esta restrição:

[DataTable].PrimaryKey
é a matriz de colunas que compõem a chave primária — aqui declarámos uma matriz de um elemento: a coluna denominada «id»

Quando a solicitação do cliente chega ao controlador [main.aspx], ambos os 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 principal função do procedimento [Page_Load] é:

  • recuperar as duas tabelas [dtThèmes] e [dtAbonnements], que se encontram na aplicação e na sessão, respetivamente, para as disponibilizar a todos os métodos da página
  • vincular os dados destas duas fontes aos seus respetivos contentores. Isto é feito apenas durante o primeiro pedido. Para pedidos subsequentes, a vinculação não precisa de ser realizada sistematicamente e, quando for necessário, poderá por vezes ser necessário aguardar que ocorra um evento após [Page_Load] para a realizar.

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


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' retrieve data sources
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' data link
        If Not IsPostBack Then
            liaisons()
        End If
        ' we hide certain information
        panelInfo.Visible = False
    End Sub
 
    Private Sub liaisons()
        'link the data source to the [datagrid] component
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        ' link the data source to the [datalist] component
        With dlAbonnements
            .DataSource = dtAbonnements
            .DataKeyField = "id"
        End With
        ' assign data to components
        Page.DataBind()
    End Sub

No procedimento [Bindings], utilizamos a propriedade [DataKeyField] dos componentes [DataList] e [DataGrid] para definir a coluna na fonte de dados que será utilizada para identificar de forma única as linhas nos contentores. Normalmente, esta coluna é a chave primária da fonte de dados, mas isso não é obrigatório. Basta que a coluna não contenha duplicados nem valores vazios. Para o contentor [dgThemes], a coluna "id" da fonte [dtThemes] servirá como chave primária e, para o contentor [dlSubscriptions], será a coluna "id" da fonte [dtSubscriptions]. Não é necessário que a coluna que serve como chave primária do contentor seja exibida pelo próprio contentor. Aqui, nenhum dos contentores exibe a coluna da chave primária. A vantagem de um contentor ter uma chave primária é que permite recuperar facilmente, a partir da fonte de dados, a informação associada à linha do contentor na qual ocorreu um evento. Na verdade, é comum que, a partir de uma linha do contentor onde ocorreu um evento, seja necessário realizar uma ação na linha correspondente na fonte de dados associada. A chave primária facilita esta tarefa.

A paginação do [DataGrid] é tratada da forma padrão:


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

As ações nos links [Mais informações] e [Subscrever] são tratadas pelo procedimento [dgThemes_ItemCommand]:


    Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
        ' evt on a [datagrid] line
        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
        ' link
        liaisons()
    End Sub

Utilizamos o facto de ambos os links possuírem um atributo [CommandName] para os distinguir. Dependendo do valor deste atributo, chamamos o procedimento [info] ou [subscribe], passando em ambos os casos a chave "id" associada ao elemento [DataGrid] onde o evento ocorreu. Com esta informação, o procedimento [info] irá apresentar os detalhes do tema selecionado pelo utilizador:

    Private Sub infos(ByVal id As String)
        ' information on the theme of key id
        ' we retrieve the line from the [datatable] corresponding to the key
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
            ' display info
            lblThème.Text = CType(ligne("thème"), String)
            lblDescription.Text = CType(ligne("description"), String)
            panelInfo.Visible = True
        End If
    End Sub

Uma vez que a tabela [dtThèmes] possui uma chave primária, o método [dtThèmes.Rows.Find("P")] permite-nos encontrar a linha com a chave primária P. Se for encontrada, obtemos um objeto [DataRow]. Aqui, precisamos de encontrar a linha com a chave primária [id], onde [id] é passado como parâmetro. Se a linha for encontrada, colocamos as informações [theme] e [description] dessa linha no painel de informações, que depois tornamos visível.

O procedimento [subscribe(id)] deve adicionar o tema com a chave [id] à lista de subscrições. O seu código é o seguinte:


    Private Sub abonner(ByVal id As String)
        ' id key theme subscription
        ' we retrieve the line from the [datatable] corresponding to the key
        Dim ligne As DataRow
        ligne = dtThèmes.Rows.Find(id)
        If Not ligne Is Nothing Then
            ' check if you are not already a subscriber
            Dim abonnement As DataRow
            abonnement = dtAbonnements.Rows.Find(id)
            If Not abonnement Is Nothing Then
                ' report the error
                lblInfo.Text = "Vous êtes déjà abonné au thème [" + ligne("thème") + "]"
            Else
                ' add the theme to the subscriptions
                abonnement = dtAbonnements.NewRow
                abonnement("id") = id
                abonnement("thème") = ligne("thème")
                dtAbonnements.Rows.Add(abonnement)
                ' we make the connections
                liaisons()
            End If
        End If
    End Sub

Na lista de temas [dtThemes], procuramos primeiro a linha com a chave [id]. Se encontrada, verificamos se este tema já não está presente na lista de subscrições para evitar adicioná-lo duas vezes. Se estiver, exibimos uma mensagem de erro. Caso contrário, adicionamos uma nova subscrição à tabela [dtSubscriptions] e ligamos os componentes da lista de dados às suas respetivas fontes.

Quando o utilizador clica num botão [Remover], um item deve ser eliminado da tabela [dtAbonnements]. Isto é feito através do seguinte procedimento:


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' withdraw a subscription
        Dim commande As String = e.CommandName
        If commande = "retirer" Then
            ' we remove the [datatable] subscription
            With dtAbonnements.Rows
                .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
            End With
            ' links
            liaisons()
        End If
    End Sub

Primeiro, verificamos a propriedade [CommandName] do elemento que desencadeou o evento. Na verdade, isto é bastante desnecessário, uma vez que o botão [Remove] é o único controlo capaz de gerar um evento no componente [DataList]. Não há, portanto, qualquer ambiguidade. Para eliminar uma linha de um objeto [DataTable], utilizamos o método [DataList.Remove(DataRow)], que remove da tabela a linha [DataRow] passada como parâmetro. Esta linha é encontrada pelo método [DataList.Find], ao qual passamos a chave primária da linha que estamos a procurar. Assim que a linha for eliminada, ligamos os dados aos componentes

9.4. Gerir um [DataList] paginado

Vamos revisitar o exemplo anterior para paginar o componente [DataList] que representa a lista de subscrições do utilizador. Ao contrário do componente [DataGrid], o componente [DataList] não oferece nenhuma funcionalidade de paginação incorporada. Veremos que implementar a paginação é complexo, o que nos ajudará a apreciar o valor da paginação automática do [DataGrid].

9.4.1. Como funciona

A única diferença é a paginação do [DataList]. Cada página exibirá duas subscrições. Se o utilizador tiver cinco subscrições, haverá três páginas. A primeira página terá este aspeto:

Image

A segunda página é acedida através do link [Next]:

Image

A terceira página:

Image

Note que os links [Anterior] e [Seguinte] só são visíveis se houver, respetivamente, uma página anterior e uma página seguinte à página atual.

9.4.2. Código de apresentação

Os links [Anterior] e [Seguinte] são criados adicionando uma tag <FooterTemplate> à [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 controlo

O ficheiro associado [global.asax.vb] é alterado da seguinte forma:

...

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)
        ' start of session - create an empty subscriptions table
        Dim dtAbonnements As New DataTable
        With dtAbonnements
            ' the columns
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            ' the primary key
            .PrimaryKey = New DataColumn() {.Columns("id")}
        End With
        ' the table is placed in the session
        Session.Item("abonnements") = dtAbonnements
        ' the current page is page 0
        Session.Item("pAC") = 0
        ' the number of subscriptions on this page is 0
        Session.Item("nbAC") = 0
    End Sub

Para além da tabela de subscrições [dtAbonnements], são armazenadas na sessão duas outras informações:

pAC
do tipo [Integer] — este é o número da página atual exibida durante a última solicitação
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 nessa página são zero.

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

....

Public Class main
    Inherits System.Web.UI.Page

....

    ' application data
    Protected dtThèmes As DataTable
    Protected dtAbonnements As DataTable
    Protected dtPA As DataTable    ' subscription page displayed
    Protected Const nbAP As Integer = 2    ' subscriptions per page
    Protected pAC As Integer    ' current subscription page
    Protected nbAC As Integer    ' number of subscriptions in current page

    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

Aqui, definimos novos dados relacionados com a paginação da subscrição:


    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 destas informações são armazenadas na sessão e são recuperadas com cada pedido no procedimento [Page_Load]:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' retrieve data sources
        dtThèmes = CType(Application("thèmes"), DataTable)
        dtAbonnements = CType(Session("abonnements"), DataTable)
        ' and subscription display information
        pAC = CType(Session("pAC"), Integer)
        nbAC = CType(Session("nbAC"), Integer)
        ' data link
        If Not IsPostBack Then
            ' display an empty subscription list
            terminer()
        End If
        ' we hide certain information
        panelInfo.Visible = False
    End Sub

As informações recuperadas [pAC] e [nbAC] são detalhes sobre a página de subscrições apresentada durante o pedido anterior:

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

O método [terminer] vincula os componentes às suas fontes de dados, tal como o método [liaisons] fez na aplicação anterior. A novidade aqui é a vinculação da [DataList] à tabela [dtPA], que é a página de subscrições a ser apresentada:


    Private Sub terminer()
        ' link the data source to the [datagrid] component
        With dgThèmes
            .DataSource = dtThèmes
            .DataKeyField = "id"
        End With
        ' link the subscriptions page to the [datalist] component, taking into account the current page pAC
        changePAC()
        ' page p is displayed
        With dlAbonnements
            .DataSource = dtPA
            .DataKeyField = "id"
        End With
        ' assign data to components
        Page.DataBind()
        ' management of [previous] and [next] links in the [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)
        ' save current page information in the session
        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 por métodos que precisam alterar este número da página atual. O método [changePAC] é responsável pela construção da tabela [dtPA], que será ligada ao componente [dlAbonnements].
  • O método [setLiens] é responsável por exibir ou ocultar os links [Anterior] e [Seguinte], dependendo se a página atual [pAC] exibida é precedida e seguida por uma página. Possui quatro parâmetros:
    • [dlAbonnements]: o controlo [DataList] cuja árvore de controlo iremos explorar para encontrar os dois links. Embora estes dois links estejam localizados num local específico — o rodapé do [DataList] — não parece haver uma forma simples de os referenciar diretamente. De qualquer forma, não foi encontrada nenhuma aqui.
    • [blPrecedent]: um valor booleano a atribuir à propriedade [visible] do link [Precedent] — é verdadeiro se a página atual não for 0
    • [blNext]: um valor booleano a ser atribuído à propriedade [visible] do link [Next] — é 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 esta contagem atingir dois, o método termina.
  • As informações [pAC] e [nbAC] são guardadas na sessão para o próximo pedido.

O método [changePAC] constrói a tabela [dtPA], que será ligada ao componente [dlAbonnements]. Faz-o com base no número [pAC] da página atual a ser exibida. A tabela [dtPA] deve exibir determinadas linhas da tabela de subscrições [dtAbonnements]. Lembre-se de que esta tabela é armazenada na sessão e atualizada (aumentada ou diminuída) à medida que as solicitações são feitas. Começamos por definir o intervalo [first,last] de números de linha na tabela [dtAbonnements] que a tabela [dtPA] deve exibir:


    Private Sub changePAC()
        ' makes the pAC page the current subscription page
        ' datalist] page management
        Dim nbAbonnements = dtAbonnements.Rows.Count
        Dim dernièrePage = (nbAbonnements - 1) \ nbAP
        ' first and last subscription
        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

Depois de feito isto, podemos criar a tabela [dtPA]. Primeiro, definimos a sua estrutura [id, theme] e, em seguida, preenchemo-la copiando as linhas de [dtSubscriptions] cujos números se encontram no intervalo [first, last] calculado anteriormente.


        ' creation of the datatable dtpa
        dtPA = New DataTable
        With dtPA
            ' the columns
            .Columns.Add("id", GetType(String))
            .Columns.Add("thème", GetType(String))
            ' the primary key
            .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

No final do método [changePAC], a tabela [dtPA] foi criada e pode ser associada ao componente [DataList]. Isto é feito no método [terminer]. Neste mesmo método, o procedimento [setLiens] é utilizado para definir o estado dos links [Anterior] e [Seguinte] no [DataList]. O código para este procedimento é o seguinte:


    Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean, ByRef nbLiensTrouvés As Integer)
        ' search for [previous] and [next] links
        ' in the [datalist] control tree
        ' have all the links been found?
        If nbLiensTrouvés = 2 Then Exit Sub
        ' review of child controls
        Dim c As Control
        For Each c In ctl.Controls
            ' we work deep down first - the links are at the bottom of the tree
            setLiens(c, blPrec, blSuivant, nbLiensTrouvés)
            ' link [Previous] ?
            If c.ID = "lnkPrecedent" Then
                CType(c, LinkButton).Visible = blPrec
                nbLiensTrouvés += 1
            End If
            ' next] link ?
            If c.ID = "lnkSuivant" Then
                CType(c, LinkButton).Visible = blSuivant
                nbLiensTrouvés += 1
            End If
        Next
    End Sub

O procedimento é recursivo. Primeiro, procura entre os controlos filhos do componente [dlAbonnements] por componentes denominados [lnkPrecedent] e [lnkSuivant], que são os IDs dos dois links de paginação. Inicia a pesquisa a partir da parte inferior da árvore de controlos, pois é aí que se encontram. Assim que um link é encontrado, o contador [nbLiensTrouvés] é incrementado e a propriedade [visible] do link é definida com um valor passado como parâmetro ao procedimento. Assim que ambos os links forem encontrados, a árvore de controlos deixa de ser percorrida e o procedimento recursivo termina.

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

    Private Sub abonner(ByVal id As String)
        ' id key theme subscription
..
            ' add the theme to the subscriptions
..
            ' update current page number - it's now the last page
            pAC = (dtAbonnements.Rows.Count - 1) \ nbAP
            ' data links
            terminer()
        End If
    End Sub

Após adicionar uma subscrição, esta aparece no final da lista de subscrições. Por isso, a vista é definida para a última página de subscrições, para que o utilizador possa ver a adição que foi feita.


    Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
        ' withdraw a subscription
        Dim commande As String = e.CommandName
        Select Case commande
            Case "retirer"
                ' we remove the [datatable] subscription
                With dtAbonnements.Rows
                    .Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
                End With
                ' is it necessary to change the current page?
                nbAC -= 1
                If nbAC = 0 Then pAC -= 1
            Case "precedent"
                ' change current page
                pAC -= 1
            Case "suivant"
                ' change current page
                pAC += 1
        End Select
        ' data links
        terminer()
    End Sub
  • [nbAC] é o número de linhas exibidas na página atual antes de uma assinatura ser cancelada. Se o novo número de linhas na página for igual a 0, o número da página atual [pAC] é diminuído em um.
  • Se o link [Anterior] for clicado, o número da página atual [pAC] é diminuído em um.
  • Quando se clica no link [Seguinte], o número da página atual [pAC] é incrementado em um.

Os restantes procedimentos permanecem os mesmos de antes.

9.4.4. Conclusão

Mostrámos neste exemplo que podemos paginar um componente [DataList]. Esta paginação é complexa, sendo preferível recorrer à paginação automática do componente [DataGrid] sempre que possível. Este exemplo também nos mostrou como aceder aos componentes presentes no rodapé do componente [DataList].

9.5. Classe para aceder a uma base de dados de produtos

Estamos novamente a centrar-nos na base de dados ACCESS [products] que já utilizámos. Recorde-se que esta possui uma única tabela denominada [list] com a seguinte estrutura:

Iremos criar uma classe de acesso para a tabela [list] que nos permitirá ler e atualizá-la. Iremos também criar um cliente de consola que utilizará a classe anterior para atualizar a tabela. Em seguida, iremos criar um cliente web para realizar a mesma tarefa.

9.5.1. A classe ProductException

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


    Public Class ExceptionProduits
        Inherits Exception
 
        ' exception error msg
        Private _erreurs As ArrayList
 
        ' manufacturer
        Public Sub New(ByVal erreurs As ArrayList)
            Me._erreurs = erreurs
        End Sub
 
        ' property
        Public ReadOnly Property erreurs() As ArrayList
            Get
                Return _erreurs
            End Get
        End Property
    End Class

9.5.2. A estrutura [sProduct]

A estrutura [product] representa um produto [id, nome, preço]:


    ' structure sProduit
    Public Structure sProduit
        ' the fields
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
 
        ' property id
        Public Property id() As Integer
            Get
                Return _id
            End Get
            Set(ByVal Value As Integer)
                _id = Value
            End Set
        End Property
 
        ' property name
        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
 
        ' property price
        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 [name] e [price].

9.5.3. A Classe Produtos

A classe [Products] é a classe que nos permitirá atualizar a tabela [list] na base de dados de produtos. A sua estrutura é a seguinte:

    Public Class produits
        ' instance data

        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 partilhados pelos vários 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
conexão
A ligação à base de dados será aberta para executar um comando SQL e, em seguida, fechada imediatamente
selectText
Consulta SQL [select] que recupera toda a tabela [list]
insertText
consulta que permite a inserção de uma linha (nome, preço) na tabela [lista]. Note que o campo [id] não é especificado. Isto porque o SGBD autoincrementa este campo, pelo que não precisamos de o especificar.
updateText
Consulta para atualizar os campos (nome, preço) da linha na tabela [list] com a chave [id]
deleteText
Consulta que elimina a linha da tabela [list] com a chave [id]
selectCommand
Objeto [OleDbCommand] que executa a consulta [selectText] na ligação [connection]
updateCommand
Objeto [OleDbCommand] que executa a consulta [updateText] na ligação [connection]
insertCommand
Objeto [OleDbCommand] que executa a consulta [insertText] na ligação [connection]
deleteCommand
Objeto [OleDbCommand] a executar a consulta [deleteText] na ligação [connection]
adapter
objeto utilizado para recuperar o resultado da execução de [selectCommand] num objeto [DataSet]

O construtor

O construtor recebe um único parâmetro [OLEDBConnectionString], que é a cadeia de ligação que especifica a base de dados a utilizar. A partir daí, são preparados os quatro comandos para consultar e atualizar a tabela, bem como o adaptador. Trata-se apenas de uma etapa de preparação, não sendo estabelecida qualquer ligação.


        Public Sub New(ByVal chaineConnexionOLEDB As String)
            ' preparing the connection
            connexion = New OleDbConnection(chaineConnexionOLEDB)
            ' prepare query orders
            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
            ' prepare the data access adapter
            adaptateur.SelectCommand = selectCommand
        End Sub

O método getProducts

Este método recupera o conteúdo da tabela [List] para um objeto [DataTable]. O seu código é o seguinte:


        Public Function getProduits() As DataTable
            ' we put the [list] table in a [dataset]
            Dim contenu As New DataSet
            ' create a DataAdapter object to read data from source 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
            ' we return the result
            Return contenu.Tables(0)
        End Function

O trabalho é realizado pelas duas instruções seguintes:


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

O método [FillSchema] define a estrutura (colunas, restrições, relações) do [DataSet] contido com base na estrutura da base de dados referenciada por [adapter.Connection]. Isto permite-nos recuperar a estrutura da tabela [list], incluindo a sua chave primária. A operação [Fill] que se segue preenche o [DataSet] contido com as linhas da tabela [list]. Com esta única operação, teríamos obtido os dados e a estrutura, mas não a chave primária. No entanto, isto será útil para atualizar a tabela [list] na memória. Aqui, tal como nos outros métodos, tratamos quaisquer erros utilizando a classe [ProductExceptions] para obter uma lista (ArrayList) de erros em vez de um único erro. O método [getProducts] devolve a tabela [list] como um objeto [DataTable].

O método addProducts

Este método permite adicionar uma linha (id, nome, preço) à tabela [list]. Esta informação é-lhe fornecida sob a forma de uma estrutura [sProduct] com os campos [id, nome, preço]. O código do método é o seguinte:


        Public Sub ajouterProduit(ByVal produit As sProduit)
            ' add a product [name,price]
            ' we prepare the parameters for the addition
            With insertCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
            End With
            ' we add
            Try
                ' opening connection
                connexion.Open()
                ' order execution
                insertCommand.ExecuteNonQuery()
            Catch ex As Exception
                ' pb
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de l'ajout : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' locking connection
                connexion.Close()
            End Try
        End Sub

Os campos da estrutura [product] são passados como parâmetros para o comando [insertCommand]. Vamos rever a configuração atual deste comando (ver construtor):


 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 reais. Isto é feito utilizando a coleção [Parameters] da classe [OleDbCommand]. Esta coleção contém elementos do tipo [OleDbParameter] que definem os parâmetros reais que devem substituir os parâmetros formais ?. Uma vez que estes não têm nome, o índice dos parâmetros reais é utilizado para determinar qual o parâmetro formal que corresponde a um determinado parâmetro real. Aqui, o parâmetro real #i na coleção [Parameters] substituirá o parâmetro formal ? #i. Para criar um parâmetro real do tipo [OleDbParameter], usamos o construtor [OleDbParameter (Byval name as String, Byval value as Object)], que define o nome e o valor do parâmetro real. O nome pode ser qualquer coisa. Além disso, não será usado aqui. Os dois parâmetros da instrução SQL [insert] recebem como valores os dos campos [name, price] da estrutura [product]. Uma vez feito isto, a inserção é realizada pela instrução [insertCommand.ExecuteNonQuery].

O método modifyProducts

Este método permite-lhe modificar uma linha na tabela [list]. A informação necessária é fornecida na estrutura [sProduct], que contém os campos [id, name, price].


        Public Sub modifierProduit(ByVal produit As sProduit)
            ' modify a product [id,name,price]
            ' prepare update parameters
            With updateCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("nom", produit.nom))
                .Add(New OleDbParameter("prix", produit.prix))
                .Add(New OleDbParameter("id", produit.id))
            End With
            ' make the change
            Try
                ' opening connection
                connexion.Open()
                ' order execution
                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
                ' pb
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la modification : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' locking connection
                connexion.Close()
            End Try
        End Sub

O código é quase idêntico ao do método [addProducts], exceto que o [OleDbCommand] relevante é [updateCommand] em vez de [insertCommand].

O método [deleteProducts]

Este método elimina a linha da tabela [list] com a chave [id] passada como parâmetro. O código é o seguinte:


        Public Sub supprimerProduit(ByVal id As Integer)
            ' deletes key product [id]
            ' prepare the parameters for the deletion
            With deleteCommand.Parameters
                .Clear()
                .Add(New OleDbParameter("id", id))
            End With
            ' we delete
            Try
                ' opening connection
                connexion.Open()
                ' order execution
                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
                ' pb
                Dim erreursCommande As New ArrayList
                erreursCommande.Add(String.Format("Erreur lors de la suppression : {0}", ex.Message))
                Throw New ExceptionProduits(erreursCommande)
            Finally
                ' locking connection
                connexion.Close()
            End Try
        End Sub

A abordagem é a mesma dos métodos anteriores.

9.5.4. Testar a classe [products]

Um programa de teste baseado na consola [testproducts.vb] poderia ter o seguinte aspeto:

Option Explicit On 
Option Strict On

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

Namespace st.istia.univangers.fr

    ' test pg
    Module testproduits
        Dim contenu As DataTable

        Sub Main(ByVal arguments() As String)
            ' displays the contents of a product table
            ' the table is in a ACCESS database whose pg receives the file name
            Const syntaxe1 As String = "pg bdACCESS"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If
            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
            ' creation of a product object
            Dim objProduits As produits = New produits(chaineConnexion)
            ' display all products
            afficheProduits(objProduits)
            ' insert a product
            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
            ' display all products
            afficheProduits(objProduits)
            ' retrieve the id of the ahjouté product
            produit.id = CType(contenu.Rows(contenu.Rows.Count - 1)("id"), Integer)
            ' modify the added product
            produit.prix = 200
            Try
                objProduits.modifierProduit(produit)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
            ' display all products
            afficheProduits(objProduits)
            ' remove the added product
            Try
                objProduits.supprimerProduit(produit.id)
            Catch ex As ExceptionProduits
                afficheErreurs(ex.erreurs)
            End Try
            ' display all products
            afficheProduits(objProduits)
        End Sub

        Sub afficheProduits(ByRef objProduits As produits)
            ' retrieve the product table from a 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
                ' table line i
                Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
                "," + lignes(i).Item("prix").ToString)
            Next
            ' stops console flow
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub

        Sub afficheErreurs(ByRef erreurs As ArrayList)
            ' displays errors on the 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
            ' stops console flow
            Console.WriteLine("...")
            Console.ReadLine()
        End Sub
    End Module
End Namespace

Compile os dois ficheiros 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

Em seguida, 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
...

Convidamos o leitor a comparar a saída de ecrã acima com o código do programa de teste.

9.6. Aplicação web para atualizar a tabela de produtos em cache

9.6.1. Introdução

Estamos agora a escrever uma aplicação web para atualizar a tabela de produtos (adicionar, eliminar, modificar). A tabela atualizada permanecerá na memória num objeto [DataTable] e será partilhada por todos os utilizadores. Queremos destacar dois pontos:

  • a gestão de um objeto [DataTable]
  • os desafios das atualizações simultâneas da tabela por vários utilizadores.

A arquitetura MVC da aplicação será a seguinte:

9.6.2. Funcionalidades e vistas

A vista inicial da aplicação é a seguinte:

Image

 

Esta vista, denominada [form], permite ao utilizador aplicar uma condição de filtro aos produtos e definir o número de produtos por página que deseja ver.

N.º
nome
Tipo
função
1
lnkFilter
LinkButton
exibe a vista [Form] utilizada para definir a condição do filtro
2
lnkUpdate
LinkButton
exibe a vista [Produtos], que é utilizada para visualizar e atualizar a tabela de produtos (editar e eliminar)
3
lnkAdd
LinkButton
exibe a vista [Adicionar], que é utilizada para adicionar um produto
4
painel
FormView
a vista [Formulário]
5
txtFilter
Caixa de Texto
a condição de filtro
6
txtPages
Caixa de Texto
número de produtos por página
7
rfvLines
Validador de campo obrigatório
verifica se existe um valor em [txtPages]
8
rvLines
RangeValidator
verifica se txtPages está no intervalo [3,10]
9
btnExecute
 
Botão [submit] que exibe a vista [products] filtrada pela condição (5)
10
lblInfo1
Label
Texto informativo em caso de erros

Por exemplo, se a vista [formulário] for preenchida da seguinte forma:

Image

obtém-se o seguinte resultado:

N.º
nome
tipo
função
1
ProductView
painel
 
2
rdAscendente
rdDescendente
Botão de opção
permite ao utilizador definir a ordem de classificação pretendida ao clicar no título de uma das colunas [nome], [preço]. Os dois botões fazem parte do grupo [rdSort].
3
DataGrid1
DataGrid
Uma grelha que apresenta uma vista filtrada da tabela de produtos. O filtro é aquele definido pela vista [form]. Também temos .AllowPaging=true, .AllowSorting=true
4
DataGrid2
DataGrid
exibe a tabela de produtos na íntegra - permite o acompanhamento de atualizações
5
LblInfo2
Label
texto informativo, especialmente em caso de erros
6
DataGrid3
DataGrid
irá apresentar os produtos eliminados na tabela de produtos
7
DataGrid4
DataGrid
exibirá os produtos modificados na tabela de produtos
8
DataGrid5
DataGrid
exibirá os produtos adicionados à tabela de produtos

Existem cinco contentores de dados nesta página. Todos eles apresentam a mesma tabela [dtProduits] através de um [DataView] diferente. Uma vista representa um subconjunto das linhas da tabela de origem da vista. Este subconjunto é criado utilizando as propriedades [RowFilter] e [RowStateFilter] da classe [DataView]:

  • [RowFilter] permite definir um filtro nas linhas, como [price>30] acima. Este tipo de filtragem será utilizado pelo [DataGrid1].
  • [RowStateFilter] permite definir um filtro com base no estado da linha da tabela. Isto indica o estado da linha em relação ao seu estado original quando a vista da tabela foi criada. Aqui, a tabela [dtProduits] provém de uma base de dados. Inicialmente, todas as suas linhas terão um estado igual a [Original] para indicar que estas são as linhas originais da tabela. Este estado pode então mudar e assumir valores diferentes, alguns dos quais estão listados abaixo:
    • [Added]: a linha foi adicionada — não fazia parte da tabela original
    • [Deleted]: a linha foi eliminada — ainda se encontra na tabela, mas está «marcada» como «a eliminar»
    • [Modified]: a linha foi modificada

[RowStateFilter] permite-lhe exibir as linhas da tabela que têm um determinado estado:

  • (continuação)
    • [DataViewRowState.Added]: apenas as linhas adicionadas são exibidas. São apresentadas com os seus valores atuais.
    • [DataViewRowState.ModifiedOriginal]: apenas as linhas modificadas são exibidas. São apresentadas com os seus valores originais.
    • [DataViewRowState.ModifiedCurrent]: apenas as linhas modificadas são apresentadas. São apresentadas com os seus valores atuais.
    • [DataViewRowState.Deleted]: apenas as linhas eliminadas são apresentadas. São apresentadas com os seus valores originais.
    • [DataViewRowState.CurrentRows]: são apresentadas as linhas não eliminadas. São apresentadas com os seus valores atuais.

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

DataGrid1
sem filtro no estado da linha
 
DataGrid2
DataViewRowState.CurrentRows
exibe o estado atual da tabela de produtos
DataGrid3
DataViewRowState.Deleted
exibe as linhas eliminadas da tabela de produtos
DataGrid4
DataViewRowState.ModifiedOriginal
exibe as linhas modificadas da tabela de produtos juntamente com os seus valores originais
DataGrid5
DataViewRowState.Added
exibe as linhas adicionadas à tabela de produtos original

Os contentores [DataGrid1-5] permitir-nos-ão acompanhar as atualizações na tabela [dtProduits]. O componente [DataGrid1] permite a modificação e eliminação de um produto. Veremos como a configuração do componente permite esta atualização. Também é paginado e ordenado. Estas duas funcionalidades já foram abordadas num exemplo anterior.

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

N.º
nome
tipo
função
1
AddView
painel
 
2
txtName
TextBox
nome do produto
3
rfvName
Validador de campo obrigatório
verifica se existe um valor em [txtName]
4
txtPrice
TextBox
preço do produto
5
rfvPrice
Validador de campo obrigatório
verifica se existe um valor em [txtPrice]
6
cvPrice
CompareValidator
verifica se o preço é >= 0
7
btnAdd
Botão
Botão [submit] para adicionar o produto
8
lblInfo3
Rótulo
Texto informativo sobre o resultado da operação Adicionar

A base de dados [products] pode não estar disponível quando a aplicação é iniciada. Neste caso, é apresentada ao utilizador a vista [errors]:

N.º
nome
tipo
função
1
visualização de erros
painel
 
2
rptErrors
Repetidor
lista de erros

9.6.3. Configuração dos contentores de dados

Os cinco contentores [DataGrid] foram configurados no [WebMatrix]. Foram formatados (cores e bordas) utilizando o link [Configuração Automática] no seu painel de propriedades. O contentor [DataGrid1] foi configurado utilizando o link [Gerador de Propriedades] no mesmo painel. A ordenação foi ativada (separador [Geral]):

Image

As colunas não são geradas automaticamente, ao contrário do que acontece nos outros quatro contentores. Foram definidas manualmente utilizando o assistente:

Image

Primeiro, foram criadas duas colunas [Related Column] denominadas [name] e [price]. Estas foram associadas, respetivamente, aos campos [name] e [price] da fonte de dados que os contentores irão apresentar. Aqui, por exemplo, está a configuração da coluna [name]:

Image

A expressão de ordenação é a expressão que deve ser colocada após a cláusula [order by] na instrução SQL [select] que será executada quando o utilizador clicar na coluna de cabeçalho [name] associada ao campo [name] do [DataGrid]. Aqui, introduzimos [nome] para que a cláusula de ordenação seja [order by name]. Veremos que iremos modificar isto para [order by name asc] ou [order by name desc], dependendo da ordem de ordenação escolhida pelo utilizador.

Criámos também duas colunas de botões:

Image

A coluna [Editar, Atualizar, Cancelar] permite-nos editar um produto, e a coluna [Eliminar] permite eliminá-lo. Cada uma destas colunas pode ser configurada. A coluna [Editar, Atualizar, Cancelar] oferece a seguinte configuração:

Image

Podemos ver que os textos dos botões podem ser modificados. No que diz respeito aos botões, temos a escolha entre links e botões (lista suspensa acima). Aqui, foram escolhidos links. A configuração da coluna [Eliminar] é semelhante. Além deste assistente de configuração, utilizámos diretamente a janela de propriedades [DataGrid] para definir a propriedade [DataKeyField], que especifica qual o campo na fonte de dados utilizado para indexar as linhas do [DataGrid]. Aqui, é utilizada a chave primária da tabela de produtos:

Image

Em última análise, esta 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, assim que tiver adquirido alguma experiência, poderá escrever todo ou parte do código acima diretamente.

Os outros contentores [DataGrid] têm a configuração padrão obtida através da geração automática das colunas [DataGrid] a partir das colunas da fonte de dados à qual estão associados.

O contentor [Repeater] é utilizado para apresentar uma lista de erros. A 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 apresenta o valor [Container.DataItem], ou seja, o valor correspondente da lista de dados. Este será do tipo [ArrayList] e representará uma lista de erros.

9.6.4. O código de apresentação da aplicação

Este encontra-se no ficheiro [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>

Tenha em atenção os seguintes pontos:

  • A página é composta por quatro contentores (painéis) [vueFormulaire, vueProduits, vueAjout, vueErreurs] que formarão as quatro vistas da aplicação.
  • Os botões ou links do tipo [submit] têm a propriedade [CausesValidation=false]. A propriedade [CausesValidation=true] aciona a execução de todas as verificações de validação na página. No entanto, neste caso, nem todas as verificações de validação precisam de ser realizadas ao mesmo tempo. Por exemplo, ao adicionar um item, não queremos que as verificações relativas ao número de linhas por página sejam executadas. Por isso, especificaremos nós próprios quais as verificações de validação que devem ser realizadas.

9.6.5. O código de controlo [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)
        ' retrieve configuration information
        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
        ' configuration errors?
        If erreurs.Count <> 0 Then
            ' errors are noted
            Application("erreurs") = erreurs
            ' we leave
            Exit Sub
        End If
        ' no configuration errors here
        ' create a product object
        Dim dtProduits As DataTable
        Try
            dtProduits = New produits(chaînedeConnexion).getProduits
        Catch ex As ExceptionProduits
            'there has been an error accessing the products, this is noted in the application
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            ' unhandled error
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            ' exit sub
        End Try
        ' no initialization errors here
        ' store the number of products per page
        Application("defaultProduitsPage") = defaultProduitsPage
        ' memorize the product table
        Application("dtProduits") = dtProduits
    End Sub
 
    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' init session variables
        If IsNothing(Application("erreurs")) Then
            ' view of the product table
            Session("dvProduits") = CType(Application("dtProduits"), DataTable).DefaultView
            ' products per page
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' current page displayed
            Session("pageCourante") = 0
        End If
    End Sub
End Class

Em [Application_Start], começamos por recuperar duas informações do ficheiro de configuração [web.config] da aplicação:

  • OLEDBStringConnection: a cadeia de ligação OLEDB à base de dados de produtos
  • defaultProductsPage: o número padrão de produtos por página exibidos

<?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 alguma destas duas informações estiver em falta, é gerada uma lista de erros e adicionada à aplicação. O mesmo se aplica se o parâmetro [defaultProduitsPage] existir, mas estiver incorreto. Se ambos os parâmetros esperados estiverem presentes e corretos, é criada uma tabela [dtProduits] e adicionada à aplicação. Esta tabela será utilizada e atualizada pelos vários clientes. A base de dados em si permanecerá inalterada. Abordaremos a atualização da base de dados numa aplicação futura. Esta tabela é construída a partir de uma instância da classe [products] discutida anteriormente e do seu método [getProducts]. A recuperação da tabela [dtProducts] pode falhar. Neste caso, sabemos que a classe [products] lança uma exceção do tipo [ProductException]. Esta é interceptada aqui, e a lista de erros associada é armazenada na aplicação sob a chave [errors]. A presença desta chave nas informações armazenadas na aplicação será verificada em cada pedido. Se for encontrada, a vista [errors] será enviada ao cliente.

Se a tabela [dtProducts] for partilhada por todos os clientes web, cada um deles terá, no entanto, a sua própria vista [dvProducts] da mesma. Isto porque cada cliente web pode definir um filtro na tabela [dtProducts], bem como uma ordem de ordenação. Esta informação específica de cada cliente web é armazenada na vista do cliente. Por conseguinte, esta vista é criada em [Session_Start] para que possa ser colocada na sessão específica de cada cliente. Utilizamos o atributo [DefaultView] da tabela [dtProduits] para ter uma vista predefinida da tabela. Inicialmente, não existe nem um filtro nem uma ordem de ordenação. Além disso, duas informações são também colocadas na sessão:

  • o número de produtos por página, identificado pela chave [nbProduitsPage]. No início da sessão, este número é igual ao valor predefinido definido no ficheiro de configuração.
  • o número da página de produtos atual. Inicialmente, esta é a primeira página.

9.6.6. O código do controlador [main.aspx.vb]

O esqueleto do controlador é o 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
 
    ' components page
    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
 
    ' data page
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
    Protected erreur As Boolean = False
 
    ' loading page
    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

    ' components page
    Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
...

    ' data page
    Protected dtProduits As DataTable
    Protected dvProduits As DataView
    Protected defaultProduitsPage As Integer
dtProducts
a tabela [DataTable] para produtos — partilhada por todos os clientes
dvProducts
o [DataView] para produtos — específico para cada cliente
defaultProductsPage
número padrão de produtos por página

9.6.8. O procedimento [Page_Load] para carregar a página

O código para o procedimento [Page_Load] é o seguinte:


    ' loading page
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' check for application errors
        If Not IsNothing(Application("erreurs")) Then
            ' the application has not initialized correctly
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        ' retrieve a reference from the product table
        dtProduits = CType(Application("dtProduits"), DataTable)
        ' product view
        dvProduits = CType(Session("dvProduits"), DataView)
        ' retrieve the number of products per page
        defaultProduitsPage = CType(Application("defaultProduitsPage"), Integer)
        ' cancels any update in progress
        DataGrid1.EditItemIndex = -1
        '1st request
        If Not IsPostBack Then
            ' the initial form is displayed
            txtPages.Text = defaultProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub
  • Primeiro, verificamos se a tabela de produtos foi carregada quando a aplicação foi iniciada. Caso contrário, exibimos a vista [errors] com as mensagens de erro apropriadas e, em seguida, saímos do procedimento após definir o booleano [error] como verdadeiro. Este indicador será verificado por determinados procedimentos.
  • Recuperamos a tabela de produtos [dtProduits] da aplicação e armazenamo-la na variável de instância [dtProduits] para que possa ser partilhada por todos os métodos da página.
  • Fazemos o mesmo com a vista [dvProducts] recuperada da sessão e com o número padrão de produtos por página recuperado da aplicação.
  • Se esta for a primeira solicitação do cliente, exibimos o formulário para definir condições de filtragem e paginação com uma paginação padrão de [defaultProduitsPage] produtos por página.
  • O modo de edição do componente [dataGrid1] é cancelado. Este componente possui um atributo [EditItemIndex], que é o índice do item atualmente a ser editado. Se [EditItemIndex]=-1, então nenhum item está atualmente a ser editado. Se [EditItemIndex]=i, então o item número i do componente [DataGrid] está atualmente a ser editado. É então apresentado de forma diferente dos outros itens no [dataGrid]:

Image

Acima, o item [Product8, 80] está no modo [edit]. Conforme mostrado acima, o utilizador pode confirmar a atualização usando o link [Update] ou cancelá-la usando o link [Cancel]. Também pode usar outros links que não têm nada a ver com o item atualmente a ser editado. Ao definir [DataGrid1.EditItemIndex=-1] sempre que a página é carregada, garantimos que o modo de edição do [DataGrid1] é sistematicamente cancelado. Só lhe atribuiremos um valor diferente se um link [Editar] tiver sido clicado. Faremos isso no procedimento que trata deste evento. Teremos as seguintes situações:

  1. O link [Editar] do item n.º 8 em [DataGrid1] foi clicado. [Page_Load] define primeiro [DataGrid1.EditItemIndex] como -1. Em seguida, o procedimento que trata do evento [Edit] definirá [DataGrid1.EditItemIndex] como 8. Por fim, quando a página for enviada de volta ao cliente, o item n.º 8 estará efetivamente no modo [edit] e aparecerá como mostrado acima.
  2. O utilizador edita o produto que está no modo [edit] e confirma a alteração utilizando o link [Update]. [Page_Load] define [DataGrid1.EditItemIndex] como -1. Em seguida, o procedimento que lida com o evento [Update] será executado, e o item n.º 8 em [dtProduits] será atualizado. Quando a página for enviada de volta ao cliente, nenhum item em [DataGrid1] estará no modo de edição (DataGrid.EditItemIndex=-1).
  3. Se um utilizador tiver iniciado a atualização de um produto, faria sentido que cancelasse essa atualização clicando em [Cancelar]. No entanto, nada o impede de clicar noutro link. Devemos, portanto, ter em conta este cenário. Neste caso, tal como nos anteriores, [Page_Load] começa por definir [DataGrid1.EditItemIndex] para -1, e depois o procedimento que trata do evento ocorrido será executado. Este não modificará a propriedade [DataGrid1.EditItemIndex], que permanecerá, portanto, em -1. Quando a página for enviada de volta ao cliente, nenhum item em [DataGrid1] estará no modo de edição.

Podemos ver que, ao definir [EditItemIndex] como -1 quando a página é carregada, evitamos ter de nos preocupar se o utilizador estava ou não no modo de edição quando clicou num link.

9.6.9. Exibição das vistas [errors], [form] e [add]

A vista [errors] é exibida quando a página carrega [Page_Load] se for detetado que a aplicação não inicializou corretamente. Isto é feito ligando o componente de dados [rptErrors] à lista de erros passada como parâmetros e tornando o contentor apropriado visível. Por fim, os três links [Filter, Update, Add] são ocultados porque a aplicação não pode ser utilizada em caso de erro.


    Private Sub afficheErreurs(ByVal erreurs As ArrayList)
        ' associate the error list with repeater rptErreurs
        With rptErreurs
            .DataSource = erreurs
            .DataBind()
        End With
        'inhibit options
        lnkAjout.Visible = False
        lnkMisajour.Visible = False
        lnkFiltre.Visible = False
        ' the [errors] view is displayed
        vueErreurs.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

As outras vistas são solicitadas com base nas opções apresentadas ao utilizador:

Image

A vista [Add] é apresentada quando o utilizador clica na ligação [Add] na página:


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        ' the [add] view is displayed
        afficheAjout()
    End Sub

    Private Sub afficheAjout()
        ' displays the add view
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

A vista [Form] é apresentada quando o utilizador clica na ligação [Filter] na página. Devemos então apresentar a vista que permite ao utilizador definir

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

É apresentado um formulário que recupera os valores armazenados na sessão:


    Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
        ' define filtering conditions
        txtFiltre.Text = dvProduits.RowFilter
        ' set pagination
        txtPages.Text = CType(Session("nbProduitsPage"), String)
        ' the filter form is displayed
        afficheFormulaire()
    End Sub

A condição de filtragem de uma vista é definida na sua propriedade [RowFilter]. Recorde-se que a vista filtrada e paginada se chama [dvProducts] e foi recuperada da sessão quando a página foi carregada [Page_Load]. A condição de filtragem é, portanto, recuperada de [dvProduits.RowFilter]. O número de produtos por página também é recuperado da sessão. Se o utilizador nunca tiver definido esta informação, este número é igual ao número padrão de produtos por página. Feito isto, apresentamos o formulário que permite ao utilizador definir estas duas informações:


    Private Sub afficheFormulaire()
        ' the [form] view is displayed
        vueFormulaire.Visible = True
        vueErreurs.Visible = False
        vueProduits.Visible = False
        vueAjout.Visible = False
    End Sub

Image

9.6.10. Validação da vista [form]

O clique no botão [Executar] acima é tratado pelo seguinte procedimento:


    Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
        ' valid page?
        rfvLignes.Validate()
        rvLignes.Validate()
        If Not rfvLignes.IsValid Or Not rvLignes.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If
        ' attaching filtered data to the grid
        Try
            dvProduits.RowFilter = txtFiltre.Text.Trim
        Catch ex As Exception
            lblinfo1.Text = "Erreur de filtrage (" + ex.Message + ")"
            afficheFormulaire()
            Exit Sub
        End Try
        ' store the number of products per page
        Session("nbProduitsPage") = txtPages.Text
        'and the current page
        Session("pageCourante") = 0
        ' all's well - data displayed
        afficheProduits(0, CType(txtPages.Text, Integer))
    End Sub

Primeiro, lembremo-nos de que a página tem dois componentes de validação:

N.º
nome
tipo
função
7
rfvLines
Validador de campo obrigatório
verifica se existe um valor em [txtPages]
8
rvLines
RangeValidator
verifica se txtPages está no intervalo [3,10]

O procedimento começa por executar o código de validação dos dois componentes acima, utilizando o seu método [Validate], e, em seguida, verifica o valor do seu atributo [IsValid]. Este atributo é definido como [true] apenas se os dados validados forem considerados válidos. Se as verificações de validade de qualquer um dos componentes falharem, a vista [form] é novamente apresentada para que o utilizador possa corrigir o(s) erro(s). A condição de filtro é aplicada ao atributo [dvProduits.RowFilter]. Aqui, pode ocorrer uma exceção se o utilizador tiver introduzido um critério de filtro incorreto, conforme mostrado abaixo:

Image

Neste caso, a vista [form] é exibida novamente. Se ambas as informações introduzidas estiverem corretas, então dois dados são colocados na sessão:

  • o número de produtos por página selecionado pelo utilizador
  • o número da página a ser exibida — inicialmente a página 0

Estas duas informações são utilizadas sempre que a vista [products] é apresentada.

9.6.11. Exibição da vista [products]

A vista [produtos] é a seguinte:

Image

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

nome
função
DataGrid1
Grelha que apresenta uma vista filtrada da tabela de produtos. O filtro é aquele definido pela vista [form]. Temos também .AllowPaging=true, .AllowSorting=true
DataGrid2
Exibe a tabela de produtos na íntegra — permite o acompanhamento de atualizações
DataGrid3
exibirá os produtos eliminados na tabela de produtos
DataGrid4
exibirá os produtos modificados na tabela de produtos
DataGrid5
exibirá os produtos adicionados na tabela de produtos

O procedimento [displayProducts] é responsável por exibir a vista anterior:


    Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
        ' attach the data to the two components [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()
        ' the [products] view is displayed
        vueProduits.Visible = True
        vueFormulaire.Visible = False
        vueErreurs.Visible = False
        vueAjout.Visible = False
        ' the current page is saved
        Session("pageCourante") = page
    End Sub

O procedimento aceita dois parâmetros:

  • o número da página [page] a apresentar em [DataGrid1]
  • o número de produtos [pageSize] por página

O [DataGrid] está ligado à tabela de produtos através de 5 vistas diferentes.

  • [DataGrid1] está ligado à vista paginada e ordenada [dvProduits].

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

É durante esta ligação do [DataGrid1] à sua fonte de dados que precisamos das duas informações passadas como parâmetros para o procedimento.

  • O [DataGrid2] está ligado a uma vista que apresenta todos os itens atuais da tabela [dtProduits]. Tal como o [DataGrid1], apresenta uma vista 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

Aqui estamos a utilizar a propriedade [RowStateFilter] da classe [DataView]. Uma linha numa tabela — ou, neste caso, numa vista — tem uma propriedade [RowState] que indica o estado da linha. Aqui estão alguns exemplos:

  • (continuação)
    • [Added]: a linha foi adicionada
    • [Modified]: a linha foi modificada
    • [Deleted]: a linha foi eliminada
    • [Unchanged]: a linha não foi alterada

Quando a tabela [dtProduits] foi inicialmente criada em [Application_Start], todas as suas linhas estavam no estado [Inalterado]. Este estado irá mudar à medida que os clientes web atualizam os dados:

  • (continuação)
    • Quando um cliente cria um novo produto, é adicionada uma linha à tabela [dtProduits]. Esta ficará no estado [Adicionado].
    • Quando um produto é modificado, o seu estado muda de [Inalterado] para [Modificado].
    • Quando um produto é eliminado, a linha não é fisicamente eliminada. Em vez disso, é marcada para eliminação e o seu estado muda para [Deleted]. É possível anular esta eliminação.

O atributo [RowStateFilter] da classe [DataView] permite-lhe filtrar uma vista com base no [RowState] das suas linhas. Os seus valores possíveis são os da enumeração [DataRowViewState]:

  • (continuação)
    • DataRowViewState.CurrentRows: As linhas que não foram eliminadas são apresentadas com os seus valores atuais
    • DataRowViewState.Added: As linhas adicionadas são apresentadas com os seus valores atuais
    • DataRowViewState.Deleted: As linhas eliminadas são apresentadas com os seus valores originais
    • DataRowViewState.ModifiedOriginal: As linhas modificadas são apresentadas com os seus valores originais
    • DataRowViewState.ModifiedCurrent: As linhas modificadas são apresentadas com os seus valores atuais

Uma linha modificada tem dois valores: o valor original, que é o valor que a linha tinha antes da primeira modificação, e o valor atual, que é o valor obtido após uma ou mais modificações. Ambos os valores são mantidos simultaneamente.

As DataGrids 2 a 5 apresentam uma vista 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 [dtProducts] é atualizada simultaneamente por diferentes clientes web, o que pode causar conflitos ao aceder à tabela. Quando um cliente apresenta as suas vistas da tabela [dtProducts], queremos impedir que o faça enquanto a tabela se encontra num estado instável, uma vez que está a ser modificada por outro cliente web. Portanto, sempre que um cliente precisar da tabela [dtProducts] para leitura, como acima, ou para gravação ao adicionar, modificar ou eliminar produtos, ele sincronizará com os outros clientes utilizando a seguinte sequência:

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

Poderá haver várias secções críticas no código da aplicação:

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

O mecanismo funciona da seguinte forma:

  1. Um ou mais clientes chegam à instrução [Application.Lock] na secção crítica 1. Trata-se de um distribuidor de tokens de entrada única. Um único cliente obtém este token. Chamemos-lhe C1.
  2. Até que o cliente C1 devolva o token através de [Application.Unlock], nenhum outro cliente pode entrar numa secção crítica controlada por [Application.Lock]. No exemplo acima, portanto, nenhum cliente pode entrar nas secções críticas 1 e 2.
  3. O cliente C1 executa [Application.Unlock] e, assim, devolve o token de entrada. Este token pode então ser atribuído a outro cliente. Os passos 1 a 3 são repetidos.

Com este mecanismo, garantimos que apenas um cliente tem acesso à tabela [dtProduits], seja para leitura ou escrita.

O link [Update] também exibe a vista [Products]:

Image

O procedimento associado é o seguinte:


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

Precisamos de exibir a vista [products]. Isto é feito utilizando o procedimento [displayProducts], que recebe dois parâmetros: o número da página atual a exibir e o número de produtos por página a exibir. Ambas estas informações encontram-se na sessão, possivelmente com os seus valores predefinidos, caso o utilizador nunca as tenha definido.

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

Já nos deparámos com os procedimentos que tratam da paginação e ordenação de um [DataGrid] noutro exemplo. Quando a página muda, exibimos a vista [products] passando o novo número da página como parâmetro para o procedimento [displayProducts].


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

O procedimento [DataGrid1_SortCommand] é executado quando o utilizador clica no cabeçalho de uma das colunas do [DataGrid1]. A expressão de ordenação deve então ser atribuída ao atributo [Sort] da vista [dvProduits] apresentada pelo [DataGrid1]. Esta expressão é sintaticamente equivalente à expressão de ordenação colocada após a cláusula [order by] na instrução SQL SELECT. O argumento [e] do procedimento possui um atributo [SortExpression] que fornece a expressão de ordenação associada à coluna cujo cabeçalho foi clicado. Quando o [ ] do componente [DataGrid1] foi criado, esta expressão de ordenação foi definida. Abaixo está a expressão definida para a coluna [name] de [DataGrid1]:

Image

Se a expressão de ordenação não tiver sido definida durante o desenho do [DataGrid], é utilizado o nome do campo de dados associado à coluna do [DataGrid]. A ordem de ordenação (ascendente, descendente) é definida aqui pelo utilizador através dos botões de opção [rdAscending, rdDescending]:

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
        ' sort the dataview
        With dvProduits
            .Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        End With
        ' we display it
        afficheProduits(0, DataGrid1.PageSize)
    End Sub

9.6.13. Eliminar um produto

Para eliminar um produto, o utilizador clica na ligação [Eliminar] 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
        ' product deletion
        ' product key to be deleted
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' product deletion
        Dim erreur As Boolean = False
        Try
            supprimerProduit(idProduit)
        Catch ex As Exception
            ' pb
            lblInfo2.Text = ex.Message
            erreur = True
        End Try
        ' change page?
        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
        ' product display
        afficheProduits(page, DataGrid1.PageSize)
    End Sub

Os produtos serão atualizados utilizando a chave [id] do produto. A coluna [id] na tabela [dtProduits] é a chave primária e, utilizando-a, podemos localizar a linha do produto na tabela [dtProduits] que está a ser atualizada no componente [DataGrid1]. O procedimento começa, portanto, por recuperar a chave do produto cujo link [Delete] foi clicado:


        ' product key to delete
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

Sabemos que [e.Item] é o elemento em [DataGrid1] que desencadeou o evento. Em termos gerais, este elemento é uma linha na [DataGrid]. [e.Item.ItemIndex] é o número da linha que desencadeou o evento. Este índice é relativo à página atualmente apresentada. Assim, a primeira linha da página apresentada tem a propriedade [ItemIndex=0], mesmo que tenha o número 17 na tabela de produtos. [DataGrid1.DataKeys] é a lista de chaves para o [DataGrid]. Como definimos [DataGrid1.DataKey=id] durante o tempo de design, as chaves para [DataGrid1] consistem nos valores da coluna [id] da tabela [dtProduits], que é também a chave primária. Assim, [DataGrid1.DataKeys(e.Item.ItemIndex)] é a chave [id] do produto a ser eliminado. Com isto obtido, solicitamos a sua eliminação utilizando o procedimento [deleteProduct]:


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

Esta eliminação pode falhar em alguns casos. Vamos ver porquê. É então lançada uma exceção. Esta é tratada aqui. Se houver um erro, o [DataGrid1] não precisa de ser alterado. Apenas é adicionada uma mensagem de erro à vista [products]. Se não houver erro e o utilizador tiver acabado de eliminar o único produto na página atual, é então apresentada a página anterior.


        ' change page?
        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 qualquer caso, a vista [products] é redesenhada utilizando o procedimento [displayProducts]:

        ' affichage produits
        afficheProduits(page, DataGrid1.PageSize)

O procedimento [deleteProduct] é o seguinte:


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            ' synchronization
            Application.Lock()
            ' search for the line to be deleted
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' delete line
                ligne.Delete()
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            ' end of synchronization
            Application.UnLock()
        End Try
        ' throw an exception if error
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

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

            ' suppression de la ligne [idProduit]
            dtProduits.Rows.Find(idProduit).Delete()

Com vários clientes a realizar atualizações ao mesmo tempo, a situação torna-se mais complicada. De facto, considere a seguinte sequência de eventos:

Hora
Ação
T1
O cliente A lê a tabela [dtProducts] — existe um produto com a chave 20
T2
O cliente B lê a tabela [dtProducts] — existe um produto com a chave 20
T3
O cliente A elimina o produto com a chave 20
T4
O cliente B elimina o produto com a chave 20

Quando os clientes A e B leem a tabela de produtos e exibem o seu conteúdo numa página web, a tabela contém o produto com a chave 20. Por isso, podem querer realizar uma ação sobre este produto, como, por exemplo, eliminá-lo. Um deles irá certamente fazê-lo primeiro. O que vier a seguir tentará então eliminar um produto que já não existe. Este cenário é tratado pelo procedimento [deleteProducts] de várias formas:

  • Primeiro, há uma sincronização utilizando [Application.Lock]. Isto significa que, quando um cliente atravessa esta barreira, nenhum outro cliente pode modificar ou ler a tabela de produtos. Na verdade, todas essas operações são sincronizadas desta forma. Já vimos isto para a leitura.
  • Em seguida, verificamos se o produto que queremos eliminar existe. Se sim, é eliminado; caso contrário, é gerada uma mensagem de erro.
<div class="odt-code-rich" data-linenums="false" style="counter-reset: odtline 0;"><pre><code class="language-csharp">
<span class="odt-code-line"><span class="odt-code-line-content">            &#x27; recherche de la ligne à supprimer</span></span>
<span class="odt-code-line"><span class="odt-code-line-content">            <span style="color:#0000ff">Dim</span> ligne <span style="color:#0000ff">As</span> DataRow = dtProduits.Rows.Find(idProduit)</span></span>
<span class="odt-code-line"><span class="odt-code-line-content">            <span style="color:#0000ff">If</span> ligne <span style="color:#0000ff">Is</span> <span style="color:#0000ff">Nothing</span> <span style="color:#0000ff">Then</span></span></span>
<span class="odt-code-line"><span class="odt-code-line-content">                erreur = <span style="color:#0000ff">String</span>.Format(&quot;Produit [{0}] inexistant&quot;, idProduit)</span></span>
<span class="odt-code-line"><span class="odt-code-line-content">            <span style="color:#0000ff">Else</span><span style="color:#0000ff"></span></span></span>
<span class="odt-code-line"><span class="odt-code-line-content">                &#x27; suppression de la ligne</span></span>
<span class="odt-code-line"><span class="odt-code-line-content">                ligne.Delete()</span></span>
<span class="odt-code-line"><span class="odt-code-line-content">            <span style="color:#0000ff">End</span> <span style="color:#0000ff">If</span></span></span>
</code></pre></div>
  • Saímos da secção crítica utilizando [Application.Unlock] para permitir que outros clientes realizem as suas atualizações.
  • Se a linha não puder ser eliminada, 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 [Update] (visualização parcial):

Image

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

Image

Com o cliente [Mozilla], eliminamos o produto [product2]. Obtemos a seguinte nova página:

Image

A operação de eliminação foi bem-sucedida. O produto eliminado aparece no [DataGrid] de produtos eliminados e já não aparece nas listas de produtos dos componentes [DataGrid1] e [DataGrid2], que apresentam os produtos atualmente presentes na tabela. Vamos fazer o mesmo com o [Internet Explorer]. Eliminamos o item [product2]:

Image

A resposta recebida é a seguinte:

Image

Uma mensagem de erro informa o utilizador de que o produto com a chave [2] não existe. A resposta devolve uma nova vista ao cliente, refletindo o estado atual da tabela. Podemos ver que o produto com a chave [2] está, de facto, na lista de produtos eliminados. A vista [products] reflete, portanto, as atualizações de todos os clientes, e não apenas de um.

9.6.14. Adicionar um produto

Para adicionar um produto, o utilizador clica na ligação [Adicionar] nas opções. Isto simplesmente apresenta a vista [Adicionar]:

Image

Image

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


    Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
        ' the [add] view is displayed
        afficheAjout()
    End Sub
 
    Private Sub afficheAjout()
        ' displays the add view
        vueAjout.Visible = True
        vueFormulaire.Visible = False
        vueProduits.Visible = False
        vueErreurs.Visible = False
    End Sub

Note que a vista [Add] tem componentes de validação:

nome
tipo
função
rfvName
RequiredFieldValidator
verifica se existe um valor em [txtName]
rfvPrice
Validador de campo obrigatório
verifica se existe um valor em [txtPrice]
cvPrice
CompareValidator
verifica se o preço é >= 0

O procedimento para tratar o clique no botão [Adicionar] é o seguinte:


    Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
        ' add a new item to the product table
        ' first of all, the data must be valid
        rfvNom.Validate()
        rfvPrix.Validate()
        cvPrix.Validate()
        If Not rfvNom.IsValid Or Not rfvPrix.IsValid Or Not cvPrix.IsValid Then
            ' the input form again
            afficheAjout()
            Exit Sub
        End If
        ' create a line
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim
        ' add the row to the
        Application.Lock()
        Try
            dtProduits.Rows.Add(produit)
            lblInfo3.Text = "Ajout réussi"
            ' cleaning
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try
        Application.UnLock()
    End Sub

Primeiro, realizamos verificações de validade nos três componentes [rfvNom, rfvPrix, cvPrix]. Se alguma das verificações falhar, a vista [Add] é exibida novamente com mensagens de erro para os componentes de validação. Aqui está um exemplo:

Image

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


        ' create a line
        Dim produit As DataRow = dtProduits.NewRow
        produit("nom") = txtNom.Text.Trim
        produit("prix") = txtPrix.Text.Trim

Primeiro, criamos uma nova linha na tabela [dtProducts] utilizando o método [DataTable.NewRow]. Esta linha terá as três colunas [id, name, price] da tabela [dtProducts]. As colunas [name, price] são preenchidas com os valores introduzidos no formulário de adição. A coluna [id] não é preenchida. Esta coluna é do tipo [AutoIncrement], o que significa que o SGBD atribuirá a chave máxima existente mais 1 como chave para um novo produto. A linha [product] criada aqui está separada da tabela [dtProducts]. Agora precisamos de a inserir nesta tabela. Uma vez que vamos atualizar a tabela [dtProducts], ativamos a sincronização entre clientes:

        Application.Lock()

Depois de feito isto, 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"
            ' nettoyage
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
        End Try

Normalmente, não devem ocorrer exceções. No entanto, o tratamento de exceções foi implementado como precaução. Assim que a adição estiver concluída, saímos da secção crítica utilizando [Application.Unlock].

9.6.15. Editar um produto

Para modificar um produto, o utilizador clica na ligação [Editar] na linha do produto:

Image

Isto muda para o modo de edição:

Image

Graças ao componente [DataGrid], este resultado é alcançado com muito pouco código:


    Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
        ' puts current element in edit mode
        DataGrid1.EditItemIndex = e.Item.ItemIndex
        ' products are re-displayed
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Clicar no link [Editar] aciona a execução, no lado do servidor, do procedimento [DataGrid1_EditCommand]. O componente [DataGrid1] possui um campo [EditItemIndex]. A linha com o valor de índice de [EditItemIndex] é colocada no modo de edição. Cada valor na linha assim atualizada pode ser modificado numa caixa de entrada, conforme mostrado na captura de ecrã acima. Recuperamos, portanto, o índice do produto em que o link [Editar] foi clicado e atribuímo-lo à propriedade [EditItemIndex] do componente [DataGrid1]. Resta apenas recarregar a vista [produtos]. Esta aparecerá idêntica à anterior, mas com uma linha no modo de edição.

O link [Cancel] do produto que está a ser editado permite ao utilizador cancelar a atualização. O procedimento associado a este link é o seguinte:


    Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
        ' products are re-displayed
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Simplesmente volta a apresentar a vista [produtos]. Poder-se-ia sentir a tentação de definir [DataGrid1.EditItemIndex] como -1 para cancelar o modo de atualização. Na verdade, sabemos que o procedimento [Page_Load] faz isso automaticamente. Não há, portanto, necessidade de o fazer novamente.

A alteração é validada pelo link [Update] na linha que está a ser editada. O seguinte procedimento é então executado:


    Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
        ' product key to be modified
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
        ' modified items
        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
        ' valid modifications?
        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
        '  if error, redisplay update page
        If lblInfo2.Text <> String.Empty Then
            ' return the line to update mode
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            ' product display
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
            ' end
            Exit Sub
        End If
        ' if no error - we modify the table
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' pb
            lblInfo2.Text = ex.Message
        End Try
        ' product display
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
    End Sub

Tal como na eliminação, precisamos de recuperar a chave do produto a modificar para encontrar a sua linha na tabela de produtos [dtProduits]:


        ' product key to be modified
        Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)

Em seguida, precisamos de recuperar os novos valores a atribuir à linha. Estes encontram-se no componente [DataGrid1]. O argumento [e] do procedimento existe para nos ajudar. [e.Item] representa a linha em [DataGrid1] que desencadeou o evento. Esta é, portanto, a linha que está atualmente a ser atualizada, uma vez que o link [Update] existe apenas nesta linha. Esta linha contém colunas designadas pela coleção [Cells] da linha. Assim, [e.Item.Cells(0)] representa a coluna 0 da linha que está a ser atualizada. Sabemos que os novos valores estão em caixas de texto. A coleção [e.Item.Cells(i).Controls] representa a coleção de controlos na coluna i da linha [e.Item]. As duas instruções seguintes recuperam os valores das caixas de texto da linha que está a ser atualizada em [DataGrid1]:


        ' modified items
        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

Agora temos os novos valores para a linha modificada como cadeias de caracteres. Em seguida, verificamos se estes dados são válidos. O nome não pode estar vazio e o preço deve ser um número positivo ou zero:


        ' valid modifications?
        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 ocorrer um erro, o rótulo [lblInfo2] exibirá uma mensagem de erro e a mesma página será simplesmente recarregada:


        '  if error, redisplay update page
        If lblInfo2.Text <> String.Empty Then
            ' return the line to update mode
            DataGrid1.EditItemIndex = e.Item.ItemIndex
            ' product display
            afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
            ' end
            Exit Sub
        End If

O que o código acima não mostra é que os valores introduzidos são perdidos. Isto acontece porque [DataGrid1] está ligado aos dados da tabela [dtProduits], que contém os valores originais da linha modificada. Aqui está um exemplo.

Image

O resultado é o seguinte:

Image

Podemos ver que os valores inseridos inicialmente foram perdidos. Numa aplicação profissional, isto seria provavelmente inaceitável. Aqui, deparamo-nos com certas limitações do modo de atualização padrão do componente [DataGrid]. Seria preferível ter uma vista [Edit] análoga à vista [Add].

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


        ' cas où pas d'erreur - on modifie la table
        Try
            modifierProduit(idProduit, e.Item)
        Catch ex As Exception
            ' pb
            lblInfo2.Text = ex.Message
        End Try
        ' affichage produits
        afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)

Pela mesma razão mencionada ao eliminar um produto, a modificação pode falhar. De facto, entre o momento em que o cliente lê a tabela [dtProduits] e o momento em que tenta modificar um dos seus produtos, esse produto pode ter sido eliminado por outro cliente. O procedimento [modifyProduct] lida com este caso lançando uma exceção. Esta exceção é tratada aqui. Após a atualização ser bem-sucedida ou falhar, a aplicação devolve a vista [products] ao cliente. Ainda precisamos de ver como o procedimento [modifyProduct] realiza a atualização:


    Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
        Dim erreur As String
        Try
            ' synchronization
            Application.Lock()
            ' search for the line to be modified
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' modify the line
                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
            ' end of synchronization
            Application.UnLock()
        End Try
        ' throw an exception if error
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

Não entraremos em detalhes sobre este procedimento, cujo código é semelhante ao do procedimento [deleteProduct], que já foi explicado em pormenor. Vejamos apenas dois exemplos. Primeiro, vamos alterar o preço do produto [product1]:

Image

Clicar na ligação [Update] acima produz a seguinte resposta:

Image

A alteração é claramente visível nos componentes [DataGrid] 1 e 2, que refletem o estado atual da tabela [dtProducts]. Também é visível no componente [DataGrid4], que apresenta uma vista das linhas modificadas, mostrando-as com os seus valores originais. Agora, vamos analisar um caso de conflito de concorrência. Tal como no exemplo de eliminação, utilizaremos dois clientes web diferentes. Um cliente [Mozilla] lê a tabela [dtProduits] e começa a editar o produto [product1]:

Image

Um cliente [Internet Explorer] está prestes a eliminar o produto [product1]:

Image

O cliente [Internet Explorer] elimina o [product1]:

Image

Note que [product1] já não se encontra na tabela de linhas modificadas, mas sim na tabela de linhas eliminadas. O cliente [Mozilla] valida a sua atualização:

Image

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

Image

É possível verificar que [product1] foi eliminado, uma vez que se encontra na lista de produtos eliminados.

9.7. Aplicação web para atualizar a tabela de produtos físicos

9.7.1. Soluções propostas

A aplicação anterior era mais um exemplo de manual destinado a demonstrar a gestão de um objeto [DataTable] em cache do que um cenário do mundo real. De facto, a certa altura, a fonte de dados real tem de ser atualizada. Podem ser escolhidas duas estratégias diferentes:

  1. Utilizamos o cache [dtProduits] na memória para atualizar a fonte de dados. Pode ser criada uma página na árvore web da aplicação anterior para fornecer acesso ao seu cache [dtProduits]. Esta página permitiria a um administrador sincronizar as alterações feitas no cache [dtProduits] com a fonte de dados física. Para tal, poderíamos adicionar um novo método à classe de acesso [products] que receba o cache [dtProduits] como parâmetro e utilize este cache para atualizar a fonte de dados física.
  2. A fonte de dados física é atualizada ao mesmo tempo que o cache.

A Estratégia n.º 1 permite abrir apenas uma ligação à fonte de dados física. A Estratégia n.º 2 requer uma ligação para cada atualização. Dependendo da disponibilidade de ligação, uma estratégia pode ser preferível à outra. Uma vez que dispomos das ferramentas para a implementar (a classe [products]), optamos pela Estratégia n.º 2.

9.7.2. Solução 1

Por uma questão de continuidade com a aplicação anterior, escolhemos a seguinte estratégia:

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

Para atualizar a fonte de dados física, precisamos de uma instância da classe de acesso ao produto. Cada cliente poderia ter a sua própria. Também podemos partilhar uma única instância que seria criada pela aplicação no arranque. Esta é a solução que estamos a escolher aqui. O código de controlo [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)
        ' retrieve configuration information
        Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
        Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
        Dim erreurs As New ArrayList
...
        ' no configuration errors here
        ' create a product object
        Dim objProduits As New produits(chaînedeConnexion)
        Dim dtProduits As DataTable
        Try
            dtProduits = objProduits.getProduits
        Catch ex As ExceptionProduits
            'there has been an error accessing the products, this is noted in the application
            Application("erreurs") = ex.erreurs
            Exit Sub
        Catch ex As Exception
            ' unhandled error
            erreurs.Add(ex.Message)
            Application("erreurs") = erreurs
            ' exit sub
        End Try
        ' no initialization errors here
...
        ' the data access instance is stored
        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 na aplicação, associada à chave [objProducts]. Cada cliente utilizará esta instância para aceder à fonte de dados física. 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
...
        ' retrieve the product access instance
        objProduits = CType(Application("objProduits"), produits)
...
    End Sub

A instância de acesso aos dados [objProducts] está acessível a todos os métodos da página. Será utilizada para as três operações de atualização: adicionar, eliminar e modificar.

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
...
        ' we add the line
        Application.Lock()
        Try
            ' add the line to the cached table
            dtProduits.Rows.Add(produit)
            ' add the line to the physical source
            Dim nouveauProduit As sProduit
            With nouveauProduit
                .nom = CType(produit("nom"), String)
                .prix = CType(produit("prix"), Double)
            End With
            objProduits.ajouterProduit(nouveauProduit)
            ' follow-up
            lblInfo3.Text = "Ajout réussi"
            ' cleaning
            txtNom.Text = ""
            txtPrix.Text = ""
        Catch ex As Exception
            ' error
            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
            ' synchronization
            Application.Lock()
            ' search for the line to be modified
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' modify the line in the 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
                ' modify the line in the physical source
                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
            ' end of synchronization
            Application.UnLock()
        End Try
        ' throw an exception if error
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

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


    Private Sub supprimerProduit(ByVal idProduit As Integer)
        Dim erreur As String
        Try
            ' synchronization
            Application.Lock()
            ' search for the line to be deleted
            Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
            If ligne Is Nothing Then
                erreur = String.Format("Produit [{0}] inexistant", idProduit)
            Else
                ' delete line from cache
                ligne.Delete()
                ' delete the line in the physical source
                objProduits.supprimerProduit(idProduit)
            End If
        Catch ex As Exception
            erreur = String.Format("Erreur de suppression : {0}", ex.Message)
        Finally
            ' end of synchronization
            Application.UnLock()
        End Try
        ' throw an exception if error
        If erreur <> String.Empty Then Throw New Exception(erreur)
    End Sub

9.7.3. Testes

Começamos com a seguinte tabela de dados num ficheiro ACCESS:

Image

É iniciado um cliente web:

Image

Adicionamos um produto:

Image

Estamos a descontinuar o [produto1]:

Image

Estamos a alterar o preço do [produto2]:

Image

Após estas alterações, analisamos o conteúdo da tabela [list] na base de dados do ACCESS:

Image

As três alterações foram corretamente refletidas na tabela física. Agora, vamos examinar um cenário de conflito. Eliminamos a linha de [product2] diretamente no Access:

Image

Voltamos ao nosso cliente web. O cliente não vê a eliminação que foi feita e também quer eliminar [product2]:

Image

Recebe a seguinte resposta:

Image

A linha [product2] foi, de facto, removida da cache, como se pode ver na lista de produtos eliminados. No entanto, a eliminação de [product2] na fonte física falhou, conforme indicado pela mensagem de erro.

9.7.4. Solução 2

Na solução anterior, os clientes web atualizam simultaneamente a fonte de dados física, mas não veem as alterações feitas por outros clientes. Eles só veem as suas próprias. Agora queremos que um cliente consiga ver a fonte de dados física tal como está atualmente, e não como estava quando a aplicação foi iniciada. Para tal, vamos oferecer ao cliente uma nova opção:

Image

Com a opção [Atualizar], o cliente força uma nova leitura da fonte de dados física. Para garantir que isto não afeta outros clientes, a tabela resultante desta leitura deve pertencer ao cliente que realiza a atualização e não deve ser partilhada com outros clientes. Esta é a primeira diferença em relação à aplicação anterior. O cache [dtProduits] da fonte de dados será construído 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)
        ' retrieve configuration information
...
        ' no configuration errors here
        ' create a product object
        Dim objProduits As New produits(chaînedeConnexion)
        ' store the number of products per page
        Application("defaultProduitsPage") = defaultProduitsPage
        ' the data access instance is stored
        Application("objProduits") = objProduits
    End Sub
 
    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' init session variables
        If IsNothing(Application("erreurs")) Then
            ' cache the data source
            Dim dtProduits As DataTable
            Try
                Application.Lock()
                dtProduits = CType(Application("objProduits"), produits).getProduits
            Catch ex As ExceptionProduits
                'there has been an error accessing the products, this is noted in the session
                Session("erreurs") = ex.erreurs
                Exit Sub
            Finally
                Application.UnLock()
            End Try
            ' cache [dtProduits] in the session
            Session("dtProduits") = dtProduits
            ' view of the product table
            Session("dvProduits") = dtProduits.DefaultView
            ' products per page
            Session("nbProduitsPage") = Application("defaultProduitsPage")
            ' current page displayed
            Session("pageCourante") = 0
        End If
    End Sub
 
End Class

A informação armazenada na sessão é recuperada com cada pedido no procedimento [Page_Load]:


    ' data page
    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
        ' check for application errors
        If Not IsNothing(Application("erreurs")) Then
            ' the application has not initialized correctly
            afficheErreurs(CType(Application("erreurs"), ArrayList))
            Exit Sub
        End If
        ' check for session errors
        If Not IsNothing(Session("erreurs")) Then
            ' the session has not initialized correctly
            afficheErreurs(CType(Session("erreurs"), ArrayList))
            Exit Sub
        End If
        ' retrieve a reference from the product table
        dtProduits = CType(Session("dtProduits"), DataTable)
        ' product view
        dvProduits = CType(Session("dvProduits"), DataView)
        ' retrieve the number of products per page
        nbProduitsPage = CType(Session("nbProduitsPage"), Integer)
        ' retrieve the current page
        pageCourante = CType(Session("pageCourante"), Integer)
        ' retrieve the product access instance
        objProduits = CType(Application("objProduits"), produits)
        ' cancels any update in progress
        DataGrid1.EditItemIndex = -1
        '1st request
        If Not IsPostBack Then
            ' the initial form is displayed
            txtPages.Text = nbProduitsPage.ToString
            afficheFormulaire()
        End If
    End Sub

As informações recuperadas durante a sessão serão guardadas no final da sessão após cada pedido:


    Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
        ' certain information is stored in the session
        Session("dtProduits") = dtProduits
        Session("dvProduits") = dvProduits
        Session("nbProduitsPage") = nbProduitsPage
        Session("pageCourante") = pageCourante
    End Sub

O evento [PreRender] sinaliza que a resposta está prestes a ser enviada ao cliente. Aproveitamos esta oportunidade para guardar todos os dados que precisam de ser mantidos na sessão. Isto é excessivo, uma vez que, muitas vezes, apenas alguns dos dados foram alterados. Esta gravação sistemática tem a vantagem de nos isentar da gestão da sessão nos outros métodos da página.

A operação de atualização do cache é tratada pelo seguinte procedimento:


    Private Sub lnkRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkRefresh.Click
        ' refresh the [dtProduits] cache with the physical data source
        ' synchro start
        Application.Lock()
        Try
            ' product table
            dtProduits = CType(Application("objProduits"), produits).getProduits
            ' the current filter is saved
            Dim filtre As String = dvProduits.RowFilter
            ' create the new filtered view
            dvProduits = New DataView(dtProduits)
            ' put the filter back on
            dvProduits.RowFilter = filtre
        Catch ex As ExceptionProduits
            'there has been an error accessing the products, this is noted in the session
            Session("erreurs") = ex.erreurs
            ' display the [errors] view
            afficheErreurs(ex.erreurs)
            ' finish
            Exit Sub
        Finally
            ' end synchro
            Application.UnLock()
        End Try
        ' it went well - the products are displayed from the 1st page
        afficheProduits(0, nbProduitsPage)
    End Sub

O procedimento regenera novos valores para o cache [dtProduits] e a vista [dvProduits]. Estes serão colocados na sessão pelo procedimento [Page_PreRender] descrito acima. Assim que o cache [dtProduits] for reconstruído, exibimos os produtos a partir da primeira página.

Aqui está 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] elimina [product1], modifica [product2] e adiciona um novo produto. Obtém a seguinte nova página:

Image

O cliente [Internet Explorer] pretende eliminar [product1].

Image

Recebe a seguinte resposta:

Image

Foi notificado de que [product1] já não existe. O utilizador decide então atualizar a sua cache utilizando o link [Refresh] acima. Recebe a seguinte resposta:

Image

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

9.8. Conclusão

Neste capítulo, dedicámos bastante tempo aos contentores de dados e às suas ligações às fontes de dados. Concluímos mostrando como atualizar uma fonte de dados utilizando um componente [DataGrid]. Utilizámos uma tabela de base de dados como fonte de dados. Embora o componente [DataGrid] facilite um pouco a apresentação dos dados, o verdadeiro desafio não reside na camada de apresentação, mas sim na gestão das atualizações à fonte de dados efetuadas por diferentes clientes. Podem surgir conflitos de acesso que têm de ser geridos. Aqui, tratámos deles no controlador utilizando [Application.Lock]. Provavelmente, seria mais sensato sincronizar o acesso à fonte de dados dentro da classe de acesso aos dados, para que o controlador não tenha de se preocupar com esses detalhes que estão fora do seu âmbito.

Na prática, as tabelas numa base de dados estão ligadas umas às outras através de relações, e as suas atualizações devem ter isso em conta. Isto afeta principalmente a classe de acesso aos dados, que se torna mais complexa do que o necessário para uma tabela independente. Também tem, geralmente, impacto na camada de apresentação da aplicação web, uma vez que é frequentemente necessário apresentar não uma única tabela, mas sim tabelas ligadas umas às outras através de relações.

Este capítulo também apresentou várias estruturas de dados, tais como [DataTable, DataView].