Skip to content

6. Esempi

In questo capitolo ci proponiamo di illustrare 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 la propria imposta. Consideriamo il caso semplificato di un contribuente che abbia da dichiarare solo il proprio stipendio (dati del 2004 relativi ai redditi del 2003):

  • si calcola il numero di quote del dipendente nbParts=nbEnfants/2 +1 se non è sposato, nbEnfants/2+2 se è sposato, dove nbEnfants è il numero dei suoi figli.
  • se ha almeno tre figli, ha diritto a una mezza quota in più
  • si calcola il suo reddito imponibile R=0,72*S, dove S è il suo stipendio annuale
  • si calcola il suo coefficiente familiare QF = R / nbParts
  • si calcola la sua imposta I. Consideriamo la seguente tabella:
4262
0
0
8382
0,0683
291,09
14753
0,1914
1322,92
23.888
0,2826
2668,39
38.868
0,3738
4846,98
47932
0,4262
6883,66
0
0,4809
9505,54

Ogni riga contiene 3 campi. Per calcolare l'imposta I, si cerca la prima riga in cui QF <= campo1. Ad esempio, se QF = 5000, si troverà la riga

    8382        0.0683        291.09

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

    0                0.4809    9505.54

il che dà l’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 del controller sarà svolto dalla pagina [main.aspx]. Saranno disponibili tre 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 del modulo di inserimento sono corretti, l’imposta viene calcolata tramite la classe di business [impots]. Il controller restituisce al client la vista [formulaire.aspx] così come era stata convalidata, con l’aggiunta dell’imposta calcolata. Se i dati del modulo di inserimento non sono corretti, il controller restituirà la vista [erreurs.aspx] con l’elenco degli errori e un link per tornare al modulo.
  • ritorno: corrisponde al ritorno al modulo dopo un errore. Il controller visualizza la vista [formulaire.aspx] così come era stata convalidata prima dell’errore.

Il controller [main.aspx] non si occupa del calcolo delle imposte. Si limita a gestire il dialogo client-server e a far eseguire le azioni richieste dal client. Per l’azione [calcul], si avvarrà della classe di business [impot].

6.1.3. La classe di business

La classe «impostazioni fiscali» sarà definita come segue:


' spazi dei nomi importati
Imports System

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

        ' costruttore
        Public Sub New(ByRef source As impotsData)
            ' i dati necessari per il calcolo dell'imposta
             ' provengono da una fonte esterna [source]
             ' vengono recuperati – potrebbe esserci un'eccezione
            Dim data() As Object = source.getData
            limites = CType(data(0), Decimal())
            coeffR = CType(data(1), Decimal())
            coeffN = CType(data(2), Decimal())
        End Sub

         ' calcolo dell'imposta
        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' calcolo del numero di quote
            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
             ' calcolo del reddito imponibile e del quoziente familiare
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' calcolo dell'imposta
            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 imposta viene creato fornendo al suo costruttore una fonte di dati di tipo [impotsData]. Questa classe dispone di un metodo pubblico [getData] che consente di ottenere i tre array di dati necessari al calcolo dell'imposta e che sono stati presentati in precedenza. Questo metodo è in grado di gestire un'eccezione qualora i dati non siano stati acquisiti o risultino errati. Una volta creato l'oggetto [impot], è possibile richiamare ripetutamente il suo metodo «calcolare», che calcola l'imposta del contribuente in base al suo stato civile (coniugato o meno), al numero di figli e al suo stipendio annuale.

6.1.4. La classe di accesso ai dati

La classe [impotsData] è la classe che consente di accedere ai dati. Si tratta di una classe astratta. È necessario creare una classe derivata per ogni nuova possibile fonte di dati (tabelle, file di testo, database, console, ...). 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

        ' metodo di accesso ai dati
        Public MustOverride Function getData() As Object()

        ' metodo di verifica dei dati
        Protected Function checkData() As Integer
            ' verifica i dati acquisiti
            ' devono essere presenti dei dati
            valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
            If Not valide Then Return 1
            ' devono esserci 3 tabelle della stessa dimensione
            If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
            If Not valide Then Return 2
            ' gli array devono essere non vuoti
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' ogni tabella deve contenere elementi >=0 e in ordine crescente
            valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
            If Not valide Then Return 4
            ' tutto è corretto
            Return 0
        End Function

        ' verifica la validità del contenuto di un array
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
            ' l'array deve avere i primi n elementi >=0 e in ordine strettamente crescente
            If tableau(0) < 0 Then Return False
            For i As Integer = 1 To n - 1
                If tableau(i) <= tableau(i - 1) Then Return False
            Next
            ' va bene
            Return True
        End Function
    End Class
End Namespace

La classe ha i seguenti attributi protetti:

limites
tabella dei limiti delle fasce di imposta
coeffr
tabella dei coefficienti applicati al reddito imponibile
coeffn
tabella dei coefficienti applicati al numero di quote
checked
valore booleano che indica se i dati (limiti, coeffr, coeffn) sono stati verificati
valide
valore booleano che indica se i dati (limiti, coeffr, coeffn) sono validi

La classe non ha un costruttore. Dispone di un metodo astratto [getData] che le classi derivate dovranno implementare. Questo metodo ha il compito di:

  • assegnare valori ai tre array limiti, 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 (limites, coeffr, coeffn). Ciò esonera le classi derivate dall’implementarli; dovranno semplicemente utilizzarli.

La prima classe derivata che utilizzeremo sarà la seguente:


Imports System.Collections
Imports System

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

        ' costruttore senza argomenti
        Public Sub New()
            ' inizializzazione degli array con costanti
            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

        ' costruttore con tre array in ingresso
        Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
            ' si memorizzano i dati
            Me.limites = limites
            Me.coeffr = coeffr
            Me.coeffn = coeffn
            checked = False
        End Sub

        Public Overrides Function getData() As Object()
            ' si verificano eventualmente i dati
            Dim erreur As Integer
            If Not checked Then erreur = checkData() : checked = True
            ' se non validi, si genera un'eccezione
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' altrimenti si restituiscono i tre array
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Questa classe, denominata [impotsArray], dispone di due costruttori:

  • un costruttore senza argomenti che inizializza gli attributi (limiti, coeffr, coeffn) della classe base con array hardcoded
  • un costruttore che inizializza gli attributi (limiti, coeffr, coeffn) della classe base con array passati come parametri

Il metodo [getData], che consentirà alle classi esterne di ottenere gli array (limiti, coeffr, coeffn), si limita a verificare 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 certificata. In questo modo, la fase di debug dell’applicazione web potrà concentrarsi sulla parte relativa al controller e alle viste. Un programma di test potrebbe essere il seguente:


' opzioni
Option Strict On
Option Explicit On 

' spazi dei nomi
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr
    Module test
        Sub Main()
             ' programma interattivo per il calcolo delle imposte
             ' l'utente digita tre dati dalla tastiera: coniugato nbEnfants stipendio
             ' il programma visualizza quindi l'imposta da pagare
            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"

             ' creazione di un oggetto «imposta»
            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
            ' ciclo infinito
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Long
            While True
                ' vengono richiesti i parametri per il calcolo dell’imposta
                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()
                ' C'è qualcosa da fare?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' verifica del numero di argomenti nella riga inserita
                Dim erreur As Boolean = False
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe)
                    erreur = True
                End If
                ' verifica della validità dei parametri
                If Not erreur Then
                    ' sposato
                    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
                    ' stipendio
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                End If
                 ' se i parametri sono corretti, si calcola l'imposta
                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 chiede all'utente di digitare le tre informazioni necessarie per calcolare la propria imposta:

  • il proprio stato civile: o per sposato, n per non sposato
  • il numero di figli
  • il suo stipendio annuale

