Skip to content

6. Ejemplos

En este capítulo nos proponemos ilustrar lo visto anteriormente mediante una serie de ejemplos.

6.1. Ejemplo 1

6.1.1. El problema

Esta aplicación debe permitir a un usuario calcular sus impuestos. Nos situamos en el caso simplificado de un contribuyente que solo tiene que declarar su salario (cifras de 2004 correspondientes a los ingresos de 2003):

  • se calcula el número de participaciones del empleado nbParts = nbEnfants/2 + 1 si no está casado, nbEnfants/2 + 2 si está casado, donde nbEnfants es el número de hijos que tiene.
  • si tiene al menos tres hijos, tiene media parte más
  • se calcula su base imponible R = 0,72 * S, donde S es su salario anual
  • se calcula su coeficiente familiar QF = R / nbParts
  • Se calcula su impuesto I. Consideremos la siguiente tabla:
4262
0
0
8382
0,0683
291,09
14 753
0,1914
1322,92
23 888
0,2826
2668,39
38 868
0,3738
4846,98
47 932
0,4262
6883,66
0
0,4809
9505,54

Cada línea tiene 3 campos. Para calcular el impuesto I, se busca la primera línea en la que QF sea menor o igual que el campo 1. Por ejemplo, si QF = 5000, se encontrará la línea

    8382        0.0683        291.09

El impuesto I es entonces igual a 0,0683*R - 291,09*nbParts. Si QF es tal que la relación QF <= campo1 nunca se cumple, entonces se utilizan los coeficientes de la última línea. En este caso:

    0                0.4809    9505.54

lo que da como resultado el impuesto I = 0,4809*R - 9505,54*nbParts.

6.1.2. La estructura MVC de la aplicación

La estructura MVC de la aplicación será la siguiente:

Image

La página [main.aspx] desempeñará la función de controlador. Habrá tres acciones posibles:

  • init: corresponde a la primera solicitud del cliente. El controlador mostrará la vista [formulaire.aspx]
  • calcul: corresponde a la solicitud de cálculo del impuesto. Si los datos del formulario de introducción son correctos, el impuesto se calcula mediante la clase de negocio [impots]. El controlador devuelve al cliente la vista [formulaire.aspx] tal y como se había validado, además del impuesto calculado. Si los datos del formulario de introducción son incorrectos, el controlador devolverá la vista [erreurs.aspx] con la lista de errores y un enlace para volver al formulario.
  • retorno: corresponde al retorno al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal y como se validó antes del error.

El controlador [main.aspx] no tiene conocimiento alguno sobre el cálculo de impuestos. Simplemente se encarga de gestionar el diálogo cliente-servidor y de ejecutar las acciones solicitadas por el cliente. Para la acción [calcul], se basará en la clase de negocio [impot].

6.1.3. La clase de negocio

La clase de impuestos se definirá de la siguiente manera:


' espacios de nombres importados
Imports System

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

        ' constructor
        Public Sub New(ByRef source As impotsData)
            ' los datos necesarios para el cálculo del impuesto
             ' proceden de una fuente externa [source]
             ' se recogen; puede haber una excepción
            Dim data() As Object = source.getData
            limites = CType(data(0), Decimal())
            coeffR = CType(data(1), Decimal())
            coeffN = CType(data(2), Decimal())
        End Sub

         ' cálculo del impuesto
        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' cálculo del número de participaciones
            Dim nbParts As Decimal
            If marié Then
                nbParts = CDec(nbEnfants) / 2 + 2
            Else
                nbParts = CDec(nbEnfants) / 2 + 1
            End If
            If nbEnfants >= 3 Then
                nbParts += 0.5D
            End If
             ' cálculo de la base imponible y del coeficiente familiar
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' cálculo del impuesto
            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

Se crea un objeto de impuestos proporcionando a su constructor una fuente de datos de tipo [impotsData]. Esta clase dispone de un método público [getData] que permite obtener las tres tablas de datos necesarias para el cálculo del impuesto y que se han presentado anteriormente. Este método puede gestionar una excepción si no se han podido obtener los datos o si estos resultan ser incorrectos. Una vez creado el objeto [impot], se puede llamar repetidamente a su método «calcular», que calcula el impuesto del contribuyente a partir de su estado civil (casado o soltero), el número de hijos y su salario anual.

6.1.4. La clase de acceso a los datos

La clase [impotsData] es la clase que permite acceder a los datos. Se trata de una clase abstracta. Es necesario crear una clase derivada para cada nueva fuente de datos posible (tablas, archivos planos, bases de datos, consola, etc.). Su definición es la siguiente:


Imports System.Collections

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

        ' método de acceso a los datos
        Public MustOverride Function getData() As Object()

        ' método de verificación de datos
        Protected Function checkData() As Integer
            ' Verifica los datos obtenidos
            ' debe haber datos
            valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
            If Not valide Then Return 1
            ' debe haber 3 tablas del mismo tamaño
            If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
            If Not valide Then Return 2
            ' las tablas no deben estar vacías
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' cada matriz debe contener elementos >=0 y en orden ascendente
            valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
            If Not valide Then Return 4
            ' todo está bien
            Return 0
        End Function

        ' comprueba la validez del contenido de una matriz
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
            ' la matriz debe tener sus primeros n elementos >=0 y en orden estrictamente ascendente
            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
            ' correcto
            Return True
        End Function
    End Class
End Namespace

La clase tiene los siguientes atributos protegidos:

limites
tabla de límites de tramos impositivos
coeffr
tabla de coeficientes aplicados a la base imponible
coeffn
tabla de coeficientes aplicados al número de participaciones
checked
valor booleano que indica si se han verificado los datos (límites, coeffr, coeffn)
valide
Valor booleano que indica si los datos (límites, coeffr, coeffn) son válidos

La clase no tiene constructor. Tiene un método abstracto [getData] que las clases derivadas deberán implementar. La función de este método es:

  • asignar valores a los tres arreglos «límites», «coeffr» y «coeffn»
  • lanzar una excepción si no se han podido obtener los datos o si estos resultan inválidos.

La clase proporciona los métodos protegidos [checkData] y [check], que comprueban la validez de los atributos (limites, coeffr, coeffn). Esto exime a las clases derivadas de tener que implementarlos. Solo tendrán que utilizarlos.

La primera clase derivada que utilizaremos será la siguiente:


Imports System.Collections
Imports System

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

        ' constructor sin argumentos
        Public Sub New()
            ' Inicialización de matrices con constantes
            limites = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
            coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
            coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
            checked = True
            valide = True
        End Sub

        ' constructor con tres matrices como argumentos
        Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
            ' se almacenan los datos
            Me.limites = limites
            Me.coeffr = coeffr
            Me.coeffn = coeffn
            checked = False
        End Sub

        Public Overrides Function getData() As Object()
            ' se comprueban los datos, si procede
            Dim erreur As Integer
            If Not checked Then erreur = checkData() : checked = True
            ' si no son válidos, se lanza una excepción
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' en caso contrario, se devuelven los tres tableros
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Esta clase, denominada [impotsArray], tiene dos constructores:

  • un constructor sin argumentos que inicializa los atributos (límites, coeffr, coeffn) de la clase base con matrices codificadas de forma «fija»
  • un constructor que inicializa los atributos (límites, coeffr, coeffn) de la clase base con matrices que se le pasan como parámetros

El método [getData], que permitirá a las clases externas obtener las matrices (limites, coeffr, coeffn), se limita a comprobar la validez de las tres matrices mediante el método [checkData] de la clase base. Lanza una excepción si los datos no son válidos.

6.1.5. Pruebas de las clases de negocio y de acceso a datos

Es importante incluir en una aplicación web únicamente clases de negocio y de acceso a datos cuya corrección haya sido certificada. De este modo, la fase de depuración de la aplicación web podrá centrarse en la parte del controlador y las vistas. Un programa de pruebas podría ser el siguiente:


' opciones
Option Strict On
Option Explicit On 

' espacios de nombres
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr
    Module test
        Sub Main()
             ' programa interactivo de cálculo de impuestos
             ' el usuario introduce tres datos mediante el teclado: casado nbEnfants salario
             ' a continuación, el programa muestra el impuesto a pagar
            Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' creación de un objeto de impuestos
            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
            ' bucle infinito
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Long
            While True
                ' se solicitan los parámetros para el cálculo del impuesto
                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()
                ' ¿Hay que hacer algo?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' Comprobación del número de argumentos en la línea introducida
                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
                ' comprobación de la validez de los parámetros
                If Not erreur Then
                    ' casado
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        erreur = True
                    End If
                    ' nbEnfants
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                    ' salario
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                End If
                 ' si los parámetros son correctos, se calcula el impuesto
                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

La aplicación pide al usuario que introduzca mediante el teclado los tres datos necesarios para calcular sus impuestos:

  • su estado civil: «o» para casado, «n» para soltero
  • el número de hijos
  • su salario anual

