Skip to content

6. Exemplos

Neste capítulo, propomos ilustrar o que foi abordado anteriormente através de uma série de exemplos.

6.1. Exemplo 1

6.1.1. O problema

Esta aplicação deve permitir que um utilizador calcule o seu imposto. Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar (dados de 2004 relativos aos rendimentos de 2003):

  • calcula-se o número de quotas do trabalhador nbParts = nbEnfants/2 + 1 se não for casado, nbEnfants/2 + 2 se for casado, em que nbEnfants corresponde ao número de filhos.
  • se tiver pelo menos três filhos, tem mais meia quota
  • calcula-se o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
  • calcula-se o seu coeficiente familiar QF = R / nbParts
  • calcula-se o seu imposto I. Consideremos a seguinte tabela:
4262
0
0
8382
0,0683
291,09
14753
0,1914
1322,92
23 888
0,2826
2668,39
38 868
0,3738
4846,98
47 932
0,4262
6883,66
0
0,4809
9505,54

Cada linha tem 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF <= campo1. Por exemplo, se QF = 5000, encontrar-se-á a linha

    8382        0.0683        291.09

O imposto I é, então, igual a 0,0683*R - 291,09*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então são utilizados os coeficientes da última linha. Neste caso:

    0                0.4809    9505.54

o que resulta no imposto I = 0,4809*R - 9505,54*nbParts.

6.1.2. A estrutura MVC da aplicação

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

Image

A função do controlador será desempenhada pela página [main.aspx]. Haverá três ações possíveis:

  • init: corresponde ao primeiro pedido do cliente. O controlador apresentará a vista [formulaire.aspx]
  • calcul: corresponde ao pedido de cálculo do imposto. Se os dados do formulário de introdução estiverem corretos, o imposto é calculado através da classe de negócio [impots]. O controlador devolve ao cliente a vista [formulaire.aspx] tal como tinha sido validada, acrescida do imposto calculado. Se os dados do formulário de introdução estiverem incorretos, o controlador devolverá a vista [erreurs.aspx] com a lista de erros e um link para regressar ao formulário.
  • retorno: corresponde ao regresso ao formulário após um erro. O controlador apresenta a vista [formulaire.aspx] tal como foi validada antes do erro.

O controlador [main.aspx] não tem qualquer conhecimento sobre o cálculo de impostos. É simplesmente responsável por gerir o diálogo cliente-servidor e por executar as ações solicitadas pelo cliente. Para a ação [calcul], irá basear-se na classe de negócio [impot].

6.1.3. A classe de negócio

A classe de impostos será definida da seguinte forma:


' espaços de nomes importados
Imports System

' classe
Namespace st.istia.univangers.fr
    Public Class impot
        Private limites(), coeffR(), coeffN() As Decimal

        ' construtor
        Public Sub New(ByRef source As impotsData)
            ' os dados necessários para o cálculo do imposto
             ' provêm de uma fonte externa [source]
             ' são obtidos — pode haver uma exceção
            Dim data() As Object = source.getData
            limites = CType(data(0), Decimal())
            coeffR = CType(data(1), Decimal())
            coeffN = CType(data(2), Decimal())
        End Sub

         ' cálculo do imposto
        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' cálculo do número de quotas
            Dim nbParts As Decimal
            If marié Then
                nbParts = CDec(nbEnfants) / 2 + 2
            Else
                nbParts = CDec(nbEnfants) / 2 + 1
            End If
            If nbEnfants >= 3 Then
                nbParts += 0.5D
            End If
             ' cálculo do rendimento tributável e do quociente familiar
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' cálculo do imposto
            limites((limites.Length - 1)) = QF + 1
            Dim i As Integer = 0
            While QF > limites(i)
                i += 1
            End While
            Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
        End Function
    End Class
End Namespace

Cria-se um objeto de imposto fornecendo ao seu construtor uma fonte de dados do tipo [impotsData]. Esta classe possui um método público [getData] que permite obter as três tabelas de dados necessárias para o cálculo do imposto e que foram apresentadas anteriormente. Este método pode gerir uma exceção caso os dados não tenham podido ser obtidos ou se se revelarem incorretos. Uma vez criado o objeto [impot], é possível chamar repetidamente o seu método «calcular», que calcula o imposto do contribuinte com base no seu estado civil (casado ou solteiro), no número de filhos e no seu salário anual.

6.1.4. A classe de acesso aos dados

A classe [impotsData] é a classe que permite aceder aos dados. Trata-se de uma classe abstrata. É necessário criar uma classe derivada para cada nova fonte de dados possível (tabelas, ficheiros simples, bases de dados, consola, etc.). A sua definição é a seguinte:


Imports System.Collections

Namespace st.istia.univangers.fr
    Public MustInherit Class impotsData
        Protected limites() As Decimal
        Protected coeffr() As Decimal
        Protected coeffn() As Decimal
        Protected checked As Boolean
        Protected valide As Boolean

        ' método de acesso aos dados
        Public MustOverride Function getData() As Object()

        ' método de verificação dos dados
        Protected Function checkData() As Integer
            ' verifica os dados obtidos
            ' é necessário dispor de dados
            valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
            If Not valide Then Return 1
            ' é necessário ter 3 tabelas com o mesmo tamanho
            If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
            If Not valide Then Return 2
            ' as tabelas não podem estar vazias
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' cada tabela deve conter elementos >=0 e por ordem crescente
            valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
            If Not valide Then Return 4
            ' está tudo correto
            Return 0
        End Function

        ' verifica a validade do conteúdo de um tabulero
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
            ' a matriz deve ter os seus primeiros n elementos >=0 e em ordem estritamente crescente
            If tableau(0) < 0 Then Return False
            For i As Integer = 1 To n - 1
                If tableau(i) <= tableau(i - 1) Then Return False
            Next
            ' está correto
            Return True
        End Function
    End Class
End Namespace

A classe possui os seguintes atributos protegidos:

limites
tabela dos limites das faixas de imposto
coeffr
tabela dos coeficientes aplicados ao rendimento tributável
coeffn
tabela dos coeficientes aplicados ao número de quotas
checked
valor booleano que indica se os dados (limites, coeffr, coeffn) foram verificados
valide
valor booleano que indica se os dados (limites, coeffr, coeffn) são válidos

A classe não tem um construtor. Possui um método abstrato [getData] que as classes derivadas terão de implementar. Este método tem como função:

  • atribuir valores aos três vetores limites, coeffr e coeffn
  • lançar uma exceção se os dados não tiverem podido ser obtidos ou se se revelarem inválidos.

A classe fornece os métodos protegidos [checkData] e [check], que verificam a validade dos atributos (limites, coeffr, coeffn). Isto dispensa as classes derivadas de os implementarem. Estas terão apenas de os utilizar.

A primeira classe derivada que iremos utilizar será a seguinte:


Imports System.Collections
Imports System

Namespace st.istia.univangers.fr
    Public Class impotsArray
        Inherits impotsData

        ' construtor sem argumentos
        Public Sub New()
            ' inicializações das tabelas com constantes
            limites = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
            coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
            coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
            checked = True
            valide = True
        End Sub

        ' construtor com três tabelas como argumentos
        Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
            ' armazenamos os dados
            Me.limites = limites
            Me.coeffr = coeffr
            Me.coeffn = coeffn
            checked = False
        End Sub

        Public Overrides Function getData() As Object()
            ' verifica-se, se necessário, os dados
            Dim erreur As Integer
            If Not checked Then erreur = checkData() : checked = True
            ' se não forem válidos, lança-se uma exceção
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' caso contrário, devolvem-se as três matrizes
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Esta classe, denominada [impotsArray], possui dois construtores:

  • um construtor sem argumentos que inicializa os atributos (limites, coeffr, coeffn) da classe base com tabelas codificadas de forma «estática»
  • um construtor que inicializa os atributos (limites, coeffr, coeffn) da classe base com matrizes que lhe são passadas como parâmetros

O método [getData], que permitirá às classes externas obter as matrizes (limites, coeffr, coeffn), limita-se a verificar a validade das três matrizes utilizando o método [checkData] da classe base. Lança uma exceção se os dados forem inválidos.

6.1.5. Testes das classes de negócio e das classes de acesso aos dados

É importante incluir numa aplicação web apenas classes de negócio e de acesso aos dados cuja correção tenha sido certificada. Desta forma, a fase de depuração da aplicação web poderá concentrar-se na parte do controlador e das vistas. Um programa de teste poderia ser o seguinte:


' opções
Option Strict On
Option Explicit On 

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

Namespace st.istia.univangers.fr
    Module test
        Sub Main()
             ' programa interativo de cálculo de impostos
             ' o utilizador introduz três dados através do teclado: casado nbEnfants salário
             ' o programa apresenta então o imposto a pagar
            Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' criação de um objeto «imposto»
            Dim objImpôt As impot = Nothing
            Try
                objImpôt = New impot(New impotsArray)
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try
            ' loop infinito
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Long
            While True
                ' solicitam-se os parâmetros para o cálculo do imposto
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()
                ' há algo a fazer?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' verificação do número de argumentos na linha introduzida
                Dim erreur As Boolean = False
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe)
                    erreur = True
                End If
                ' verificação da validade dos parâmetros
                If Not erreur Then
                    ' casado
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        erreur = True
                    End If
                    ' nbEnfants
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                    ' salário
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                End If
                 ' se os parâmetros estiverem corretos - calcula-se o imposto
                If Not erreur Then
                    Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " euro(s)"))
                Else
                    Console.Error.WriteLine(syntaxe)
                End If
            End While
        End Sub
    End Module
End Namespace

A aplicação pede ao utilizador que introduza no teclado as três informações necessárias para calcular o seu imposto:

  • o seu estado civil: o para casado, n para solteiro
  • o número de filhos
  • o seu salário anual

O cálculo do imposto é efetuado com a ajuda de um objeto do tipo [impot], criado logo que a aplicação é iniciada:


             ' criação de um objeto de imposto
            Dim objImpôt As impot = Nothing
            Try
                objImpôt = New impot(New impotsArray)
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try

