Skip to content

6. Esempi

In questo capitolo illustreremo quanto visto in precedenza attraverso una serie di esempi.

6.1. Esempio 1

6.1.1. Il problema

Questa applicazione deve consentire a un utente di calcolare le proprie imposte. Stiamo considerando il caso semplificato di un contribuente che deve dichiarare solo il proprio stipendio (dati del 2004 relativi al reddito del 2003):

  • Calcoliamo il numero di scaglioni fiscali per il dipendente come nbParts = nbEnfants / 2 + 1 se è celibe, e nbEnfants / 2 + 2 se è coniugato, dove nbEnfants è il numero di figli.
  • Se ha almeno tre figli, ha diritto a una quota aggiuntiva pari a metà
  • Il suo reddito imponibile R è calcolato come R = 0,72 * S, dove S è il suo stipendio annuale
  • Calcoliamo il suo coefficiente familiare QF = R / nbParts
  • Calcoliamo la sua imposta I. Consideriamo la seguente tabella:
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

Ogni riga ha 3 campi. Per calcolare l'imposta I, trova la prima riga in cui QF <= campo1. Ad esempio, se QF = 5000, la riga trovata sarà

    8382        0.0683        291.09

L'imposta I è quindi pari a 0,0683*R - 291,09*nbParts. Se QF è tale che la condizione QF<=campo1 non viene mai soddisfatta, vengono utilizzati i coefficienti dell'ultima riga. Qui:

    0                0.4809    9505.54

il che dà imposta I = 0,4809*R - 9505,54*nbParts.

6.1.2. La struttura MVC dell'applicazione

La struttura MVC dell'applicazione sarà la seguente:

Image

Il ruolo di controller sarà gestito dalla pagina [main.aspx]. Ci saranno tre possibili azioni:

  • init: corrisponde alla prima richiesta del client. Il controller visualizzerà la vista [formulaire.aspx]
  • calcul: corrisponde alla richiesta di calcolo dell'imposta. Se i dati nel modulo di input sono corretti, l'imposta viene calcolata utilizzando la classe di business [impots]. Il controller restituisce al client la vista [form.aspx] così come è stata convalidata, insieme all'imposta calcolata. Se i dati nel modulo di input sono errati, il controller restituirà la vista [errors.aspx] con un elenco di errori e un link per tornare al modulo.
  • return: corrisponde al ritorno al modulo dopo un errore. Il controller visualizza la vista [form.aspx] così come era stata convalidata prima dell'errore.

Il controller [main.aspx] non sa nulla dei calcoli delle imposte. È semplicemente responsabile della gestione del dialogo client-server e dell'esecuzione delle azioni richieste dal client. Per l'azione [calculate], si affiderà alla classe di business [tax].

6.1.3. La classe di business

La classe **impot** sarà definita come segue:


' 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

Un oggetto tax fornendo al suo costruttore una fonte dati di tipo [impotsData]. Questa classe dispone di un metodo pubblico [getData] che recupera i tre array di dati necessari per calcolare l’imposta, come descritto in precedenza. Questo metodo è in grado di gestire un’eccezione se i dati non possono essere recuperati o se risultano errati. Una volta creato l'oggetto [tax], il suo metodo calculate può essere chiamato ripetutamente per calcolare l'imposta del contribuente in base al suo stato civile (coniugato o celibe), al numero di figli e allo stipendio annuale.

6.1.4. La classe di accesso ai dati

La classe [impotsData] è la classe che fornisce l'accesso ai dati. Si tratta di una classe astratta. È necessario creare una classe derivata per ogni nuova possibile fonte di dati (array, file flat, database, console, ecc.). La sua definizione è la seguente:


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

La classe ha i seguenti attributi protetti:

limiti
array dei limiti delle fasce di imposta
coeffr
array di coefficienti applicati al reddito imponibile
coeffn
tabella dei coefficienti applicati al numero di azioni
checked
Booleano che indica se i dati (limits, coeffr, coeffn) sono stati verificati
valido
Booleano che indica se i dati (limits, coeffr, coeffn) sono validi

La classe non ha un costruttore. Ha un metodo astratto [getData] che le classi derivate devono implementare. Lo scopo di questo metodo è:

  • assegnare valori ai tre array limits, coeffr, coeffn
  • generare un'eccezione se i dati non sono stati acquisiti o se risultano non validi.

La classe fornisce i metodi protetti [checkData] e [check] che verificano la validità degli attributi (limits, coeffr, coeffn). Ciò solleva le classi derivate dalla necessità di implementarli. Dovranno semplicemente utilizzarli.

La prima classe derivata che useremo è la seguente:


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

Questa classe, denominata [impotsArray], ha due costruttori:

  • un costruttore senza argomenti che inizializza gli attributi (limits, coeffr, coeffn) della classe base con array hard-coded
  • un costruttore che inizializza gli attributi (limits, coeffr, coeffn) della classe base con array passati come parametri

Il metodo [getData], che consente alle classi esterne di recuperare gli array (limits, coeffr, coeffn), verifica semplicemente la validità dei tre array utilizzando il metodo [checkData] della classe base. Genera un'eccezione se i dati non sono validi.

6.1.5. Test delle classi di business e delle classi di accesso ai dati

È importante includere in un'applicazione web solo classi di business e di accesso ai dati la cui correttezza sia stata verificata. In questo modo, la fase di debug dell'applicazione web può concentrarsi sui livelli del controller e della vista. Un programma di test potrebbe presentarsi come segue:


' 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

L'applicazione richiede all'utente di inserire le tre informazioni necessarie per calcolare le imposte:

  • stato civile: o per sposato, n per non sposato
  • numero di figli
  • stipendio annuo

Il calcolo dell'imposta viene effettuato utilizzando un oggetto di tipo [tax] creato all'avvio dell'applicazione:


            ' 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

Come origine dati, utilizziamo un oggetto di tipo [impotsArray]. Viene utilizzato il costruttore senza argomenti per questa classe, fornendo ai tre array (limites, coeffr, coeffn) valori hard-coded. La creazione di un oggetto [impot] può teoricamente generare un'eccezione perché, per crearsi, l'oggetto richiederà i dati (limites, coeffr, coeffn) dalla sua origine dati, che gli è stata passata come parametro, e questo recupero dei dati potrebbe innescare un'eccezione. Tuttavia, in questo caso, il metodo utilizzato per ottenere i dati (valori hard-coded) non può causare un'eccezione. Tuttavia, abbiamo lasciato attiva la gestione delle eccezioni per richiamare l'attenzione del lettore sulla possibilità che l'oggetto [impot] possa essere costruito in modo errato.

Ecco un esempio del programma precedente in azione:

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

Compiliamo tutte le classi [impot, impotsData, impotsArray] in un unico 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

Compiliamo il programma di test:

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

Possiamo eseguire i test:

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. Viste dell'applicazione web