Il calcolo dell’imposta viene effettuato tramite un oggetto di tipo [impot] creato all’avvio dell’applicazione:


             ' creazione di un oggetto imposta
            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 fonte di dati si utilizza un oggetto di tipo [impotsArray]. Viene utilizzato il costruttore senza argomenti di questa classe, che fornisce i tre array (limiti, coeffr, coeffn) con valori "fissi". La creazione di un oggetto [impot] può, in teoria, generare un'eccezione poiché, per crearsi, l'oggetto richiederà i dati (limiti, coeffr, coeffn) alla propria fonte di dati che gli è stata passata come parametro, e tale acquisizione di dati può far scattare un'eccezione. In questo caso, il metodo di acquisizione dei dati (codifica hardcoded) non può causare alcuna eccezione. Abbiamo tuttavia lasciato la gestione di quest’ultima per richiamare l’attenzione del lettore sulla possibilità che l’oggetto [impot] venga costruito in modo errato.

Ecco un esempio di esecuzione del programma precedente:

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 l'insieme delle classi [impot, impotsData, impotsArray] in un 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. Le viste dell'applicazione web

L'applicazione avrà due viste: [formulaire.aspx] e [erreurs.aspx]. Illustriamo il funzionamento dell'applicazione tramite alcune schermate. La vista [formulaire.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 [Calculer] per ottenere la seguente risposta:

Image

Potrebbe commettere un errore nell'inserimento dei dati:

Image

L'utilizzo del pulsante [Calculer] porta quindi a un'altra risposta [erreurs.aspx]:

Image

Può utilizzare il link [Retour au formulaire] sopra riportato per tornare alla vista [formulaire.aspx] così come l'aveva confermata prima dell'errore:

Image

6.1.7. La vista [formulaire.aspx]

La pagina [formulaire.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 di questa pagina sono i seguenti:

rdouichecked
"checked" se la casella [oui] deve essere selezionata, "" in caso contrario
rdnonchecked
idem per la casella [non]
txtEnfants
valore da inserire nel campo di immissione [txtEnfants]
txtSalaire
valore da inserire nel campo di immissione [txtSalaire]
txtImpot
valore da inserire nel campo di immissione [txtImpot]

La pagina presenta due moduli, ciascuno dei quali contiene un pulsante [submit]. Il pulsante [Calculer] è il pulsante [submit] del modulo seguente:


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

Si può notare che i parametri del modulo verranno inviati al controller con [action=calcul]. Il pulsante [Effacer] corrisponde al pulsante [submit] del modulo seguente:


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

Si nota che i parametri del modulo verranno inviati al controller con [action=effacer]. In questo caso, il modulo non ha alcun parametro. È solo l'azione che conta.

I campi di [formulaire.aspx] vengono calcolati da [formulaire.aspx.vb]:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

    ' campi della pagina
    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
        ' si recupera la richiesta precedente dal contesto
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' si prepara la pagina da visualizzare
        ' pulsanti di opzione
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' il resto
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

Il calcolo dei campi di [main.aspx] viene effettuato sulla base di due informazioni inserite dal controllore nel contesto della pagina:

  • Context.Items("modulo"): dizionario di tipo 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] è quella che visualizza gli eventuali errori che possono verificarsi durante il ciclo di vita 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:

erreursHTML
codice HTML di un elenco di errori
href
URL di un link
lien
testo del link

Questi campi vengono calcolati dalla parte di controllo della pagina in [erreurs.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class erreurs
    Inherits System.Web.UI.Page

    ' parametro della pagina
    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
        ' si recuperano gli elementi del contesto
        Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
        ' si genera il codice HTML dell'elenco
        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("erreurs"
oggetto ArrayList contenente l'elenco dei messaggi di errore da visualizzare
Context.Items("href"
URL di un link
Context.Items("lien"
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]

Ricordiamo lo schema MVC della nostra applicazione:

Image

ClienteLogica applicativa

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

  • init: corrisponde alla prima richiesta del client. Il controller visualizza la vista [formulaire.aspx]
  • calcolo: corrisponde alla richiesta di calcolo dell'imposta. Se i dati del modulo di inserimento sono corretti, l'imposta viene calcolata tramite la classe di business [impots]. Il controller restituisce al client la vista [formulaire.aspx] così come era stata convalidata, con l’aggiunta dell’imposta calcolata. Se i dati del modulo di inserimento non sono corretti, il controller restituisce la vista [erreurs.aspx] con l’elenco degli errori e un link per tornare al modulo.
  • ritorno: corrisponde al ritorno al modulo dopo un errore. Il controller visualizza la vista [formulaire.aspx] così come era stata convalidata prima dell’errore.

Si sa inoltre che ogni richiesta all’applicazione transita attraverso il controller [global.asax], se presente. All’ingresso dell’applicazione abbiamo quindi una catena di due controller:

  • [global.asax] che, in base all’architettura di ASP.NET, riceve tutte le richieste dirette all’applicazione
  • [main.aspx] che, per scelta dello sviluppatore, riceve anch’esso tutte le richieste dirette all’applicazione

La necessità di [main.aspx] deriva dal fatto che dovremo gestire una sessione. Abbiamo visto che [global.asax] non era adatto come controller in questo caso. Qui potremmo fare completamente a meno di [global.asax]. Lo useremo comunque per eseguire del codice all’avvio dell’applicazione. Lo schema MVC sopra riportato mostra che dovremo creare un oggetto [impot] per calcolare l’imposta. È inutile crearlo più volte, ne basta uno solo. Lo creeremo quindi all’avvio dell’applicazione durante l’evento [Application_Start] gestito dal controller [global.asax]. Il codice di quest’ultimo è 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)
        ' si crea un oggetto import
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsArray)
            ' si inserisce l'oggetto nell'applicazione
            Application("objImpot") = objImpot
            ' nessun errore
            Application("erreur") = False
        Catch ex As Exception
            'si è verificato un errore, lo si registra nell'applicazione
            Application("erreur") = True
        End Try
    End Sub
End Class

Una volta creato, l'oggetto di tipo [impot] viene inserito nell'applicazione. È da lì che le diverse richieste dei vari clienti andranno a prelevarlo. Poiché la creazione dell’oggetto [impot] potrebbe non andare a buon fine, gestiamo l’eventuale eccezione e inseriamo una chiave [erreur] nell’applicazione per segnalare se si è verificato o meno un errore durante la creazione dell’oggetto [impot].

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
        ' prima di tutto, si verifica se l'applicazione è riuscita a inizializzarsi correttamente
        If CType(Application("erreur"), Boolean) Then
            ' si reindirizza alla pagina degli errori
            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
        ' si recupera l'azione da eseguire
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' si esegue l'azione
        Select Case action
            Case "init"
                ' inizializzazione dell'applicazione
                initAppli()
            Case "calcul"
                ' calcolo delle imposte
                calculImpot()
            Case "retour"
                ' ritorno al modulo
                retourFormulaire()
            Case "effacer"
                ' inizializzazione dell'applicazione
                initAppli()
            Case Else
                ' azione sconosciuta = inizializzazione
                initAppli()
        End Select
    End Sub

    Private Sub initAppli()
        ' visualizzazione del modulo precompilato
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function initForm() As NameValueCollection
        ' si inizializza il modulo
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' si verifica la validità dei dati inseriti
        Dim erreurs As ArrayList = checkData()
        ' se ci sono errori, li si segnala
        If erreurs.Count <> 0 Then
            ' si salvano i dati inseriti
            Session.Item("formulaire") = Request.Form
            ' si prepara la pagina degli errori
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' in questo caso non ci sono errori: si calcola l'imposta
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' si visualizza la pagina dei risultati
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Sub retourFormulaire()
        ' si visualizza il modulo con i valori prelevati dalla sessione
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function checkData() As ArrayList
        ' inizialmente nessun errore
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' pulsante di opzione «sposato»
        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
        ' numero di figli
        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
        ' stipendio
        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
        ' viene visualizzato l'elenco degli errori
        Return erreurs
    End Function
End Class

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


         ' prima di tutto, si verifica se l'applicazione è riuscita a inizializzarsi correttamente
        If CType(Application("erreur"), Boolean) Then
            ' si reindirizza alla pagina degli errori
            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 è riuscita a inizializzarsi correttamente (non è stato possibile creare l'oggetto [impot] necessario per il calcolo), visualizza la pagina degli errori con i parametri adeguati. In questo caso, non è necessario inserire il link di ritorno al modulo poiché l'intera applicazione risulta indisponibile. Un messaggio di errore generico viene inserito in [Context.Items("erreurs")] di tipo [ArrayList].

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

6.1.9.1. Le azioni init, cancellare

Queste due azioni devono visualizzare il modulo di immissione vuoto. Ricordiamo che quest’ultimo (cfr. viste) ha due parametri:

  • Context.Items("modulo"): dizionario di tipo [NameValueCollection] contenente i valori dei campi HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): valore dell’imposta

La funzione [initAppli] inizializza questi due parametri in modo da visualizzare un modulo vuoto.

6.1.9.2. L'azione di calcolo

Questa azione deve calcolare l’imposta da pagare in base ai dati inseriti nel modulo e restituire quest’ultimo precompilato con i valori inseriti e, inoltre, con l’importo dell’imposta calcolata. La funzione [calculImpot], incaricata di questo compito, inizia verificando che i dati del modulo siano corretti:

  • il campo [rdMarie] deve essere presente e avere il valore [oui] o [non]
  • 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 risultano non validi, il controller visualizza la vista [erreurs.aspx] dopo aver inserito nel contesto i valori da essa previsti:

  • i messaggi di errore vengono inseriti in un oggetto [ArrayList], oggetto che viene poi inserito nel contesto [Context.Items("erreurs")]
  • 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 cliente, i valori inseriti nel modulo (Request.Form) vengono inseriti nella sessione, associati alla chiave "modulo". Ciò consentirà a una richiesta successiva di recuperarli.

A questo punto ci si può chiedere se sia utile verificare che i campi [rdMarie, txtEnfants, txtSalaire] siano presenti nella richiesta inviata dal client. È inutile se si è certi che il nostro client sia un browser che ha ricevuto la vista [formulaire.aspx] contenente tali campi. Non si può mai esserne certi. Mostreremo più avanti un esempio in cui il client è l’applicazione [curl] già incontrata. Interrogheremo l’applicazione senza inviare i campi che essa si aspetta e vedremo come reagirà. Si tratta di una regola già enunciata più volte e che ricordiamo qui: un’applicazione non deve mai formulare ipotesi sul tipo di client che la interroga. Per motivi di sicurezza, deve considerare che possa essere interrogata da un’applicazione programmata in grado di inviarle stringhe di parametri inaspettate. Deve comportarsi correttamente in ogni caso.

Nel nostro caso, abbiamo verificato che i campi [rdMarie, txtEnfants, txtSalaire] fossero presenti nella richiesta, ma non che questa potesse contenerne altri. In questa applicazione, tali campi verrebbero ignorati. Tuttavia, sempre per motivi di sicurezza, sarebbe opportuno registrare questo tipo di richiesta in un file di log e inviare un avviso all’amministratore dell’applicazione, affinché quest’ultimo sia a conoscenza del fatto che l’applicazione riceve richieste “insolite”. Analizzando tali richieste nel file di log, potrebbe individuare un eventuale attacco all’applicazione e adottare quindi le misure necessarie per proteggerla.

Se i dati attesi sono corretti, il controller avvia il calcolo dell’imposta con l’oggetto [impot] memorizzato nell’applicazione. Quindi memorizza nel contesto le due informazioni attese dalla vista [formulaire.aspx]:

  • Context.Items("modulo"): dizionario di tipo [NameValueCollection] contenente i valori dei campi HTML, [rdmarie,txtEnfants,txtSalaire], in questo caso [Request.Form)] e c.a.d. i valori inseriti in precedenza nel modulo
  • Context.Items("txtImpot"): valore dell’imposta appena calcolato

Il lettore attento potrebbe essersi posto una domanda leggendo quanto sopra: poiché l’oggetto [impot] creato all’avvio dell’applicazione è condiviso tra tutte le richieste, non potrebbero verificarsi conflitti di accesso che causino il danneggiamento dei dati dell’oggetto [impot]? Per rispondere a questa domanda, dobbiamo tornare al codice della classe [impot]. Le richieste utilizzano il metodo [impot].calculerImpot per ottenere l’imposta da pagare. È quindi questo il codice che dobbiamo esaminare:

        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' calcolo del numero di quote
            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
             ' calcolo del reddito imponibile e del quoziente familiare
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' calcolo dell'imposta
            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. A quel punto, un altro thread esegue il metodo. Quali sono i rischi? Per scoprirlo, abbiamo aggiunto il seguente codice:


            Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
            ' si attende 10 secondi
            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]? Essendo 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 quest’ultimo viene interrotto. Quando il thread 2 entra in esecuzione, il suo contesto viene inizializzato con un nuovo stack e quindi con una nuova variabile locale [impot]. Quando a sua volta il thread 2 verrà interrotto, anche il suo contesto verrà salvato. Quando il thread 1 viene riavviato, il suo contesto viene ripristinato, compreso il suo stack. Ritrova 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 effettuati con la pausa di 10 secondi sopra descritta hanno confermato che le richieste simultanee ottenevano effettivamente il risultato atteso.