Como fonte de dados, utiliza-se um objeto do tipo [impotsArray]. É o construtor sem argumentos desta classe que é utilizado e que fornece as três tabelas (limites, coeffr, coeffn) com valores «fixos». A criação de um objeto [impot] pode, em teoria, gerar uma exceção, uma vez que, para se criar, o objeto irá solicitar os dados (limites, coeffr, coeffn) à sua fonte de dados que lhe foi passada como parâmetro, e essa aquisição de dados pode lançar uma exceção. Acontece que, neste caso, o método de obtenção dos dados (codificação estática) não pode provocar uma exceção. No entanto, mantivemos o tratamento da exceção para chamar a atenção do leitor para a possibilidade de o objeto [impot] ser construído incorretamente.

Eis um exemplo de execução do programa anterior:

dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  08:42                2 490 testimpots1.vb

Compilamos o conjunto de classes [impot, impotsData, impotsArray] num conjunto [impot.dll]:

dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  08:42                2 490 testimpots1.vb
21/04/2004  09:21                5 632 impot.dll

Compilamos o programa de teste:

dos>vbc /r:impot.dll testimpots1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  08:42                2 490 testimpots1.vb
21/04/2004  09:21                5 632 impot.dll
21/04/2004  09:23                4 608 testimpots1.exe

Podemos realizar os testes:

dos>testimpots1
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 60000
impôt=6872 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :

6.1.6. As vistas da aplicação web

A aplicação terá duas vistas: [formulaire.aspx] e [erreurs.aspx]. Vamos ilustrar o funcionamento da aplicação através de capturas de ecrã. A vista [formulaire.aspx] é apresentada quando o URL [main.aspx] é solicitado pela primeira vez:

Image

O utilizador preenche o formulário:

Image

e utiliza o botão [Calculer] para obter a seguinte resposta:

Image

Pode cometer erros nos dados introduzidos:

Image

Ao utilizar o botão [Calculer], obtém-se então outra resposta, [erreurs.aspx]:

Image

Pode utilizar o link [Retour au formulaire] acima para recuperar a vista [formulaire.aspx] tal como a validou antes do erro:

Image

6.1.7. A vista [formulaire.aspx]

A página [formulaire.aspx] será a seguinte:


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR>
        <form method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui 
                      <INPUT type="radio"  value="non" name="rdMarie" <%=rdnonchecked%>>Non
                     </TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
                </TR>
                <TR>
                    <TD>Impôt à payer :
                    </TD>
                    <TD><%=txtImpot%></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="submit" value="Calculer">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
                <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Os campos dinâmicos desta página são os seguintes:

rdouichecked
«checked» se a caixa [oui] tiver de ser assinalada, «» caso contrário
rdnonchecked
o mesmo se aplica à caixa de seleção [non]
txtEnfants
valor a inserir no campo de introdução de dados [txtEnfants]
txtSalaire
valor a inserir no campo de introdução [txtSalaire]
txtImpot
valor a inserir no campo de entrada [txtImpot]

A página tem dois formulários, cada um com um botão [submit]. O botão [Calculer] é o botão [submit] do formulário seguinte:


        <form method="post" action="main.aspx?action=calcul">
...
            <P>
                <INPUT type="submit" value="Calculer">
            </P>
        </form>

Vê-se que os parâmetros do formulário serão enviados para o controlador com [action=calcul]. O botão [Effacer] é o botão [submit] do formulário seguinte:


        <form method="post" action="main.aspx?action=effacer">
                <INPUT type="submit" value="Effacer">
        </form>

Vê-se que os parâmetros do formulário serão enviados para o controlador com [action=effacer]. Neste caso, o formulário não tem quaisquer parâmetros. Apenas a ação é relevante.

Os campos de [formulaire.aspx] são calculados por [formulaire.aspx.vb]:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

    ' campos da página
    Protected rdouichecked As String
    Protected rdnonchecked As String
    Protected txtEnfants As String
    Protected txtSalaire As String
    Protected txtImpot As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' recupera-se a consulta anterior no contexto
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' prepara-se a página a apresentar
        ' botões de opção
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' o restante
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

O cálculo dos campos de [main.aspx] é efetuado a partir de duas informações inseridas pelo controlador no contexto da página:

  • Context.Items("formulário"): dicionário do tipo NameValueCollection que contém os valores dos campos HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): valor do imposto

6.1.8. A vista [erreurs.aspx]

A vista [erreurs.aspx] é a que apresenta os eventuais erros que possam ocorrer durante o funcionamento da aplicação. O seu código de apresentação é o seguinte:


<%@ page src="erreurs.aspx.vb" inherits="erreurs" AutoEventWireup="false"%>
<HTML>
    <HEAD>
        <title>Impôt</title>
    </HEAD>
    <body>
        <P>Les erreurs suivantes se sont produites :</P>
        <HR>
        <ul>
            <%=erreursHTML%>
        </ul>
        <a href="<%=href%>">
            <%=lien%>
        </a>
    </body>
</HTML>

A página tem três campos dinâmicos:

erreursHTML
código HTML de uma lista de erros
href
URL de um link
lien
texto do link