L'applicazione avrà due viste: [form.aspx] e [errors.aspx]. Illustriamo il funzionamento dell'applicazione utilizzando alcuni screenshot. La vista [form.aspx] viene visualizzata quando l'URL [main.aspx] viene richiesto per la prima volta:

Image

L'utente compila il modulo:

Image

e utilizza il pulsante [Calcola] per ottenere il seguente risultato:

Image

È possibile che vengano inseriti dati errati:

Image

Cliccando sul pulsante [Calcola] si ottiene quindi una risposta diversa [errors.aspx]:

Image

Può utilizzare il link [Back to Form] in alto per tornare alla vista [form.aspx] com'era prima dell'errore:

Image

6.1.7. La vista [form.aspx]

La pagina [form.aspx] sarà la seguente:


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

I campi dinamici in questa pagina sono i seguenti:

rdouichecked
"checked" se la casella di controllo [yes] deve essere selezionata, "" in caso contrario
rdnonchecked
lo stesso vale per la casella di controllo [no]
txtChildren
valore da inserire nel campo di immissione [txtChildren]
txtSalary
valore da inserire nel campo di immissione [txtSalary]
txtTax
Valore da inserire nel campo di immissione [txtTax]

La pagina presenta due moduli, ciascuno con un pulsante [submit]. Il pulsante [Calculate] funge da pulsante [submit] per il seguente modulo:


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

Possiamo vedere che i parametri del modulo verranno inviati al controller con [action=calcul]. Il pulsante [Clear] è il pulsante [submit] per il seguente modulo:


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

Possiamo vedere che i parametri del modulo verranno inviati al controller con [action=effacer]. Qui, il modulo non ha parametri. Conta solo l'azione.

I campi in [formulaire.aspx] vengono calcolati da [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

I campi in [main.aspx] vengono calcolati sulla base di due informazioni inserite dal controller nel contesto della pagina:

  • Context.Items("form"): un dizionario NameValueCollection contenente i valori dei campi HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): valore dell'imposta

6.1.8. La vista [erreurs.aspx]

La vista [erreurs.aspx] visualizza eventuali errori che potrebbero verificarsi durante l'esecuzione dell'applicazione. Il suo codice di presentazione è il seguente:


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

La pagina presenta tre campi dinamici:

HTMLErrors
Codice HTML per un elenco di errori
href
URL di un link
link
testo del link