6.1.9.3. L'azione di ritorno

Questa azione corrisponde all’attivazione del link [Retour vers le formulaire] dalla vista [erreurs.aspx] per tornare alla vista [formulaire.aspx] precompilata con i valori inseriti in precedenza e salvati nella sessione. La funzione [retourFormulaire] recupera queste informazioni. I due parametri richiesti dalla vista [formulaire.aspx] vengono inizializzati:

  • Context.Items("modulo") con i valori inseriti in precedenza e salvati nella sessione
  • Context.Items("txtImpot") con la stringa vuota

6.1.10. Test dell'applicazione web

Tutti i file precedenti sono inseriti in una cartella <application-path>.

Image

All'interno di questa cartella viene creata una sottocartella denominata [bin], nella quale viene inserito l'assembly [impot.dll] ottenuto dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb]. Di seguito viene riportato il comando di compilazione necessario:

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 inserito in <application-path>\bin affinché l'applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<application-path>,/impots1). Tramite un browser, richiediamo l'URL [http://localhost/impots1/main.aspx]:

Image

Compiliamo il modulo:

Image

Quindi avviamo il calcolo dell’imposta con il pulsante [Calculer]. Otteniamo la seguente risposta:

Image

Quindi inseriamo dati errati:

Image

Premendo il pulsante [Calculer] si ottiene la seguente risposta:

Image

L'utilizzo del link [Retour au formulaire] ci riporta al modulo nello stato in cui si trovava al momento della convalida:

Image

Infine, cliccando sul pulsante [Effacer] si reimposta 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 della convalida, il browser rinvierà i valori di tali parametri al server. Un altro client potrebbe non farlo e quindi il server riceverebbe una richiesta in cui mancherebbero alcuni parametri. Deve sapere come comportarsi in questo caso. Un altro esempio è quello delle verifiche dei dati inseriti effettuate sul lato client. Se il modulo contiene dati da verificare, tale verifica può essere effettuata sul lato client grazie a script inclusi nel documento contenente il modulo. Il browser invierà il modulo solo se tutti i dati verificati sul lato client sono validi. Si potrebbe quindi essere tentati, lato server, di dare per scontato che si riceveranno dati già verificati e di non voler eseguire questa verifica una seconda volta. Sarebbe un errore. Infatti, un client diverso da un browser potrebbe inviare al server dati non validi e l’applicazione web rischierebbe quindi di avere un comportamento imprevisto. Illustreremo questi punti utilizzando il client [curl].

Innanzitutto, 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 parametri:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url  http://localhost/impots1/main.aspx?action=calcolo 

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 notare che l'applicazione web ha restituito la vista [erreurs] con tre messaggi di errore relativi ai tre parametri mancanti. Inviamo ora dei 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 correttamente rilevati. 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 calcolato correttamente l’imposta da versare: 4300 euro. Da questo esempio trarremo la conclusione che non bisogna lasciarsi ingannare dal fatto che si stia scrivendo un'applicazione web destinata a client che sono browser. Un'applicazione web è un servizio TCP/IP e questo protocollo di rete non permette di determinare la natura dell'applicazione client di un servizio. Pertanto, non è possibile sapere se il client di un'applicazione web sia un browser o meno. Seguiamo quindi due regole:

  • alla ricezione di una richiesta da parte di un client, non si formulano ipotesi sul client e si verifica che i parametri previsti nella richiesta siano effettivamente presenti e validi
  • si elabora una risposta destinata ai browser, quindi in genere documenti HTML

Un’applicazione web può essere progettata per servire contemporaneamente diversi tipi di client, ad esempio browser e telefoni cellulari. È quindi possibile includere in ogni richiesta un nuovo parametro che indichi il tipo di client. In questo modo, un browser richiederà il calcolo dell’imposta tramite una richiesta all’URL http://machine/impots/main.aspx?client=browser&action=calcolo, mentre il cellulare effettuerà una richiesta all’URL http://machine/impots/main.aspx?client=mobile&action=calcul. La struttura MVC semplifica la scrittura di un’applicazione di questo tipo. Il codice risulterà il seguente:

Image

Il blocco [Classes métier, Classes d'accès aux données] rimane invariato. Si tratta infatti di una parte che non interessa al cliente. Il blocco [Contrôleur] subisce poche modifiche, ma deve tenere conto di un nuovo parametro nella richiesta, ovvero il parametro [client] che indica con quale tipo di cliente si ha a che fare. Il blocco [vues] deve generare viste per ogni tipo di cliente. Potrebbe essere interessante tenere conto, fin dalla fase di progettazione dell’applicazione, della presenza del parametro [client] nella richiesta, anche se l’obiettivo a breve o medio termine riguarda solo i browser. Se in futuro l’applicazione dovesse gestire un nuovo tipo di client, sarà sufficiente scrivere solo le viste adatte a quest’ultimo.

6.2. Esempio 2

6.2.1. Il problema

In questa sede ci proponiamo di affrontare lo stesso problema di prima, ma modificando la fonte dei dati dell’oggetto [impot] creato dall’applicazione web. Nella versione precedente, quella utilizzata forniva i valori di tabelle "hardcoded" nel codice. Questa volta, la nuova fonte di dati li preleverà da una fonte di dati ODBC associata a un database MySQL.

6.2.2. La fonte dati ODBC

I dati si troveranno in una tabella denominata [IMPOTS] di 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 una fonte di dati ODBC a questo database. Prima di procedere, ricordiamo innanzitutto i diversi modi per accedere a un database con la piattaforma .NET.

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

Image

Nello schema sopra riportato, il driver presenta due interfacce:

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

Per evitare che un'applicazione scritta per un database B1 debba essere riscritta in caso di migrazione verso un database B2 diverso, è stato effettuato un lavoro di standardizzazione sull'interfaccia I1. Se si utilizzano database che impiegano driver “normalizzati”, il database B1 verrà fornito con un driver P1, il database B2 con un driver P2, e l’interfaccia I1 di questi due driver sarà identica. Pertanto non sarà necessario riscrivere l’applicazione. Sarà così possibile, ad esempio, migrare un database ACCESS verso un database MySQL senza modificare l'applicazione.

Esistono due tipi di driver standardizzati:

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

I driver ODBC consentono l'accesso ai database. Le fonti di dati per i driver OLE e DB sono più varie: database, sistemi di messaggistica, rubriche, ... Non ci sono limiti. Qualsiasi fonte di dati può essere oggetto di un driver Ole DB, se un editore lo decide. Il vantaggio è ovviamente notevole: si ottiene un accesso uniforme a una grande varietà di dati.

La piattaforma .NET 1.1 viene fornita con tre tipi di classi di accesso ai dati:

  1. le classi SQL e Server.NET, per accedere ai database SQL Server di Microsoft
  2. le classi Ole Db.NET, per accedere ai database SGBD che offrono un driver OLE DB
  3. le classi odbc.net, per accedere ai database SGBD che offrono un driver ODBC

Il SGBD MySQL dispone da tempo di un driver ODBC. È proprio questo che utilizziamo ora. Su Windows, scegliamo l'opzione [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. A seconda della versione di Windows, questo percorso può variare leggermente. Si ottiene la seguente applicazione che ci consentirà di creare la nostra sorgente ODBC:

Image

Creeremo una fonte dati di sistema, c.a.d. Una fonte dati a cui potrà accedere qualsiasi utente del computer utiliser.Aussi; nella schermata sopra selezioniamo la scheda [Source de données système]. La pagina visualizzata presenta un pulsante [Ajouter] che utilizzeremo per creare una nuova fonte dati ODBC:

Image

La procedura guidata richiede di selezionare il driver ODBC da utilizzare. Windows include una serie di driver ODBC preinstallati. Il driver ODBC di MySQL non fa parte del pacchetto. È quindi necessario installarlo prima. È possibile trovarlo su Internet digitando la stringa "MySQL ODBC" o "MyODBC" in un motore di ricerca. In questo caso, abbiamo installato il driver [MySQL ODBC 3.51]. Lo selezioniamo e inseriamo [Terminer]:

Image

È necessario fornire alcune informazioni:

Data Source Name
il nome che identificherà la fonte di dati ODBC. Qualsiasi applicazione Windows potrà accedere alla fonte tramite questo nome
Description
un testo arbitrario che descrive la fonte di dati
Host Name
il nome del computer che ospita SGBD MySQL. In questo caso si tratta del computer locale. Potrebbe trattarsi di un computer remoto. Ciò consentirebbe a un’applicazione Windows di accedere a un database remoto senza alcuna codifica specifica. Questo è uno dei principali vantaggi della fonte ODBC.
Database Name
un SGBD MySQL può gestire più database. Qui si specifica quale si desidera gestire: dbimpots
User
nome di un utente registrato all'interno di SGBD MySQL. È con questo nome che avverranno gli accessi alla fonte dei dati. In questo caso: admimpots
Password
la password di questo utente. In questo caso: mdpimpots
Port
porta di lavoro di SGBD MySQL. Per impostazione predefinita è la porta 3306. Non l'abbiamo modificata

Fatto ciò, verifichiamo la validità dei nostri parametri di connessione con il pulsante [Test Data Source]:

Image

A questo punto, siamo certi della nostra fonte di dati ODBC. Ora possiamo utilizzarla. Ripetiamo l’operazione [OK] tutte le volte necessarie per uscire dalla procedura guidata ODBC.

Se il lettore non dispone di SGBD mySQL, può scaricarlo gratuitamente all'indirizzo [http://www.mysql.com]. Di seguito illustriamo la procedura per creare una fonte ODBC con Access. I primi passaggi sono identici a quanto descritto in precedenza. Si aggiunge una nuova fonte di dati di sistema:

Image

Il driver selezionato sarà [Microsoft Access Driver]. Si esegue [Terminer] per passare alla definizione della fonte ODBC:

Image

Le informazioni da fornire sono le seguenti:

Nom de la source de données
il nome che identificherà la fonte di dati ODBC. Qualsiasi applicazione Windows potrà accedere alla fonte tramite questo nome
Description
un testo arbitrario che descrive la fonte di dati
Base de données
il nome completo del file ACCESS da utilizzare

6.2.3. Una nuova classe di accesso ai dati

Torniamo alla struttura MVC della nostra applicazione:

Image

Nello schema sopra riportato, la classe [impotsData] ha il compito di recuperare i dati. In questo caso dovrà farlo dalla base MySQL [dbimpots]. Sappiamo già dalla versione precedente di questa applicazione che [impotsData] è una classe astratta da cui occorre derivare ogni volta che si desidera adattarla a una nuova fonte di dati. Ricordiamo 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

        ' metodo di accesso ai dati
        Public MustOverride Function getData() As Object()

        ' metodo di verifica dei dati
        Protected Function checkData() As Integer
            ' verifica i dati acquisiti
...
        End Function

        ' verifica la validità del contenuto di una tabella
        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 senza argomenti di [impotsData] non è adatto alle sue esigenze
  • il metodo [getData] che restituisce i tre array (limiti, coeffr, coeffn)

Creiamo la classe [impotsODBC] che recupererà i dati (limiti, coeffr, coeffn) da una fonte ODBC a cui assegneremo il nome:


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

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

        ' variabili di istanza
        Protected DSNimpots As String

        ' costruttore
        Public Sub New(ByVal DSNimpots As String)
            ' si annotano le tre informazioni
            Me.DSNimpots = DSNimpots
        End Sub

        Public Overrides Function getdata() As Object()
            ' inizializza i tre array limites, coeffr, coeffn a partire
            ' del contenuto della tabella [impots] del database ODBC DSNimpots
            ' limiti, coeffr, coeffn sono le tre colonne di questa tabella
            ' può generare diverse eccezioni

            Dim connectString As String = "DSN=" + DSNimpots + ";"         ' chaîne de connexion à la base
            Dim impotsConn As OdbcConnection = Nothing         ' la connexion
            Dim sqlCommand As OdbcCommand = Nothing         ' la commande SQL
            ' la query SELECT
            Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
            ' tabelle per il recupero dei dati
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
            Try
                ' si tenta di accedere al database
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
                ' si crea un oggetto comando
                sqlCommand = New OdbcCommand(selectCommand, impotsConn)
                ' si esegue la query
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
                ' Analisi della tabella recuperata
                While myReader.Read()
                    ' i dati della riga corrente vengono inseriti nelle tabelle
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' liberazione delle risorse
                myReader.Close()
                impotsConn.Close()
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' gli array dinamici vengono trasferiti in array statici
            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
            ' Si verificano i dati acquisiti
            Dim erreur As Integer = checkData()
            ' se i dati non sono validi, viene generata un'eccezione
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' altrimenti si restituiscono le tre tabelle
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Diamo un'occhiata al produttore:


        ' costruttore
        Public Sub New(ByVal DSNimpots As String)
            ' si annotano le tre informazioni
            Me.DSNimpots = DSNimpots
        End Sub

Come parametro riceve il nome della fonte ODBC in cui si trovano i dati da acquisire. Il costruttore si limita a memorizzare questo nome. Il metodo [getData] ha il compito di leggere i dati dalla tabella [impots] e di inserirli in tre array (limiti, coeffr, coeffn). Commentiamo il suo codice:

  • i parametri di connessione alla fonte dati ODBC sono definiti, ma la fonte dati non è aperta
             ' stringa di connessione al database
            Dim connectString As String = "DSN=" + DSNimpots + ";"
            ' si crea un oggetto di connessione al database - questa connessione non è aperta
            Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
  • Si definiscono tre oggetti [ArrayList] per recuperare i dati dalla tabella [impots]:

             ' tabelle per recuperare i dati
            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. Si apre la connessione al database:

                 ' si tenta di accedere al database
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • si esegue il comando [select] sulla connessione aperta. Si ottiene un oggetto [OdbcDataReader] che ci consentirà di scorrere le righe della tabella risultante dal select:

                 ' si crea un oggetto comando
                Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
                ' si esegue la query
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
  • Si scorre la tabella dei risultati, riga per riga. A tal fine si utilizza il metodo [Read] dell’oggetto [OdbcDataReader] ottenuto in precedenza. Questo metodo svolge due operazioni:
    • avanza di una riga nella tabella. All’inizio ci si trova 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. Si ottiene un oggetto che rappresenta il valore della colonna. Si percorre l’intera tabella per inserire il suo contenuto nei tre oggetti [ArrayList]:


                 ' Analisi della tabella recuperata
                While myReader.Read()
                    ' i dati della riga corrente vengono inseriti nelle tabelle
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
  • Fatto ciò, liberiamo le risorse associate alla connessione:
                 ' liberazione delle risorse
                myReader.Close()
                impotsConn.Close()
  • Il contenuto dei tre oggetti [ArrayList] viene trasferito in tre array standard:

             ' gli array dinamici vengono trasferiti in array statici
            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 che i dati della tabella [impots] sono stati inseriti nelle tre tabelle, non resta che verificarne il contenuto utilizzando il metodo [checkData] della classe base [impotsData]:
             ' Si verificano i dati acquisiti
            Dim erreur As Integer = checkData()
             ' se i dati non sono validi, viene generata un'eccezione
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' altrimenti si restituiscono le tre tabelle
            Return New Object() {limites, coeffr, coeffn}

6.2.4. Test della classe di accesso ai dati

Un programma di test potrebbe essere il seguente:

Option Explicit On 
Option Strict On

' spazi dei nomi
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' pagina di test
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' programma interattivo per il calcolo delle imposte
             ' l'utente digita tre dati dalla tastiera: coniugato nbEnfants stipendio
             ' il programma visualizza quindi l’imposta da pagare
            Const syntaxe1 As String = "pg DSNimpots"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' verifica dei parametri del programma
            If arguments.Length <> 1 Then
                 ' messaggio di errore
                Console.Error.WriteLine(syntaxe1)
                 ' fine
                Environment.Exit(1)
            End If
             ' si recuperano gli argomenti
            Dim DSNimpots As String = arguments(0)

             ' creazione di un oggetto imposta
            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

             ' ciclo infinito
            While True
                 ' all'inizio nessun errore
                Dim erreur As Boolean = False

                 ' vengono richiesti i parametri per il calcolo dell'imposta
                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()

                 ' Cosa fare?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' verifica del numero di argomenti nella riga inserita
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                     ' Verifica della validità dei parametri
                     ' coniugato
                    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
                     ' stipendio
                    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
                     ' i parametri sono corretti - si calcola l'imposta
                    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 della fonte dati ODBC da utilizzare

Il calcolo dell'imposta viene effettuato tramite un oggetto di tipo [impot] creato al momento dell'avvio dell'applicazione:


             ' creazione di un oggetto imposta
            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 avviata, l'applicazione chiede ripetutamente all'utente di digitare sulla tastiera le tre informazioni necessarie per calcolare la sua imposta:

  • il proprio stato civile: o per sposato, n per non sposato
  • il numero di figli
  • il suo stipendio annuale

L'insieme delle classi viene compilato:

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

A sua volta, viene compilato il programma di test:

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 innanzitutto con la fonte dati ODBC MySQL:

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

Si passa dalla fonte ODBC a una fonte Access:

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

6.2.5. Le viste dell'applicazione web

Sono quelle dell'applicazione precedente: formulaire.aspx e erreurs.aspx

6.2.6. I controller dell’applicazione [global.asax, main.aspx]

È necessario modificare solo il controller [global.asax]. Esso è infatti responsabile della creazione dell'oggetto [impot] all'avvio dell'applicazione. L'unico parametro del costruttore di questo oggetto è l'oggetto di tipo [impotsData], incaricato di recuperare i dati. Questo parametro varia quindi per ogni nuovo tipo di fonte di 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)
        ' si crea un oggetto imposta
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' inserimento dell'oggetto nell'applicazione
            Application("objImpot") = objImpot
            ' nessun errore
            Application("erreur") = False
        Catch ex As Exception
            'si è verificato un errore, lo si registra nell'applicazione
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

La fonte dati dell'oggetto [impot] è ora un oggetto [impotODBC]. Quest'ultimo ha come parametro il nome DSN della fonte dati ODBC da utilizzare. Anziché inserire questo nome in modo statico nel codice, lo si inserisce 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>

È noto che il valore di una chiave C della sezione <appSettings> del file [web.config] viene ottenuto nel codice dell'applicazione tramite [ConfigurationSettings.AppSettings(C)].

Per individuare la causa dell’eccezione, il messaggio relativo viene registrato nell’applicazione in modo che rimanga disponibile per le richieste. 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
        ' prima di tutto, si verifica se l'applicazione è riuscita a inizializzarsi correttamente
        If CType(Application("erreur"), Boolean) Then
            ' si reindirizza alla pagina degli errori
            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
        ' si recupera l'azione da eseguire
...

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 per l'accesso ai dati
  2. il controller [global.asax.vb] è stato modificato in due punti: creazione dell'oggetto [impot] e registrazione nell'applicazione del messaggio relativo a un'eventuale eccezione
  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 effettuato essenzialmente in 1, c.a.d, al di fuori dell’applicazione web. Ciò è stato reso possibile grazie all’architettura MVC dell’applicazione, che separa il controller dalle classi di business. È proprio questo il vantaggio di tale architettura. Si potrebbe dimostrare che, con un file di configurazione adeguato, sarebbe stato possibile evitare qualsiasi modifica al controller dell’applicazione. È possibile inserire nel file il nome della classe di accesso ai dati da istanziare dinamicamente, nonché i vari parametri necessari per tale istanziazione. Con queste informazioni, [global.asax] può istanziare l’oggetto di accesso ai dati. Cambiare la fonte di dati equivale quindi a:

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

6.2.8. Test dell'applicazione web

Tutti i file precedenti vengono inseriti in una cartella <application-path>.

Image

In questa cartella viene creata una sottocartella [bin] in cui viene inserito l’assembly [impot.dll] derivante dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Di seguito viene riportato il comando di compilazione necessario:

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 <application-path>\bin affinché l'applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<application-path>,/impots2). I test danno gli stessi risultati della versione precedente, poiché la presenza del database è trasparente per l'utente. Per illustrare comunque tale presenza, facciamo in modo che la fonte ODBC non sia disponibile arrestando i file SGBD e MySQL e richiediamo l'URL [http://localhost/impots2/main.aspx]. Otteniamo la seguente risposta:

Image

6.3. Esempio 3

6.3.1. Il problema

In questa sede ci proponiamo di affrontare lo stesso problema modificando nuovamente la fonte dei dati dell’oggetto [impot] creato dall’applicazione web. Questa volta, la nuova fonte dei dati sarà un database ACCESS a cui si accederà tramite un driver OLEDB. Il nostro obiettivo è illustrare un altro modo per accedere a un database.

6.3.2. La fonte dati OLEDB

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

Image

6.3.3. La classe di accesso ai dati

Torniamo alla struttura MVC della nostra applicazione:

Image

  • Nello schema sopra riportato, la classe [impotsData] ha il compito di recuperare i dati. Questa volta dovrà farlo da una fonte OLEDB.

Creiamo la classe [impotsOLEDB] che andrà a recuperare i dati (limiti, coeffr, coeffn) da una fonte ODBC a cui assegneremo il nome:


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

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

        ' variabili di istanza
        Protected chaineConnexion As String

        ' costruttore
        Public Sub New(ByVal chaineConnexion As String)
            ' si annotano le tre informazioni
            Me.chaineConnexion = chaineConnexion
        End Sub

        Public Overrides Function getData() As Object()
            ' inizializza i tre array limites, coeffr, coeffn a partire
            ' del contenuto della tabella [impots] del database OLEDB [chaineConnexion]
            ' limiti, coeffr, coeffn sono le tre colonne di questa tabella
            ' può generare diverse eccezioni

            ' si crea un oggetto DataAdapter per leggere i dati dalla fonte OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
            ' si crea un'immagine in memoria del risultato della selezione
            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
            ' si recupera il contenuto della tabella impots
            Dim lignesImpots As DataRowCollection = contenu.Rows
            ' si definiscono le dimensioni delle tabelle di ricezione
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' si trasferisce il contenuto della tabella impots negli array
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' riga i della tabella
                    ligne = lignesImpots.Item(i)
                    ' si recupera il contenuto della riga
                    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
            ' si verificano i dati acquisiti
            Dim erreur As Integer = checkData()
            ' se i dati non sono validi, si genera un'eccezione
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' altrimenti si restituiscono i tre array
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Diamo un'occhiata al produttore:


         ' costruttore
        Public Sub New(ByVal chaineConnexion As String)
             ' si annotano le tre informazioni
            Me.chaineConnexion = chaineConnexion
        End Sub

Riceve come parametro la stringa di connessione della sorgente OLEDB, nella quale si trovano i dati da acquisire. Il costruttore si limita a memorizzarla. Una stringa di connessione contiene tutti i parametri necessari al driver OLEDB per connettersi alla sorgente OLEDB. In genere è piuttosto complessa. Per scoprire quella delle basi ACCESS, è possibile avvalersi dello strumento [WebMatrix]. Si avvia questo strumento. Esso offre una finestra che consente di connettersi a una fonte di dati:

Grazie all’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 [Data], alcune icone indicano la connessione:

Image

Ora creiamo un nuovo file .aspx tramite [Files/New File]:

Image

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

Image

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

Image

Facciamo clic con il tasto destro sull’oggetto [AccessDataSourceControl] qui sotto per accedere alle sue proprietà:

Image

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

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

Si nota che questa stringa è composta da una parte fissa e da una parte variabile, che corrisponde semplicemente al nome del file ACCESS. Sfrutteremo questa caratteristica per generare la stringa di connessione alla nostra fonte di dati OLEDB.

Torniamo ora alla nostra classe [impotsOLEDB]. Il metodo [getData] ha il compito di leggere i dati dalla tabella [impots] e di inserirli 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 SELECT SQL. A tal fine, definiamo la query [select] da eseguire e la associamo all’oggetto [DataAdapter]. Il costruttore di quest’ultimo richiede anche la stringa di connessione che dovrà utilizzare per connettersi alla fonte OLEDB

             ' si crea un oggetto DataAdapter per leggere i dati dalla sorgente OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • si esegue il comando [select] tramite il metodo [Fill] dell’oggetto [DataAdapter]. Il risultato di [select] viene inserito in un oggetto [DataTable] creato appositamente. Un oggetto [DataTable] rappresenta in memoria una tabella del database, c.a.d, ovvero un insieme di righe e colonne. Gestiamo un'eccezione che può verificarsi se, ad esempio, la stringa di connessione non è corretta.

             ' si crea un'immagine in memoria del risultato della selezione
            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 [contenu], abbiamo la tabella [impots] importata da [select]. Un oggetto [DataTable] è una tabella, quindi un insieme di righe. Queste sono accessibili tramite la proprietà [rows] di [datatable]:

             ' si recupera il contenuto della tabella impots
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • ogni elemento della collezione [lignesImpots] è un oggetto di tipo [DataRow] che rappresenta una riga della tabella. Questa presenta colonne accessibili tramite l’oggetto [DataRow] tramite la sua proprietà [Item]. [DataRow].[Item(i)] è la colonna n. i della riga [DataRow]. Scorrendo la collezione delle righe (la collezione DataRows di lignesImpots) e le collezioni delle colonne di ciascuna riga, è possibile ottenere l’intera tabella:
             ' si definiscono le dimensioni delle tabelle di ricezione
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
             ' si trasferisce il contenuto della tabella impots negli array
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                     ' riga i della tabella
                    ligne = lignesImpots.Item(i)
                     ' si recupera il contenuto della riga
                    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 che i dati della tabella [impots] sono stati inseriti nelle tre tabelle, non resta che verificarne il contenuto utilizzando il metodo [checkData] della classe base [impotsData]:
            ' si verificano i dati acquisiti
            Dim erreur As Integer = checkData()
             ' se i dati non sono validi, si genera un'eccezione
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' altrimenti si restituiscono i tre array
            Return New Object() {limites, coeffr, coeffn}

6.3.4. Test della classe di accesso ai dati

Un programma di test potrebbe essere il seguente:

Option Explicit On 
Option Strict On

' spazi dei nomi
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' pagina di test
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' programma interattivo per il calcolo delle imposte
             ' l'utente digita tre dati dalla tastiera: coniugato nbEnfants stipendio
             ' il programma visualizza quindi l’imposta da pagare
            Const syntaxe1 As String = "pg bdACCESS"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' verifica dei parametri del programma
            If arguments.Length <> 1 Then
                 ' messaggio di errore
                Console.Error.WriteLine(syntaxe1)
                 ' fine
                Environment.Exit(1)
            End If
             ' si recuperano gli argomenti
            Dim chemin As String = arguments(0)
             ' si prepara la stringa di connessione
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' creazione di un oggetto fiscale
            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

             ' ciclo infinito
            While True
                 ' all'inizio nessun errore
                Dim erreur As Boolean = False

                 ' si richiedono i parametri per il calcolo dell'imposta
                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()

                 ' C'è qualcosa da fare?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' verifica del numero di argomenti nella riga inserita
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                     ' Verifica della validità dei parametri
                     ' coniugato
                    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
                     ' stipendio
                    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
                     ' i parametri sono corretti - si calcola l'imposta
                    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 elaborare

Il calcolo dell'imposta viene effettuato tramite un oggetto di tipo [impot] creato al momento dell'avvio dell'applicazione:


             ' si recuperano gli argomenti
            Dim chemin As String = arguments(0)
            ' si prepara la stringa di connessione
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' creazione di un oggetto imposta
            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 generata sulla base delle informazioni ottenute con [WebMatrix].

Una volta avviata, l'applicazione richiede ripetutamente all'utente di digitare sulla tastiera le tre informazioni necessarie per calcolare la sua imposta:

  • il proprio stato civile: o per sposato, n per non sposato
  • il numero di figli
  • il suo stipendio annuale

Viene compilato l'insieme delle classi:

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 test, che 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 avviare l’applicazione con un file ACCESS non corretto:

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 quelle dell'applicazione precedente: formulaire.aspx e erreurs.aspx

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

È necessario modificare solo il controller [global.asax]. Esso, infatti, si occupa di creare l'oggetto [impot] all'avvio dell'applicazione. L'unico parametro del costruttore di questo oggetto è l'oggetto di tipo [impotsData] incaricato di recuperare i dati. Questo parametro cambia quindi poiché si cambia la fonte dei 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)
        ' si crea un oggetto imposta
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' si inserisce l'oggetto nell'applicazione
            Application("objImpot") = objImpot
            ' nessun errore
            Application("erreur") = False
        Catch ex As Exception
            'si è verificato un errore, lo si registra nell'applicazione
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

La fonte dati dell'oggetto [impot] è ora un oggetto [impotOLEDB]. Quest'ultimo ha come parametro la stringa di connessione della fonte dati OLEDB da utilizzare. Questa è inserita 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 rispetto alla versione precedente:

  1. è stata creata una nuova classe per l’accesso ai dati
  2. il controller [global.asax.vb] è stato modificato in un punto: creazione dell'oggetto [impot]
  3. è stato aggiunto un file [web.config]

6.3.8. Test dell’applicazione web

Tutti i file precedenti sono collocati in una cartella <application-path>.

Image

In questa cartella viene creata una sottocartella [bin] in cui viene inserito l'assembly [impot.dll] derivante dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. Di seguito viene riportato il comando di compilazione necessario:

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 <application-path>\bin affinché l'applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<application-path>,/impots3). I test danno gli stessi risultati della versione precedente.

6.4. Esempio 4

6.4.1. Il problema

Ora intendiamo trasformare la nostra applicazione in un'applicazione di simulazione del calcolo delle imposte. Un utente potrà effettuare calcoli successivi delle imposte, 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 diventa la seguente:

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] non cambia. 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 [formulaire] include uno script JavaScript che verifica la validità dei dati inseriti prima di inviarli al server, come mostra l’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(){
          // verifica dei parametri prima di inviarli al server
        with(document.frmImpots){
          //numero di figli
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // il modello non è stato verificato
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//if
          //stipendio
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // il modello non è stato verificato
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//se
          // Va bene - inviamo il modulo al server
          submit();
        }//con
      }//calcolare  
        </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 della pagina sono quelli delle versioni precedenti. Il campo dinamico relativo all'importo dell'imposta è stato rimosso. Il pulsante [Calculer] non è più un pulsante di tipo [submit]. È di tipo [button] e, quando viene cliccato, viene eseguita la funzione JavaScript [calculer()]:


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

Al modulo è stato assegnato il nome [frmImpots] per poterlo referenziare nello script [calculer]:


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

La funzione JavaScript [calculer] utilizza espressioni regolari per verificare la validità dei campi dei 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 propri campi dinamici dal seguente controller [formulaire.aspx.vb]:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

    ' campi della pagina
    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
        ' recuperiamo la richiesta precedente dal contesto
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' si prepara la pagina da visualizzare
        ' pulsanti di opzione
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' il resto
        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é tale campo è stato rimosso dalla pagina.

La vista [simulations.aspx] si presenta visivamente 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 presenta tre campi dinamici:

simulationsHTML
codice HTML di un elenco di simulazioni sotto forma di righe di tabella HTML
href
URL di un link
lien
testo del link

Sono generati dal modulo di controllo [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
        'si recuperano le simulazioni dal contesto
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' ogni simulazione è un array di 4 elementi di tipo string
        Dim simulation() As String
        Dim i, j As Integer
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML += "</tr>" + ControlChars.CrLf
        Next
        ' si recuperano gli altri elementi del contesto
        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 di caratteri che rappresentano le informazioni (stato civile, figli, stipendio, imposta) della simulazione.
Context.Items("href")
URL di un link
Context.Items("lien")
testo del link

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

Ricordiamo lo schema 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 [formulaire.aspx]
  • calcolo: corrisponde alla richiesta di calcolo dell’imposta. Se i dati del modulo di inserimento sono corretti, l’imposta viene calcolata tramite la classe di business [impotsODBC]. Il controller restituisce al client la vista [simulations.aspx] con il risultato della simulazione corrente e di tutte quelle precedenti. Se i dati del modulo di inserimento non sono corretti, il controller restituisce la vista [erreurs.aspx] con l’elenco degli errori e un link per tornare al modulo.
  • ritorno: corrisponde al ritorno al modulo dopo un errore. Il controller visualizza la vista [formulaire.aspx] così come era stata convalidata prima dell’errore.

In questa nuova versione, è cambiata solo l’azione [calcul]. Infatti, se i dati sono validi, essa deve portare alla vista [simulations.aspx], mentre in precedenza portava alla vista [formulaire.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
        ' prima di tutto, si verifica se l'applicazione è riuscita a inizializzarsi correttamente
...
        ' si esegue l'azione
        Select Case action
            Case "init"
                ' inizializzazione dell'applicazione
                initAppli()
            Case "calcul"
                ' calcolo imposta
                calculImpot()
            Case "retour"
                ' ritorno al modulo
                retourFormulaire()
            Case "effacer"
                ' inizializzazione dell'applicazione
                initAppli()
            Case Else
                ' azione sconosciuta = inizializzazione
                initAppli()
        End Select
    End Sub

...
    Private Sub calculImpot()
        ' si salvano i dati inseriti
        Session.Item("formulaire") = Request.Form
        ' si verifica la validità dei dati inseriti
        Dim erreurs As ArrayList = checkData()
        ' se ci sono errori, li si segnala
        If erreurs.Count <> 0 Then
            ' si prepara la pagina degli errori
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' in questo caso non ci sono errori: si calcola l'imposta
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' si aggiunge il risultato alle simulazioni esistenti
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' aggiunta della simulazione corrente
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' si inseriscono le simulazioni nella sessione e nel contesto
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' visualizzazione della pagina dei risultati
        context.Items("href") = "main.aspx?action=retour"
        context.Items("lien") = "Retour au formulaire"
        Server.Transfer("simulations.aspx", True)
    End Sub
...
End Class

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

  • innanzitutto la funzione salva il modulo [Request.Form] nella sessione, in modo da poterlo rigenerare nello stato in cui è stato convalidato. Questa operazione va eseguita in ogni caso, poiché sia che l’operazione porti alla risposta [erreurs.aspx] sia che porti alla risposta [simulations.aspx], si ritorna al modulo tramite il link [Retour au formulaire]. Per ripristinarlo correttamente, è necessario averne precedentemente salvato i valori nella sessione.
  • Se i dati inseriti risultano corretti, la funzione inserisce la simulazione corrente (stato civile, figli, stipendio, imposta) nell’elenco delle simulazioni. Quest’ultimo si trova nella sessione associata alla chiave "simulazioni".
  • L’elenco delle simulazioni viene reinserito nella sessione per un utilizzo futuro. Viene inoltre inserito nel contesto corrente poiché è lì che lo attende la vista [simulations.aspx]
  • La vista [simulations.aspx] viene visualizzata una volta inserite nel contesto le altre informazioni che richiede

6.4.5. Riepilogo delle modifiche

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

  1. è stata creata una nuova vista
  2. il controller [main.aspx.vb] è stato modificato in un punto: elaborazione dell’azione [calcul]

6.4.6. Test dell’applicazione web

Il lettore è invitato a eseguire i test. Ricordiamo la procedura. Tutti i file dell’applicazione sono collocati in una cartella <application-path>. All’interno di questa cartella viene creata una sottocartella [bin] in cui è collocato l’assembly [impot.dll] derivante dalla compilazione dei file delle classi di business: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. Il file [impot.dll] generato da questo comando deve essere collocato in <application-path>\bin affinché l’applicazione web possa accedervi. Il server Cassini viene avviato con i parametri (<application-path>,/impots4).

6.5. Conclusion

Gli esempi precedenti hanno illustrato, su un caso concreto, i meccanismi comunemente utilizzati nello sviluppo web. Abbiamo utilizzato sistematicamente l’architettura MVC per il suo valore didattico. Si sarebbero potuti trattare questi stessi esempi in modo diverso e forse più semplice senza questa architettura. Tuttavia, essa offre grandi vantaggi non appena l’applicazione diventa un po’ complessa con più pagine.

Potremmo proseguire con i nostri esempi in diversi modi. Eccone alcuni:

  • l’utente potrebbe voler conservare le proprie simulazioni nel tempo. Potrebbe effettuare delle simulazioni in un giorno J e ritrovarle, ad esempio, il giorno J+3. Una possibile soluzione a questo problema è l’uso dei cookie. Sappiamo che il token di sessione tra il server e un client viene trasportato tramite questo meccanismo. Si potrebbe utilizzare lo stesso meccanismo anche per trasportare le simulazioni tra il client e il server.
    • Contemporaneamente all’invio della pagina con i risultati delle simulazioni, il server invia nelle sue intestazioni HTTP un cookie contenente una stringa di caratteri che rappresenta le simulazioni. Poiché queste si trovano in un oggetto [ArrayList], è necessario effettuare una trasformazione di tale oggetto in [String]. Il server assegnerebbe al cookie una durata, ad esempio 30 giorni.
    • Il browser client memorizza i cookie ricevuti in un file e li rinvia ogni volta che effettua una richiesta al server che glieli ha inviati, purché siano ancora validi (durata di validità non superata). Ai fini delle simulazioni, il server riceverà una stringa di caratteri [String] che dovrà trasformare nell’oggetto [ArrayList].

I cookie vengono gestiti da [Response.Cookies] al momento dell’invio al client e da [Request.Cookies] al momento della ricezione sul server.

  • Il meccanismo descritto sopra può diventare piuttosto oneroso se il numero di simulazioni è elevato. Inoltre, capita spesso che un utente pulisca periodicamente i propri cookie cancellandoli tutti, anche se autorizza il proprio browser a utilizzarli. Pertanto, prima o poi si perderà il cookie relativo alle simulazioni. Si potrebbe quindi decidere di memorizzarle sul server anziché sul client, ad esempio in un database. Per associare le simulazioni a un utente specifico, l’applicazione potrebbe iniziare con una fase di identificazione che richieda un nome utente e una password, a loro volta memorizzati in un database o in qualsiasi altro tipo di archivio dati.
  • Potremmo inoltre voler rendere più sicuro il funzionamento della nostra applicazione. Attualmente essa si basa su due presupposti:
    • l’utente passa sempre attraverso il controller [main.aspx]
    • e, in tal caso, utilizza sempre le azioni proposte nella pagina che gli è stata inviata

Cosa succede, ad esempio, se l’utente richiede direttamente l’URL [http://localhost/impots4/formulaire.aspx]? Questo caso è poco probabile, poiché l’utente non è a conoscenza dell’esistenza di tale URL. Tuttavia, va previsto. Può essere gestito dal controller dell’applicazione [global.asax], che intercetta tutte le richieste inviate all’applicazione. In questo modo, può verificare che la risorsa richiesta sia effettivamente [main.aspx].

Un caso più probabile è che un utente non utilizzi le azioni presenti 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

Si è verificato un crash del server. È normale. Per l'azione [retour], 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 permette di fornire 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. È possibile utilizzare il seguente meccanismo:

  • il controller, prima di inviare la risposta al cliente, memorizza nella sessione di quest’ultimo un’informazione che identifica quella pagina
  • quando riceve una nuova richiesta dal cliente, verifica che l’azione richiesta appartenga effettivamente all’ultima pagina inviata a quel cliente
  • le informazioni che collegano le pagine e le azioni autorizzate in tali pagine possono essere inserite nel file di configurazione [web.config] dell’applicazione.
  • L’esperienza dimostra che i controller delle applicazioni hanno un’ampia base comune ed è possibile realizzare un controller generico, la cui specializzazione per una determinata applicazione avviene tramite un file di configurazione. È questa la strada seguita, ad esempio, dallo strumento [Struts] nell’ambito della programmazione web in Java.