El cálculo de los impuestos se realiza mediante un objeto de tipo [impot] que se crea al iniciar la aplicación:


             ' creación de un objeto de impuesto
            Dim objImpôt As impot = Nothing
            Try
                objImpôt = New impot(New impotsArray)
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try

Como fuente de datos se utiliza un objeto de tipo [impotsArray]. Se utiliza el constructor sin argumentos de esta clase, que proporciona las tres matrices (límites, coeffr, coeffn) con valores fijos. La creación de un objeto [impot] puede, en teoría, provocar una excepción, ya que, para crearse, el objeto solicitará los datos (límites, coeffr, coeffn) a su fuente de datos, que se le ha pasado como parámetro, y esta obtención de datos puede lanzar una excepción. Resulta que, en este caso, el método de obtención de datos (código fijo) no puede provocar ninguna excepción. No obstante, hemos dejado la gestión de la misma para llamar la atención del lector sobre la posibilidad de que el objeto [impot] se construya incorrectamente.

A continuación se muestra un ejemplo de ejecución del programa anterior:

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

Compilamos todas las clases [impot, impotsData, impotsArray] en un ensamblado [impot.dll]:

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

Compilamos el programa de prueba:

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

Podemos realizar las pruebas:

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. Las vistas de la aplicación web

La aplicación tendrá dos vistas: [formulaire.aspx] y [erreurs.aspx]. Ilustremos el funcionamiento de la aplicación mediante capturas de pantalla. La vista [formulaire.aspx] se muestra cuando se solicita por primera vez la URL [main.aspx]:

Image

El usuario rellena el formulario:

Image

y utiliza el botón [Calculer] para obtener la siguiente respuesta:

Image

Puede cometer un error al introducir los datos:

Image

Al pulsar el botón [Calculer], se obtiene entonces otra respuesta, [erreurs.aspx]:

Image

Puede utilizar el enlace [Retour au formulaire] anterior para volver a la vista [formulaire.aspx] tal y como la validó antes del error:

Image

6.1.7. La vista [formulaire.aspx]

La página [formulaire.aspx] será la siguiente:


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

Los campos dinámicos de esta página son los siguientes:

rdouichecked
«checked» si la casilla [oui] debe marcarse, «» en caso contrario
rdnonchecked
lo mismo para la casilla [non]
txtEnfants
valor que debe introducirse en el campo de entrada [txtEnfants]
txtSalaire
valor que debe introducirse en el campo de entrada [txtSalaire]
txtImpot
valor que debe introducirse en el campo de entrada [txtImpot]

La página tiene dos formularios, cada uno con un botón [submit]. El botón [Calculer] es el botón [submit] del siguiente formulario:


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

Se puede observar que los parámetros del formulario se enviarán al controlador con [action=calcul]. El botón [Effacer] es el botón [submit] del siguiente formulario:


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

Se observa que los parámetros del formulario se enviarán al controlador con [action=effacer]. En este caso, el formulario no tiene ningún parámetro. Lo único que importa es la acción.

Los campos de [formulaire.aspx] se calculan mediante [formulaire.aspx.vb]:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

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

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' se recupera la consulta anterior del contexto
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' se prepara la página para mostrarla
        ' botones de opción
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' el resto
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

El cálculo de los campos de [main.aspx] se realiza a partir de dos datos introducidos por el controlador en el contexto de la página:

  • Context.Items («formulario»): diccionario de tipo NameValueCollection que contiene los valores de los campos HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): valor del impuesto

6.1.8. La vista [erreurs.aspx]

La vista [erreurs.aspx] es la que muestra los posibles errores que puedan producirse durante el funcionamiento de la aplicación. Su código de presentación es el siguiente:


<%@ 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 página tiene tres campos dinámicos:

erreursHTML
código HTML de una lista de errores
href
URL de un enlace
lien
Texto del enlace

