Skip to content

6. Exemplos

Neste capítulo, iremos ilustrar o que vimos 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 os seus impostos. Estamos a considerar o caso simplificado de um contribuinte que tem apenas o seu salário para declarar (valores de 2004 para rendimentos de 2003):

  • Calculamos o número de escalões de imposto para o trabalhador como nbParts = nbEnfants / 2 + 1 se for solteiro, e nbEnfants / 2 + 2 se for casado, sendo nbEnfants o número de filhos.
  • Se tiver pelo menos três filhos, tem direito a uma meia quota adicional
  • O seu rendimento tributável R é calculado como R = 0,72 * S, em que S é o seu salário anual
  • Calculamos o seu coeficiente familiar QF = R / nbParts
  • Calculamos o seu imposto I. Considere a seguinte tabela:
4262
0
0
8382
0,0683
291,09
14 753
0,1914
1.322,92
23.888
0,2826
2.668,39
38 868
0,3738
4.846,98
47 932
0,4262
6.883,66
0
0,4809
9.505,54

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

    8382        0.0683        291.09

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

    0                0.4809    9505.54

o que dá 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 de controlador será desempenhada pela página [main.aspx]. Haverá três ações possíveis:

  • init: corresponde ao primeiro pedido do cliente. O controlador exibirá a vista [formulaire.aspx]
  • calcul: corresponde ao pedido para calcular o imposto. Se os dados no formulário de entrada estiverem corretos, o imposto é calculado utilizando a classe de negócio [impots]. O controlador devolve a vista [form.aspx] ao cliente tal como foi validada, juntamente com o imposto calculado. Se os dados no formulário de entrada estiverem incorretos, o controlador devolverá a vista [errors.aspx] com uma lista de erros e um link para regressar ao formulário.
  • return: corresponde ao regresso ao formulário após um erro. O controlador apresenta a vista [form.aspx] tal como foi validada antes do erro.

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

6.1.3. A classe de negócios

A classe **impot** será definida da seguinte forma:


' imported namespaces
Imports System
 
' class
Namespace st.istia.univangers.fr
    Public Class impot
        Private limites(), coeffR(), coeffN() As Decimal
 
        ' manufacturer
        Public Sub New(ByRef source As impotsData)
            ' data required for tax calculation
            ' come from an external source [source]
            ' we retrieve them - there may be an exception
            Dim data() As Object = source.getData
            limites = CType(data(0), Decimal())
            coeffR = CType(data(1), Decimal())
            coeffN = CType(data(2), Decimal())
        End Sub
 
        ' tAX CALCULATION
        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
            ' calculating the number of shares
            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
            ' calculation of taxable income & family quota
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
            ' tAX CALCULATION
            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

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 recupera as três matrizes de dados necessárias para calcular o imposto, conforme descrito anteriormente. Este método pode lidar com uma exceção caso os dados não possam ser recuperados ou sejam considerados incorretos. Assim que o objeto [tax] for criado, o seu método calculate pode ser chamado repetidamente para calcular o imposto do contribuinte com base no seu estado civil (casado ou solteiro), número de filhos e salário anual.

6.1.4. A classe de acesso aos dados