Estes campos são calculados pela parte de controlo da página em [erreurs.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class erreurs
    Inherits System.Web.UI.Page

    ' parâmetro da página
    Protected erreursHTML As String = ""
    Protected href As String
    Protected lien As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' recuperam-se os elementos do contexto
        Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
        ' gera-se o código HTML da lista
        Dim i As Integer
        For i = 0 To erreurs.Count - 1
            erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
    End Sub

End Class

O controlador da página recupera informações colocadas pelo controlador da aplicação no contexto da página:

Context.Items("erreurs"
objeto ArrayList contendo a lista de mensagens de erro a apresentar
Context.Items("href"
URL de um link
Context.Items("lien"
texto do link

Agora que sabemos o que o utilizador da aplicação vê, podemos passar à criação do controlador da mesma.

6.1.9. Os controladores [global.asax, main.aspx]

Recordemos o esquema MVC da nossa aplicação:

Image

Cliente Lógica de aplicação

O controlador [main.aspx] tem de processar três ações:

  • init: corresponde ao primeiro pedido do cliente. O controlador apresenta a vista [formulaire.aspx]
  • calcul: corresponde ao pedido de cálculo do imposto. Se os dados do formulário de introdução estiverem corretos, o imposto é calculado através da classe de negócio [impots]. O controlador devolve ao cliente a vista [formulaire.aspx] tal como tinha sido validada, acrescentando ainda o imposto calculado. Se os dados do formulário de introdução estiverem incorretos, o controlador devolve a vista [erreurs.aspx] com a lista de erros e um link para regressar ao formulário.
  • retorno: corresponde ao regresso ao formulário após um erro. O controlador apresenta a vista [formulaire.aspx] tal como foi validada antes do erro.

Sabe-se, além disso, que qualquer pedido à aplicação passa pelo controlador [global.asax], caso este exista. Temos, assim, na entrada da aplicação, uma cadeia de dois controladores:

  • [global.asax] que, devido à arquitetura do ASP.NET, recebe todas as solicitações dirigidas à aplicação
  • [main.aspx] que, por decisão do programador, também recebe todas as solicitações dirigidas à aplicação

A necessidade do [main.aspx] decorre do facto de termos de gerir uma sessão. Vimos que o [global.asax] não era adequado como controlador neste caso. Aqui, poderíamos prescindir totalmente do [global.asax]. No entanto, vamos utilizá-lo para executar código no arranque da aplicação. O esquema MVC acima mostra que vamos precisar de criar um objeto [impot] para calcular o imposto. Não faz sentido criá-lo várias vezes; basta uma vez. Vamos, portanto, criá-lo no arranque da aplicação, durante o evento [Application_Start] gerido pelo controlador [global.asax]. O código deste é o seguinte:

[global.asax]

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

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' cria-se um objeto «impot»
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsArray)
            ' insere-se o objeto na aplicação
            Application("objImpot") = objImpot
            ' sem erros
            Application("erreur") = False
        Catch ex As Exception
            'ocorreu um erro, que é registado na aplicação
            Application("erreur") = True
        End Try
    End Sub
End Class

Depois de criado, o objeto do tipo [impot] é inserido na aplicação. É aí que as diferentes consultas dos vários clientes irão buscá-lo. Como a criação do objeto [impot] pode falhar, tratamos a eventual exceção e colocamos uma chave [erreur] na aplicação para indicar se ocorreu ou não um erro durante a criação do objeto [impot].

O código do controlador [main.aspx, main.aspx.vb] será o seguinte:

[main.aspx]

<%@ page src="main.aspx.vb" inherits="main" AutoEventWireup="false"%>

[main.aspx.vb]


Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr

Public Class main
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' antes de mais nada, verifica-se se a aplicação conseguiu inicializar-se corretamente
        If CType(Application("erreur"), Boolean) Then
            ' redireciona-se para a página de erros
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If
        ' recuperamos a ação a realizar
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' executa-se a ação
        Select Case action
            Case "init"
                ' inicializar a aplicação
                initAppli()
            Case "calcul"
                ' cálculo do imposto
                calculImpot()
            Case "retour"
                ' regresso ao formulário
                retourFormulaire()
            Case "effacer"
                ' inicialização da aplicação
                initAppli()
            Case Else
                ' ação desconhecida = inicialização
                initAppli()
        End Select
    End Sub

    Private Sub initAppli()
        ' exibe-se o formulário pré-preenchido
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function initForm() As NameValueCollection
        ' inicialização do formulário
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' verifica-se a validade dos dados introduzidos
        Dim erreurs As ArrayList = checkData()
        ' se houver erros, estes são assinalados
        If erreurs.Count <> 0 Then
            ' guardam-se os dados introduzidos
            Session.Item("formulaire") = Request.Form
            ' prepara-se a página de erros
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' Aqui não há erros — calcula-se o imposto
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' exibe-se a página de resultados
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Sub retourFormulaire()
        ' exibe-se o formulário com os valores obtidos da sessão
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function checkData() As ArrayList
        ' Inicialmente, sem erros
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' botão de opção «casado»
        Try
            Dim rdMarie As String = Request.Form("rdMarie").ToString
            If rdMarie <> "oui" And rdMarie <> "non" Then
                Throw New Exception
            End If
        Catch
            erreurs.Add("Vous n'avez pas indiqué votre statut marital")
        End Try
        ' número de filhos
        Try
            Dim txtEnfants As String = Request.Form("txtEnfants").ToString
            Dim nbEnfants As Integer = CType(txtEnfants, Integer)
            If nbEnfants < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le nombre d'enfants est incorrect")
        End Try
        ' salário
        Try
            Dim txtSalaire As String = Request.Form("txtSalaire").ToString
            Dim salaire As Integer = CType(txtSalaire, Long)
            If salaire < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le salaire annuel est incorrect")
        End Try
        ' apresenta-se a lista de erros
        Return erreurs
    End Function
End Class

O controlador começa por verificar se a aplicação foi inicializada corretamente:


         ' antes de mais nada, verifica-se se a aplicação conseguiu inicializar-se corretamente
        If CType(Application("erreur"), Boolean) Then
            ' redireciona para a página de erros
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If

Se o controlador detetar que a aplicação não conseguiu inicializar-se corretamente (o objeto [impot] necessário para o cálculo não pôde ser criado), então exibe a página de erros com os parâmetros adequados. Neste caso, não é necessário colocar o link de retorno no formulário, uma vez que toda a aplicação se encontra indisponível. É inserida uma mensagem de erro geral no [Context.Items("erreurs")], do tipo [ArrayList].

Se o controlador verificar que a aplicação está operacional, analisa então a ação que lhe é solicitada através do parâmetro [action]. Já nos deparámos várias vezes com este modo de funcionamento. O processamento de cada tipo de ação é delegado a uma função.

6.1.9.1. As ações init e effacer

Estas duas ações devem fazer com que o formulário de introdução de dados seja apresentado em branco. Recorde-se que este (ver vistas) tem dois parâmetros:

  • Context.Items("formulário"): dicionário do tipo [NameValueCollection] contendo os valores dos campos HTML e [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): valor do imposto

A função [initAppli] inicializa estes dois parâmetros de forma a apresentar um formulário em branco.

6.1.9.2. A ação de cálculo

Esta ação deve calcular o imposto a pagar com base nos dados introduzidos no formulário e devolver o mesmo pré-preenchido com os valores introduzidos e, além disso, com o montante do imposto calculado. A função [calculImpot], responsável por esta tarefa, começa por verificar se os dados do formulário estão corretos:

  • o campo [rdMarie] deve estar presente e ter o valor [oui] ou [non]
  • o campo [txtEnfants] deve estar presente e ser um número inteiro >=0
  • o campo [txtSalaire] deve estar presente e ser um número inteiro >=0

Se os dados introduzidos forem inválidos, o controlador exibe a vista [erreurs.aspx], tendo previamente inserido no contexto os valores esperados por esta:

  • as mensagens de erro são colocadas num objeto [ArrayList], objeto esse que é posteriormente colocado no contexto [Context.Items("erreurs")]
  • o URL do link de retorno e o texto desse link são igualmente colocados no contexto.

Antes de passar o controlo para a página [erreurs.aspx], que irá enviar a resposta ao cliente, os valores introduzidos no formulário (Request.Form) são colocados na sessão, associados à chave «formulário». Isto permitirá que uma solicitação posterior os recupere.

Podemos questionar-nos aqui se é útil verificar se os campos [rdMarie, txtEnfants, txtSalaire] estão presentes na solicitação enviada pelo cliente. Isso é desnecessário se tivermos a certeza de que o nosso cliente é um navegador que recebeu a vista [formulaire.aspx], que contém esses campos. No entanto, nunca podemos ter a certeza disso. Mais adiante, apresentaremos um exemplo em que o cliente é a aplicação [curl], já mencionada anteriormente. Interrogaremos a aplicação sem enviar os campos que ela espera e veremos como ela reagirá. Esta é uma regra já enunciada várias vezes e que aqui relembramos: uma aplicação nunca deve fazer suposições sobre o tipo de cliente que a consulta. Por motivos de segurança, deve considerar que pode ser consultada por uma aplicação programada que lhe possa enviar cadeias de parâmetros inesperadas. Deve comportar-se corretamente em todos os casos.

No nosso caso, verificámos que os campos [rdMarie, txtEnfants, txtSalaire] estavam presentes na solicitação, mas não que esta pudesse conter outros. Nesta aplicação, esses campos seriam ignorados. No entanto, sempre por uma questão de segurança, seria interessante registar este tipo de pedido num ficheiro de registos e enviar um alerta ao administrador da aplicação, para que este saiba que a aplicação está a receber pedidos «estranhos». Ao analisar essas solicitações no ficheiro de registos, o administrador poderia detetar um eventual ataque à aplicação e tomar as medidas necessárias para a sua proteção.

Se os dados esperados estiverem corretos, o controlador inicia o cálculo do imposto com o objeto [impot] armazenado na aplicação. Em seguida, armazena no contexto as duas informações esperadas pela vista [formulaire.aspx]:

  • Context.Items («formulário»): dicionário do tipo [NameValueCollection] contendo os valores dos campos HTML, [rdmarie,txtEnfants,txtSalaire], aqui [Request.Form)] e c.a.d. os valores introduzidos anteriormente no formulário
  • Context.Items("txtImpot"): valor do imposto que acabou de ser obtido

O leitor atento talvez se tenha questionado ao ler o que precede: uma vez que o objeto [impot] criado no arranque da aplicação é partilhado entre todas as consultas, não poderão ocorrer conflitos de acesso que provoquem a corrupção dos dados do objeto [impot]? Para responder a esta questão, temos de voltar ao código da classe [impot]. As consultas recorrem ao método [impot].calculerImpot para obter o imposto a pagar. É, portanto, este código que temos de examinar:

        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' cálculo do número de quotas
            Dim nbParts As Decimal
            If marié Then
                nbParts = CDec(nbEnfants) / 2 + 2
            Else
                nbParts = CDec(nbEnfants) / 2 + 1
            End If
            If nbEnfants >= 3 Then
                nbParts += 0.5D
            End If
             ' cálculo do rendimento tributável e do quociente familiar
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' cálculo do imposto
            limites((limites.Length - 1)) = QF + 1
            Dim i As Integer = 0
            While QF > limites(i)
                i += 1
            End While
            Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
            Return impot
        End Function

Suponhamos que um thread esteja a executar o método anterior e seja interrompido. Outro thread passa então a executar o método. Quais são os riscos? Para o descobrir, adicionámos o seguinte código:


            Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
            ' aguarda-se 10 segundos
            Thread.Sleep(10000)
            Return impot

O thread 1, após ter calculado o valor [impot1] da variável local [impot], é interrompido. O thread 2 é então executado e calcula um novo valor, [impot2], para essa mesma variável [impot], antes de ser interrompido. O thread 1 recupera o controlo. O que encontra na variável local [impot]? Sendo esta variável local a um método, está armazenada numa estrutura da memória denominada pilha. Esta pilha faz parte do contexto do thread, que é guardado quando este é interrompido. Quando o thread 2 é iniciado, o seu contexto é estabelecido com uma nova pilha e, portanto, uma nova variável local [impot]. Quando a thread 2 for, por sua vez, interrompida, o seu contexto será também guardado. Quando a thread 1 é reiniciada, o seu contexto é restaurado, incluindo a sua pilha. Assim, recupera a sua variável local [impot] e não a da thread 2. Estamos, portanto, numa situação em que não há conflitos de acesso entre as solicitações. Os testes realizados com a pausa de 10 segundos acima confirmaram que as solicitações simultâneas obtinham, de facto, o resultado esperado.

6.1.9.3. A ação de retorno

Esta ação corresponde à ativação do link [Retour vers le formulaire] da vista [erreurs.aspx] para regressar à vista [formulaire.aspx], pré-preenchida com os valores introduzidos anteriormente e guardados na sessão. A função [retourFormulaire] recupera esta informação. Os dois parâmetros esperados pela vista [formulaire.aspx] são inicializados:

  • Context.Items("formulário") com os valores introduzidos anteriormente e guardados na sessão
  • Context.Items("txtImpot") com a cadeia vazia

6.1.10. Teste da aplicação web

Todos os ficheiros anteriores são colocados numa pasta <application-path>.

Image

Nesta pasta, é criada uma subpasta [bin], na qual é colocado o conjunto [impot.dll], resultante da compilação dos ficheiros das classes de negócio: [impots.vb, impotsData.vb, impotsArray.vb]. Recorda-se abaixo o comando de compilação necessário:

dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  09:21                5 632 impot.dll

O ficheiro [impot.dll] acima deve ser colocado em <application-path>\bin para que a aplicação web tenha acesso ao mesmo. O servidor Cassini é iniciado com os parâmetros (<application-path>,/impots1). Num navegador, acedemos à URL [http://localhost/impots1/main.aspx]:

Image

Preenchemos o formulário:

Image

Em seguida, iniciamos o cálculo do imposto através do botão [Calculer]. Obtemos a seguinte resposta:

Image

Em seguida, introduzimos dados errados:

Image

Ao clicar no botão [Calculer], obtém-se a seguinte resposta:

Image

Ao utilizar o link [Retour au formulaire], voltamos ao formulário no estado em que se encontrava quando foi validado:

Image

Por fim, ao clicar no botão [Effacer], a página é reiniciada:

Image

6.1.11. Utilização do cliente [curl]

É importante testar as aplicações web com outros clientes além dos navegadores. Se enviarmos a um navegador um formulário com parâmetros a serem enviados quando for validado, o navegador reenviará os valores desses parâmetros ao servidor. Outro cliente poderá não o fazer e, nesse caso, o servidor receberá um pedido em que faltarão parâmetros. O servidor deve saber o que fazer nessa situação. Outro exemplo é o das verificações de entrada de dados efetuadas do lado do cliente. Se o formulário contiver dados a verificar, essa verificação pode ser feita do lado do cliente através de scripts incluídos no documento que contém o formulário. O navegador só enviará o formulário se todos os dados verificados do lado do cliente forem válidos. Poderíamos então sentir-nos tentados, do lado do servidor, a considerar que vamos receber dados validados e a não querer realizar essa verificação uma segunda vez. Isso seria um erro. Com efeito, um cliente que não seja um navegador poderia enviar dados inválidos ao servidor e, nesse caso, a aplicação web corre o risco de apresentar um comportamento inesperado. Vamos ilustrar estes pontos utilizando o cliente [curl].

Em primeiro lugar, solicitamos a URL [http://localhost/impots1/main.aspx]:

dos>curl --include --url http://localhost/impots1/main.aspx

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:18:10 GMT
Set-Cookie: ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 982
Connection: Close


<html>
    <head>
        <title>Impôt</title>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <form method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" >Oui <INPUT type="radio"  value="non" name="rdMarie" checked>Non</TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value=""></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value=""></TD>
                </TR>
                <TR>
                    <TD>Impôt à payer :
                    </TD>
                    <TD></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="submit" value="Calculer">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
                <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

O servidor enviou-nos o código HTML do formulário. Nos cabeçalhos HTTP temos o cookie de sessão. Vamos utilizá-lo nas solicitações seguintes para manter a sessão. Solicitemos a ação [calcul] sem fornecer parâmetros:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url  http://localhost/impostos1/main.aspx?action=cálculo 

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:22:42 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close


<HTML>
    <HEAD>
        <title>Impôt</title>
    </HEAD>
    <body>
        <P>Les erreurs suivantes se sont produites :</P>
        <HR>
        <ul>
            <li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
        </ul>
        <a href="main.aspx?action=retour">
            Retour au formulaire
        </a>
    </body>
</HTML>

Podemos constatar que a aplicação web devolveu a vista [erreurs] com três mensagens de erro relativas aos três parâmetros em falta. Vamos agora enviar parâmetros errados:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=xx --data txtEnfants=xx --data txtSalaire=xx --url http://localhost/impots1/main.aspx?action=calcul 

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:25:50 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close


<HTML>
    <HEAD>
        <title>Impôt</title>
    </HEAD>
    <body>
        <P>Les erreurs suivantes se sont produites :</P>
        <HR>
        <ul>
            <li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
        </ul>
        <a href="main.aspx?action=retour">
            Retour au formulaire
        </a>
    </body>
</HTML>

Os três erros foram detetados corretamente. Agora, vamos enviar parâmetros válidos:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=oui --data txtEnfants=2 --data txtSalaire=60000 --url http://localhost/impots1/main.aspx?action=calcul 

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:28:24 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1000
Connection: Close


<html>
    <head>
        <title>Impôt</title>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <form method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" checked>Oui <INPUT type="radio"  value="non" name="rdMarie" >Non</TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="2"></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="60000"></TD>
                </TR>
                <TR>
                    <TD>Impôt à payer :
                    </TD>
                    <TD>4300 euro(s)</TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="submit" value="Calculer">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
                <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Conseguimos, de facto, recuperar o imposto a pagar: 4300 euros. O que retemos desta ilustração é que não devemos deixar-nos iludir pelo facto de estarmos a escrever uma aplicação web destinada a clientes que são navegadores. Uma aplicação web é um serviço TCP/IP e este protocolo de rede não permite determinar a natureza da aplicação cliente de um serviço. Por isso, não é possível saber se o cliente de uma aplicação web é um navegador ou não. Seguimos, então, duas regras:

  • ao receber um pedido de um cliente, não fazemos qualquer suposição sobre o cliente e verificamos se os parâmetros esperados no pedido estão efetivamente presentes e são válidos
  • elaboramos uma resposta destinada aos navegadores, ou seja, geralmente documentos HTML

Uma aplicação web pode ser concebida para servir simultaneamente diferentes clientes, por exemplo, navegadores e telemóveis. É então possível incluir em cada pedido um novo parâmetro que indique o tipo de cliente. Assim, um navegador solicitará o cálculo do imposto através de um pedido para o URL http://machine/impots/main.aspx?client=navegador&action=cálculo, enquanto o telemóvel enviará uma solicitação para a URL http://machine/impots/main.aspx?client=mobile&action=calcul. A estrutura MVC facilita a criação de uma aplicação deste tipo. Fica da seguinte forma:

Image

O bloco [Classes métier, Classes d'accès aux données] não sofre alterações. Trata-se, de facto, de uma parte indiferente para o cliente. O bloco [Contrôleur] sofre poucas alterações, mas deve ter em conta um novo parâmetro na solicitação: o parâmetro [client], que indica com que tipo de cliente se está a lidar. O bloco [vues] deve gerar visualizações para cada tipo de cliente. Pode ser interessante ter em conta, desde a conceção da aplicação, a presença do parâmetro [client] na consulta, mesmo que o objetivo a curto ou médio prazo se limite apenas aos navegadores. Se, posteriormente, a aplicação tiver de gerir um novo tipo de cliente, bastará escrever apenas as visualizações adaptadas a esse tipo.

6.2. Exemplo 2

6.2.1. O problema

Propomos aqui abordar o mesmo problema que anteriormente, mas alterando a fonte de dados do objeto [impot] criado pela aplicação web. Na versão anterior, a fonte de dados utilizada fornecia os valores de tabelas «fixas» no código. Desta vez, a nova fonte de dados irá obtê-los de uma fonte de dados ODBC associada a uma base de dados MySQL.

6.2.2. A fonte de dados ODBC

Os dados estarão numa tabela denominada [IMPOTS] de uma base de dados MySQL denominada [dbimpots]. O conteúdo desta tabela será o seguinte:

Image

O proprietário da base de dados é o utilizador [admimpots], com a palavra-passe [mdpimpots]. Associamos uma fonte de dados ODBC a esta base de dados. Antes de o fazer, recordemos primeiro as diferentes formas de aceder a uma base de dados com a plataforma .NET.

Existem inúmeras bases de dados para as plataformas Windows. Para aceder às mesmas, as aplicações utilizam programas denominados controladores (drivers).

Image

No esquema acima, o controlador apresenta duas interfaces:

  • a interface I1 apresentada à aplicação
  • a interface I2 para a base de dados

Para evitar que uma aplicação escrita para uma base de dados B1 tenha de ser reescrita caso se migre para uma base de dados B2 diferente, foi realizado um esforço de normalização na interface I1. Se forem utilizadas bases de dados que utilizem controladores «normalizados», a base B1 será fornecida com um controlador P1, a base de dados B2 com um controlador P2, e a interface I1 destes dois controladores será idêntica. Assim, não será necessário reescrever a aplicação. Assim, será possível, por exemplo, migrar uma base de dados ACCESS para uma base de dados MySQL sem alterar a aplicação.

Existem dois tipos de controladores normalizados:

  • os controladores ODBC (Open DataBase Connectivity)
  • os controladores OLE e DB (Object Linking and Embedding DataBase)

Os controladores ODBC permitem o acesso a bases de dados. As fontes de dados para os controladores OLE e DB são mais variadas: bases de dados, sistemas de correio eletrónico, diretórios, etc. Não há limites. Qualquer fonte de dados pode ser alvo de um controlador OLE DB, caso um editor assim o decida. A vantagem é, evidentemente, grande: obtém-se um acesso uniforme a uma grande variedade de dados.

A plataforma .NET 1.1 é fornecida com três tipos de classes de acesso aos dados:

  1. as classes SQL e Server.NET, para aceder às bases de dados SQL Server da Microsoft
  2. as classes Ole Db.NET, para aceder às bases de dados SGBD que oferecem um controlador OLE DB
  3. as classes odbc.net, para aceder às bases de dados SGBD, que disponibilizam um controlador ODBC

O SGBD MySQL dispõe, há muito tempo, de um controlador ODBC. É este que utilizamos agora. No Windows, selecionamos a opção [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Dependendo da versão do Windows, este caminho pode variar ligeiramente. Obtemos a seguinte aplicação, que nos permitirá criar a nossa fonte ODBC:

Image

Vamos criar uma fonte de dados do sistema, c.a.d. Uma fonte de dados que qualquer utilizador do computador poderá aceder (utiliser.Aussi); acima, selecionamos o separador [Source de données système]. A página apresentada tem um botão [Ajouter] que utilizamos para criar uma nova fonte de dados ODBC:

Image

O assistente solicita que se selecione o controlador ODBC a utilizar. O Windows inclui vários controladores ODBC pré-instalados. O controlador ODBC do MySQL não faz parte desse conjunto. Por isso, é necessário instalá-lo previamente. Pode encontrá-lo na Internet digitando a sequência de palavras-chave «MySQL ODBC» ou ainda «MyODBC» num motor de busca. Neste caso, instalámos o controlador [MySQL ODBC 3.51]. Selecionamo-lo e passamos para o [Terminer]:

Image

É necessário fornecer alguns dados:

Data Source Name
o nome que designará a fonte de dados ODBC. Qualquer aplicação do Windows poderá aceder à fonte através deste nome
Description
um texto arbitrário que descreva a fonte de dados
Host Name
o nome do computador que aloja o SGBD MySQL. Neste caso, trata-se da máquina local. Poderia ser uma máquina remota. Isto permitiria a uma aplicação Windows aceder a uma base de dados remota sem qualquer codificação específica. Este é um grande vantagem da fonte ODBC.
Database Name
Um SGBD MySQL pode gerir várias bases de dados. Aqui especifica-se qual se pretende gerir: dbimpots
User
nome de um utilizador registado no SGBD MySQL. É com este nome que serão efetuados os acessos à fonte de dados. Neste caso: admimpots
Password
a palavra-passe deste utilizador. Aqui: mdpimpots
Port
porta de trabalho do SGBD MySQL. Por predefinição, é a porta 3306. Não a alterámos

Feito isto, testamos a validade dos nossos parâmetros de ligação com o botão [Test Data Source]:

Image

Feito isto, temos a certeza da nossa fonte de dados ODBC. Podemos agora utilizá-la. Repetimos o [OK] tantas vezes quantas forem necessárias para sair do assistente ODBC.

Se o leitor não dispuser do SGBD mySQL, pode obtê-lo gratuitamente na URL [http://www.mysql.com]. Apresentamos abaixo o procedimento para criar uma fonte ODBC com o Access. Os primeiros passos são idênticos aos descritos anteriormente. Adiciona-se uma nova fonte de dados do sistema:

Image

O controlador selecionado será o [Microsoft Access Driver]. Executa-se o [Terminer] para passar à definição da fonte ODBC:

Image

As informações a fornecer são as seguintes:

Nom de la source de données
o nome que designará a fonte de dados ODBC. Qualquer aplicação Windows poderá aceder à fonte através deste nome
Description
um texto arbitrário que descreva a fonte de dados
Base de données
o nome completo do ficheiro ACCESS a ser utilizado

6.2.3. Uma nova classe de acesso aos dados

Voltemos à estrutura MVC da nossa aplicação:

Image

No esquema acima, a classe [impotsData] é responsável por recuperar os dados. Nesta instância, terá de o fazer a partir da base de dados MySQL [dbimpots]. Sabemos, desde a versão anterior desta aplicação, que a [impotsData] é uma classe abstrata que tem de ser derivada sempre que se pretenda adaptá-la a uma nova fonte de dados. Recorde-se a estrutura desta classe abstrata:


Imports System.Collections

Namespace st.istia.univangers.fr
    Public MustInherit Class impotsData
        Protected limites() As Decimal
        Protected coeffr() As Decimal
        Protected coeffn() As Decimal
        Protected checked As Boolean
        Protected valide As Boolean

        ' método de acesso aos dados
        Public MustOverride Function getData() As Object()

        ' método de verificação dos dados
        Protected Function checkData() As Integer
            ' verifica os dados recolhidos
...
        End Function

        ' verifica a validade do conteúdo de uma tabela
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
        ...
        End Function
    End Class
End Namespace

A classe que deriva de [impotsData] deve implementar dois métodos:

  • um construtor, caso o construtor sem argumentos de [impotsData] não seja adequado
  • o método [getData], que devolve os três vetores (limites, coeffr, coeffn)

Criamos a classe [impotsODBC], que irá buscar os dados (limites, coeffr, coeffn) a partir de uma fonte ODBC, à qual daremos o nome:


Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System

Namespace st.istia.univangers.fr
    Public Class impotsODBC
        Inherits impotsData

        ' variáveis de instância
        Protected DSNimpots As String

        ' construtor
        Public Sub New(ByVal DSNimpots As String)
            ' regista-se a informação relativa às três variáveis
            Me.DSNimpots = DSNimpots
        End Sub

        Public Overrides Function getdata() As Object()
            ' inicializa as três tabelas «limites», «coeffr» e «coeffn» a partir
            ' do conteúdo da tabela [impots] da base de dados ODBC DSNimpots
            ' limites, coeffr e coeffn são as três colunas desta tabela
            ' pode lançar várias exceções

            Dim connectString As String = "DSN=" + DSNimpots + ";"         ' chaîne de connexion à la base
            Dim impotsConn As OdbcConnection = Nothing         ' la connexion
            Dim sqlCommand As OdbcCommand = Nothing         ' la commande SQL
            ' a consulta SELECT
            Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
            ' tabelas para recuperar os dados
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
            Try
                ' tenta-se aceder à base de dados
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
                ' está a ser criado um objeto de comando
                sqlCommand = New OdbcCommand(selectCommand, impotsConn)
                ' executa-se a consulta
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
                ' Análise da tabela recuperada
                While myReader.Read()
                    ' os dados da linha atual são colocados nas tabelas
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' libertação dos recursos
                myReader.Close()
                impotsConn.Close()
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' as tabelas dinâmicas são transferidas para tabelas estáticas
            Me.limites = New Decimal(aLimites.Count - 1) {}
            Me.coeffr = New Decimal(aLimites.Count - 1) {}
            Me.coeffn = New Decimal(aLimites.Count - 1) {}
            Dim i As Integer
            For i = 0 To aLimites.Count - 1
                limites(i) = Decimal.Parse(aLimites(i).ToString())
                coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
                coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
            Next i
            ' verifica-se os dados obtidos
            Dim erreur As Integer = checkData()
            ' se os dados não forem válidos, é lançada uma exceção
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' caso contrário, devolvem-se as três tabelas
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Vamos analisar o fabricante:


        ' construtor
        Public Sub New(ByVal DSNimpots As String)
            ' regista-se as três informações
            Me.DSNimpots = DSNimpots
        End Sub

Recebe como parâmetro o nome da fonte ODBC, na qual se encontram os dados a adquirir. O construtor limita-se a memorizar esse nome. O método [getData] é responsável por ler os dados da tabela [impots] e colocá-los em três matrizes (limites, coeffr, coeffn). Vamos comentar o seu código:

  • os parâmetros da ligação à fonte de dados ODBC estão definidos, mas esta não está aberta
             ' cadeia de ligação à base de dados
            Dim connectString As String = "DSN=" + DSNimpots + ";"
            ' criamos um objeto de ligação à base de dados — esta ligação não está aberta
            Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
  • São definidos três objetos [ArrayList] para recuperar os dados da tabela [impots]:

             ' tabelas para recuperar os dados
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
  • Todo o código de acesso à base de dados está envolto num try/catch para gerir um eventual erro de acesso. Estabelece-se a ligação à base de dados:

                 ' tenta-se aceder à base de dados
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • Executa-se o comando [select] na ligação aberta. Obtém-se um objeto [OdbcDataReader] que nos permitirá percorrer as linhas da tabela resultante da consulta SELECT:

                 ' cria-se um objeto de comando
                Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
                ' executa-se a consulta
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
  • Percorremos a tabela de resultados, linha a linha. Para tal, utilizamos o método [Read] do objeto [OdbcDataReader] obtido anteriormente. Este método faz duas coisas:
    • avança uma linha na tabela. Inicialmente, estamos posicionados antes da primeira linha
    • retorna o valor booleano [true] se tiver sido possível avançar, e [false] caso contrário, sendo que este último caso indica que todas as linhas foram processadas.

As colunas da linha atual do objeto [OdbcDataReader] são obtidas por OdbcDataReader. Obtém-se um objeto que representa o valor da coluna. Percorremos toda a tabela para colocar o seu conteúdo nos três objetos [ArrayList]:


                 ' Análise da tabela recuperada
                While myReader.Read()
                    ' os dados da linha atual são colocados nas tabelas
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
  • Feito isto, libertamos os recursos associados à ligação:
                 ' libertação dos recursos
                myReader.Close()
                impotsConn.Close()
  • O conteúdo dos três objetos [ArrayList] é transferido para três tabelas clássicas:

             ' as tabelas dinâmicas são transferidas para tabelas estáticas
            limites = New Decimal(aLimites.Count - 1) {}
            coeffr = New Decimal(aLimites.Count - 1) {}
            coeffn = New Decimal(aLimites.Count - 1) {}
            Dim i As Integer
            For i = 0 To aLimites.Count - 1
                limites(i) = CType(aLimites(i), Decimal)
                coeffR(i) = CType(aCoeffR(i), Decimal)
                coeffN(i) = CType(aCoeffN(i), Decimal)
            Next i
  • Assim que os dados da tabela [impots] forem transferidos para as três tabelas, basta verificar o conteúdo destas utilizando o método [checkData] da classe base [impotsData]:
             ' verifica-se os dados obtidos
            Dim erreur As Integer = checkData()
             ' se os dados não forem válidos, é lançada uma exceção
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' caso contrário, devolvem-se as três tabelas
            Return New Object() {limites, coeffr, coeffn}

6.2.4. Testes da classe de acesso aos dados

Um programa de teste poderia ser o seguinte:

Option Explicit On 
Option Strict On

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

Namespace st.istia.univangers.fr

     ' página de teste
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' programa interativo de cálculo de impostos
             ' o utilizador introduz três dados através do teclado: casado nbEnfants salário
             ' o programa apresenta então o imposto a pagar
            Const syntaxe1 As String = "pg DSNimpots"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' verificação dos parâmetros do programa
            If arguments.Length <> 1 Then
                 ' mensagem de erro
                Console.Error.WriteLine(syntaxe1)
                 ' fim
                Environment.Exit(1)
            End If
             ' recuperam-se os argumentos
            Dim DSNimpots As String = arguments(0)

             ' criação de um objeto de imposto
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsODBC(DSNimpots))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

             ' loop infinito
            While True
                 ' inicialmente sem erros
                Dim erreur As Boolean = False

                 ' solicitam-se os parâmetros para o cálculo do imposto
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()

                 ' há algo a fazer?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' verificação do número de argumentos na linha introduzida
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                     ' verificação da validade dos parâmetros
                     ' casado
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                     ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                     ' salário
                    salaire = 0
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                End If
                If Not erreur Then
                     ' os parâmetros estão corretos — calcula-se o imposto
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

A aplicação é iniciada com um parâmetro:

  • DSNimpots: nome da fonte de dados ODBC a ser utilizada

O cálculo do imposto é efetuado com a ajuda de um objeto do tipo [impot], criado logo que a aplicação é iniciada:


             ' criação de um objeto de imposto
            Dim objImpôt As impot = Nothing
            Try
                objImpot = New impot(New impotsODBC(DSNimpots))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try

Depois de inicializada, a aplicação solicita repetidamente ao utilizador que introduza no teclado as três informações necessárias para calcular o seu imposto:

  • o seu estado civil: o para casado, n para solteiro
  • o número de filhos
  • o seu salário anual

Todas as classes são compiladas:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004  19:34             7 168 impot.dll
01/04/2004  19:31             1 360 impots.vb
21/04/2004  08:23             1 311 impotsArray.vb
21/04/2004  08:26             1 634 impotsData.vb
01/04/2004  19:34             2 735 impotsODBC.vb
01/04/2004  19:32             3 210 testimpots.vb

O programa de teste é, por sua vez, compilado:

dos>vbc /r:impot.dll testimpots.vb
dir>dir
01/04/2004  19:34             7 168 impot.dll
01/04/2004  19:31             1 360 impots.vb
21/04/2004  08:23             1 311 impotsArray.vb
21/04/2004  08:26             1 634 impotsData.vb
01/04/2004  19:34             2 735 impotsODBC.vb
01/04/2004  19:34             6 144 testimpots.exe
01/04/2004  19:32             3 210 testimpots.vb

O programa de teste é executado primeiro com a fonte de dados ODBC MySQL:

dos>testimpots odbc-mysql-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)

Mudamos a fonte para ODBC, passando a utilizar uma fonte do Access:

dos>testimpots odbc-access-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 F

6.2.5. As vistas da aplicação web

São as mesmas da aplicação anterior: formulaire.aspx e erreurs.aspx

6.2.6. Os controladores da aplicação [global.asax, main.aspx]

Apenas o controlador [global.asax] deve ser alterado. Este é, de facto, responsável por criar o objeto [impot] no arranque da aplicação. O construtor deste objeto tem como único parâmetro o objeto do tipo [impotsData], responsável por recuperar os dados. Este parâmetro altera-se, portanto, para cada novo tipo de fonte de dados. O controlador [global.asax.vb] passa a ter o seguinte aspeto:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' cria-se um objeto de imposto
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' o objeto é inserido na aplicação
            Application("objImpot") = objImpot
            ' sem erros
            Application("erreur") = False
        Catch ex As Exception
            'ocorreu um erro, que é registado na aplicação
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

A fonte de dados do objeto [impot] é agora um objeto [impotODBC]. Este último tem como parâmetro o nome DSN da fonte de dados ODBC a ser utilizada. Em vez de escrever este nome diretamente no código, coloca-se no ficheiro de configuração [web.config] da aplicação:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="DSNimpots" value="odbc-mysql-dbimpots" />
    </appSettings>
</configuration>

Sabe-se que o valor de uma chave C da secção <appSettings> do ficheiro [web.config] é obtido no código da aplicação através de [ConfigurationSettings.AppSettings(C)].

Para determinar a causa da exceção, a mensagem da mesma é registada na aplicação, de modo a que permaneça disponível para consultas. O controlador [main.aspx.vb] incluirá esta mensagem na sua lista de erros:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' antes de mais nada, verifica-se se a aplicação conseguiu inicializar-se corretamente
        If CType(Application("erreur"), Boolean) Then
            ' redireciona-se para a página de erros
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...(" + Application("message").ToString + ")")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If
        ' recuperamos a ação a realizar
...

6.2.7. Resumo das alterações

A aplicação está pronta para ser testada. Enumeremos as alterações introduzidas em relação à versão anterior:

  1. foi criada uma nova classe de acesso aos dados
  2. o controlador [global.asax.vb] foi alterado em dois pontos: criação do objeto [impot] e registo na aplicação da mensagem relacionada com uma eventual exceção
  3. o controlador [main.aspx.vb] foi alterado num ponto para apresentar a mensagem de exceção anterior
  4. foi adicionado um ficheiro [web.config]

O trabalho de modificação foi realizado essencialmente no 1, c.a.d, fora da aplicação web. Isto foi possível graças à arquitetura MVC da aplicação, que separa o controlador das classes de negócio. É aí que reside todo o interesse desta arquitetura. Seria possível demonstrar que, com um ficheiro de configuração [web.config] adequado, se poderia ter evitado qualquer alteração no controlador da aplicação. É possível definir no [web.config] o nome da classe de acesso aos dados a instanciar dinamicamente, bem como os vários parâmetros necessários para essa instanciação. Com estas informações, o [global.asax] pode instanciar o objeto de acesso aos dados. Mudar de fonte de dados equivale, então, a:

  • criar a classe de acesso a essa fonte, caso ainda não exista
  • modificar o ficheiro [web.config] para permitir a criação dinâmica de uma instância dessa classe em [global.asax]

6.2.8. Teste da aplicação web

Todos os ficheiros anteriores são colocados numa pasta <application-path>.

Image

Nesta pasta, é criada uma subpasta [bin], na qual é colocado o assembly [impot.dll] resultante da compilação dos ficheiros das classes de negócio: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Recorda-se abaixo o comando de compilação necessário:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004  19:34             7 168 impot.dll
01/04/2004  19:31             1 360 impots.vb
21/04/2004  08:23             1 311 impotsArray.vb
21/04/2004  08:26             1 634 impotsData.vb
01/04/2004  19:34             2 735 impotsODBC.vb
01/04/2004  19:32             3 210 testimpots.vb

O ficheiro [impot.dll] acima referido deve ser colocado em <application-path>\bin para que a aplicação web tenha acesso ao mesmo. O servidor Cassini é iniciado com os parâmetros (<application-path>,/impots2). Os testes apresentam os mesmos resultados que na versão anterior, sendo a presença da base de dados transparente para o utilizador. No entanto, para ilustrar essa presença, fazemos com que a fonte ODBC não esteja disponível, parando os ficheiros SGBD e MySQL, e acedemos à URL [http://localhost/impots2/main.aspx]. Obtemos a seguinte resposta:

Image

6.3. Exemplo 3

6.3.1. O problema

Propomos aqui abordar o mesmo problema, alterando novamente a fonte de dados do objeto [impot] criado pela aplicação web. Desta vez, a nova fonte de dados será uma base de dados ACCESS, à qual acederemos através de um controlador OLEDB. O nosso objetivo é mostrar outra forma de aceder a uma base de dados.

6.3.2. A fonte de dados OLEDB

Os dados estarão numa tabela chamada [IMPOTS], pertencente a uma base de dados ACCESS. O conteúdo desta tabela será o seguinte:

Image

6.3.3. A classe de acesso aos dados

Voltemos à estrutura MVC da nossa aplicação:

Image

  • No esquema acima, a classe [impotsData] é responsável por recuperar os dados. Desta vez, terá de o fazer a partir de uma fonte OLEDB.

Criamos a classe [impotsOLEDB], que irá buscar os dados (limites, coeffr, coeffn) numa fonte ODBC, à qual daremos o nome:


Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb

Namespace st.istia.univangers.fr
    Public Class impotsOLEDB
        Inherits impotsData

        ' variáveis de instância
        Protected chaineConnexion As String

        ' construtor
        Public Sub New(ByVal chaineConnexion As String)
            ' regista-se a informação
            Me.chaineConnexion = chaineConnexion
        End Sub

        Public Overrides Function getData() As Object()
            ' inicializa os três tabuletos «limites», «coeffr» e «coeffn» a partir
            ' do conteúdo da tabela [impots] da base de dados OLEDB [chaineConnexion]
            ' «limites», «coeffr» e «coeffn» são as três colunas desta tabela
            ' pode lançar várias exceções

            ' cria-se um objeto DataAdapter para ler os dados da fonte OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
            ' cria-se uma imagem na memória do resultado da consulta
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' recupera-se o conteúdo da tabela «impots»
            Dim lignesImpots As DataRowCollection = contenu.Rows
            ' dimensiona-se as tabelas de receção
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' transfere-se o conteúdo da tabela «impots» para as matrizes
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' linha i da tabela
                    ligne = lignesImpots.Item(i)
                    ' recupera-se o conteúdo da linha
                    limites(i) = CType(ligne.Item(0), Decimal)
                    coeffr(i) = CType(ligne.Item(1), Decimal)
                    coeffn(i) = CType(ligne.Item(2), Decimal)
                Next
            Catch
                Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
            End Try
            ' verifica-se os dados obtidos
            Dim erreur As Integer = checkData()
            ' se os dados não forem válidos, lança-se uma exceção
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' caso contrário, devolvem-se os três tabuletos
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Vamos analisar o fabricante:


         ' construtor
        Public Sub New(ByVal chaineConnexion As String)
             ' regista-se as três informações
            Me.chaineConnexion = chaineConnexion
        End Sub

Recebe como parâmetro a cadeia de ligação da fonte OLEDB, na qual se encontram os dados a adquirir. O controlador limita-se a memorizá-la. Uma cadeia de ligação contém todos os parâmetros necessários para que o controlador OLEDB se ligue à fonte OLEDB. É, geralmente, bastante complexa. Para descobrir a cadeia de ligação das bases de dados ACCESS, pode-se recorrer à ferramenta [WebMatrix]. Inicie esta ferramenta. Ela apresenta uma janela que permite ligar-se a uma fonte de dados:

Através do ícone indicado pela seta acima, é possível estabelecer uma ligação a dois tipos de bases de dados da Microsoft: SQL Server e ACCESS. Vamos selecionar ACCESS:

Image

Utilizámos o botão [...] para selecionar a base de dados ACCESS. Confirmamos o assistente. No separador [Data], os ícones representam a ligação:

Image

Agora, vamos criar um novo ficheiro .aspx através do [Files/New File]:

Image

Obtemos uma folha em branco na qual podemos desenhar a nossa interface web:

Image

Arraste a tabela [impots] do separador [Data] para a folha acima. Obtemos o seguinte resultado:

Image

Clicamos com o botão direito do rato no objeto [AccessDataSourceControl] abaixo para aceder às seguintes propriedades:

Image

A cadeia de ligação OLEDB à base de dados ACCESS é fornecida pela propriedade [ConnectionString] acima:

Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb

Vemos que esta cadeia é composta por uma parte fixa e uma parte variável, que é simplesmente o nome do ficheiro ACCESS. Utilizaremos este facto para gerar a cadeia de ligação à nossa fonte de dados OLEDB.

Voltemos agora à nossa classe [impotsOLEDB]. O método [getData] é responsável por ler os dados da tabela [impots] e colocá-los em três matrizes (limites, coeffr, coeffn). Vamos comentar o seu código:

  • definimos o objeto [DataAdapter], que nos permitirá transferir para a memória o resultado de uma consulta SELECT da tabela SQL. Para tal, definimos a consulta [select] a executar e associamo-la ao objeto [DataAdapter]. O construtor deste último requer também a cadeia de ligação que deverá utilizar para se ligar à fonte OLEDB

             ' cria-se um objeto DataAdapter para ler os dados da fonte OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • executa-se o comando [select] através do método [Fill] do objeto [DataAdapter]. O resultado do [select] é inserido num objeto [DataTable] criado para o efeito. Um objeto [DataTable] é a representação em memória de uma tabela de base de dados, c.a.d, um conjunto de linhas e colunas. Tratamos uma exceção que pode ocorrer se, por exemplo, a cadeia de ligação estiver incorreta.

             ' cria-se uma imagem na memória do resultado da consulta
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
  • em [contenu], temos a tabela [impots], que é recuperada pelo [select]. Um objeto [DataTable] é uma tabela, ou seja, um conjunto de linhas. Estas são acessíveis através da propriedade [rows] de [datatable]:

             ' recupera-se o conteúdo da tabela «impots»
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • cada elemento da coleção [lignesImpots] é um objeto do tipo [DataRow] que representa uma linha da tabela. Esta tabela possui colunas acessíveis através do objeto [DataRow], por meio da sua propriedade [Item]. [DataRow].[Item(i)] é a coluna n.º i da linha [DataRow]. Ao percorrer a coleção de linhas (a coleção DataRows de lignesImpots) e as coleções de colunas de cada linha, é possível obter a tabela na sua totalidade:
             ' dimensiona-se as tabelas de receção
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
             ' transfere-se o conteúdo da tabela «impots» para as matrizes
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                     ' linha i da tabela
                    ligne = lignesImpots.Item(i)
                     ' recupera-se o conteúdo da linha
                    limites(i) = CType(ligne.Item(0), Decimal)
                    coeffr(i) = CType(ligne.Item(1), Decimal)
                    coeffn(i) = CType(ligne.Item(2), Decimal)
                Next
            Catch
                Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
            End Try
  • Assim que os dados da tabela [impots] forem transferidos para as três tabelas, basta verificar o conteúdo destas utilizando o método [checkData] da classe base [impotsData]:
            ' verifica-se os dados obtidos
            Dim erreur As Integer = checkData()
             ' se os dados não forem válidos, lança-se uma exceção
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' caso contrário, devolvem-se os três tabuletos
            Return New Object() {limites, coeffr, coeffn}

6.3.4. Testes da classe de acesso aos dados

Um programa de teste poderia ser o seguinte:

Option Explicit On 
Option Strict On

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

Namespace st.istia.univangers.fr

     ' página de teste
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' programa interativo de cálculo de impostos
             ' o utilizador introduz três dados através do teclado: casado nbEnfants salário
             ' o programa apresenta então o imposto a pagar
            Const syntaxe1 As String = "pg bdACCESS"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' verificação dos parâmetros do programa
            If arguments.Length <> 1 Then
                 ' mensagem de erro
                Console.Error.WriteLine(syntaxe1)
                 ' fim
                Environment.Exit(1)
            End If
             ' recuperam-se os argumentos
            Dim chemin As String = arguments(0)
             ' prepara-se a cadeia de ligação
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' criação de um objeto de imposto
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsOLEDB(chaineConnexion))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

             ' loop infinito
            While True
                 ' inicialmente, sem erros
                Dim erreur As Boolean = False

                 ' solicitam-se os parâmetros para o cálculo do imposto
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()

                 ' Há algo a fazer?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' verificação do número de argumentos na linha introduzida
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                     ' verificação da validade dos parâmetros
                     ' casado
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                     ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                     ' salário
                    salaire = 0
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                End If
                If Not erreur Then
                     ' os parâmetros estão corretos — calcula-se o imposto
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

A aplicação é executada com um parâmetro:

  • bdACCESS: nome do ficheiro ACCESS a ser processado

O cálculo do imposto é efetuado com a ajuda de um objeto do tipo [impot], criado logo que a aplicação é iniciada:


             ' recuperam-se os argumentos
            Dim chemin As String = arguments(0)
            ' prepara-se a cadeia de ligação
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' criação de um objeto de imposto
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsOLEDB(chaineConnexion))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

A cadeia de ligação à fonte OLEDB foi criada a partir das informações obtidas com [WebMatrix].

Depois de inicializada, a aplicação solicita repetidamente ao utilizador que introduza no teclado as três informações necessárias para calcular o seu imposto:

  • o seu estado civil: o para casado, n para solteiro
  • o número de filhos
  • o seu salário anual

Todas as classes são compiladas:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
dos>vbc /r:impot.dll testimpots.vb

O ficheiro [impots.mdb] é colocado na pasta da aplicação de teste e esta é executada da seguinte forma:

dos>testimpots impots.mdb
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)

É possível iniciar a aplicação com um ficheiro ACCESS incorreto:

dos>testimpots xx
L'erreur suivante s'est produite : Erreur d'accès à la base de données (Ficher 'D:\data\serge\devel\aspnet\poly\chap5\impots\3\xx' introuvable.)

6.3.5. As vistas da aplicação web

São as mesmas da aplicação anterior: formulaire.aspx e erreurs.aspx

6.3.6. Os controladores da aplicação [global.asax, main.aspx]

Apenas o controlador [global.asax] deve ser alterado. Este é, de facto, responsável por criar o objeto [impot] no arranque da aplicação. O construtor deste objeto tem como único parâmetro o objeto do tipo [impotsData], responsável por recuperar os dados. Este parâmetro altera-se, portanto, uma vez que se muda de fonte de dados. O controlador [global.asax.vb] passa a ter o seguinte código:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' criação de um objeto de imposto
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' insere-se o objeto na aplicação
            Application("objImpot") = objImpot
            ' sem erros
            Application("erreur") = False
        Catch ex As Exception
            'ocorreu um erro, que é registado na aplicação
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

A fonte de dados do objeto [impot] é agora um objeto [impotOLEDB]. Este último tem como parâmetro a cadeia de ligação da fonte de dados OLEDB a ser utilizada. Esta encontra-se no ficheiro de configuração [web.config] da aplicação:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="chaineConnexion" 
        value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots2\impots.mdb" />
    </appSettings>
</configuration>

O controlador [main.aspx] não sofre alterações.

6.3.7. Resumo das alterações

A aplicação está pronta para ser testada. Enumeremos as alterações introduzidas em relação à versão anterior:

  1. foi criada uma nova classe de acesso aos dados
  2. o controlador [global.asax.vb] foi alterado num ponto: criação do objeto [impot]
  3. foi adicionado um ficheiro [web.config]

6.3.8. Teste da aplicação web

Todos os ficheiros acima referidos são colocados numa pasta <application-path>.

Image

Nesta pasta, é criada uma subpasta [bin], na qual é colocado o conjunto [impot.dll] resultante da compilação dos ficheiros das classes de negócio: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. Recorda-se abaixo o comando de compilação necessário:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb

O ficheiro [impot.dll] produzido por este comando deve ser colocado em <application-path>\bin para que a aplicação web tenha acesso ao mesmo. O servidor Cassini é iniciado com os parâmetros (<application-path>,/impots3). Os testes apresentam os mesmos resultados que na versão anterior.

6.4. Exemplo 4

6.4.1. O problema

Propomos agora transformar a nossa aplicação numa aplicação de simulação de cálculos de impostos. Um utilizador poderá efetuar cálculos sucessivos de impostos, os quais lhe serão apresentados numa nova vista semelhante a esta:

Image

6.4.2. A estrutura MVC da aplicação

A estrutura MVC da aplicação passa a ser a seguinte:

Image

Aparece uma nova vista [simulations.aspx], da qual acabámos de apresentar uma captura de ecrã. A classe de acesso aos dados será a classe [impotsODBC] do exemplo 2.

6.4.3. As vistas da aplicação web

A vista [erreurs.aspx] não sofre alterações. A vista [formulaire.aspx] sofre uma ligeira alteração. Com efeito, o montante do imposto já não aparece nesta vista. Encontra-se agora na vista [simulations.aspx]. Assim, no arranque, a página apresentada ao utilizador é a seguinte:

Image

Além disso, a vista [formulaire] inclui um script JavaScript que verifica a validade dos dados introduzidos antes de os enviar para o servidor, como mostra o exemplo seguinte:

Image

O código de apresentação é o seguinte:


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
        <script language="javascript">
        function calculer(){
          // verificação dos parâmetros antes de os enviar para o servidor
        with(document.frmImpots){
          //número de filhos
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // o modelo não foi verificado
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//if
          //salário
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // o modelo não foi verificado
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//se
          // Está tudo bem — enviamos o formulário para o servidor
          submit();
        }//com
      }//calcular  
        </script>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <form name="frmImpots" method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui 
                      <INPUT type="radio"  value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="button" value="Calculer" onclick="calculer()">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
            <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Os campos dinâmicos da página são os das versões anteriores. O campo dinâmico relativo ao montante do imposto desapareceu. O botão [Calculer] já não é um botão do tipo [submit]. É do tipo [button] e, quando clicado, a função JavaScript [calculer()] é executada:


                <INPUT type="button" value="Calculer" onclick="calculer()">

Atribuímos ao formulário o nome [frmImpots] para podermos referenciá-lo no script [calculer]:


        <form name="frmImpots" method="post" action="main.aspx?action=calcul">

A função JavaScript [calculer] utiliza expressões regulares para verificar a validade dos campos dos formulários [document.frmImpots.txtEnfants] e [document.frmImpots.txtSalaire]. Se os valores introduzidos estiverem corretos, são enviados para o servidor pela função [document.frmImpots.submit()].

A página de apresentação obtém os seus campos dinâmicos a partir do seu controlador [formulaire.aspx.vb], da seguinte forma:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

    ' campos da página
    Protected rdouichecked As String
    Protected rdnonchecked As String
    Protected txtEnfants As String
    Protected txtSalaire As String
    Protected txtImpot As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' recuperamos a solicitação anterior no contexto
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' preparamos a página a apresentar
        ' botões de opção
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' o resto
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
    End Sub
End Class

O controlador [formulaire.aspx.vb] é idêntico às versões anteriores, com a única diferença de que já não precisa de recuperar o campo [txtImpot] do contexto, uma vez que esse campo desapareceu da página.

A vista [simulations.aspx] apresenta-se visualmente da seguinte forma:

Image

e corresponde ao seguinte código de apresentação:


<%@ page src="simulations.aspx.vb" inherits="simulations" autoeventwireup="false" %>
<HTML>
    <HEAD>
        <title>simulations</title>
    </HEAD>
    <body>
        <P>Résultats des simulations</P>
        <HR width="100%" SIZE="1">
        <table>
            <tr>
                <th>
                    Marié</th>
                <th>
                    Enfants</th>
                <th>
                    Salaire annuel (euro)</th>
                <th>
                    Impôt à payer (euro)</th>
            </tr>
            <%=simulationsHTML%>
        </table>
        <p></p>
        <a href="<%=href%>">
            <%=lien%>
        </a>
    </body>
</HTML>

Este código apresenta três campos dinâmicos:

simulationsHTML
código HTML de uma lista de simulações na forma de linhas de tabela HTML
href
URL de um link
lien
texto do link

São gerados pela parte controladora [simulations.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class simulations
    Inherits System.Web.UI.Page

    Protected simulationsHTML As String = ""
    Protected href As String
    Protected lien As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'recuperam-se as simulações no contexto
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' cada simulação é um array de 4 elementos do tipo string
        Dim simulation() As String
        Dim i, j As Integer
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML += "</tr>" + ControlChars.CrLf
        Next
        ' recuperam-se os restantes elementos do contexto
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
    End Sub
End Class

O controlador da página recupera informações colocadas pelo controlador da aplicação no contexto da página:

Context.Items("simulations")
objeto ArrayList contendo a lista de simulações a apresentar. Cada elemento é um tabuleiro com 4 cadeias de caracteres que representam as informações (casado, filhos, salário, imposto) da simulação.
Context.Items("href")
URL de um link
Context.Items("lien")
texto do link

6.4.4. Os controladores [global.asax, main.aspx]

Recordemos o esquema MVC da nossa aplicação:

Image

O controlador [main.aspx] tem de processar três ações:

  • init: corresponde ao primeiro pedido do cliente. O controlador apresenta a vista [formulaire.aspx]
  • calcul: corresponde ao pedido de cálculo do imposto. Se os dados do formulário de introdução estiverem corretos, o imposto é calculado através da classe de negócio [impotsODBC]. O controlador devolve ao cliente a vista [simulations.aspx] com o resultado da simulação atual e de todas as anteriores. Se os dados do formulário de introdução estiverem incorretos, o controlador devolve a vista [erreurs.aspx] com a lista de erros e um link para regressar ao formulário.
  • retorno: corresponde ao regresso ao formulário após um erro. O controlador apresenta a vista [formulaire.aspx] tal como foi validada antes do erro.

Nesta nova versão, apenas a ação [calcul] sofreu alterações. Com efeito, se os dados forem válidos, esta ação deve conduzir à vista [simulations.aspx], enquanto anteriormente conduzia à vista [formulaire.aspx]. O controlador [main.aspx.vb] passa a ser o seguinte:


Imports System
...

Public Class main
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' antes de mais nada, verifica-se se a aplicação conseguiu inicializar-se corretamente
...
        ' executa-se a ação
        Select Case action
            Case "init"
                ' inicializar a aplicação
                initAppli()
            Case "calcul"
                ' cálculo do imposto
                calculImpot()
            Case "retour"
                ' regresso ao formulário
                retourFormulaire()
            Case "effacer"
                ' inicialização da aplicação
                initAppli()
            Case Else
                ' ação desconhecida = inicialização
                initAppli()
        End Select
    End Sub

...
    Private Sub calculImpot()
        ' guardar os dados introduzidos
        Session.Item("formulaire") = Request.Form
        ' verifica-se a validade dos dados introduzidos
        Dim erreurs As ArrayList = checkData()
        ' se houver erros, estes são assinalados
        If erreurs.Count <> 0 Then
            ' prepara-se a página de erros
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' aqui não há erros — calcula-se o imposto
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' adiciona-se o resultado às simulações existentes
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' adição da simulação atual
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' colocam-se as simulações na sessão e no contexto
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' exibe-se a página de resultados
        context.Items("href") = "main.aspx?action=retour"
        context.Items("lien") = "Retour au formulaire"
        Server.Transfer("simulations.aspx", True)
    End Sub
...
End Class

No texto acima, mantivemos apenas o que era necessário para compreender as alterações que se encontram exclusivamente na função [calculImpots]:

  • em primeiro lugar, a função guarda o formulário [Request.Form] na sessão, para poder regenerar o formulário no estado em que foi validado. Isto deve ser feito em todos os casos, uma vez que, quer a operação resulte na resposta [erreurs.aspx] ou na resposta [simulations.aspx], regressa-se ao formulário através do link [Retour au formulaire]. Para o restabelecer corretamente, é necessário ter guardado previamente os seus valores na sessão.
  • Se os dados introduzidos estiverem corretos, a função insere a simulação atual (casado, filhos, salário, imposto) na lista de simulações. Esta lista encontra-se na sessão associada à chave «simulações».
  • A lista de simulações é recolocada na sessão para utilização futura. É também colocada no contexto atual, pois é aí que a vista [simulations.aspx] a espera
  • A vista [simulations.aspx] é apresentada assim que as restantes informações de que necessita forem colocadas no contexto

6.4.5. Resumo das alterações

A aplicação está pronta para ser testada. Enumeremos as alterações introduzidas em relação às versões anteriores:

  1. foi criada uma nova vista
  2. o controlador [main.aspx.vb] foi alterado num ponto: tratamento da ação [calcul]

6.4.6. Teste da aplicação web

Convidamos o leitor a realizar os testes. Recordamos o procedimento. Todos os ficheiros da aplicação estão colocados numa pasta <application-path>. Nesta pasta, é criada uma subpasta [bin], na qual se encontra o assembly [impot.dll], resultante da compilação dos ficheiros das classes de negócio: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. O ficheiro [impot.dll] gerado por este comando deve ser colocado em <application-path>\bin para que a aplicação web tenha acesso ao mesmo. O servidor Cassini é iniciado com os parâmetros (<application-path>,/impots4).

6.5. Conclusion

Os exemplos anteriores demonstraram, num caso concreto, os mecanismos habitualmente utilizados no desenvolvimento web. Utilizámos sistematicamente a arquitetura MVC pelo seu valor pedagógico. Teríamos podido tratar estes mesmos exemplos de forma diferente e talvez mais simples sem esta arquitetura. No entanto, esta oferece grandes vantagens assim que a aplicação se torna um pouco mais complexa, com várias páginas.

Poderíamos dar continuidade aos nossos exemplos de várias formas. Eis algumas delas:

  • o utilizador poderá querer guardar as suas simulações ao longo do tempo. Realizaria simulações no dia D e poderia recuperá-las no dia D+3, por exemplo. Uma solução possível para este problema é a utilização de cookies. Sabemos que o token de sessão entre o servidor e um cliente é transportado por este mecanismo. Poderíamos também utilizá-lo para transportar as simulações entre o cliente e o servidor.
    • Ao mesmo tempo que o servidor envia a página com os resultados das simulações, envia nos seus cabeçalhos HTTP um cookie contendo uma cadeia de caracteres que representa as simulações. Como estas se encontram num objeto [ArrayList], é necessário realizar uma transformação desse objeto para [String]. O servidor atribuiria ao cookie um período de validade, por exemplo, 30 dias.
    • O navegador do cliente armazena os cookies recebidos num ficheiro e reenvia-os sempre que faz uma solicitação a um servidor que lhos tenha enviado, caso ainda estejam válidos (período de validade não ultrapassado). Para efeitos de simulação, o servidor receberá uma cadeia de caracteres [String], que deverá transformar no objeto [ArrayList].

Os cookies são geridos por [Response.Cookies] quando são enviados para o cliente e por [Request.Cookies] quando são recebidos no servidor.

  • O mecanismo anterior pode tornar-se bastante pesado se houver um número significativo de simulações. Além disso, é frequente que um utilizador limpe periodicamente os seus cookies, eliminando-os todos, mesmo que, por outro lado, autorize o seu navegador a utilizá-los. Assim, mais cedo ou mais tarde, perder-se-á o cookie das simulações. Podemos, então, querer armazená-las no servidor em vez de no cliente, numa base de dados, por exemplo. Para associar simulações a um utilizador específico, a aplicação poderia iniciar com uma fase de identificação que exigisse um nome de utilizador e uma palavra-passe, os quais estariam armazenados numa base de dados ou em qualquer outro tipo de repositório de dados.
  • Também poderemos querer garantir a segurança do funcionamento da nossa aplicação. Atualmente, esta parte de duas premissas:
    • o utilizador passa sempre pelo controlador [main.aspx]
    • e, nesse caso, utiliza sempre as ações propostas na página que lhe foi enviada

O que acontece, por exemplo, se o utilizador solicitar diretamente a URL [http://localhost/impots4/formulaire.aspx]? Este caso é pouco provável, uma vez que o utilizador desconhece a existência desta URL. No entanto, deve ser previsto. Pode ser gerido pelo controlador de aplicação [global.asax], que recebe todas as solicitações feitas à aplicação. Desta forma, pode verificar se o recurso solicitado é, de facto, [main.aspx].

Um caso mais provável é o de um utilizador não utilizar as ações presentes na página que o servidor lhe enviou. Por exemplo, o que acontece se o utilizador solicitar diretamente a URL [http://localhost/impots4/main.aspx?action=retour] sem, previamente, preencher o formulário? Vamos experimentar. Obtemos a seguinte resposta:

Image

O servidor falhou. É normal. Para a ação [retour], o controlador espera encontrar na sessão um objeto [NameValueCollection] que represente os valores do formulário que deve apresentar. Não os encontra. O mecanismo do controlador permite dar uma solução elegante a este problema. Para cada pedido, o controlador [main.aspx] pode verificar se a ação solicitada é, de facto, uma das ações da página anteriormente enviada ao utilizador. Pode utilizar-se o seguinte mecanismo:

  • antes de enviar a sua resposta ao cliente, o controlador armazena na sessão deste uma informação que identifica essa página
  • quando recebe um novo pedido do cliente, verifica se a ação solicitada pertence efetivamente à última página enviada a esse cliente
  • as informações que associam as páginas às ações autorizadas nessas páginas podem ser introduzidas no ficheiro de configuração [web.config] da aplicação.
  • A experiência mostra que os controladores de aplicações têm uma ampla base comum e que é possível construir um controlador genérico, sendo a sua especialização para uma determinada aplicação efetuada através de um ficheiro de configuração. Este é o caminho seguido, por exemplo, pela ferramenta [Struts] no domínio da programação web em Java.