Estos campos los calcula la parte de control de la página en [erreurs.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class erreurs
    Inherits System.Web.UI.Page

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

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

End Class

El controlador de la página recupera la información que el controlador de la aplicación ha colocado en el contexto de la página:

Context.Items("erreurs"
objeto ArrayList que contiene la lista de mensajes de error que se deben mostrar
Context.Items("href"
URL de un enlace
Context.Items("lien"
Texto del enlace

Ahora que sabemos lo que ve el usuario de la aplicación, podemos pasar a escribir el controlador de la misma.

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

Recordemos el esquema MVC de nuestra aplicación:

Image

Cliente Lógica de aplicación

El controlador [main.aspx] debe gestionar tres acciones:

  • init: corresponde a la primera solicitud del cliente. El controlador muestra la vista [formulaire.aspx]
  • calcul: corresponde a la solicitud de cálculo del impuesto. Si los datos del formulario de introducción son correctos, el impuesto se calcula mediante la clase de negocio [impots]. El controlador devuelve al cliente la vista [formulaire.aspx] tal y como se había validado, además del impuesto calculado. Si los datos del formulario de introducción son incorrectos, el controlador devuelve la vista [erreurs.aspx] con la lista de errores y un enlace para volver al formulario.
  • retorno: corresponde al retorno al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal y como se validó antes del error.

Por otra parte, sabemos que toda solicitud dirigida a la aplicación pasa por el controlador [global.asax], si existe. Así pues, en la entrada de la aplicación tenemos una cadena de dos controladores:

  • [global.asax], que, debido a la arquitectura de ASP.NET, recibe todas las solicitudes dirigidas a la aplicación
  • [main.aspx], que, por decisión del desarrollador, también recibe todas las solicitudes dirigidas a la aplicación

La necesidad de [main.aspx] se debe a que tendremos que gestionar una sesión. Ya hemos visto que [global.asax] no era adecuado como controlador en este caso. Aquí podríamos prescindir por completo de [global.asax]. Sin embargo, lo utilizaremos para ejecutar código al iniciar la aplicación. El esquema MVC anterior muestra que necesitaremos crear un objeto [impot] para calcular el impuesto. No es necesario crearlo varias veces, con una vez basta. Por lo tanto, lo crearemos al iniciar la aplicación, durante el evento [Application_Start] gestionado por el controlador [global.asax]. El código de este es el siguiente:

[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)
        ' se crea un objeto «impost»
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsArray)
            ' se inserta el objeto en la aplicación
            Application("objImpot") = objImpot
            ' sin errores
            Application("erreur") = False
        Catch ex As Exception
            ': se ha producido un error; se anota en la aplicación
            Application("erreur") = True
        End Try
    End Sub
End Class

Una vez creado, el objeto de tipo [impot] se incorpora a la aplicación. Es ahí donde las distintas consultas de los distintos clientes lo buscarán. Dado que la creación del objeto [impot] puede fallar, gestionamos la posible excepción e introducimos una clave [erreur] en la aplicación para indicar si se ha producido o no un error al crear el objeto [impot].

El código del controlador [main.aspx, main.aspx.vb] será el siguiente:

[main.aspx]

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

[main.aspx.vb]


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

Public Class main
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
        If CType(Application("erreur"), Boolean) Then
            ' se redirige a la página de errores
            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 recupera la acción que hay que realizar
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' se ejecuta la acción
        Select Case action
            Case "init"
                ' inicializar la aplicación
                initAppli()
            Case "calcul"
                ' cálculo de la renta
                calculImpot()
            Case "retour"
                ' volver al formulario
                retourFormulaire()
            Case "effacer"
                ' inicialización de la aplicación
                initAppli()
            Case Else
                ' acción desconocida = inicialización
                initAppli()
        End Select
    End Sub

    Private Sub initAppli()
        ' se muestra el formulario prellenado
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function initForm() As NameValueCollection
        ' se inicializa el formulario
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' se comprueba la validez de los datos introducidos
        Dim erreurs As ArrayList = checkData()
        ' si hay errores, se avisa
        If erreurs.Count <> 0 Then
            ' se guardan los datos introducidos
            Session.Item("formulaire") = Request.Form
            ' se prepara la página de errores
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' Si no hay errores, se calcula el impuesto
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' se muestra la página de resultados
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Sub retourFormulaire()
        ' se muestra el formulario con los valores tomados de la sesión
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function checkData() As ArrayList
        ' al principio no hay errores
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' botón de opción «casado»
        Try
            Dim rdMarie As String = Request.Form("rdMarie").ToString
            If rdMarie <> "oui" And rdMarie <> "non" Then
                Throw New Exception
            End If
        Catch
            erreurs.Add("Vous n'avez pas indiqué votre statut marital")
        End Try
        ' Número de hijos
        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
        ' salario
        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
        ' se muestra la lista de errores
        Return erreurs
    End Function
End Class

El controlador comienza por comprobar que la aplicación se ha inicializado correctamente:


         ' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
        If CType(Application("erreur"), Boolean) Then
            ' se redirige a la página de errores
            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 el controlador detecta que la aplicación no se ha podido inicializar correctamente (no se ha podido crear el objeto [impot] necesario para el cálculo), muestra la página de errores con los parámetros adecuados. En este caso, no es necesario incluir el enlace de retorno al formulario, ya que toda la aplicación resulta estar indisponible. Se inserta un mensaje de error general en [Context.Items("erreurs")], del tipo [ArrayList].

Si el controlador detecta que la aplicación está operativa, analiza entonces la acción que se le solicita que ejecute a través del parámetro [action]. Ya nos hemos encontrado en numerosas ocasiones con este modo de funcionamiento. El procesamiento de cada tipo de acción se delega a una función.

6.1.9.1. Las acciones init y borrar

Estas dos acciones deben mostrar el formulario de entrada vacío. Recordemos que este (véanse las vistas) tiene dos parámetros:

  • Context.Items("formulario"): diccionario de tipo [NameValueCollection] que contiene los valores de los campos HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): valor del impuesto

La función [initAppli] inicializa estos dos parámetros para mostrar un formulario en blanco.

6.1.9.2. La acción «calcular»

Esta acción debe calcular el impuesto a pagar a partir de los datos introducidos en el formulario y devolverlo ya rellenado con los valores introducidos y, además, con el importe del impuesto calculado. La función [calculImpot], encargada de esta tarea, comienza por comprobar que los datos del formulario sean correctos:

  • el campo [rdMarie] debe estar presente y tener el valor [oui] o [non]
  • el campo [txtEnfants] debe estar presente y ser un número entero >=0
  • el campo [txtSalaire] debe estar presente y ser un número entero >=0

Si los datos introducidos resultan inválidos, el controlador muestra la vista [erreurs.aspx] tras haber introducido previamente en el contexto los valores que esta espera:

  • los mensajes de error se colocan en un objeto [ArrayList], objeto que a continuación se coloca en el contexto [Context.Items("erreurs")]
  • la URL del enlace de retorno y el texto de dicho enlace también se colocan en el contexto.

Antes de pasar el control a la página [erreurs.aspx], que enviará la respuesta al cliente, los valores introducidos en el formulario (Request.Form) se almacenan en la sesión, asociados a la clave «formulario». Esto permitirá recuperarlos en una solicitud posterior.

Cabe preguntarse aquí si resulta útil comprobar que los campos [rdMarie, txtEnfants, txtSalaire] estén presentes en la solicitud enviada por el cliente. Es innecesario si estamos seguros de que nuestro cliente es un navegador que ha recibido la vista [formulaire.aspx], que contiene dichos campos. Nunca se puede estar seguro de ello. Más adelante mostraremos un ejemplo en el que el cliente es la aplicación [curl] que ya hemos visto. Realizaremos una consulta a la aplicación sin enviar los campos que espera y veremos cómo reacciona. Se trata de una regla que ya se ha mencionado en varias ocasiones y que recordamos aquí: una aplicación nunca debe hacer suposiciones sobre el tipo de cliente que la consulta. Por seguridad, debe considerar que puede ser consultada por una aplicación programada que pueda enviarle cadenas de parámetros inesperadas. Debe comportarse correctamente en todos los casos.

En nuestro caso, hemos comprobado que los campos [rdMarie, txtEnfants, txtSalaire] estaban presentes en la consulta, pero no que esta pudiera contener otros. En esta aplicación, se ignorarían. No obstante, siempre por motivos de seguridad, sería interesante registrar este tipo de solicitud en un archivo de registros y enviar una alerta al administrador de la aplicación para que este sepa que la aplicación está recibiendo solicitudes «extrañas». Al analizarlas en el archivo de registro, podría detectar un posible ataque a la aplicación y tomar entonces las medidas necesarias para protegerla.

Si los datos esperados son correctos, el controlador inicia el cálculo del impuesto con el objeto [impot] almacenado en la aplicación. A continuación, almacena en el contexto los dos datos que espera la vista [formulaire.aspx]:

  • Context.Items («formulario»): diccionario de tipo [NameValueCollection] que contiene los valores de los campos HTML, [rdmarie,txtEnfants,txtSalaire], en este caso [Request.Form)] y c.a.d. los valores introducidos anteriormente en el formulario
  • Context.Items("txtImpot"): valor del impuesto que se acaba de obtener

Es posible que al lector atento le haya surgido una duda al leer lo anterior: dado que el objeto [impot] creado al iniciar la aplicación es compartido por todas las consultas, ¿no podrían producirse conflictos de acceso que provocaran la corrupción de los datos del objeto [impot]? Para responder a esta pregunta, debemos volver al código de la clase [impot]. Las consultas utilizan el método [impot].calculerImpot para obtener el impuesto a pagar. Por lo tanto, es este código el que debemos examinar:

        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' cálculo del número de participaciones
            Dim nbParts As Decimal
            If marié Then
                nbParts = CDec(nbEnfants) / 2 + 2
            Else
                nbParts = CDec(nbEnfants) / 2 + 1
            End If
            If nbEnfants >= 3 Then
                nbParts += 0.5D
            End If
             ' cálculo de la base imponible y del coeficiente familiar
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' cálculo del impuesto
            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

Supongamos que un hilo está ejecutando el método anterior y se interrumpe. Entonces, otro hilo ejecuta el método. ¿Cuáles son los riesgos? Para averiguarlo, hemos añadido el siguiente código:


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

El hilo 1, tras calcular el valor [impot1] de la variable local [impot], se interrumpe. A continuación, se ejecuta el hilo 2 y calcula un nuevo valor, [impot2], para esa misma variable [impot] antes de ser interrumpido. El hilo 1 recupera el control. ¿Qué encuentra en la variable local [impot]? Al ser una variable local de un método, se almacena en una estructura de memoria denominada pila. Esta pila forma parte del contexto del hilo, que se guarda cuando este se interrumpe. Cuando el hilo 2 se inicia, su contexto se establece con una nueva pila y, por lo tanto, con una nueva variable local [impot]. Cuando el hilo 2 sea interrumpido a su vez, su contexto también se guardará. Cuando se reinicia el hilo 1, se restaura su contexto, incluida su pila. Así, recupera su variable local [impot] y no la del hilo 2. Por lo tanto, nos encontramos en una situación en la que no hay conflictos de acceso entre las solicitudes. Las pruebas realizadas con la pausa de 10 segundos mencionada anteriormente han confirmado que las solicitudes simultáneas obtenían efectivamente el resultado esperado.

6.1.9.3. La acción de retorno

Esta acción corresponde a la activación del enlace [Retour vers le formulaire] de la vista [erreurs.aspx] para volver a la vista [formulaire.aspx], que aparece prellenada con los valores introducidos anteriormente y guardados en la sesión. La función [retourFormulaire] recupera esta información. Se inicializan los dos parámetros que espera la vista [formulaire.aspx]:

  • Context.Items("formulario") con los valores introducidos anteriormente y guardados en la sesión
  • Context.Items("txtImpot") con la cadena vacía

6.1.10. Prueba de la aplicación web

Todos los archivos anteriores se colocan en una carpeta <ruta-de-la-aplicación>.

Image

En esta carpeta se crea una subcarpeta [bin] en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb]. A continuación se recuerda el comando de compilación necesario:

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