Questi campi vengono calcolati dal controller della pagina in [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

Il controller della pagina recupera le informazioni inserite dal controller dell'applicazione nel contesto della pagina:

Context.Items("errors"
Oggetto ArrayList contenente l'elenco dei messaggi di errore da visualizzare
Context.Items("href"
URL di un link
Context.Items("link"
testo del link

Ora che sappiamo cosa vede l'utente dell'applicazione, possiamo passare alla scrittura del controller dell'applicazione.

6.1.9. I controller [global.asax, main.aspx]

Rivediamo l'architettura MVC della nostra applicazione:

Image

Logica dell'applicazioneClient

Il controller [main.aspx] deve gestire tre azioni:

  • init: corrisponde alla prima richiesta del client. Il controller visualizza la vista [formulaire.aspx]
  • calcul: corrisponde alla richiesta di calcolo dell'imposta. Se i dati nel modulo di input sono corretti, l'imposta viene calcolata utilizzando la classe di business [taxes]. Il controller restituisce al client la vista [form.aspx] così come è stata convalidata, insieme all'imposta calcolata. Se i dati nel modulo di input sono errati, il controller restituisce la vista [errors.aspx] con un elenco di errori e un link per tornare al modulo.
  • return: corrisponde al ritorno al modulo dopo un errore. Il controller visualizza la vista [form.aspx] così come era stata convalidata prima dell'errore.

È inoltre noto che ogni richiesta all'applicazione passa attraverso il controller [global.asax], se presente. Abbiamo quindi, nel punto di ingresso dell'applicazione, una catena di due controller:

  • [global.asax], che, a causa dell'architettura ASP.NET, riceve tutte le richieste all'applicazione
  • [main.aspx], che, per scelta dello sviluppatore, riceve anch'esso tutte le richieste all'applicazione

La necessità di [main.aspx] deriva dal fatto che avremo una sessione da gestire. Abbiamo visto che [global.asax] non è adatto come controller in questo caso. Potremmo fare a meno di [global.asax] del tutto qui. Tuttavia, lo useremo per eseguire codice all'avvio dell'applicazione. Il diagramma MVC sopra riportato mostra che dovremo creare un oggetto [tax] per calcolare l'imposta. Non è necessario creare questo oggetto più volte; una volta è sufficiente. Lo creeremo quindi all'avvio dell'applicazione durante l'evento [Application_Start] gestito dal controller [global.asax]. Il codice per questo è il seguente:

[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

Una volta creato, l'oggetto [import] viene memorizzato nell'applicazione. È qui che le richieste provenienti da diversi client lo recupereranno. Poiché la creazione dell'oggetto [import] potrebbe fallire, gestiamo eventuali eccezioni e impostiamo una chiave [error] nell'applicazione per indicare se si è verificato o meno un errore durante la creazione dell'oggetto [import].

Il codice del controller [main.aspx, main.aspx.vb] sarà il seguente:

[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

Il controller inizia verificando che l'applicazione sia stata inizializzata correttamente:


        ' 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 il controller rileva che l'applicazione non è stata inizializzata correttamente (non è stato possibile creare l'oggetto [import] necessario per il calcolo), visualizza la pagina di errore con i parametri appropriati. In questo caso, non è necessario inserire il link di ritorno nel modulo poiché l'intera applicazione non è disponibile. Un messaggio di errore generico viene inserito in [Context.Items("errors")] di tipo [ArrayList].

Se il controller determina che l'applicazione è operativa, analizza l'azione che gli viene richiesto di eseguire tramite il parametro [action]. Abbiamo già incontrato questa modalità di funzionamento molte volte. L'elaborazione di ciascun tipo di azione viene delegata a una funzione.

6.1.9.1. Le azioni init e delete

Queste due azioni devono visualizzare il modulo di input vuoto. Ricordiamo che questo modulo (vedi viste) ha due parametri:

  • Context.Items("form"): un dizionario [NameValueCollection] contenente i valori dei campi HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): valore fiscale

La funzione [initAppli] inizializza questi due parametri per visualizzare un modulo vuoto.

6.1.9.2. L'azione di calcolo

Questa azione deve calcolare l'imposta dovuta in base ai dati inseriti nel modulo e restituire il modulo precompilato con i valori inseriti e l'importo dell'imposta calcolata. La funzione [calculImpot] responsabile di questo compito inizia verificando che i dati del modulo siano corretti:

  • il campo [rdMarie] deve essere presente e avere il valore [yes] o [no]
  • il campo [txtEnfants] deve essere presente ed essere un numero intero >=0
  • il campo [txtSalaire] deve essere presente ed essere un numero intero >=0

Se i dati inseriti non sono validi, il controller visualizza la vista [erreurs.aspx] dopo aver prima impostato i valori attesi per essa nel contesto:

  • I messaggi di errore vengono inseriti in un oggetto [ArrayList], che viene poi aggiunto al contesto [Context.Items("errors")]
  • Anche l'URL del link di ritorno e il testo di tale link vengono inseriti nel contesto.

Prima di passare il controllo alla pagina [erreurs.aspx], che invierà la risposta al client, i valori inseriti nel modulo (Request.Form) vengono inseriti nella sessione, associati alla chiave "form". Ciò consentirà a una richiesta successiva di recuperarli.

Ci si potrebbe chiedere se sia utile verificare che i campi [rdMarie, txtEnfants, txtSalaire] siano presenti nella richiesta inviata dal cliente. Ciò non è necessario se siamo certi che il nostro cliente sia un browser che ha ricevuto la vista [formulaire.aspx] contenente questi campi. Non possiamo mai esserne certi. Mostreremo un esempio più avanti in cui il client è l'applicazione [curl] che abbiamo già incontrato. Interrogheremo l'applicazione senza inviare i campi che si aspetta e vedremo come reagisce. Si tratta di una regola che è stata enunciata più volte e che ribadiamo qui: un'applicazione non deve mai fare supposizioni sul tipo di client che la interroga. Per sicurezza, deve presumere che possa essere interrogata da un'applicazione programmata che potrebbe inviarle stringhe di parametri inaspettate. Deve comportarsi correttamente in tutti i casi.

Nel nostro caso, abbiamo verificato che i campi [rdMarie, txtEnfants, txtSalaire] fossero presenti nella richiesta, ma non abbiamo controllato se potesse contenerne altri. In questa applicazione, verrebbero ignorati. Tuttavia, sempre come misura di sicurezza, sarebbe utile registrare questo tipo di richiesta in un file di log e inviare un avviso all'amministratore dell'applicazione in modo che sia a conoscenza del fatto che l'applicazione sta ricevendo richieste "strane". Analizzandole nel file di log, potrebbe rilevare un potenziale attacco all'applicazione e quindi adottare le misure necessarie per proteggerla.

Se i dati previsti sono corretti, il controller avvia il calcolo delle imposte utilizzando l'oggetto [tax] memorizzato nell'applicazione. Quindi memorizza le due informazioni previste dalla vista [form.aspx] nel contesto:

  • Context.Items("formulaire"): un dizionario [NameValueCollection] contenente i valori dei campi HTML [rdmarie,txtEnfants,txtSalaire], qui [Request.Form)], ovvero i valori precedentemente inseriti nel modulo
  • Context.Items("txtImpot"): il valore dell'imposta appena calcolato

Il lettore attento potrebbe essersi chiesto durante la lettura di quanto sopra: dato che l'oggetto [impot] creato all'avvio dell'applicazione è condiviso tra tutte le richieste, potrebbero esserci conflitti di accesso che portano al danneggiamento dei dati dell'oggetto [impot]? Per rispondere a questa domanda, dobbiamo tornare al codice della classe [impot]. Le richieste chiamano il metodo [impot].calculateTax per ottenere l'imposta dovuta. Quindi questo è il codice che dobbiamo esaminare:

        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

Supponiamo che un thread stia eseguendo il metodo precedente e venga interrotto. Un altro thread esegue quindi il metodo. Quali sono i rischi? Per scoprirlo, abbiamo aggiunto il seguente codice:


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

Il thread 1, dopo aver calcolato il valore [impot1] della variabile locale [impot], viene interrotto. Il thread 2 viene quindi eseguito e calcola un nuovo valore [impot2] per la stessa variabile [impot] prima di essere interrotto. Il thread 1 riprende il controllo. Cosa trova nella variabile locale [impot]? Poiché questa variabile è locale a un metodo, è memorizzata in una struttura di memoria chiamata stack. Questo stack fa parte del contesto del thread, che viene salvato quando il thread viene sospeso. Quando il thread 2 si avvia, il suo contesto viene configurato con un nuovo stack e quindi con una nuova variabile locale [impot]. Quando a sua volta il thread 2 viene sospeso, anche il suo contesto viene salvato. Quando il thread 1 viene riavviato, il suo contesto viene ripristinato, compreso lo stack. Recupera quindi la sua variabile locale [impot] e non quella del thread 2. Ci troviamo quindi in una situazione in cui non vi sono conflitti di accesso tra le richieste. I test condotti con la pausa di 10 secondi descritta sopra hanno confermato che le richieste simultanee hanno effettivamente prodotto il risultato atteso.

6.1.9.3. L'azione di ritorno

Questa azione corrisponde al clic sul link [Torna al modulo] nella vista [errors.aspx] per tornare alla vista [form.aspx], che è precompilata con i valori precedentemente inseriti e salvati nella sessione. La funzione [returnForm] recupera queste informazioni. I due parametri previsti dalla vista [form.aspx] vengono inizializzati:

  • Context.Items("form") con i valori precedentemente inseriti e salvati nella sessione
  • Context.Items("txtImpot") con la stringa vuota

6.1.10. Test dell'applicazione web

Tutti i file sopra indicati sono collocati in una cartella denominata <application-path>.

Image

In questa cartella viene creata una sottocartella [bin], nella quale viene collocato l'assembly [impot.dll] — generato dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb] —. Il comando di compilazione richiesto è mostrato di seguito:

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

Il file [impot.dll] sopra indicato deve essere collocato in <percorso-applicazione>\bin affinché l'applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<percorso-applicazione>,/impots1). Utilizzando un browser, richiediamo l'URL [http://localhost/impots1/main.aspx]:

Image

Compiliamo il modulo:

Image

Quindi avviamo il calcolo delle imposte utilizzando il pulsante [Calcola]. Otteniamo la seguente risposta:

Image

Quindi inseriamo dati errati:

Image

Cliccando sul pulsante [Calcola] si ottiene la seguente risposta:

Image

Cliccando sul link [Torna al modulo] si torna al modulo così com'era al momento dell'invio:

Image

Infine, cliccando sul pulsante [Cancella] si resetta la pagina:

Image

6.1.11. Utilizzo del client [curl]

È importante testare le applicazioni web con client diversi dai browser. Se si invia a un browser un modulo con parametri da inviare al momento dell'invio, il browser rinvierà i valori di tali parametri al server. Un altro client potrebbe non farlo, e in tal caso il server riceverebbe una richiesta con parametri mancanti. Il server deve sapere come comportarsi in questo caso. Un altro esempio è la convalida dei dati sul lato client. Se il modulo contiene dati da convalidare, questa convalida può essere eseguita sul lato client utilizzando script inclusi nel documento contenente il modulo. Il browser invierà il modulo solo se tutti i dati convalidati sul lato client sono validi. Si potrebbe quindi essere tentati, sul lato server, di presumere che riceveremo dati convalidati e non voler eseguire questa convalida una seconda volta. Sarebbe un errore. Infatti, un client diverso da un browser potrebbe inviare dati non validi al server e l'applicazione web potrebbe quindi comportarsi in modo imprevisto. Illustreremo questi punti utilizzando il client [curl].

Per prima cosa, richiediamo l'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>

Il server ci ha inviato il codice HTML del modulo. Nelle intestazioni HTTP è presente il cookie di sessione. Lo useremo nelle richieste successive per mantenere la sessione. Richiediamo l'azione [calcul] senza fornire alcun parametro:

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>

Possiamo vedere che l'applicazione web ha restituito la vista [errors] con tre messaggi di errore per i tre parametri mancanti. Ora inviamo alcuni parametri errati:

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>

I tre errori sono stati rilevati correttamente. Ora inviamo dei parametri validi:

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>

Abbiamo recuperato con successo l'imposta dovuta: 4.300 euro. La lezione che possiamo trarre da questo esempio è che non dobbiamo lasciarci fuorviare dal fatto che stiamo scrivendo un'applicazione web destinata a client che sono browser. Un'applicazione web è un servizio TCP/IP e questo protocollo di rete non rivela la natura dell'applicazione client di un servizio. Pertanto, non possiamo sapere se il client di un'applicazione web sia un browser o meno. Seguiamo quindi due regole:

  • Alla ricezione di una richiesta da un client, non facciamo ipotesi sul client e verifichiamo che i parametri previsti nella richiesta siano presenti e validi
  • costruiamo una risposta destinata ai browser, che generalmente consiste in documenti HTML

Un'applicazione web può essere realizzata per servire contemporaneamente diversi tipi di client, come browser e telefoni cellulari. Possiamo quindi includere un nuovo parametro in ogni richiesta che indichi il tipo di client. Pertanto, un browser richiederà il calcolo delle imposte tramite una richiesta all'URL http://machine/impots/main.aspx?client=navigateur&action=calcul, mentre un telefono cellulare invierà una richiesta all'URL http://machine/impots/main.aspx?client=mobile&action=calcul. La struttura MVC facilita lo sviluppo di un'applicazione di questo tipo. Essa assume la seguente forma:

Image

Il blocco [Classi di business, classi di accesso ai dati] rimane invariato. Questo perché si tratta di un componente indipendente dal client. Il blocco [Controller] cambia solo leggermente, ma deve tenere conto di un nuovo parametro nella richiesta: il parametro [client], che indica il tipo di client gestito. Il blocco [Viste] deve generare viste per ogni tipo di client. Potrebbe essere utile tenere conto della presenza del parametro [client] nella query fin dalla fase di progettazione dell'applicazione, anche se l'obiettivo a breve o medio termine è limitato ai browser. Se in seguito l'applicazione dovesse supportare un nuovo tipo di client, sarà necessario scrivere solo le viste su misura per quel client.

6.2. Esempio 2

6.2.1. Il problema

Qui proponiamo di affrontare lo stesso problema di prima, ma modificando l'origine dati per l'oggetto [tax] creato dall'applicazione web. Nella versione precedente, l'origine dati forniva valori provenienti da array hard-coded nel codice. Questa volta, la nuova origine dati li recupererà da un'origine dati ODBC associata a un database MySQL.

6.2.2. L'origine dati ODBC

I dati si troveranno in una tabella denominata [IMPOTS] in un database MySQL denominato [dbimpots]. Il contenuto di questa tabella sarà il seguente:

Image

Il proprietario del database è l'utente [admimpots] con password [mdpimpots]. Associamo un'origine dati ODBC a questo database. Prima di farlo, esaminiamo i diversi modi per accedere a un database utilizzando la piattaforma .NET.

Esistono molti database disponibili per le piattaforme Windows. Per accedervi, le applicazioni utilizzano programmi chiamati driver.

Image

Nel diagramma sopra, il driver ha due interfacce:

  • l'interfaccia I1 presentata all'applicazione
  • l'interfaccia I2 verso il database

Per evitare che un'applicazione scritta per il database B1 debba essere riscritta in caso di migrazione a un database diverso B2, sono stati compiuti sforzi di standardizzazione sull'interfaccia I1. Se si utilizzano database che impiegano driver "standardizzati", al database B1 verrà fornito il driver P1, al database B2 il driver P2 e l'interfaccia I1 di questi due driver sarà identica. Pertanto, non sarà necessario riscrivere l'applicazione. Ad esempio, è possibile migrare un database Access a un database MySQL senza modificare l'applicazione.

Esistono due tipi di driver standardizzati:

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

I driver ODBC forniscono l'accesso ai database. Le origini dati per i driver OLE DB sono più varie: database, sistemi di posta elettronica, directory, ecc. Non ci sono limiti. Qualsiasi origine dati può essere oggetto di un driver OLE DB se un fornitore decide di farlo. Il vantaggio è ovviamente significativo: si ha un accesso uniforme a un'ampia varietà di dati.

La piattaforma .NET 1.1 include tre tipi di classi di accesso ai dati:

  1. classi SQL Server.NET, per l'accesso ai database Microsoft SQL Server
  2. le classi Ole Db.NET, per l'accesso ai database da DBMS che offrono un driver OLE DB
  3. Classi ODBC.NET, per accedere a database da DBMS che offrono un driver ODBC

Il DBMS MySQL dispone da tempo di un driver ODBC. È quello che useremo ora. In Windows, selezioniamo [Menu Start/Pannello di controllo/Strumenti di amministrazione/Origini ODBC a 32 bit]. A seconda della versione di Windows, questo percorso può variare leggermente. Si aprirà la seguente applicazione, che ci consentirà di creare la nostra origine ODBC:

Image

Creeremo una fonte dati di sistema, ovvero una fonte dati che qualsiasi utente del computer può utilizzare. Pertanto, in alto, selezioniamo la scheda [Fonte dati di sistema]. La pagina visualizzata presenta un pulsante [Aggiungi], che utilizzeremo per creare una nuova fonte dati ODBC:

Image

La procedura guidata richiede di selezionare il driver ODBC da utilizzare. Windows viene fornito con una serie di driver ODBC preinstallati. Il driver ODBC di MySQL non è incluso in questo set. È quindi necessario installarlo prima. È possibile trovarlo online digitando i termini di ricerca "MySQL ODBC" o "MyODBC" in un motore di ricerca. In questo caso, abbiamo installato il driver [MySQL ODBC 3.51]. Lo selezioniamo e facciamo clic su [Fine]:

Image

Dovrai fornire le seguenti informazioni:

Nome origine dati
il nome che identificherà l'origine dati ODBC. Qualsiasi applicazione Windows potrà accedere all'origine utilizzando questo nome
Descrizione
Qualsiasi testo che descriva l'origine dati
Nome host
Il nome del computer che ospita il DBMS MySQL. In questo caso, si tratta del computer locale. Potrebbe essere un computer remoto. Ciò consentirebbe a un'applicazione Windows di accedere a un database remoto senza alcuna codifica speciale. Questo è uno dei principali vantaggi della sorgente ODBC.
Nome del database
Un DBMS MySQL può gestire più database. Qui specifichiamo quale vogliamo gestire: dbimpots
Utente
Il nome di un utente registrato nel sistema di gestione del database MySQL. L'accesso alla fonte dati avverrà con il nome di questo utente. In questo caso: admimpots
Password
La password di questo utente. In questo caso: mdpimpots
Porta
Porta operativa del DBMS MySQL. Per impostazione predefinita, è la porta 3306. Non l'abbiamo modificata

Una volta fatto questo, verifichiamo la validità delle nostre impostazioni di connessione utilizzando il pulsante [Test Data Source]:

Image

Una volta fatto ciò, possiamo fidarci della nostra origine dati ODBC. Ora possiamo utilizzarla. Facciamo clic su [OK] tutte le volte necessarie per uscire dalla procedura guidata ODBC.

Se il lettore non dispone del DBMS MySQL, può scaricarlo gratuitamente all'indirizzo [http://www.mysql.com]. Di seguito, illustriamo i passaggi per creare una sorgente ODBC con Access. I primi passaggi sono identici a quelli descritti in precedenza. Aggiungiamo una nuova sorgente dati di sistema:

Image

Il driver selezionato sarà [Microsoft Access Driver]. Fare clic su [Fine] per procedere alla definizione della sorgente ODBC:

Image

Le informazioni da fornire sono le seguenti:

Nome dell'origine dati
Il nome che identificherà l'origine dati ODBC. Qualsiasi applicazione Windows potrà accedere all'origine utilizzando questo nome
Descrizione
Qualsiasi testo che descriva l'origine dati
Database
Nome completo del file di Access da utilizzare

6.2.3. Una nuova classe di accesso ai dati

Rivediamo la struttura MVC della nostra applicazione:

Image

Nel diagramma sopra, la classe [impotsData] è responsabile del recupero dei dati. In questo caso, recupererà i dati dal database MySQL [dbimpots]. Come abbiamo appreso nella versione precedente di questa applicazione, [impotsData] è una classe astratta che deve essere derivata ogni volta che vogliamo adattarla a una nuova fonte di dati. Rivediamo la struttura di questa classe astratta:


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

La classe che deriva da [impotsData] deve implementare due metodi:

  • un costruttore se il costruttore predefinito di [impotsData] non è adatto
  • il metodo [getData], che restituisce i tre array (limites, coeffr, coeffn)

Creiamo la classe [impotsODBC], che recupererà i dati (limits, coeffr, coeffn) da una sorgente ODBC di cui forniremo il nome:


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

Diamo un'occhiata al costruttore:


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

Accetta come parametro il nome dell'origine dati ODBC contenente i dati da recuperare. Il costruttore si limita a memorizzare questo nome. Il metodo [getData] è responsabile della lettura dei dati dalla tabella [impots] e del loro inserimento in tre array (limites, coeffr, coeffn). Commentiamo il codice:

  • i parametri per la connessione all'origine dati ODBC sono definiti, ma la connessione non è aperta
            ' 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)
  • Definire tre oggetti [ArrayList] per recuperare i dati dalla tabella [impots]:

            ' tables to retrieve data
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
  • Tutto il codice di accesso al database è racchiuso in un blocco try/catch per gestire eventuali errori di accesso. Apriamo la connessione al database:

                ' on tente d'accéder à la base de données
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • Eseguiamo il comando [select] sulla connessione aperta. Otteniamo un oggetto [OdbcDataReader] che ci permetterà di iterare attraverso le righe della tabella risultante dal 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()
  • Percorriamo la tabella dei risultati, riga per riga. Per farlo, utilizziamo il metodo [Read] dell'oggetto [OdbcDataReader] ottenuto in precedenza. Questo metodo esegue due operazioni:
    • Avanza di una riga nella tabella. Inizialmente, il cursore è posizionato prima della prima riga
    • restituisce il valore booleano [true] se è stato possibile avanzare, [false] in caso contrario; quest'ultimo caso indica che tutte le righe sono state elaborate.

Le colonne della riga corrente dell'oggetto [OdbcDataReader] si ottengono tramite OdbcDataReader. Questo restituisce un oggetto che rappresenta il valore della colonna. Iteriamo attraverso l'intera tabella per popolare il suo contenuto nei tre oggetti [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"))
  • Una volta fatto questo, liberiamo le risorse associate alla connessione:
                ' libération des ressources
                myReader.Close()
                impotsConn.Close()
  • Il contenuto dei tre oggetti [ArrayList] viene trasferito in tre array standard:

            ' 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
  • Una volta caricati i dati della tabella [impots] nei tre array, non resta che verificarne il contenuto utilizzando il metodo [checkData] della 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. Test per la classe di accesso ai dati

Un programma di test potrebbe essere simile a questo:

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

L'applicazione viene avviata con un parametro:

  • DSNimpots: nome dell'origine dati ODBC da utilizzare

Il calcolo delle imposte viene eseguito utilizzando un oggetto di tipo [tax] creato all'avvio dell'applicazione:


            ' 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

Una volta inizializzata, l'applicazione richiede ripetutamente all'utente di inserire le tre informazioni necessarie per calcolare l'imposta:

  • stato civile: o per sposato, n per non sposato
  • il numero di figli
  • stipendio annuo

Tutte le classi sono compilate:

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

Il programma di test viene quindi compilato:

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

Il programma di test viene eseguito inizialmente con l'origine dati ODBC di 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)

Passiamo dall'origine ODBC a un'origine 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. Viste dell'applicazione web

Sono le stesse dell'applicazione precedente: formulaire.aspx e erreurs.aspx

6.2.6. Controller dell'applicazione [global.asax, main.aspx]

È necessario modificare solo il controller [global.asax]. È responsabile della creazione dell'oggetto [impot] all'avvio dell'applicazione. Il costruttore di questo oggetto ha un unico parametro: l'oggetto [impotsData], responsabile del recupero dei dati. Questo parametro cambia quindi per ogni nuovo tipo di origine dati. Il controller [global.asax.vb] diventa il seguente:


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

L'origine dati per l'oggetto [impot] è ora un oggetto [impotODBC]. Questo oggetto accetta come parametro il nome DSN dell'origine dati ODBC da utilizzare. Anziché codificare in modo rigido questo nome nel codice, lo inseriamo nel file di configurazione [web.config] dell'applicazione:


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

Sappiamo che il valore di una chiave C nella sezione <appSettings> del file [web.config] viene recuperato nel codice dell'applicazione utilizzando [ConfigurationSettings.AppSettings(C)].

Per determinare la causa dell'eccezione, registriamo il messaggio di eccezione nell'applicazione in modo che rimanga disponibile per le query. Il controllo [main.aspx.vb] includerà questo messaggio nel proprio elenco di errori:


    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. Riepilogo delle modifiche

L'applicazione è pronta per essere testata. Elenchiamo le modifiche apportate rispetto alla versione precedente:

  1. è stata creata una nuova classe di accesso ai dati
  2. il controller [global.asax.vb] è stato modificato in due punti: la costruzione dell'oggetto [import] e la registrazione del messaggio relativo a qualsiasi eccezione nell'applicazione
  3. Il controller [main.aspx.vb] è stato modificato in un punto per visualizzare il messaggio di eccezione precedente
  4. È stato aggiunto un file [web.config]

Il lavoro di modifica è stato svolto principalmente in 1, ovvero al di fuori dell'applicazione web. Ciò è stato reso possibile dall'architettura MVC dell'applicazione, che separa il controller dalle classi di business. Questo è il punto centrale di tale architettura. Si potrebbe dimostrare che, con un file [web.config] appropriato, qualsiasi modifica al controller dell'applicazione avrebbe potuto essere evitata. È possibile specificare in [web.config] il nome della classe di accesso ai dati da istanziare dinamicamente, ad esempio —, nonché i vari parametri richiesti per tale istanziazione. Con queste informazioni, [global.asax] può istanziare l'oggetto di accesso ai dati. La modifica dell'origine dati equivale quindi a:

  • creare la classe di accesso ai dati per quella fonte, se non esiste ancora
  • modificare il file [web.config] per consentire la creazione dinamica di un'istanza di questa classe in [global.asax]

6.2.8. Test dell'applicazione web

Tutti i file sopra indicati sono collocati in una cartella denominata <application-path>.

Image

In questa cartella viene creata una sottocartella [bin], nella quale viene collocato l'assembly [impot.dll] — generato dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb] —. Il comando di compilazione richiesto è mostrato di seguito:

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

Il file [impot.dll] sopra indicato deve essere collocato in <percorso-applicazione>\bin affinché l'applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<percorso-applicazione>,/impots2). I test danno gli stessi risultati della versione precedente, poiché la presenza del database è trasparente per l'utente. Per dimostrare questa presenza, tuttavia, ci assicuriamo che la sorgente ODBC non sia disponibile arrestando il DBMS MySQL e richiediamo l'URL [http://localhost/impots2/main.aspx]. Riceviamo la seguente risposta:

Image

6.3. Esempio 3

6.3.1. Il problema

Qui proponiamo di affrontare lo stesso problema modificando nuovamente l'origine dati dell'oggetto [impot] creato dall'applicazione web. Questa volta, la nuova origine dati sarà un database ACCESS accessibile tramite un driver OLEDB. Il nostro obiettivo è dimostrare un altro modo per accedere a un database.

6.3.2. L'origine dati OLEDB

I dati si troveranno in una tabella denominata [IMPOTS] in un database ACCESS. Il contenuto di questa tabella sarà il seguente:

Image

6.3.3. La classe di accesso ai dati

Rivediamo la struttura MVC della nostra applicazione:

Image

  • Nel diagramma sopra, la classe [impotsData] è responsabile del recupero dei dati. Questa volta, dovrà farlo da una fonte OLEDB.

Creiamo la classe [impotsOLEDB], che recupererà i dati (limits, coeffr, coeffn) da una fonte ODBC che chiameremo:


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

Diamo un'occhiata al costruttore:


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

Riceve come parametro la stringa di connessione per l'origine OLEDB contenente i dati da recuperare. Il costruttore si limita a memorizzarla. Una stringa di connessione contiene tutti i parametri richiesti dal driver OLEDB per connettersi all'origine OLEDB. In genere è piuttosto complessa. Per trovare la stringa di connessione per i database ACCESS, è possibile utilizzare lo strumento [WebMatrix]. Avviare questo strumento. Esso fornisce una finestra per la connessione a un'origine dati:

Utilizzando l'icona indicata dalla freccia sopra, è possibile creare una connessione a due tipi di database Microsoft: SQL Server e ACCESS. Scegliamo ACCESS:

Image

Abbiamo utilizzato il pulsante [...] per selezionare il database ACCESS. Confermiamo la procedura guidata. Nella scheda [Dati], le icone rappresentano la connessione:

Image

Ora, creiamo un nuovo file .aspx tramite [File/Nuovo file]:

Image

Otteniamo una pagina vuota su cui possiamo progettare la nostra interfaccia web:

Image

Trascinamo la tabella [impots] dalla scheda [Dati] sul foglio sopra. Otteniamo il seguente risultato:

Image

Facciamo clic con il tasto destro sull'oggetto [AccessDataSourceControl] sottostante per accedere alle sue proprietà:

Image

La stringa di connessione OLEDB al database ACCESS è fornita dalla proprietà [ConnectionString] sopra:

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

Possiamo vedere che questa stringa è composta da una parte fissa e una parte variabile, che è semplicemente il nome del file ACCESS. Useremo questo fatto per generare la stringa di connessione alla nostra origine dati OLEDB.

Torniamo ora alla nostra classe [impotsOLEDB]. Il metodo [getData] è responsabile della lettura dei dati dalla tabella [impots] e del loro inserimento in tre array (limites, coeffr, coeffn). Commentiamo il suo codice:

  • Definiamo l'oggetto [DataAdapter], che ci consentirà di trasferire in memoria il risultato di una query SQL SELECT. A tal fine, definiamo la query [select] da eseguire e la associamo all'oggetto [DataAdapter]. Il costruttore di [DataAdapter] richiede anche la stringa di connessione che utilizzerà per connettersi all'origine 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)
  • Eseguiamo il comando [select] utilizzando il metodo [Fill] dell'oggetto [DataAdapter]. Il risultato del [select] viene caricato in un oggetto [DataTable] creato a questo scopo. Un oggetto [DataTable] è la rappresentazione in memoria di una tabella di database, ovvero un insieme di righe e colonne. Gestiamo un'eccezione che potrebbe verificarsi se, ad esempio, la stringa di connessione non è corretta.

            ' 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
  • In [content], abbiamo la tabella [taxes] restituita dall'istruzione [select]. Un oggetto [DataTable] è una tabella, ovvero un insieme di righe. Queste sono accessibili tramite la proprietà [rows] di [DataTable]:

            ' retrieve the contents of the impots table
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • Ogni elemento della raccolta [taxRows] è un oggetto [DataRow] che rappresenta una riga della tabella. Questa riga ha colonne accessibili tramite l'oggetto [DataRow] attraverso la sua proprietà [Item]. [DataRow].[Item(i)] è la colonna numero i di [DataRow]. Iterando attraverso la raccolta di righe (la raccolta DataRows di taxRows) e la raccolta di colonne per ogni riga, possiamo recuperare l'intera tabella:
            ' 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
  • Una volta caricati i dati della tabella [taxes] nei tre array, non resta che verificarne il contenuto utilizzando il metodo [checkData] della 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. Test per la classe di accesso ai dati

Un programma di test potrebbe essere simile a questo:

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

L'applicazione viene avviata con un parametro:

  • bdACCESS: nome del file ACCESS da utilizzare

Il calcolo delle imposte viene eseguito utilizzando un oggetto di tipo [tax] creato all'avvio dell'applicazione:


            ' 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

La stringa di connessione alla fonte OLEDB è stata creata utilizzando le informazioni ottenute con [WebMatrix].

Una volta inizializzata, l'applicazione richiede ripetutamente all'utente di inserire le tre informazioni necessarie per calcolare le imposte:

  • stato civile: o per sposato, n per non sposato
  • numero di figli
  • stipendio annuo

Tutte le classi sono state compilate:

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

Il file [impots.mdb] viene inserito nella cartella dell'applicazione di prova e l'applicazione viene avviata come segue:

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)

È possibile eseguire l'applicazione con un file ACCESS errato:

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. Le viste dell'applicazione web

Sono le stesse dell'applicazione precedente: formulaire.aspx e erreurs.aspx

6.3.6. Controller dell'applicazione [global.asax, main.aspx]

È necessario modificare solo il controller [global.asax]. Esso è responsabile della creazione dell'oggetto [impot] all'avvio dell'applicazione. Il costruttore di questo oggetto ha un unico parametro: l'oggetto [impotsData] responsabile del recupero dei dati. Questo parametro cambia quindi poiché stiamo cambiando le origini dati. Il controller [global.asax.vb] diventa il seguente:


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

L'origine dati per l'oggetto [impot] è ora un oggetto [impotOLEDB]. Questo oggetto accetta come parametro la stringa di connessione per l'origine dati OLEDB da utilizzare. Questa stringa è memorizzata nel file di configurazione [web.config] dell'applicazione:


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

Il controller [main.aspx] rimane invariato.

6.3.7. Riepilogo delle modifiche

L'applicazione è pronta per essere testata. Elenchiamo le modifiche apportate alla versione precedente:

  1. è stata creata una nuova classe di accesso ai dati
  2. il controller [global.asax.vb] è stato modificato in un punto: la costruzione dell'oggetto [import]
  3. È stato aggiunto un file [web.config]

6.3.8. Test dell'applicazione web

Tutti i file sopra indicati sono collocati in una cartella denominata <percorso-applicazione>.

Image

In questa cartella viene creata una sottocartella [bin], nella quale viene collocato l'assembly [impot.dll] — generato dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb] —. Il comando di compilazione richiesto è mostrato di seguito:

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

Il file [impot.dll] generato da questo comando deve essere collocato in <percorso-applicazione>\bin in modo che l'applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<percorso-applicazione>,/impots3). I test producono gli stessi risultati della versione precedente.

6.4. Esempio 4

6.4.1. Il problema

Proponiamo ora di trasformare la nostra applicazione in un'applicazione di simulazione del calcolo delle imposte. Un utente potrà eseguire calcoli fiscali successivi, che gli verranno presentati in una nuova vista simile a questa:

Image

6.4.2. La struttura MVC dell'applicazione

La struttura MVC dell'applicazione risulta così:

Image

Viene visualizzata una nuova vista [simulations.aspx], di cui abbiamo appena fornito uno screenshot. La classe di accesso ai dati sarà la classe [impotsODBC] dell'Esempio 2.

6.4.3. Le viste dell'applicazione web

La vista [erreurs.aspx] rimane invariata. La vista [formulaire.aspx] subisce una leggera modifica. Infatti, l'importo dell'imposta non compare più in questa vista. Ora si trova nella vista [simulations.aspx]. Pertanto, all'avvio, la pagina presentata all'utente è la seguente:

Image

Inoltre, la vista [form] include uno script JavaScript che convalida i dati inseriti prima di inviarli al server, come mostrato nell'esempio seguente:

Image

Il codice di presentazione è il seguente:


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

I campi dinamici nella pagina sono gli stessi delle versioni precedenti. Il campo dinamico per l'importo dell'imposta è stato rimosso. Il pulsante [Calcola] non è più un pulsante [invia]. È un [pulsante] e, quando viene cliccato, viene eseguita la funzione JavaScript [calculate()]:


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

Abbiamo assegnato al modulo il nome [frmImpots] in modo da poterlo richiamare nello script [calculer]:


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

La funzione JavaScript [calculate] utilizza espressioni regolari per convalidare i campi nei moduli [document.frmImpots.txtEnfants] e [document.frmImpots.txtSalaire]. Se i valori inseriti sono corretti, vengono inviati al server tramite [document.frmImpots.submit()].

La pagina di presentazione ottiene i suoi campi dinamici dal seguente controller [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

Il controller [formulaire.aspx.vb] è identico alle versioni precedenti, tranne per il fatto che non deve più recuperare il campo [txtImpot] dal contesto, poiché questo campo è stato rimosso dalla pagina.

La vista [simulations.aspx] appare come segue:

Image

e corrisponde al seguente codice di presentazione:


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

Questo codice contiene tre campi dinamici:

simulationsHTML
Codice HTML per un elenco di simulazioni sotto forma di righe di una tabella HTML
href
URL di un link
link
testo del link

Sono generati dal controller [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

Il controller della pagina recupera le informazioni inserite dal controller dell'applicazione nel contesto della pagina:

Context.Items("simulations")
Oggetto ArrayList contenente l'elenco delle simulazioni da visualizzare. Ogni elemento è un array di 4 stringhe che rappresentano le informazioni della simulazione (coniugato, figli, stipendio, imposte).
Context.Items("href")
URL di un link
Context.Items("link")
Testo del link

6.4.4. I controller [global.asax, main.aspx]

Esaminiamo l'architettura MVC della nostra applicazione:

Image

Il controller [main.aspx] deve gestire tre azioni:

  • init: corrisponde alla prima richiesta del client. Il controller visualizza la vista [form.aspx]
  • calcul: corrisponde alla richiesta di calcolo delle imposte. Se i dati nel modulo di input sono corretti, l'imposta viene calcolata utilizzando la classe di business [impotsODBC]. Il controller restituisce al client la vista [simulations.aspx] con il risultato della simulazione corrente più tutte quelle precedenti. Se i dati nel modulo di input non sono corretti, il controller restituisce la vista [erreurs.aspx] con l'elenco degli errori e un link per tornare al modulo.
  • return: corrisponde al ritorno al modulo dopo un errore. Il controller visualizza la vista [form.aspx] così come era stata convalidata prima dell'errore.

In questa nuova versione, è cambiata solo l'azione [calcul]. Infatti, se i dati sono validi, deve portare alla vista [simulations.aspx], mentre in precedenza portava alla vista [form.aspx]. Il controller [main.aspx.vb] diventa il seguente:


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

Abbiamo incluso sopra solo ciò che è necessario per comprendere le modifiche presenti esclusivamente nella funzione [calculImpots]:

  • In primo luogo, la funzione salva il modulo [Request.Form] nella sessione in modo che il modulo possa essere rigenerato nello stato in cui è stato convalidato. Ciò deve essere fatto in tutti i casi, poiché sia che l'operazione dia come risultato la risposta [erreurs.aspx] o la risposta [simulations.aspx], si ritorna al modulo tramite il link [Torna al modulo]. Per ripristinare correttamente il modulo, i suoi valori devono essere stati salvati in precedenza nella sessione.
  • Se i dati inseriti sono corretti, la funzione aggiunge la simulazione corrente (stato civile, figli, stipendio, imposte) all'elenco delle simulazioni. Tale elenco si trova nella sessione associata alla chiave "simulations".
  • L'elenco delle simulazioni viene memorizzato nella sessione per un uso futuro. Viene inoltre inserito nel contesto corrente poiché è lì che la vista [simulations.aspx] si aspetta di trovarlo
  • La vista [simulations.aspx] viene visualizzata una volta che le altre informazioni di cui ha bisogno sono state inserite nel contesto

6.4.5. Riepilogo delle modifiche

L'applicazione è pronta per essere testata. Elenchiamo le modifiche apportate alle versioni precedenti:

  1. È stata creata una nuova vista
  2. Il controller [main.aspx.vb] è stato modificato in un punto: la gestione dell'azione [calcul]

6.4.6. Test dell'applicazione web

Il lettore è invitato a eseguire i test. Ecco un promemoria della procedura. Tutti i file dell'applicazione sono collocati in una cartella denominata <percorso-applicazione>. All'interno di questa cartella viene creata una sottocartella [bin], contenente l'assembly [impot.dll] generato dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Il file [impot.dll] prodotto da questo comando deve essere collocato in <application-path>\bin in modo che l'applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<application-path>,/impots4).

6.5. Conclusione

Gli esempi precedenti hanno illustrato, in uno scenario concreto, i meccanismi comunemente utilizzati nello sviluppo web. Abbiamo utilizzato costantemente l'architettura MVC per il suo valore didattico. Avremmo potuto gestire questi stessi esempi in modo diverso e forse più semplice senza questa architettura. Tuttavia, essa offre vantaggi significativi non appena l'applicazione diventa un po' complessa con più pagine.

Potremmo continuare i nostri esempi in vari modi. Eccone alcuni:

  • L'utente potrebbe voler salvare le proprie simulazioni nel tempo. Potrebbe eseguire simulazioni il giorno D e recuperarle il giorno D+3, ad esempio. Una possibile soluzione a questo problema è l'uso dei cookie. Sappiamo che il token di sessione tra il server e un client viene trasmesso tramite questo meccanismo. Potremmo anche utilizzare questo meccanismo per trasmettere le simulazioni tra il client e il server.
    • Contemporaneamente all'invio della pagina contenente i risultati della simulazione, il server invia un cookie nelle sue intestazioni HTTP contenente una stringa che rappresenta le simulazioni. Poiché queste si trovano in un oggetto [ArrayList], tale oggetto deve essere convertito in una [String]. Il server imposterebbe una durata per il cookie, ad esempio 30 giorni.
    • Il browser del client memorizza i cookie ricevuti in un file e li rinvia ogni volta che effettua una richiesta al server che li ha inviati, a condizione che siano ancora validi (durata non superata). Il server riceverà una stringa di caratteri [String] per le simulazioni, che dovrà convertire in un oggetto [ArrayList].

I cookie sono gestiti da [Response.Cookies] quando vengono inviati al client e da [Request.Cookies] quando vengono ricevuti sul server.

  • Il meccanismo sopra descritto può diventare piuttosto dispendioso in termini di risorse se il numero di simulazioni è elevato. Inoltre, è comune che un utente cancelli periodicamente i propri cookie eliminandoli tutti, anche se altrimenti consente al proprio browser di utilizzarli. Quindi, prima o poi, i cookie di simulazione andranno persi. Potremmo quindi volerli memorizzare sul server piuttosto che sul client, ad esempio in un database. Per collegare le simulazioni a un utente specifico, l'applicazione potrebbe iniziare con una fase di autenticazione che richiede un nome utente e una password, che a loro volta sono memorizzati in un database o in qualsiasi altro tipo di archivio dati.
  • Potremmo anche voler rendere più sicura l'operazione della nostra applicazione. Attualmente essa si basa su due presupposti:
    1. l'utente passa sempre attraverso il controller [main.aspx]
    2. e in tal caso, utilizza sempre le azioni disponibili sulla pagina che gli è stata inviata

Cosa succede, ad esempio, se l'utente richiede direttamente l'URL [http://localhost/impots4/formulaire.aspx]? Questo scenario è improbabile poiché l'utente non è a conoscenza di questo URL. Tuttavia, deve essere preso in considerazione. Può essere gestito dal controller dell'applicazione [global.asax], che elabora tutte le richieste effettuate all'applicazione. Può quindi verificare che la risorsa richiesta sia effettivamente [main.aspx].

Uno scenario più probabile è che un utente non utilizzi le azioni disponibili nella pagina che il server gli ha inviato. Ad esempio, cosa succede se l'utente richiede direttamente l'URL [http://localhost/impots4/main.aspx?action=retour] senza prima compilare il modulo? Proviamo. Otteniamo la seguente risposta:

Image

Il server va in crash. Questo è normale. Per l'azione [return], il controller si aspetta di trovare nella sessione un oggetto [NameValueCollection] che rappresenti i valori del modulo che deve visualizzare. Non li trova. Il meccanismo del controller fornisce una soluzione elegante a questo problema. Per ogni richiesta, il controller [main.aspx] può verificare che l'azione richiesta sia effettivamente una delle azioni della pagina precedentemente inviata all'utente. Possiamo utilizzare il seguente meccanismo:

  • Prima di inviare la risposta al client, il controller memorizza le informazioni che identificano questa pagina nella sessione del client
  • quando riceve una nuova richiesta dal client, verifica che l'azione richiesta appartenga effettivamente all'ultima pagina inviata a quel client
  • Le informazioni che collegano le pagine e le azioni consentite su tali pagine possono essere inserite nel file di configurazione [web.config] dell'applicazione.
  • L'esperienza dimostra che i controller delle applicazioni condividono un'ampia base comune e che è possibile costruire un controller generico, la cui specializzazione per una data applicazione viene gestita tramite un file di configurazione. Questo è l'approccio adottato, ad esempio, dallo strumento [Struts] nel campo della programmazione web Java.