A classe [impotsData] é a classe que fornece acesso aos dados. É uma classe abstrata. Deve ser criada uma classe derivada para cada nova fonte de dados possível (matrizes, 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
 
        ' data access method
        Public MustOverride Function getData() As Object()
 
        ' data verification method
        Protected Function checkData() As Integer
            ' verifies acquired data
            ' we need data
            valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
            If Not valide Then Return 1
            ' we must have 3 arrays of the same size
            If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
            If Not valide Then Return 2
            ' tables must be non-empty
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' each array must contain elements >=0 in ascending order
            valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
            If Not valide Then Return 4
            ' all is good
            Return 0
        End Function
 
        ' checks the validity of an array's contents
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
            ' array must have its first n elements >=0 and in strictly ascending order
            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
            ' it's good
            Return True
        End Function
    End Class
End Namespace

A classe tem os seguintes atributos protegidos:

limites
matriz de limites de escalões fiscais
coeffr
matriz de coeficientes aplicados ao rendimento tributável
coeffn
tabela de coeficientes aplicados ao número de ações
verificado
Booleano que indica se os dados (limites, coeffr, coeffn) foram verificados
válido
Booleano que indica se os dados (limites, coeffr, coeffn) são válidos

A classe não tem construtor. Possui um método abstrato [getData] que as classes derivadas devem implementar. O objetivo deste método é:

  • atribuir valores às três matrizes limits, coeffr, coeffn
  • lançar uma exceção se os dados não puderem 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 da necessidade de os implementar. Estas terão simplesmente de os utilizar.

A primeira classe derivada que iremos utilizar é a seguinte:


Imports System.Collections
Imports System
 
Namespace st.istia.univangers.fr
    Public Class impotsArray
        Inherits impotsData
 
        ' constructor with no arguments
        Public Sub New()
            ' initializing tables with constants
            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
 
        ' builder with three input tables
        Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
            ' data storage
            Me.limites = limites
            Me.coeffr = coeffr
            Me.coeffn = coeffn
            checked = False
        End Sub
 
        Public Overrides Function getData() As Object()
            ' check data if necessary
            Dim erreur As Integer
            If Not checked Then erreur = checkData() : checked = True
            ' if invalid, then throw an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            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 (limits, coeffr, coeffn) da classe base com matrizes codificadas
  • um construtor que inicializa os atributos (limits, coeffr, coeffn) da classe base com matrizes que lhe são passadas como parâmetros

O método [getData], que permite que classes externas recuperem as matrizes (limits, coeffr, coeffn), verifica simplesmente 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. Testar classes de negócio e classes de acesso a dados

É importante incluir apenas classes de negócio e de acesso a dados que tenham sido verificadas como corretas numa aplicação web. Desta forma, a fase de depuração da aplicação web pode concentrar-se nas camadas de controlador e de visualização. Um programa de teste pode ter o seguinte aspeto:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports Microsoft.VisualBasic
 
Namespace st.istia.univangers.fr
    Module test
        Sub Main()
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            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"
 
            ' tax object creation
            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
            ' infinite loop
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Long
            While True
                ' tax calculation parameters are requested
                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()
                ' anything to do?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' check the number of arguments in the input line
                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
                ' checking the validity of parameters
                If Not erreur Then
                    ' married
                    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
                    ' salary
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                End If
                ' if the parameters are correct - the tax is calculated
                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 solicita ao utilizador que introduza as três informações necessárias para calcular o seu imposto:

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

O cálculo do imposto é efetuado utilizando um objeto do tipo [tax] criado no momento do arranque da aplicação:


            ' tax object creation
            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, utilizamos um objeto do tipo [impotsArray]. É utilizado o construtor sem argumentos desta classe, fornecendo às três matrizes (limites, coeffr, coeffn) valores codificados. A criação de um objeto [impot] pode, teoricamente, lançar uma exceção porque, para se criar, o objeto solicitará os dados (limites, coeffr, coeffn) da sua fonte de dados, que lhe foi passada como parâmetro, e esta recuperação de dados pode desencadear uma exceção. No entanto, neste caso, o método utilizado para obter os dados (valores codificados) não pode causar uma exceção. Contudo, mantivemos o tratamento de exceções para chamar a atenção do leitor para a possibilidade de o objeto [impot] poder ser construído incorretamente.

Aqui está um exemplo do programa anterior em ação:

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 todas as classes [impot, impotsData, impotsArray] num único assembly [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 executar 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. Visualizações da aplicação web

A aplicação terá duas visualizações: [form.aspx] e [errors.aspx]. Vamos ilustrar como a aplicação funciona utilizando capturas de ecrã. A visualização [form.aspx] é apresentada quando o URL [main.aspx] é solicitado pela primeira vez:

Image

O utilizador preenche o formulário:

Image

e utiliza o botão [Calcular] para obter o seguinte resultado:

Image

Podem introduzir dados incorretos:

Image

Clicar no botão [Calcular] devolve então uma resposta diferente [errors.aspx]:

Image

Pode utilizar o link [Voltar ao formulário] acima para regressar à vista [form.aspx] tal como estava antes do erro:

Image

6.1.7. A visualização [form.aspx]

A página [form.aspx] terá o seguinte aspeto:


<%@ 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 nesta página são os seguintes:

rdouichecked
"checked" se a caixa de seleção [sim] estiver marcada, "" caso contrário
rdnonchecked
o mesmo para a caixa de seleção [não]
txtChildren
valor a inserir no campo de entrada [txtChildren]
txtSalary
valor a ser inserido no campo de entrada [txtSalary]
txtTax
Valor a introduzir no campo de entrada [txtTax]

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


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

Podemos ver que os parâmetros do formulário serão enviados para o controlador com [action=calcul]. O botão [Clear] é o botão [submit] para o seguinte formulário:


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

Podemos ver que os parâmetros do formulário serão enviados para o controlador com [action=effacer]. Aqui, o formulário não tem parâmetros. Apenas a ação importa.

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


Imports System.Collections.Specialized
 
Public Class formulaire
    Inherits System.Web.UI.Page
 
    ' page fields
    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
        ' we retrieve the previous request in the
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' prepare the page to be displayed
        ' radio buttons
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' the rest
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

Os campos em [main.aspx] são calculados com base em duas informações colocadas pelo controlador no contexto da página:

  • Context.Items("form"): um dicionário NameValueCollection contendo 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] apresenta quaisquer erros que possam ocorrer durante a execução 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:

HTMLErrors
Código HTML para uma lista de erros
href
URL de um link
link
texto do link

Estes campos são calculados pelo controlador da página em [errors.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic
 
Public Class erreurs
    Inherits System.Web.UI.Page
 
    ' page parameter
    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
        ' retrieve context elements
        Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
        ' we generate the HTML code from the list
        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 as informações colocadas pelo controlador da aplicação no contexto da página:

Context.Items("errors"
Objeto ArrayList contendo a lista de mensagens de erro a exibir
Context.Items("href"
URL de um link
Context.Items("link"
texto do link

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

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

Vamos rever a arquitetura MVC da nossa aplicação:

Image

Lógica da aplicaçãoCliente

O controlador [main.aspx] deve tratar de 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 no formulário de entrada estiverem corretos, o imposto é calculado utilizando a classe de negócios [taxes]. O controlador devolve a vista [form.aspx] ao cliente tal como foi validada, juntamente com o imposto calculado. Se os dados no formulário de entrada estiverem incorretos, o controlador devolve a vista [errors.aspx] com uma lista de erros e um link para regressar ao formulário.
  • return: corresponde ao regresso ao formulário após um erro. O controlador apresenta a vista [form.aspx] tal como foi validada antes do erro.

Sabe-se também que todos os pedidos à aplicação passam pelo controlador [global.asax], se este existir. Temos, portanto, no ponto de entrada da aplicação, uma cadeia de dois controladores:

  • [global.asax], que, devido à arquitetura ASP.NET, recebe todas as solicitações para a aplicação
  • [main.aspx], que, por escolha do programador, também recebe todos os pedidos para a aplicação

A necessidade de [main.aspx] decorre do facto de termos uma sessão para gerir. Vimos que [global.asax] não é adequado como controlador neste caso. Poderíamos prescindir totalmente de [global.asax] aqui. No entanto, iremos utilizá-lo para executar código quando a aplicação for iniciada. O diagrama MVC acima mostra que precisaremos de criar um objeto [tax] para calcular o imposto. Não há necessidade de criar este objeto várias vezes; uma vez é suficiente. Iremos, portanto, criá-lo no arranque da aplicação durante o evento [Application_Start] tratado pelo controlador [global.asax]. O código para isto é 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)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsArray)
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
        End Try
    End Sub
End Class

Uma vez criado, o objeto [impot] é armazenado na aplicação. É aqui que os pedidos de diferentes clientes o irão recuperar. Uma vez que a criação do objeto [impot] pode falhar, tratamos quaisquer exceções e definimos uma chave [error] 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
        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            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
        ' retrieve the action to be performed
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' execute the action
        Select Case action
            Case "init"
                ' init application
                initAppli()
            Case "calcul"
                ' tax calculation
                calculImpot()
            Case "retour"
                ' back to form
                retourFormulaire()
            Case "effacer"
                ' init application
                initAppli()
            Case Else
                ' unknown action = init
                initAppli()
        End Select
    End Sub
 
    Private Sub initAppli()
        ' the pre-filled form is displayed
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Function initForm() As NameValueCollection
        ' initialize the form
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' check the validity of the data entered
        Dim erreurs As ArrayList = checkData()
        ' if there are errors, we report them
        If erreurs.Count <> 0 Then
            ' save entries
            Session.Item("formulaire") = Request.Form
            ' prepare the error page
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' no errors here - the tax is calculated
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' the result page is displayed
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Sub retourFormulaire()
        ' displays the form with values taken from the session
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Function checkData() As ArrayList
        ' initially no errors
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' married radio button
        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
        ' no. of children
        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
        ' salary
        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
        ' return the list of errors
        Return erreurs
    End Function
End Class

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


        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            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 corretamente (o objeto [impot] necessário para o cálculo não pôde ser criado), exibe a página de erro com os parâmetros apropriados. Aqui, não há necessidade de colocar o link de retorno no formulário, uma vez que toda a aplicação está indisponível. Uma mensagem de erro geral é colocada em [Context.Items("errors")] do tipo [ArrayList].

Se o controlador determinar 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 com este modo de funcionamento muitas vezes. O processamento de cada tipo de ação é delegado a uma função.

6.1.9.1. As ações init e delete

Estas duas ações devem apresentar o formulário de entrada vazio. Recorde-se que este formulário (ver vistas) tem dois parâmetros:

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

A função [initAppli] inicializa estes dois parâmetros para apresentar um formulário vazio.

6.1.9.2. A ação de cálculo

Esta ação deve calcular o imposto devido com base nos dados introduzidos no formulário e devolver o formulário pré-preenchido com os valores introduzidos e 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 [yes] ou [no]
  • 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 apresenta a vista [erreurs.aspx] após definir primeiro os valores esperados para a mesma no contexto:

  • As mensagens de erro são colocadas num objeto [ArrayList], que é então adicionado ao contexto [Context.Items("errors")]
  • O URL do link de retorno e o texto desse link também são colocados no contexto.

Antes de passar o controlo para a página [erreurs.aspx], que enviará a resposta ao cliente, os valores introduzidos no formulário (Request.Form) são colocados na sessão, associados à chave "form". Isto permitirá que um pedido subsequente os recupere.

Poder-se-á questionar aqui se é útil verificar se os campos [rdMarie, txtEnfants, txtSalaire] estão presentes na solicitação enviada pelo cliente. Isto é desnecessário se tivermos a certeza de que o nosso cliente é um navegador que recebeu a visualização [formulaire.aspx] contendo esses campos. Nunca podemos ter certeza disso. Mostraremos um exemplo um pouco mais adiante em que o cliente é a aplicação [curl] que já conhecemos. Iremos consultar a aplicação sem enviar os campos que ela espera e ver como reage. Esta é uma regra que já foi referida várias vezes e que reiteramos aqui: uma aplicação nunca deve fazer suposições sobre o tipo de cliente que a consulta. Por segurança, deve assumir 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 verificámos se poderia conter outros. Nesta aplicação, estes seriam ignorados. No entanto, mais uma vez como medida de segurança, seria útil registar este tipo de pedido num ficheiro de registo e enviar um alerta ao administrador da aplicação para que este tenha conhecimento de que a aplicação está a receber pedidos «estranhos». Ao analisar estes no ficheiro de registo, poderia detetar um potencial ataque à aplicação e, em seguida, tomar as medidas necessárias para a proteger.

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

  • Context.Items("formulaire"): um dicionário [NameValueCollection] contendo os valores dos campos HTML [rdmarie,txtEnfants,txtSalaire], aqui [Request.Form)], ou seja, os valores previamente introduzidos no formulário
  • Context.Items("txtImpot"): o valor do imposto que acaba de ser calculado

O leitor atento pode ter-se questionado ao ler o texto acima: uma vez que o objeto [impot] criado no arranque da aplicação é partilhado entre todos os pedidos, poderão existir conflitos de acesso que levem à corrupção dos dados do objeto [impot]? Para responder a esta questão, precisamos de voltar ao código da classe [impot]. As solicitações chamam o método [impot].calculateTax para obter o imposto devido. Portanto, este é o código que precisamos de examinar:

        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
            ' calculating the number of shares
            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
            ' calculation of taxable income & family quota
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
            ' tAX CALCULATION
            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

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


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

A thread 1, após calcular o valor [impot1] da variável local [impot], é interrompida. A thread 2 é então executada e calcula um novo valor [impot2] para a mesma variável [impot] antes de ser interrompida. A thread 1 recupera o controlo. O que encontra na variável local [impot]? Uma vez que esta variável é local a um método, é armazenada numa estrutura de memória chamada pilha. Esta pilha faz parte do contexto da thread, que é guardado quando a thread é suspensa. Quando a thread 2 é iniciada, o seu contexto é configurado com uma nova pilha e, portanto, uma nova variável local [impot]. Quando a thread 2 for suspensa por sua vez, o seu contexto também será guardado. Quando a thread 1 for reiniciada, o seu contexto é restaurado, incluindo a sua pilha. Em seguida, 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 os pedidos. Os testes realizados com a pausa de 10 segundos descrita acima confirmaram que os pedidos simultâneos produziram, de facto, o resultado esperado.

6.1.9.3. A ação de retorno

Esta ação corresponde a clicar na ligação [Voltar ao Formulário] na vista [errors.aspx] para regressar à vista [form.aspx], que é pré-preenchida com os valores anteriormente introduzidos e guardados na sessão. A função [returnForm] recupera esta informação. Os dois parâmetros esperados pela vista [form.aspx] são inicializados:

  • Context.Items("form") com os valores previamente introduzidos e guardados na sessão
  • Context.Items("txtImpot") com a string vazia

6.1.10. Testar a aplicação web

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

Image

Nesta pasta, é criada uma subpasta [bin], na qual é colocado o assembly [impot.dll] — gerado a partir da compilação dos ficheiros da classe de negócios: [impots.vb, impotsData.vb, impotsArray.vb]. O comando de compilação necessário é apresentado abaixo:

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 <caminho-da-aplicação>\bin para que a aplicação web possa aceder ao mesmo. O servidor Cassini é iniciado com os parâmetros (<caminho-da-aplicação>,/impots1). Utilizando um navegador, solicitamos o URL [http://localhost/impots1/main.aspx]:

Image

Preenchemos o formulário:

Image

Em seguida, iniciamos o cálculo do imposto utilizando o botão [Calcular]. Obtemos a seguinte resposta:

Image

Em seguida, introduzimos dados incorretos:

Image

Clicar no botão [Calcular] produz a seguinte resposta:

Image

Clicar na ligação [Voltar ao formulário] leva-nos de volta ao formulário tal como estava quando foi enviado:

Image

Por fim, clicar no botão [Limpar] reinicia a página:

Image

6.1.11. Utilizando o cliente [curl]

É importante testar aplicações web com clientes que não sejam navegadores. Se enviar a um navegador um formulário com parâmetros a serem enviados quando este for submetido, o navegador reenviará os valores desses parâmetros para o servidor. Outro cliente poderá não fazer isso, e nesse caso o servidor receberia um pedido com parâmetros em falta. O servidor deve saber o que fazer nessa situação. Outro exemplo é a validação de dados do lado do cliente. Se o formulário contiver dados a serem validados, essa validação pode ser realizada do lado do cliente utilizando scripts incluídos no documento que contém o formulário. O navegador só enviará o formulário se todos os dados validados do lado do cliente forem válidos. Pode-se então sentir a tentação, no lado do servidor, de assumir que receberemos dados validados e não querer realizar esta validação uma segunda vez. Isso seria um erro. De facto, um cliente que não seja um navegador poderia enviar dados inválidos para o servidor, e a aplicação web poderia então comportar-se de forma inesperada. Iremos ilustrar estes pontos utilizando o cliente [curl].

Primeiro, 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. Iremos utilizá-lo em pedidos subsequentes para manter a sessão. Vamos solicitar a ação [calcul] sem fornecer quaisquer parâmetros:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --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: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 ver que a aplicação web devolveu a vista [errors] com três mensagens de erro para os três parâmetros em falta. Agora vamos enviar alguns parâmetros incorretos:

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>

Recuperámos com sucesso o imposto devido: 4.300 euros. A lição a retirar deste exemplo é que não devemos deixar-nos enganar 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 revela a natureza da aplicação cliente de um serviço. Por conseguinte, não podemos saber se o cliente de uma aplicação web é um navegador ou não. Seguimos, portanto, duas regras:

  • Ao receber um pedido de um cliente, não fazemos suposições sobre o cliente e verificamos se os parâmetros esperados no pedido estão presentes e são válidos
  • construímos uma resposta destinada a navegadores, que geralmente consiste em documentos HTML

Uma aplicação web pode ser construída para servir diferentes clientes simultaneamente, tais como navegadores e telemóveis. Podemos então incluir um novo parâmetro em cada pedido indicando o tipo de cliente. Assim, um navegador solicitará um cálculo de impostos através de uma solicitação para a URL http://machine/impots/main.aspx?client=navigateur&action=calcul, enquanto um telemóvel enviará uma solicitação para a URL http://machine/impots/main.aspx?client=mobile&action=calcul. A estrutura MVC facilita o desenvolvimento de tal aplicação. Assume a seguinte forma:

Image

O bloco [Classes de Negócio, Classes de Acesso a Dados] permanece inalterado. Isto porque é um componente independente do cliente. O bloco [Controlador] muda apenas ligeiramente, mas deve ter em conta um novo parâmetro na solicitação: o parâmetro [client], que indica o tipo de cliente que está a ser tratado. O bloco [Visualizações] deve gerar visualizações para cada tipo de cliente. Pode valer a pena ter em conta a presença do parâmetro [client] na consulta logo na fase de conceção da aplicação, mesmo que o objetivo a curto ou médio prazo se limite aos navegadores. Se a aplicação precisar posteriormente de suportar um novo tipo de cliente, apenas será necessário escrever visualizações adaptadas a esse cliente.

6.2. Exemplo 2

6.2.1. O Problema

Aqui, propomos abordar o mesmo problema de antes, mas modificando a fonte de dados para o objeto [tax] criado pela aplicação web. Na versão anterior, a fonte de dados fornecia valores a partir de matrizes codificadas no código. Desta vez, a nova fonte de dados irá recuperá-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 localizados numa tabela chamada [IMPOTS] numa base de dados MySQL chamada [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, vamos primeiro rever as diferentes formas de aceder a uma base de dados utilizando a plataforma .NET.

Existem muitas bases de dados disponíveis para plataformas Windows. Para aceder às mesmas, as aplicações utilizam programas chamados controladores.

Image

No diagrama acima, o controlador tem duas interfaces:

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

Para evitar que uma aplicação escrita para a base de dados B1 tenha de ser reescrita caso se migre para uma base de dados diferente, a B2, foram envidados esforços de normalização na interface I1. Se forem utilizadas bases de dados que utilizem controladores «normalizados», a base de dados B1 será fornecida com o controlador P1, a base de dados B2 com o controlador P2, e a interface I1 destes dois controladores será idêntica. Assim, a aplicação não precisará de ser reescrita. Por exemplo, pode-se migrar uma base de dados Access para uma base de dados MySQL sem alterar a aplicação.

Existem dois tipos de controladores padronizados:

  • Drivers ODBC (Open DataBase Connectivity)
  • Drivers OLE DB (Object Linking and Embedding DataBase)

Os controladores ODBC fornecem acesso a bases de dados. As fontes de dados para os controladores OLE DB são mais variadas: bases de dados, sistemas de e-mail, diretórios, etc. Não há limites. Qualquer fonte de dados pode ser objeto de um controlador OLE DB, se um fornecedor assim o decidir. A vantagem é obviamente significativa: tem acesso uniforme a uma ampla variedade de dados.

A plataforma .NET 1.1 inclui três tipos de classes de acesso a dados:

  1. Classes SQL Server.NET, para aceder a bases de dados Microsoft SQL Server
  2. as classes Ole Db.NET, para aceder a bases de dados de SGBDs que oferecem um controlador OLE DB
  3. Classes ODBC.NET, para aceder a bases de dados de SGBDs que oferecem um controlador ODBC

O SGBD MySQL dispõe há muito de um controlador ODBC. É este que iremos utilizar agora. No Windows, selecionamos [Menu Iniciar/Painel de Controlo/Ferramentas Administrativas/Fontes ODBC de 32 bits]. Dependendo da versão do Windows, este caminho pode variar ligeiramente. Isto abre a seguinte aplicação, que nos permitirá criar a nossa fonte ODBC:

Image

Iremos criar uma fonte de dados do sistema, ou seja, uma fonte de dados que qualquer utilizador da máquina possa utilizar. Por isso, acima, selecionamos o separador [Fonte de dados do sistema]. A página apresentada tem um botão [Adicionar], que utilizamos para criar uma nova fonte de dados ODBC:

Image

O assistente solicita que selecione o controlador ODBC a utilizar. O Windows vem com vários controladores ODBC pré-instalados. O controlador ODBC do MySQL não está incluído neste conjunto. Por isso, deve instalá-lo primeiro. Pode encontrá-lo online digitando os termos de pesquisa «MySQL ODBC» ou «MyODBC» num motor de busca. Aqui, instalámos o controlador [MySQL ODBC 3.51]. Selecionamo-lo e clicamos em [Concluir]:

Image

Terá de fornecer as seguintes informações:

Nome da fonte de dados
o nome que identificará a fonte de dados ODBC. Qualquer aplicação do Windows poderá aceder à fonte utilizando este nome
Descrição
Qualquer texto que descreva a fonte de dados
Nome do anfitrião
O nome da máquina que hospeda o SGBD MySQL. Aqui, trata-se da máquina local. Pode ser uma máquina remota. Isto permitiria a uma aplicação Windows aceder a uma base de dados remota sem qualquer codificação especial. Esta é uma grande vantagem da fonte ODBC.
Nome da base de dados
Um SGBD MySQL pode gerir várias bases de dados. Aqui, especificamos qual queremos gerir: dbimpots
Utilizador
O nome de um utilizador registado no sistema de gestão de bases de dados MySQL. O acesso à fonte de dados será efetuado com o nome deste utilizador. Aqui: admimpots
Palavra-passe
A palavra-passe deste utilizador. Aqui: mdpimpots
Porta
Porta de funcionamento do SGBD MySQL. Por predefinição, esta é a porta 3306. Não a alterámos

Depois de fazer isto, testamos a validade das nossas definições de ligação utilizando o botão [Testar fonte de dados]:

Image

Depois de concluir este passo, já temos a certeza de que a nossa fonte de dados ODBC está pronta. Podemos agora utilizá-la. Clicamos em [OK] tantas vezes quantas forem necessárias para sair do assistente ODBC.

Se o leitor não tiver o SGBD MySQL, pode descarregá-lo gratuitamente em [http://www.mysql.com]. Abaixo, descrevemos os passos para criar uma fonte ODBC com o Access. Os primeiros passos são idênticos aos descritos anteriormente. Adicionamos uma nova fonte de dados do sistema:

Image

O controlador selecionado será [Microsoft Access Driver]. Clique em [Concluir] para avançar para a definição da fonte ODBC:

Image

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

Nome da fonte de dados
O nome que identificará a fonte de dados ODBC. Qualquer aplicação Windows poderá aceder à fonte utilizando este nome
Descrição
Qualquer texto que descreva a fonte de dados
Base de dados
O nome completo do ficheiro do Access a ser utilizado

6.2.3. Uma nova classe de acesso a dados

Vamos rever a estrutura MVC da nossa aplicação:

Image

No diagrama acima, a classe [impotsData] é responsável por recuperar dados. Neste caso, irá recuperar dados da base de dados MySQL [dbimpots]. Tal como aprendemos na versão anterior desta aplicação, [impotsData] é uma classe abstrata que deve ser derivada sempre que quisermos adaptá-la a uma nova fonte de dados. Vamos rever 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
 
        ' data access method
        Public MustOverride Function getData() As Object()
 
        ' data verification method
        Protected Function checkData() As Integer
            ' verifies acquired data
...
        End Function
 
        ' checks the validity of an array's contents
        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 padrão 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á recuperar os dados (limites, coeffr, coeffn) de uma fonte ODBC cujo nome iremos fornecer:


Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
 
Namespace st.istia.univangers.fr
    Public Class impotsODBC
        Inherits impotsData
 
        ' instance variables
        Protected DSNimpots As String
 
        ' manufacturer
        Public Sub New(ByVal DSNimpots As String)
            ' we note the three pieces of information
            Me.DSNimpots = DSNimpots
        End Sub
 
        Public Overrides Function getdata() As Object()
            ' initializes the three limit tables, coeffr, coeffn from
            ' the contents of the [impots] table in the ODBC DSNimpots database
            ' limites, coeffr, coeffn are the three columns of this table
            ' can launch various exceptions
 
            Dim connectString As String = "DSN=" + DSNimpots + ";"         ' base connection chain
            Dim impotsConn As OdbcConnection = Nothing         ' the connection
            Dim sqlCommand As OdbcCommand = Nothing         ' the SQL command
            ' the SELECT query
            Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
            ' tables to retrieve data
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
            Try
                ' attempt to access the database
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
                ' create a command object
                sqlCommand = New OdbcCommand(selectCommand, impotsConn)
                ' execute the query
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
                ' Using the recovered table
                While myReader.Read()
                    ' the data of the current line are put in the tables
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' freeing up resources
                myReader.Close()
                impotsConn.Close()
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' dynamic tables are placed in static tables
            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
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Vamos dar uma olhada no construtor:


        ' manufacturer
        Public Sub New(ByVal DSNimpots As String)
            ' we note the three pieces of information
            Me.DSNimpots = DSNimpots
        End Sub

Recebe como parâmetro o nome da fonte de dados ODBC que contém os dados a recuperar. O construtor limita-se a armazenar este 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 código:

  • os parâmetros para a ligação à fonte de dados ODBC estão definidos, mas a ligação não está aberta
            ' base connection chain
            Dim connectString As String = "DSN=" + DSNimpots + ";"
            ' a database connection object is created - this connection is not open
            Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
  • Defina três objetos [ArrayList] para recuperar dados da tabela [impots]:

            ' tables to retrieve data
            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á incluído num bloco try/catch para lidar com quaisquer erros de acesso. Abrimos a ligação à base de dados:

                ' on tente d'accéder à la base de données
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • Executamos o comando [select] na ligação aberta. Obtemos um objeto [OdbcDataReader] que nos permitirá percorrer as linhas da tabela resultante do select:

                ' on crée un objet command
                Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
                ' on exécute la requête
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
  • Percorremos a tabela de resultados, linha a linha. Para isso, utilizamos o método [Read] do objeto [OdbcDataReader] obtido anteriormente. Este método faz duas coisas:
    • Avança uma linha na tabela. Inicialmente, o cursor está posicionado antes da primeira linha
    • retorna o valor booleano [true] se conseguiu avançar, [false] caso contrário, indicando que todas as linhas foram processadas.

As colunas da linha atual do objeto [OdbcDataReader] são obtidas através de OdbcDataReader. Isto devolve um objeto que representa o valor da coluna. Percorremos toda a tabela para preencher o seu conteúdo nos três objetos [ArrayList]:


                ' Exploitation de la table récupérée
                While myReader.Read()
                    ' les données de la ligne courante sont mis dans les tableaux
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
  • Depois de feito isto, libertamos os recursos associados à ligação:
                ' libération des ressources
                myReader.Close()
                impotsConn.Close()
  • O conteúdo dos três objetos [ArrayList] é transferido para três matrizes padrão:

            ' dynamic tables are placed in static tables
            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
  • Depois de os dados da tabela [impots] terem sido carregados nas três matrizes, basta verificar o seu conteúdo utilizando o método [checkData] da classe base [impotsData]:
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}

6.2.4. Testes para a classe de acesso aos dados

Um programa de teste poderia ter o seguinte aspeto:

Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' test pg
    Module testimpots
        Sub Main(ByVal arguments() As String)
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            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"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If
            ' retrieve the arguments
            Dim DSNimpots As String = arguments(0)

            ' tax object creation
            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

            ' infinite loop
            While True
                ' initially no errors
                Dim erreur As Boolean = False

                ' tax calculation parameters are requested
                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()

                ' anything to do?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                ' check the number of arguments in the input line
                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
                    ' checking the validity of parameters
                    ' married
                    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
                    ' salary
                    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
                    ' parameters are correct - tax is calculated
                    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 é realizado utilizando um objeto do tipo [tax] criado quando a aplicação é iniciada:


            ' tax object creation
            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

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

  • estado civil: o para casado, n para solteiro
  • o número de filhos
  • 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 é então 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 do 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 ODBC para uma fonte 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. Visualizações da aplicação web

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

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

Apenas o controlador [global.asax] precisa de ser modificado. É responsável por criar o objeto [impot] quando a aplicação é iniciada. O construtor deste objeto tem um único parâmetro: o objeto [impotsData], que é responsável por recuperar os dados. Este parâmetro muda, portanto, para cada novo tipo de fonte de dados. O controlador [global.asax.vb] passa a ser o seguinte:


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)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

A fonte de dados para o objeto [impot] é agora um objeto [impotODBC]. Este objeto recebe o nome DSN da fonte de dados ODBC a ser utilizada como parâmetro. Em vez de codificar este nome no código, colocamo-lo 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>

Sabemos que o valor de uma chave C na secção <appSettings> do ficheiro [web.config] é recuperado no código da aplicação utilizando [ConfigurationSettings.AppSettings(C)].

Para determinar a causa da exceção, registamos a mensagem de exceção na aplicação para que permaneça disponível para consultas. O controlo [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
        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            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
        ' retrieve the action to be performed
...

6.2.7. Resumo das alterações

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

  1. foi criada uma nova classe de acesso a dados
  2. o controlador [global.asax.vb] foi modificado em dois pontos: a construção do objeto [import] e o registo da mensagem relacionada com qualquer exceção na aplicação
  3. O controlador [main.aspx.vb] foi modificado num ponto para exibir a mensagem de exceção anterior
  4. Foi adicionado um ficheiro [web.config]

O trabalho de modificação foi feito principalmente em 1, ou seja, 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. Esse é o objetivo desta arquitetura. Foi possível demonstrar que, com um ficheiro [web.config] adequado, qualquer modificação no controlador da aplicação poderia ter sido evitada. É possível especificar no [web.config] o nome da classe de acesso a dados a ser instanciada dinamicamente — tal como — bem como os vários parâmetros necessários para esta instanciação. Com esta informação, o [global.asax] pode instanciar o objeto de acesso a dados. Alterar a fonte de dados equivale então a:

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

6.2.8. Testar a aplicação web

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

Image

Nesta pasta, é criada uma subpasta [bin], na qual é colocado o assembly [impot.dll] — gerado a partir da compilação dos ficheiros da classe de negócios: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. O comando de compilação necessário é apresentado abaixo:

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 deve ser colocado em <caminho-da-aplicação>\bin para que a aplicação web possa aceder ao mesmo. O servidor Cassini é iniciado com os parâmetros (<caminho-da-aplicação>,/impots2). Os testes produzem os mesmos resultados que na versão anterior, uma vez que a presença da base de dados é transparente para o utilizador. Para demonstrar essa presença, no entanto, garantimos que a fonte ODBC não está disponível, parando o SGBD MySQL e solicitando a URL [http://localhost/impots2/main.aspx]. Recebemos a seguinte resposta:

Image

6.3. Exemplo 3

6.3.1. O Problema

Aqui, propomos resolver o mesmo problema modificando 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 acedida através de um controlador OLEDB. O nosso objetivo é demonstrar outra forma de aceder a uma base de dados.

6.3.2. A fonte de dados OLEDB

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

Image

6.3.3. A Classe de Acesso aos Dados

Vamos rever a estrutura MVC da nossa aplicação:

Image

  • No diagrama 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á recuperar os dados (limits, coeffr, coeffn) de uma fonte ODBC a que 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
 
        ' instance variables
        Protected chaineConnexion As String
 
        ' manufacturer
        Public Sub New(ByVal chaineConnexion As String)
            ' we note the three pieces of information
            Me.chaineConnexion = chaineConnexion
        End Sub
 
        Public Overrides Function getData() As Object()
            ' initializes the three limit tables, coeffr, coeffn from
            ' the contents of the [impots] table in the OLEDB [chaineConnexion] database
            ' limits, coeffr, coeffn are the three columns of this table
            ' can launch various exceptions
 
            ' create a DataAdapter object to read data from source OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
            ' create a memory image of the select result
            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
            ' retrieve the contents of the impots table
            Dim lignesImpots As DataRowCollection = contenu.Rows
            ' dimensioning of reception panels
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' transfer the contents of the impots table to the tables
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' table line i
                    ligne = lignesImpots.Item(i)
                    ' retrieve the contents of the line
                    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
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Vamos dar uma olhada no construtor:


        ' manufacturer
        Public Sub New(ByVal chaineConnexion As String)
            ' we note the three pieces of information
            Me.chaineConnexion = chaineConnexion
        End Sub

Recebe como parâmetro a cadeia de ligação para a fonte OLEDB que contém os dados a recuperar. O construtor limita-se a armazená-la. Uma cadeia de ligação contém todos os parâmetros necessários ao controlador OLEDB para se ligar à fonte OLEDB. É geralmente bastante complexa. Para encontrar a cadeia de ligação para bases de dados ACCESS, pode utilizar a ferramenta [WebMatrix]. Inicie esta ferramenta. Ela disponibiliza uma janela para se ligar a uma fonte de dados:

Usando o ícone indicado pela seta acima, pode criar uma ligação a dois tipos de bases de dados da Microsoft: SQL Server e ACCESS. Vamos escolher o ACCESS:

Image

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

Image

Agora, vamos criar um novo ficheiro .aspx através de [Ficheiros/Novo Ficheiro]:

Image

Aparece uma página em branco na qual podemos projetar a nossa interface web:

Image

Vamos arrastar a tabela [impots] da guia [Dados] para a folha acima. Obtemos o seguinte resultado:

Image

Clique com o botão direito do rato no objeto [AccessDataSourceControl] abaixo para aceder às suas 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

Podemos ver que esta cadeia é composta por uma parte fixa e uma parte variável, que é simplesmente o nome do ficheiro ACCESS. Vamos utilizar 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 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 o resultado de uma consulta SQL SELECT para a memória. Para tal, definimos a consulta [select] a ser executada e associamo-la ao objeto [DataAdapter]. O construtor do [DataAdapter] também requer a cadeia de ligação que irá utilizar para se ligar à fonte OLEDB

            ' on crée un objet DataAdapter pour lire les données de la source OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • Executamos o comando [select] utilizando o método [Fill] do objeto [DataAdapter]. O resultado do [select] é carregado num objeto [DataTable] criado para este fim. Um objeto [DataTable] é a representação em memória de uma tabela de base de dados, ou seja, um conjunto de linhas e colunas. Tratamos uma exceção que pode ocorrer se, por exemplo, a cadeia de ligação estiver incorreta.

            ' on crée une image en mémoire du résultat du select
            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 [content], temos a tabela [taxes] devolvida pela instrução [select]. Um objeto [DataTable] é uma tabela, ou seja, um conjunto de linhas. Estas são acessíveis através da propriedade [rows] de [DataTable]:

            ' retrieve the contents of the impots table
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • Cada elemento da coleção [taxRows] é um objeto [DataRow] que representa uma linha na tabela. Esta linha tem colunas acessíveis através do objeto [DataRow] por meio da sua propriedade [Item]. [DataRow].[Item(i)] é a coluna número i do [DataRow]. Ao percorrer a coleção de linhas (a coleção DataRows de taxRows) e a coleção de colunas para cada linha, podemos recuperar a tabela inteira:
            ' dimensioning of reception panels
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' transfer the contents of the impots table to the tables
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' table line i
                    ligne = lignesImpots.Item(i)
                    ' retrieve the contents of the line
                    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
  • Depois de os dados da tabela [taxes] terem sido carregados nas três matrizes, basta verificar o seu conteúdo utilizando o método [checkData] da classe base [taxData]:
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}

6.3.4. Testes para a classe de acesso aos dados

Um programa de teste poderia ter o seguinte aspeto:

Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' test pg
    Module testimpots
        Sub Main(ByVal arguments() As String)
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            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"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If
            ' retrieve the arguments
            Dim chemin As String = arguments(0)
            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

            ' tax object creation
            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

            ' infinite loop
            While True
                ' initially no errors
                Dim erreur As Boolean = False

                ' tax calculation parameters are requested
                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()

                ' anything to do?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                ' check the number of arguments in the input line
                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
                    ' checking the validity of parameters
                    ' married
                    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
                    ' salary
                    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
                    ' parameters are correct - tax is calculated
                    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:

  • bdACCESS: nome do ficheiro ACCESS a utilizar

O cálculo do imposto é realizado utilizando um objeto do tipo [tax] criado quando a aplicação é iniciada:


            ' retrieve the arguments
            Dim chemin As String = arguments(0)
            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
 
            ' tax object creation
            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 construída utilizando informações obtidas com o [WebMatrix].

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

  • estado civil: o para casado, n para solteiro
  • número de filhos
  • 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 a aplicação é iniciada 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)

Pode executar 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. Controladores da aplicação [global.asax, main.aspx]

Apenas o controlador [global.asax] precisa de ser modificado. Este é responsável por criar o objeto [impot] quando a aplicação é iniciada. O construtor deste objeto tem um único parâmetro: o objeto [impotsData] responsável por recuperar os dados. Este parâmetro altera-se, portanto, uma vez que estamos a mudar 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)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

A fonte de dados para o objeto [impot] é agora um objeto [impotOLEDB]. Este objeto recebe como parâmetro a cadeia de ligação para a fonte de dados OLEDB a ser utilizada. Esta cadeia está armazenada 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] permanece inalterado.

6.3.7. Resumo das alterações

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

  1. foi criada uma nova classe de acesso a dados
  2. o controlador [global.asax.vb] foi modificado num ponto: construção do objeto [import]
  3. Foi adicionado um ficheiro [web.config]

6.3.8. Testar a aplicação web

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

Image

Nesta pasta, é criada uma subpasta [bin], na qual é colocado o assembly [impot.dll] — gerado a partir da compilação dos ficheiros da classe de negócios: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. O comando de compilação necessário é apresentado abaixo:

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] gerado por este comando deve ser colocado em <caminho-da-aplicação>\bin para que a aplicação web possa aceder ao mesmo. O servidor Cassini é iniciado com os parâmetros (<caminho-da-aplicação>,/impots3). Os testes produzem 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álculo de impostos. Um utilizador poderá realizar cálculos de impostos sucessivos, e estes 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 fica assim:

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] permanece inalterada. A vista [formulaire.aspx] sofre uma ligeira alteração. Na verdade, o montante do imposto já não aparece nesta vista. Encontra-se agora na vista [simulations.aspx]. Assim, ao iniciar, a página apresentada ao utilizador é a seguinte:

Image

Além disso, a vista [form] inclui um script JavaScript que valida os dados introduzidos antes de os enviar para o servidor, conforme mostrado no 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(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer  
        </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 na página são os mesmos das versões anteriores. O campo dinâmico para o valor do imposto foi removido. O botão [Calcular] já não é um botão [submit]. É um [button] e, quando clicado, a função JavaScript [calculate()] é executada:


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

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


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

A função JavaScript [calculate] utiliza expressões regulares para validar os campos nos formulários [document.frmImpots.txtEnfants] e [document.frmImpots.txtSalaire]. Se os valores introduzidos estiverem corretos, são enviados para o servidor através de [document.frmImpots.submit()].

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


Imports System.Collections.Specialized
 
Public Class formulaire
    Inherits System.Web.UI.Page
 
    ' page fields
    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
        ' we retrieve the previous request in the
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' prepare the page to be displayed
        ' radio buttons
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' the rest
        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, exceto que já não precisa de recuperar o campo [txtImpot] do contexto, uma vez que este campo foi removido da página.

A vista [simulations.aspx] apresenta-se 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 contém três campos dinâmicos:

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

São gerados pelo controlador [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
        'simulations are retrieved from the context
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' each simulation is an array of 4 string elements
        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
        ' recover the other elements of the context
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
    End Sub
End Class

O controlador da página recupera as 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 é uma matriz de 4 cadeias de caracteres que representam as informações da simulação (casado, filhos, salário, impostos).
Context.Items("href")
URL de um link
Context.Items("link")
Texto do link

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

Vamos rever a arquitetura MVC da nossa aplicação:

Image

O controlador [main.aspx] deve lidar com três ações:

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

Nesta nova versão, apenas a ação [calcul] foi alterada. Com efeito, se os dados forem válidos, deve conduzir à vista [simulations.aspx], enquanto anteriormente conduzia à vista [form.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
        ' first of all, we check whether the application has initialized correctly
...
        ' execute the action
        Select Case action
            Case "init"
                ' init application
                initAppli()
            Case "calcul"
                ' tax calculation
                calculImpot()
            Case "retour"
                ' back to form
                retourFormulaire()
            Case "effacer"
                ' init application
                initAppli()
            Case Else
                ' unknown action = init
                initAppli()
        End Select
    End Sub
 
...
    Private Sub calculImpot()
        ' save entries
        Session.Item("formulaire") = Request.Form
        ' check the validity of the data entered
        Dim erreurs As ArrayList = checkData()
        ' if there are errors, we report them
        If erreurs.Count <> 0 Then
            ' prepare the error page
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' no errors here - the tax is calculated
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' the result is added to existing simulations
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' add current simulation
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' put simulations in session and context
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' the result page is displayed
        context.Items("href") = "main.aspx?action=retour"
        context.Items("lien") = "Retour au formulaire"
        Server.Transfer("simulations.aspx", True)
    End Sub
...
End Class

Incluímos acima apenas o que é necessário para compreender as alterações encontradas exclusivamente na função [calculImpots]:

  • Primeiro, a função guarda o formulário [Request.Form] na sessão para que o formulário possa ser regenerado 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], regressamos ao formulário através do link [Voltar ao formulário]. Para restaurar o formulário corretamente, os seus valores devem ter sido guardados na sessão previamente.
  • Se os dados introduzidos estiverem corretos, a função adiciona a simulação atual (casado, filhos, salário, impostos) à lista de simulações. Esta lista encontra-se na sessão associada à chave «simulations».
  • A lista de simulações é armazenada na sessão para utilização futura. É também colocada no contexto atual, porque é aí que a vista [simulations.aspx] espera encontrá-la
  • A vista [simulations.aspx] é apresentada assim que as outras informações necessárias forem colocadas no contexto

6.4.5. Resumo das alterações

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

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

6.4.6. Testar a aplicação web

O leitor é convidado a realizar os testes. Aqui fica um lembrete do procedimento. Todos os ficheiros da aplicação são colocados numa pasta denominada <application-path>. Dentro desta pasta, é criada uma subpasta [bin], contendo o assembly [impot.dll] gerado a partir da compilação dos ficheiros da classe de negócio: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. O ficheiro [impot.dll] produzido por este comando deve ser colocado em <application-path>\bin para que a aplicação web possa aceder ao mesmo. O servidor Cassini é iniciado com os parâmetros (<application-path>,/impots4).

6.5. Conclusão

Os exemplos anteriores demonstraram, num cenário concreto, mecanismos comumente utilizados no desenvolvimento web. Utilizámos consistentemente a arquitetura MVC pelo seu valor educativo. Poderíamos ter tratado estes mesmos exemplos de forma diferente e talvez mais simples sem esta arquitetura. No entanto, ela oferece vantagens significativas assim que a aplicação se torna um pouco complexa, com várias páginas.

Poderíamos continuar os nossos exemplos de várias maneiras. Aqui estão algumas:

  • O utilizador pode querer guardar as suas simulações ao longo do tempo. Poderia executar simulações no dia D e 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 é transmitido através deste mecanismo. Também poderíamos utilizar este mecanismo para transmitir as simulações entre o cliente e o servidor.
    • Ao mesmo tempo que o servidor envia a página contendo os resultados da simulação, envia um cookie nos seus cabeçalhos HTTP contendo uma string que representa as simulações. Uma vez que estas se encontram num objeto [ArrayList], este objeto deve ser convertido para um [String]. O servidor definiria um prazo de validade para o cookie, por exemplo, 30 dias.
    • O navegador do cliente armazena os cookies recebidos num ficheiro e reenvia-os sempre que faz um pedido ao servidor que os enviou, desde que ainda sejam válidos (tempo de vida não excedido). O servidor receberá uma cadeia de caracteres [String] para as simulações, que deve converter num objeto [ArrayList].

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

  • O mecanismo descrito acima pode tornar-se bastante exigente em termos de recursos se houver um grande número de simulações. Além disso, é comum que um utilizador limpe periodicamente os seus cookies, eliminando-os todos, mesmo que, de outra forma, permita que o seu navegador os utilize. Assim, mais cedo ou mais tarde, os cookies de simulação serão perdidos. Podemos, portanto, querer armazená-los no servidor em vez de no cliente, por exemplo, numa base de dados. Para associar simulações a um utilizador específico, a aplicação poderia começar com uma fase de autenticação que exigisse um nome de utilizador e uma palavra-passe, os quais são, por sua vez, armazenados numa base de dados ou em qualquer outro tipo de repositório de dados.
  • Também podemos querer proteger o funcionamento da nossa aplicação. Atualmente, esta parte de duas premissas:
    1. o utilizador passa sempre pelo controlador [main.aspx]
    2. e, nesse caso, utiliza sempre as ações disponíveis na página que lhe foi enviada

O que acontece, por exemplo, se o utilizador solicitar diretamente o URL [http://localhost/impots4/formulaire.aspx]? Este cenário é improvável, uma vez que o utilizador desconhece este URL. No entanto, deve ser tido em conta. Pode ser tratado pelo controlador da aplicação [global.asax], que processa todos os pedidos feitos à aplicação. Pode, assim, verificar se o recurso solicitado é de facto [main.aspx].

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

Image

O servidor falha. Isto é normal. Para a ação [return], o controlador espera encontrar um objeto [NameValueCollection] na sessão que represente os valores do formulário que precisa de apresentar. Não os encontra. O mecanismo do controlador fornece uma solução elegante para 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. Podemos utilizar o seguinte mecanismo:

  • Antes de enviar a sua resposta ao cliente, o controlador armazena informações que identificam esta página na sessão do cliente
  • 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 ligam as páginas e as ações permitidas 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 partilham uma ampla base comum e que é possível construir um controlador genérico, com a sua especialização para uma determinada aplicação tratada através de um ficheiro de configuração. Esta é a abordagem adotada, por exemplo, pela ferramenta [Struts] no campo da programação web Java.