El archivo [impot.dll] anterior debe colocarse en <ruta-de-la-aplicación>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<ruta-de-la-aplicación>,/impots1). Desde un navegador, solicitamos la URL [http://localhost/impots1/main.aspx]:

Image

Rellenamos el formulario:

Image

A continuación, iniciamos el cálculo del impuesto con el botón [Calculer]. Obtenemos la siguiente respuesta:

Image

A continuación, introducimos datos erróneos:

Image

Al pulsar el botón [Calculer], aparece la siguiente respuesta:

Image

Al utilizar el enlace [Retour au formulaire], volvemos al formulario tal y como estaba cuando se validó:

Image

Por último, al hacer clic en el botón [Effacer], la página se reinicia:

Image

6.1.11. Uso del cliente [curl]

Es importante probar las aplicaciones web con clientes distintos de los navegadores. Si se envía a un navegador un formulario con parámetros que deben enviarse al validarlo, el navegador devolverá los valores de dichos parámetros al servidor. Otro cliente podría no hacerlo y, en ese caso, el servidor recibiría una solicitud en la que faltarían algunos parámetros. Debe saber qué hacer en ese caso. Otro ejemplo es el de las validaciones de entrada realizadas del lado del cliente. Si el formulario contiene datos que deben validarse, esta validación puede realizarse del lado del cliente mediante scripts incluidos en el documento que contiene el formulario. El navegador solo enviará el formulario si todos los datos validados del lado del cliente son válidos. Por lo tanto, en el lado del servidor, podríamos sentir la tentación de dar por hecho que vamos a recibir datos ya validados y no querer realizar esta validación por segunda vez. Esto sería un error. De hecho, un cliente que no sea un navegador podría enviar datos no válidos al servidor, con lo que la aplicación web correría el riesgo de comportarse de forma inesperada. Vamos a ilustrar estos puntos utilizando el cliente [curl].

En primer lugar, solicitamos la 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>

El servidor nos ha enviado el código HTML del formulario. En los encabezados HTTP tenemos la cookie de sesión. La utilizaremos en las siguientes solicitudes para mantener la sesión. Solicitemos la acción [calcul] sin proporcionar parámetros:

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

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


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

Podemos observar que la aplicación web ha devuelto la vista [erreurs] con tres mensajes de error correspondientes a los tres parámetros que faltan. Enviemos ahora unos parámetros erróneos:

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>

Los tres errores se han detectado correctamente. Ahora enviemos parámetros válidos:

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

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


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

Hemos calculado correctamente el impuesto a pagar: 4300 euros. De este ejemplo aprenderemos que no hay que dejarse engañar por el hecho de que estemos escribiendo una aplicación web destinada a clientes que son navegadores. Una aplicación web es un servicio TCP/IP y este protocolo de red no permite determinar la naturaleza de la aplicación cliente de un servicio. Por lo tanto, no podemos saber si el cliente de una aplicación web es un navegador o no. Por lo tanto, seguimos dos reglas:

  • al recibir una solicitud de un cliente, no se hace ninguna suposición sobre el cliente y se comprueba que los parámetros esperados en la solicitud estén presentes y sean válidos
  • se elabora una respuesta destinada a los navegadores, es decir, por lo general, documentos HTML

Una aplicación web puede diseñarse para atender simultáneamente a diferentes clientes, por ejemplo, navegadores y teléfonos móviles. En ese caso, se puede incluir en cada solicitud un nuevo parámetro que indique el tipo de cliente. Así, un navegador solicitará el cálculo del impuesto mediante una solicitud a la URL http://machine/impots/main.aspx?client=navegador&action=cálculo, mientras que el teléfono móvil realizará una solicitud a la URL http://machine/impots/main.aspx?client=mobile&action=calcul. La estructura MVC facilita la programación de una aplicación de este tipo. Queda de la siguiente manera:

Image

El bloque [Classes métier, Classes d'accès aux données] no cambia. De hecho, es una parte que no afecta al cliente. El bloque [Contrôleur] cambia ligeramente, pero debe tener en cuenta un nuevo parámetro en la solicitud: el parámetro [client], que indica a qué tipo de cliente se dirige. El bloque [vues] debe generar vistas para cada tipo de cliente. Podría resultar interesante tener en cuenta, desde el diseño de la aplicación, la presencia del parámetro [client] en la consulta, aunque el objetivo a corto o medio plazo se limite únicamente a los navegadores. Si la aplicación tiene que gestionar posteriormente un nuevo tipo de cliente, solo habrá que escribir las vistas adaptadas a este.

6.2. Ejemplo 2

6.2.1. El problema

Aquí nos proponemos abordar el mismo problema que antes, pero modificando la fuente de datos del objeto [impot] creado por la aplicación web. En la versión anterior, la fuente de datos utilizada proporcionaba los valores de tablas «fijas» en el código. En esta ocasión, la nueva fuente de datos los obtendrá de una fuente de datos ODBC asociada a una base de datos MySQL.

6.2.2. La fuente de datos ODBC

Los datos se encontrarán en una tabla denominada [IMPOTS] de una base de datos MySQL denominada [dbimpots]. El contenido de esta tabla será el siguiente:

Image

El propietario de la base de datos es el usuario [admimpots], cuya contraseña es [mdpimpots]. Asociamos una fuente de datos ODBC a esta base de datos. Antes de hacerlo, recordemos primero las diferentes formas de acceder a una base de datos con la plataforma .NET.

Existen numerosas bases de datos para las plataformas Windows. Para acceder a ellas, las aplicaciones utilizan programas denominados controladores (drivers).

Image

En el esquema anterior, el controlador presenta dos interfaces:

  • la interfaz I1, que se presenta a la aplicación
  • la interfaz I2 hacia la base de datos

Para evitar que una aplicación escrita para una base de datos B1 tenga que reescribirse si se migra a una base de datos B2 diferente, se ha llevado a cabo un trabajo de normalización en la interfaz I1. Si se utilizan bases de datos que emplean controladores «normalizados», la base B1 se suministrará con un controlador P1, la base B2 con un controlador P2, y la interfaz I1 de ambos controladores será idéntica. Por lo tanto, no será necesario reescribir la aplicación. De este modo, por ejemplo, se podrá migrar una base de datos ACCESS a una base de datos MySQL sin modificar la aplicación.

Existen dos tipos de controladores estandarizados:

  • los controladores ODBC (Open DataBase Connectivity)
  • los controladores OLE y DB (Object Linking and Embedding DataBase)

Los controladores ODBC permiten el acceso a bases de datos. Las fuentes de datos para los controladores OLE y DB son más variadas: bases de datos, sistemas de mensajería, directorios... No hay ningún límite. Cualquier fuente de datos puede ser objeto de un controlador OLE DB si así lo decide el desarrollador. El interés es, evidentemente, enorme: se dispone de un acceso uniforme a una gran variedad de datos.

La plataforma .NET 1.1 incluye tres tipos de clases de acceso a datos:

  1. las clases SQL y Server.NET, para acceder a las bases de datos de Microsoft SQL Server
  2. las clases Ole Db.NET, para acceder a las bases de datos de SGBD que ofrecen un controlador OLE y DB
  3. las clases odbc.net, para acceder a las bases de datos de SGBD, que ofrecen un controlador ODBC

La clase SGBD MySQL dispone desde hace tiempo de un controlador ODBC. Este es el que utilizamos ahora. En Windows, seleccionamos la opción [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Dependiendo de la versión de Windows, esta ruta puede variar ligeramente. Obtenemos la siguiente aplicación, que nos permitirá crear nuestra fuente ODBC:

Image

Vamos a crear una fuente de datos del sistema, c.a.d. Una fuente de datos a la que podrá acceder cualquier usuario del equipo utiliser.Aussi; para ello, seleccionamos la pestaña [Source de données système]. La página que aparece tiene un botón [Ajouter] que utilizaremos para crear una nueva fuente de datos ODBC:

Image

El asistente nos pide que seleccionemos el controlador ODBC que queremos utilizar. Windows incluye de serie varios controladores ODBC preinstalados. El controlador ODBC de MySQL no forma parte de ese conjunto. Por lo tanto, hay que instalarlo previamente. Se puede encontrar en Internet escribiendo la cadena de búsqueda «MySQL ODBC» o «MyODBC» en un buscador. En este caso, hemos instalado el controlador [MySQL ODBC 3.51]. Lo seleccionamos y hacemos [Terminer]:

Image

Hay que introducir una serie de datos:

Data Source Name
el nombre con el que se identificará la fuente de datos ODBC. Cualquier aplicación de Windows podrá acceder a la fuente mediante este nombre
Description
un texto arbitrario que describa la fuente de datos
Host Name
El nombre del equipo que aloja el SGBD MySQL. En este caso se trata del equipo local. Podría ser un equipo remoto. Esto permitiría a una aplicación de Windows acceder a una base de datos remota sin necesidad de ninguna codificación específica. Este es uno de los grandes ventajas de la fuente ODBC.
Database Name
Un SGBD MySQL puede gestionar varias bases de datos. Aquí se especifica cuál se quiere gestionar: dbimpots
User
Nombre de un usuario registrado en SGBD MySQL. Los accesos a la fuente de datos se realizarán con este nombre. En este caso: admimpots
Password
la contraseña de este usuario. En este caso: mdpimpots
Port
puerto de trabajo del SGBD MySQL. Por defecto es el puerto 3306. No lo hemos cambiado

Una vez hecho esto, comprobamos la validez de nuestros parámetros de conexión con el botón [Test Data Source]:

Image

Una vez hecho esto, estamos seguros de nuestra fuente de datos ODBC. Ahora ya podemos utilizarla. Repetimos [OK] tantas veces como sea necesario para salir del asistente ODBC.

Si el lector no dispone de SGBD mySQL, puede descargarlo gratuitamente en la URL [http://www.mysql.com]. A continuación se describe el procedimiento para crear una fuente ODBC con Access. Los primeros pasos son idénticos a los descritos anteriormente. Se añade una nueva fuente de datos del sistema:

Image

El controlador seleccionado será [Microsoft Access Driver]. Se ejecuta [Terminer] para pasar a la definición de la fuente ODBC:

Image

Los datos que hay que proporcionar son los siguientes:

Nom de la source de données
el nombre que designará la fuente de datos ODBC. Cualquier aplicación de Windows podrá acceder a la fuente a través de este nombre
Description
un texto arbitrario que describa la fuente de datos
Base de données
El nombre completo del archivo ACCESS que se va a utilizar

6.2.3. Una nueva clase de acceso a los datos

Volvamos a la estructura MVC de nuestra aplicación:

Image

En el esquema anterior, la clase [impotsData] se encarga de recuperar los datos. En este caso, deberá hacerlo de la base de datos MySQL [dbimpots]. Sabemos, desde la versión anterior de esta aplicación, que [impotsData] es una clase abstracta de la que hay que derivar cada vez que se quiera adaptar a una nueva fuente de datos. Recordemos la estructura de esta clase abstracta:


Imports System.Collections

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

        ' método de acceso a los datos
        Public MustOverride Function getData() As Object()

        ' método de verificación de datos
        Protected Function checkData() As Integer
            ' comprueba los datos obtenidos
...
        End Function

        ' comprueba la validez del contenido de una tabla
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
        ...
        End Function
    End Class
End Namespace

La clase que deriva de [impotsData] debe implementar dos métodos:

  • un constructor, si el constructor sin argumentos de [impotsData] no le conviene
  • el método [getData], que devuelve los tres arreglos (límites, coeffr, coeffn)

Creamos la clase [impotsODBC], que recuperará los datos (límites, coeffr, coeffn) de una fuente ODBC a la que le daremos el nombre:


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

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

        ' variables de instancia
        Protected DSNimpots As String

        ' constructor
        Public Sub New(ByVal DSNimpots As String)
            ' se anotan los tres datos
            Me.DSNimpots = DSNimpots
        End Sub

        Public Overrides Function getdata() As Object()
            ' inicializa las tres tablas «limites», «coeffr» y «coeffn» a partir de
            ' del contenido de la tabla [impots] de la base de datos ODBC DSNimpots
            ' «límites», «coeffr» y «coeffn» son las tres columnas de esta tabla
            ' puede generar diversas excepciones

            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 consulta SELECT
            Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
            ' tablas para recuperar los datos
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
            Try
                ' se intenta acceder a la base de datos
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
                ' se crea un objeto de comando
                sqlCommand = New OdbcCommand(selectCommand, impotsConn)
                ' se ejecuta la consulta
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
                ' Análisis de la tabla recuperada
                While myReader.Read()
                    ' Los datos de la fila actual se introducen en las tablas
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' Liberación de recursos
                myReader.Close()
                impotsConn.Close()
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' Las tablas dinámicas se transfieren a tablas estáticas
            Me.limites = New Decimal(aLimites.Count - 1) {}
            Me.coeffr = New Decimal(aLimites.Count - 1) {}
            Me.coeffn = New Decimal(aLimites.Count - 1) {}
            Dim i As Integer
            For i = 0 To aLimites.Count - 1
                limites(i) = Decimal.Parse(aLimites(i).ToString())
                coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
                coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
            Next i
            ' Se comprueban los datos obtenidos
            Dim erreur As Integer = checkData()
            ' si los datos no son válidos, se lanza una excepción
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' de lo contrario, se devuelven las tres tablas
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Echemos un vistazo al fabricante:


        ' constructor
        Public Sub New(ByVal DSNimpots As String)
            ' se anotan los tres datos
            Me.DSNimpots = DSNimpots
        End Sub

Recibe como parámetro el nombre de la fuente ODBC en la que se encuentran los datos que se van a adquirir. El constructor se limita a almacenar este nombre. El método [getData] se encarga de leer los datos de la tabla [impots] y de colocarlos en tres matrices (límites, coeffr, coeffn). Comentaremos su código:

  • se definen los parámetros de conexión a la fuente de datos ODBC, pero esta no está abierta
             ' cadena de conexión a la base de datos
            Dim connectString As String = "DSN=" + DSNimpots + ";"
            ' se crea un objeto de conexión a la base de datos; esta conexión no está abierta
            Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
  • Se definen tres objetos [ArrayList] para recuperar los datos de la tabla [impots]:

             ' tablas para recuperar los datos
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
  • Todo el código de acceso a la base de datos está rodeado por un bloque «try/catch» para gestionar un posible error de acceso. Se abre la conexión con la base de datos:

                 ' Se intenta acceder a la base de datos
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • Se ejecuta el comando [select] en la conexión abierta. Se obtiene un objeto [OdbcDataReader] que nos permitirá recorrer las filas de la tabla resultante de la consulta SELECT:

                 ' se crea un objeto de comando
                Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
                ' se ejecuta la consulta
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
  • Recorremos la tabla de resultados, línea por línea. Para ello, utilizamos el método [Read] del objeto [OdbcDataReader] obtenido anteriormente. Este método realiza dos acciones:
    • avanza una línea en la tabla. Al principio, nos encontramos situados antes de la primera línea
    • devuelve el valor booleano [true] si ha podido avanzar, y [false] en caso contrario; este último caso indica que se han procesado todas las líneas.

Las columnas de la línea actual del objeto [OdbcDataReader] se obtienen mediante OdbcDataReader. Se obtiene un objeto que representa el valor de la columna. Recorremos toda la tabla para introducir su contenido en los tres objetos [ArrayList]:


                 ' Se procesa la tabla recuperada
                While myReader.Read()
                    ' Los datos de la fila actual se introducen en las tablas
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
  • Una vez hecho esto, liberamos los recursos asociados a la conexión:
                 ' Liberación de recursos
                myReader.Close()
                impotsConn.Close()
  • El contenido de los tres objetos [ArrayList] se transfiere a tres matrices clásicas:

             ' Las tablas dinámicas se transfieren a tablas estáticas
            limites = New Decimal(aLimites.Count - 1) {}
            coeffr = New Decimal(aLimites.Count - 1) {}
            coeffn = New Decimal(aLimites.Count - 1) {}
            Dim i As Integer
            For i = 0 To aLimites.Count - 1
                limites(i) = CType(aLimites(i), Decimal)
                coeffR(i) = CType(aCoeffR(i), Decimal)
                coeffN(i) = CType(aCoeffN(i), Decimal)
            Next i
  • Una vez que los datos de la tabla [impots] se han transferido a las tres tablas, solo queda comprobar su contenido mediante el método [checkData] de la clase base [impotsData]:
             ' Se comprueban los datos obtenidos
            Dim erreur As Integer = checkData()
             ' si los datos no son válidos, se lanza una excepción
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' de lo contrario, se devuelven las tres tablas
            Return New Object() {limites, coeffr, coeffn}

6.2.4. Pruebas de la clase de acceso a datos

Un programa de prueba podría ser el siguiente:

Option Explicit On 
Option Strict On

' espacios de nombres
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' página de prueba
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' programa interactivo de cálculo de impuestos
             ' el usuario introduce tres datos mediante el teclado: casado nbEnfants salario
             ' a continuación, el programa muestra el impuesto a pagar
            Const syntaxe1 As String = "pg DSNimpots"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' comprobación de los parámetros del programa
            If arguments.Length <> 1 Then
                 ' mensaje de error
                Console.Error.WriteLine(syntaxe1)
                 ' fin
                Environment.Exit(1)
            End If
             ' se recuperan los argumentos
            Dim DSNimpots As String = arguments(0)

             ' creación de un objeto de impuesto
            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

             ' bucle infinito
            While True
                 ' al principio no hay errores
                Dim erreur As Boolean = False

                 ' se solicitan los parámetros para el cálculo del impuesto
                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()

                 ' ¿Hay que hacer algo?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' Comprobación del número de argumentos en la línea introducida
                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
                     ' Comprobación de la validez de los parámetros
                     ' casado
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                     ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                     ' salario
                    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
                     ' los parámetros son correctos: se calcula el impuesto
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

La aplicación se inicia con un parámetro:

  • DSNimpots: nombre de la fuente de datos ODBC que se va a utilizar

El cálculo del impuesto se realiza mediante un objeto de tipo [impot] creado al iniciar la aplicación:


             ' creación de un objeto de impuesto
            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 vez iniciada, la aplicación solicita repetidamente al usuario que introduzca mediante el teclado los tres datos necesarios para calcular sus impuestos:

  • su estado civil: «o» para casado, «n» para soltero
  • el número de hijos
  • su salario anual

Se compilan todas las clases:

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 continuación, se compila el programa de prueba:

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

El programa de prueba se ejecuta primero con la fuente de datos 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)

Se cambia la fuente ODBC por una fuente de 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. Las vistas de la aplicación web

Son las mismas que las de la aplicación anterior: formulaire.aspx y erreurs.aspx

6.2.6. Los controladores de la aplicación [global.asax, main.aspx]

Solo hay que modificar el controlador [global.asax]. De hecho, este se encarga de crear el objeto [impot] al iniciar la aplicación. El constructor de este objeto tiene como único parámetro el objeto de tipo [impotsData], encargado de recuperar los datos. Por lo tanto, este parámetro cambia para cada nuevo tipo de fuente de datos. El controlador [global.asax.vb] queda así:


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)
        ' se crea un objeto de impuesto
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' se introduce el objeto en la aplicación
            Application("objImpot") = objImpot
            ' sin errores
            Application("erreur") = False
        Catch ex As Exception
            'se ha producido un error; se anota en la aplicación
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

La fuente de datos del objeto [impot] es ahora un objeto [impotODBC]. Este último tiene como parámetro el nombre DSN de la fuente de datos ODBC que se va a utilizar. En lugar de escribir este nombre de forma fija en el código, se incluye en el archivo de configuración [web.config] de la aplicación:


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

Se sabe que el valor de una clave C de la sección <appSettings> del archivo [web.config] se obtiene en el código de la aplicación mediante [ConfigurationSettings.AppSettings(C)].

Para conocer la causa de la excepción, se registra el mensaje de la misma en la aplicación, de modo que quede disponible para las consultas. El controlador [main.aspx.vb] incluirá este mensaje en su lista de errores:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
        If CType(Application("erreur"), Boolean) Then
            ' se redirige a la página de errores
            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
        ' se recupera la acción que hay que realizar
...

6.2.7. Resumen de los cambios

La aplicación está lista para ser probada. A continuación, enumeramos los cambios introducidos con respecto a la versión anterior:

  1. se ha creado una nueva clase de acceso a datos
  2. se ha modificado el controlador [global.asax.vb] en dos puntos: la creación del objeto [impot] y el registro en la aplicación del mensaje relacionado con una posible excepción
  3. se ha modificado el controlador [main.aspx.vb] en un punto para mostrar el mensaje de excepción anterior
  4. Se ha añadido un archivo [web.config]

El trabajo de modificación se llevó a cabo principalmente en 1, c.a.d, fuera de la aplicación web. Esto fue posible gracias a la arquitectura MVC de la aplicación, que separa el controlador de las clases de negocio. Ahí radica todo el interés de esta arquitectura. Se podría demostrar que, con un archivo de configuración adecuado, se habría podido evitar cualquier modificación del controlador de la aplicación. Es posible introducir en el archivo el nombre de la clase de acceso a datos que se va a instanciar dinámicamente, así como los diversos parámetros necesarios para dicha instanciación. Con esta información, [global.asax] puede instanciar el objeto de acceso a los datos. Cambiar de fuente de datos equivale entonces a:

  • crear la clase de acceso a dicha fuente si aún no existe
  • modificar el archivo [web.config] para permitir la creación dinámica de una instancia de esta clase en [global.asax]

6.2.8. Prueba de la aplicación web

Todos los archivos anteriores se colocan en una carpeta <application-path>.

Image

En esta carpeta se crea una subcarpeta [bin] en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. A continuación se recuerda el comando de compilación necesario:

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

El archivo [impot.dll] anterior debe colocarse en <ruta-de-la-aplicación>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<ruta-de-la-aplicación>,/impots2). Las pruebas arrojan los mismos resultados que en la versión anterior, ya que la presencia de la base de datos es transparente para el usuario. No obstante, para ilustrar esta presencia, nos aseguramos de que la fuente ODBC no esté disponible deteniendo los archivos SGBD y MySQL, y solicitamos la URL [http://localhost/impots2/main.aspx]. Obtenemos la siguiente respuesta:

Image

6.3. Ejemplo 3

6.3.1. El problema

Aquí nos proponemos abordar el mismo problema modificando de nuevo la fuente de datos del objeto [impot] creado por la aplicación web. En esta ocasión, la nueva fuente de datos será una base de datos ACCESS a la que se accederá mediante un controlador OLEDB. Nuestro objetivo es mostrar otra forma de acceder a una base de datos.

6.3.2. La fuente de datos OLEDB

Los datos se encontrarán en una tabla llamada [IMPOTS] de una base de datos ACCESS. El contenido de esta tabla será el siguiente:

Image

6.3.3. La clase de acceso a los datos

Volvamos a la estructura MVC de nuestra aplicación:

Image

  • En el esquema anterior, la clase [impotsData] se encarga de recuperar los datos. En esta ocasión, deberá hacerlo a partir de una fuente OLEDB.

Creamos la clase [impotsOLEDB], que irá a buscar los datos (límites, coeffr, coeffn) en una fuente ODBC a la que le daremos el nombre:


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

        ' variables de instancia
        Protected chaineConnexion As String

        ' constructor
        Public Sub New(ByVal chaineConnexion As String)
            ' se anotan los tres datos
            Me.chaineConnexion = chaineConnexion
        End Sub

        Public Overrides Function getData() As Object()
            ' inicializa los tres arrays «limites», «coeffr» y «coeffn» a partir de
            ' del contenido de la tabla [impots] de la base de datos OLEDB [chaineConnexion]
            ' «límites», «coeffr» y «coeffn» son las tres columnas de esta tabla
            ' puede lanzar diversas excepciones

            ' se crea un objeto DataAdapter para leer los datos de la fuente OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
            ' se crea una imagen en memoria del resultado de la consulta SELECT
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' se recupera el contenido de la tabla «impots»
            Dim lignesImpots As DataRowCollection = contenu.Rows
            ' se dimensionan las tablas de recepción
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' se transfiere el contenido de la tabla «impots» a los arrays
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' línea i de la tabla
                    ligne = lignesImpots.Item(i)
                    ' se recupera el contenido de la línea
                    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
            ' se comprueban los datos obtenidos
            Dim erreur As Integer = checkData()
            ' si los datos no son válidos, se lanza una excepción
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' en caso contrario, se devuelven los tres arrays
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Echemos un vistazo al fabricante:


         ' constructor
        Public Sub New(ByVal chaineConnexion As String)
             ' se anotan los tres datos
            Me.chaineConnexion = chaineConnexion
        End Sub

Recibe como parámetro la cadena de conexión de la fuente OLEDB, en la que se encuentran los datos que se van a adquirir. El controlador se limita a almacenarla. Una cadena de conexión contiene todos los parámetros necesarios para que el controlador OLEDB se conecte a la fuente OLEDB. Por lo general, es bastante compleja. Para averiguar cuál es la cadena de conexión de las bases de datos ACCESS, podemos utilizar la herramienta [WebMatrix]. Iniciamos esta herramienta. Nos muestra una ventana que permite conectarnos a una fuente de datos:

Gracias al icono señalado por la flecha anterior, es posible establecer una conexión con dos tipos de bases de datos de Microsoft: SQL Server y ACCESS. Seleccionemos ACCESS:

Image

Hemos utilizado el botón [...] para seleccionar la base de datos ACCESS. Confirmamos el asistente. En la pestaña [Data], hay iconos que representan la conexión:

Image

Ahora, vamos a crear un nuevo archivo .aspx mediante [Files/New File]:

Image

Obtenemos una hoja en blanco en la que podemos diseñar nuestra interfaz web:

Image

Arrastramos desde la pestaña [Data] la tabla [impots] a la hoja anterior. Obtenemos el siguiente resultado:

Image

Hagamos clic con el botón derecho del ratón sobre el objeto [AccessDataSourceControl] que aparece a continuación para acceder a sus propiedades:

Image

La cadena de conexión OLEDB a la base de datos ACCESS viene dada por la propiedad [ConnectionString] anterior:

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

Se observa que esta cadena está formada por una parte fija y una parte variable, que no es más que el nombre del archivo ACCESS. Aprovecharemos este dato para generar la cadena de conexión a nuestra fuente de datos OLEDB.

Volvamos ahora a nuestra clase [impotsOLEDB]. El método [getData] se encarga de leer los datos de la tabla [impots] y de colocarlos en tres matrices (limites, coeffr, coeffn). Comentaremos su código:

  • definimos el objeto [DataAdapter], que nos permitirá transferir a la memoria el resultado de una consulta SELECT de SQL. Para ello, definimos la consulta [select] que se va a ejecutar y la asociamos al objeto [DataAdapter]. El constructor de este último también requiere la cadena de conexión que deberá utilizar para conectarse a la fuente OLEDB

             ' se crea un objeto DataAdapter para leer los datos de la fuente OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • Se ejecuta el comando [select] mediante el método [Fill] del objeto [DataAdapter]. El resultado de [select] se inserta en un objeto [DataTable] creado para la ocasión. Un objeto [DataTable] es la representación en memoria de una tabla de base de datos, c.a.d, un conjunto de filas y columnas. Gestionamos una excepción que puede producirse si, por ejemplo, la cadena de conexión es incorrecta.

             ' se crea una imagen en memoria del resultado de la consulta
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
  • En [contenu], tenemos la tabla [impots] traída por [select]. Un objeto [DataTable] es una tabla, es decir, un conjunto de filas. Se puede acceder a ellas a través de la propiedad [rows] de [datatable]:

             ' se recupera el contenido de la tabla «impots»
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • Cada elemento de la colección [lignesImpots] es un objeto de tipo [DataRow] que representa una fila de la tabla. Esta tiene columnas a las que se puede acceder a través del objeto [DataRow] mediante su propiedad [Item]. [DataRow].[Item(i)] es la columna n.º i de la fila [DataRow]. Al recorrer la colección de filas (la colección DataRows de lignesImpots) y la colección de columnas de cada fila, se puede obtener la tabla completa:
             ' se dimensionan las tablas de recepción
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
             ' se transfiere el contenido de la tabla «impots» a los arrays
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                     ' línea i de la tabla
                    ligne = lignesImpots.Item(i)
                     ' se recupera el contenido de la línea
                    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 vez que los datos de la tabla [impots] se han transferido a las tres tablas, solo queda comprobar su contenido mediante el método [checkData] de la clase base [impotsData]:
            ' se comprueban los datos obtenidos
            Dim erreur As Integer = checkData()
             ' si los datos no son válidos, se lanza una excepción
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' en caso contrario, se devuelven los tres arrays
            Return New Object() {limites, coeffr, coeffn}

6.3.4. Pruebas de la clase de acceso a datos

Un programa de prueba podría ser el siguiente:

Option Explicit On 
Option Strict On

' espacios de nombres
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' página de prueba
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' programa interactivo de cálculo de impuestos
             ' el usuario introduce tres datos mediante el teclado: casado nbEnfants salario
             ' a continuación, el programa muestra el impuesto a pagar
            Const syntaxe1 As String = "pg bdACCESS"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' comprobación de los parámetros del programa
            If arguments.Length <> 1 Then
                 ' mensaje de error
                Console.Error.WriteLine(syntaxe1)
                 ' fin
                Environment.Exit(1)
            End If
             ' se recuperan los argumentos
            Dim chemin As String = arguments(0)
             ' se prepara la cadena de conexión
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' creación de un objeto de impuestos
            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

             ' bucle infinito
            While True
                 ' al principio no hay errores
                Dim erreur As Boolean = False

                 ' Se solicitan los parámetros para el cálculo del impuesto
                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()

                 ' ¿Hay que hacer algo?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' Comprobación del número de argumentos en la línea introducida
                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
                     ' Comprobación de la validez de los parámetros
                     ' casado
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                     ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                     ' salario
                    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
                     ' los parámetros son correctos: se calcula el impuesto
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

La aplicación se ejecuta con un parámetro:

  • bdACCESS: nombre del archivo ACCESS que se va a procesar

El cálculo del impuesto se realiza mediante un objeto de tipo [impot] creado al iniciar la aplicación:


             ' se recuperan los argumentos
            Dim chemin As String = arguments(0)
            ' se prepara la cadena de conexión
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' creación de un objeto de impuestos
            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 cadena de conexión a la fuente OLEDB se ha creado a partir de la información obtenida con [WebMatrix].

Una vez inicializada, la aplicación solicita repetidamente al usuario que introduzca mediante el teclado los tres datos necesarios para calcular sus impuestos:

  • su estado civil: «o» para casado, «n» para soltero
  • el número de hijos
  • su salario anual

Se compilan todas las clases:

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

El archivo [impots.mdb] se coloca en la carpeta de la aplicación de prueba y esta se ejecuta de la siguiente manera:

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)

Se puede ejecutar la aplicación con un archivo ACCESS incorrecto:

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. Las vistas de la aplicación web

Son las mismas que las de la aplicación anterior: formulaire.aspx y erreurs.aspx

6.3.6. Los controladores de la aplicación [global.asax, main.aspx]

Solo hay que modificar el controlador [global.asax]. De hecho, este se encarga de crear el objeto [impot] al iniciar la aplicación. El constructor de este objeto tiene como único parámetro el objeto de tipo [impotsData], encargado de recuperar los datos. Por lo tanto, este parámetro cambia al cambiar las fuentes de datos. El controlador [global.asax.vb] queda así:


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)
        ' se crea un objeto de impuesto
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' se inserta el objeto en la aplicación
            Application("objImpot") = objImpot
            ' sin errores
            Application("erreur") = False
        Catch ex As Exception
            'se ha producido un error; se anota en la aplicación
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

La fuente de datos del objeto [impot] es ahora un objeto [impotOLEDB]. Este último tiene como parámetro la cadena de conexión de la fuente de datos OLEDB que se va a utilizar. Dicha cadena se encuentra en el archivo de configuración [web.config] de la aplicación:


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

El controlador [main.aspx] no cambia.

6.3.7. Resumen de los cambios

La aplicación está lista para ser probada. Enumeremos los cambios introducidos con respecto a la versión anterior:

  1. se ha creado una nueva clase de acceso a datos
  2. se ha modificado el controlador [global.asax.vb] en un punto: creación del objeto [impot]
  3. Se ha añadido un archivo [web.config]

6.3.8. Prueba de la aplicación web

Todos los archivos anteriores se colocan en una carpeta <ruta-de-la-aplicación>.

Image

En esta carpeta se crea una subcarpeta [bin] en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. A continuación se recuerda el comando de compilación necesario:

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

El archivo [impot.dll] generado por este comando debe colocarse en <application-path>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<application-path>,/impots3). Las pruebas arrojan los mismos resultados que en la versión anterior.

6.4. Ejemplo 4

6.4.1. El problema

Ahora nos proponemos transformar nuestra aplicación en una aplicación de simulación de cálculos de impuestos. Un usuario podrá realizar cálculos sucesivos de impuestos y estos se le mostrarán en una nueva vista similar a esta:

Image

6.4.2. La estructura MVC de la aplicación

La estructura MVC de la aplicación queda así:

Image

Aparece una nueva vista [simulations.aspx], de la que acabamos de mostrar una captura de pantalla. La clase de acceso a los datos será la clase [impotsODBC] del ejemplo 2.

6.4.3. Las vistas de la aplicación web

La vista [erreurs.aspx] no cambia. La vista [formulaire.aspx] cambia ligeramente. De hecho, el importe del impuesto ya no aparece en esta vista. Ahora se encuentra en la vista [simulations.aspx]. Así, al iniciar la aplicación, la página que se muestra al usuario es la siguiente:

Image

Por otra parte, la vista [formulaire] incluye un script de JavaScript que comprueba la validez de los datos introducidos antes de enviarlos al servidor, tal y como muestra el siguiente ejemplo:

Image

El código de presentación es el siguiente:


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
        <script language="javascript">
        function calculer(){
          // Se comprueban los parámetros antes de enviarlos al servidor
        with(document.frmImpots){
          //Número de hijos
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // no se ha comprobado el modelo
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//si
          //salario
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // No se ha comprobado el modelo
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//si
          // Todo correcto: se envía el formulario al servidor
          submit();
        }//con
      }//calcular  
        </script>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <form name="frmImpots" method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui 
                      <INPUT type="radio"  value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="button" value="Calculer" onclick="calculer()">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
            <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Los campos dinámicos de la página son los mismos que en las versiones anteriores. El campo dinámico del importe del impuesto ha desaparecido. El botón [Calculer] ya no es un botón de tipo [submit]. Es de tipo [button] y, al hacer clic en él, se ejecuta la función JavaScript [calculer()]:


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

Se le ha asignado al formulario el nombre [frmImpots] para poder hacer referencia a él en el script [calculer]:


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

La función JavaScript [calculer] utiliza expresiones regulares para comprobar la validez de los campos de los formularios [document.frmImpots.txtEnfants] y [document.frmImpots.txtSalaire]. Si los valores introducidos son correctos, [document.frmImpots.submit()] los envía al servidor.

La página de presentación obtiene sus campos dinámicos de su controlador [formulaire.aspx.vb] de la siguiente manera:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

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

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' se recupera la solicitud anterior del contexto
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' se prepara la página para mostrarla
        ' botones de opción
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' el resto
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
    End Sub
End Class

El controlador [formulaire.aspx.vb] es idéntico a las versiones anteriores, salvo que ya no tiene que recuperar el campo [txtImpot] del contexto, ya que dicho campo ha desaparecido de la página.

La vista [simulations.aspx] tiene el siguiente aspecto:

Image

y se corresponde con el siguiente código de presentación:


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

Este código presenta tres campos dinámicos:

simulationsHTML
código HTML de una lista de simulaciones en forma de filas de tabla HTML
href
URL de un enlace
lien
Texto del enlace

Son generados por el controlador [simulations.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class simulations
    Inherits System.Web.UI.Page

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

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'se recuperan las simulaciones del contexto
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' cada simulación es una matriz de 4 elementos de tipo cadena
        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
        ' se recuperan los demás elementos del contexto
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
    End Sub
End Class

El controlador de la página recupera la información que el controlador de la aplicación ha colocado en el contexto de la página:

Context.Items("simulations")
objeto ArrayList que contiene la lista de simulaciones que se van a mostrar. Cada elemento es una matriz de 4 cadenas de caracteres que representan la información (estado civil, hijos, salario, impuestos) de la simulación.
Context.Items("href")
URL de un enlace
Context.Items("lien")
Texto del enlace

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

Recordemos el esquema MVC de nuestra aplicación:

Image

El controlador [main.aspx] debe gestionar tres acciones:

  • init: corresponde a la primera solicitud del cliente. El controlador muestra la vista [formulaire.aspx]
  • calcul: corresponde a la solicitud de cálculo del impuesto. Si los datos del formulario de introducción son correctos, el impuesto se calcula mediante la clase de negocio [impotsODBC]. El controlador devuelve al cliente la vista [simulations.aspx] con el resultado de la simulación actual más todas las anteriores. Si los datos del formulario de introducción son incorrectos, el controlador devuelve la vista [erreurs.aspx] con la lista de errores y un enlace para volver al formulario.
  • retorno: corresponde al retorno al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal y como estaba validada antes del error.

En esta nueva versión, solo ha cambiado la acción [calcul]. De hecho, si los datos son válidos, debe conducir a la vista [simulations.aspx], mientras que antes conducía a la vista [formulaire.aspx]. El controlador [main.aspx.vb] queda así:


Imports System
...

Public Class main
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
...
        ' se ejecuta la acción
        Select Case action
            Case "init"
                ' inicializar la aplicación
                initAppli()
            Case "calcul"
                ' cálculo del impuesto
                calculImpot()
            Case "retour"
                ' volver al formulario
                retourFormulaire()
            Case "effacer"
                ' inicialización de la aplicación
                initAppli()
            Case Else
                ' acción desconocida = inicialización
                initAppli()
        End Select
    End Sub

...
    Private Sub calculImpot()
        ' se guardan los datos introducidos
        Session.Item("formulaire") = Request.Form
        ' se comprueba la validez de los datos introducidos
        Dim erreurs As ArrayList = checkData()
        ' si hay errores, se avisa
        If erreurs.Count <> 0 Then
            ' se prepara la página de errores
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' aquí no hay errores: se calcula el impuesto
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' se añade el resultado a las simulaciones existentes
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' se añade la simulación actual
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' se incluyen las simulaciones en la sesión y el contexto
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' se muestra la página de resultados
        context.Items("href") = "main.aspx?action=retour"
        context.Items("lien") = "Retour au formulaire"
        Server.Transfer("simulations.aspx", True)
    End Sub
...
End Class

En lo anterior solo hemos conservado lo necesario para comprender las modificaciones que se encuentran exclusivamente en la función [calculImpots]:

  • en primer lugar, la función guarda el formulario [Request.Form] en la sesión, con el fin de poder regenerar el formulario tal y como quedó validado. Esto hay que hacerlo en todos los casos, ya que, tanto si la operación da como resultado la respuesta [erreurs.aspx] como si da como resultado la respuesta [simulations.aspx], se vuelve al formulario mediante el enlace [Retour au formulaire]. Para recuperarlo correctamente, es necesario haber guardado previamente sus valores en la sesión.
  • Si los datos introducidos son correctos, la función añade la simulación actual (casado, hijos, salario, impuestos) a la lista de simulaciones. Esta se encuentra en la sesión asociada a la clave «simulations».
  • La lista de simulaciones se vuelve a guardar en la sesión para su uso futuro. También se coloca en el contexto actual, ya que es allí donde la espera la vista [simulations.aspx]
  • La vista [simulations.aspx] se muestra una vez que se ha introducido en el contexto el resto de la información que espera

6.4.5. Resumen de los cambios

La aplicación está lista para ser probada. Enumeremos los cambios introducidos con respecto a las versiones anteriores:

  1. se ha creado una nueva vista
  2. Se ha modificado el controlador [main.aspx.vb] en un punto: el tratamiento de la acción [calcul]

6.4.6. Prueba de la aplicación web

Se invita al lector a realizar las pruebas. Recordemos el procedimiento. Todos los archivos de la aplicación se encuentran en una carpeta <application-path>. En esta carpeta se crea una subcarpeta [bin], en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. El archivo [impot.dll] generado por este comando debe colocarse en <ruta-de-la-aplicación>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<ruta-de-la-aplicación>,/impots4).

6.5. Conclusion

Los ejemplos anteriores han mostrado, a partir de un caso concreto, los mecanismos que se utilizan habitualmente en el desarrollo web. Hemos utilizado sistemáticamente la arquitectura MVC por su interés didáctico. Se podrían haber abordado estos mismos ejemplos de otra manera y quizá de forma más sencilla sin esta arquitectura. Sin embargo, esta aporta grandes ventajas en cuanto la aplicación se vuelve un poco compleja y cuenta con múltiples páginas.

Podríamos continuar con nuestros ejemplos de diferentes maneras. He aquí algunas de ellas:

  • el usuario podría querer conservar sus simulaciones a lo largo del tiempo. Realizaría simulaciones un día D y podría recuperarlas el día D+3, por ejemplo. Una posible solución a este problema es el uso de cookies. Sabemos que el token de sesión entre el servidor y un cliente se transmite mediante este mecanismo. También podríamos utilizarlo para transmitir las simulaciones entre el cliente y el servidor.
    • Al mismo tiempo que el servidor envía la página con el resultado de las simulaciones, envía en sus encabezados HTTP una cookie que contiene una cadena de caracteres que representa las simulaciones. Dado que estas se encuentran en un objeto [ArrayList], es necesario realizar una transformación de dicho objeto a [String]. El servidor asignaría a la cookie un periodo de validez, por ejemplo, de 30 días.
    • El navegador del cliente almacena las cookies recibidas en un archivo y las reenvía cada vez que realiza una solicitud al servidor que se las envió, siempre que sigan siendo válidas (que no se haya superado su vida útil). Para las simulaciones, el servidor recibirá una cadena de caracteres [String] que deberá transformar en el objeto [ArrayList].

Las cookies son gestionadas por [Response.Cookies] al enviarlas al cliente y por [Request.Cookies] al recibirlas en el servidor.

  • El mecanismo anterior puede resultar bastante pesado si hay un gran número de simulaciones. Además, es frecuente que un usuario borre periódicamente sus cookies, eliminándolas todas, aunque por lo demás autorice a su navegador a utilizarlas. Por lo tanto, tarde o temprano se perderá la cookie de las simulaciones. Por ello, puede ser conveniente almacenarlas en el servidor en lugar de en el cliente, por ejemplo, en una base de datos. Para vincular las simulaciones a un usuario concreto, la aplicación podría comenzar con una fase de identificación en la que se solicite un nombre de usuario y una contraseña, almacenados a su vez en una base de datos o en cualquier otro tipo de repositorio de datos.
  • También podríamos querer garantizar la seguridad del funcionamiento de nuestra aplicación. Actualmente, esta parte de dos supuestos:
    • el usuario siempre pasa por el controlador [main.aspx]
    • y, en ese caso, siempre utiliza las acciones propuestas en la página que se le ha enviado

¿Qué ocurre, por ejemplo, si el usuario solicita directamente la URL [http://localhost/impots4/formulaire.aspx]? Este caso es poco probable, ya que el usuario desconoce la existencia de esta URL. Sin embargo, hay que tenerlo en cuenta. Puede gestionarse mediante el controlador de aplicación [global.asax], que supervisa todas las solicitudes realizadas a la aplicación. De este modo, puede verificar que el recurso solicitado es efectivamente [main.aspx].

Un caso más probable es que un usuario no utilice las acciones presentes en la página que el servidor le ha enviado. Por ejemplo, ¿qué ocurre si el usuario solicita directamente la URL [http://localhost/impots4/main.aspx?action=retour] sin haber rellenado previamente el formulario? Probémoslo. Obtenemos la siguiente respuesta:

Image

Se ha producido un fallo en el servidor. Es normal. Para la acción [retour], el controlador espera encontrar en la sesión un objeto [NameValueCollection] que represente los valores del formulario que debe mostrar. No los encuentra. El mecanismo del controlador permite ofrecer una solución elegante a este problema. Para cada solicitud, el controlador [main.aspx] puede comprobar que la acción solicitada es, efectivamente, una de las acciones de la página enviada previamente al usuario. Se puede utilizar el siguiente mecanismo:

  • el controlador, antes de enviar su respuesta al cliente, almacena en la sesión de este una información que identifica dicha página
  • cuando recibe una nueva solicitud del cliente, comprueba que la acción solicitada pertenece efectivamente a la última página enviada a dicho cliente
  • la información que vincula las páginas y las acciones autorizadas en dichas páginas puede introducirse en el archivo de configuración [web.config] de la aplicación.
  • La experiencia demuestra que los controladores de aplicaciones tienen una amplia base común y que es posible crear un controlador genérico, cuya especialización para una aplicación concreta se realiza mediante un archivo de configuración. Este es el enfoque que sigue, por ejemplo, la herramienta [Struts] en el ámbito de la programación web en Java.