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. Consideramos el caso simplificado de un contribuyente que solo tiene que declarar su salario (cifras de 2004 para ingresos de 2003):

  • se calcula el número de partes 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, cuenta con media parte adicional
  • se calcula su ingreso 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 entrada son correctos, el impuesto se calcula mediante la clase de negocio [impots]. El controlador devuelve al cliente la vista [formulaire.aspx] tal como se había validado, además del impuesto calculado. Si los datos del formulario de entrada son incorrectos, el controlador devolverá la vista [erreurs.aspx] con la lista de errores y un enlace para regresar al formulario.
  • retorno: corresponde al regreso al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal como fue validada 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 calcular el impuesto
             ' provienen de una fuente externa [source]
             ' se obtienen de ahí; 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 renta imponible y el 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 cuenta con 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 manejar una excepción si no se pudieron obtener los datos o si estos resultan incorrectos. Una vez creado el objeto [impot], se puede llamar repetidamente a su método «calcular», el cual calcula el impuesto del contribuyente a partir de su estado civil (casado o soltero), su 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. Se debe 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 deben estar llenas
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' cada matriz debe contener elementos >=0 y en orden creciente
            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

        ' verifica 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 creciente
            If tableau(0) < 0 Then Return False
            For i As Integer = 1 To n - 1
                If tableau(i) <= tableau(i - 1) Then Return False
            Next
            ' está bien
            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 los ingresos imponibles
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. Cuenta con 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, coeffn
  • lanzar una excepción si no se pudieron obtener los datos o si estos resultaron inválidos.

La clase proporciona los métodos protegidos [checkData] y [check] que verifican la validez de los atributos (limites, coeffr, coeffn). Esto exime a las clases derivadas de 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 las tablas 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 entrada
        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 verifican los datos si es necesario
            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 + ")")
            ' de lo contrario, se devuelven los tres arreglos
            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 tablas codificadas de forma «fija»
  • un constructor que inicializa los atributos (límites, coeffr, coeffn) de la clase base con tablas que se le pasan como parámetros

El método [getData], que permitirá a las clases externas obtener los arreglos (limites, coeffr, coeffn), se limita a verificar la validez de los tres arreglos 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 que hayan sido certificadas como correctas. De esta manera, la fase de depuración de la aplicación web podrá centrarse en la parte del controlador y las vistas. Un programa de prueba 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 ingresa tres datos con el teclado: casado nbEnfants salario
             ' el programa muestra entonces 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 algo que hacer?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' verificación del número de argumentos en la línea ingresada
                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
                ' verificació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 le pide al usuario que ingrese con el teclado los tres datos necesarios para calcular su impuesto:

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

El cálculo del impuesto 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, el cual proporciona las tres tablas (límites, coeffr, coeffn) con valores fijos. La creación de un objeto [impot] podría, en teoría, generar 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 esa obtención de datos podría lanzar una excepción. Resulta que, en este caso, el método de obtención de datos (código fijo) no puede provocar una excepción. Sin embargo, 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]. Veamos cómo funciona la aplicación con algunas capturas de pantalla. La vista [formulaire.aspx] se muestra cuando se solicita por primera vez la URL [main.aspx]:

Image

El usuario completa el formulario:

Image

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

Image

Puede cometer un error al ingresar los datos:

Image

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

Image

Puede usar el enlace [Retour au formulaire] anterior para volver a la vista [formulaire.aspx] tal 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 se debe marcar la casilla [oui], "" en caso contrario
rdnonchecked
lo mismo para la casilla [non]
txtEnfants
valor que se debe ingresar en el campo de entrada [txtEnfants]
txtSalaire
valor que se debe ingresar en el campo de entrada [txtSalaire]
txtImpot
valor que se debe ingresar 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 observa 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 que el controlador coloca en el contexto de la página:

  • Context.Items («formulario»): diccionario de tipo NameValueCollection que contiene los valores de los campos HTML y [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 ocurrir durante el ciclo de vida 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 son calculados por 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 la aplicación

El controlador [main.aspx] debe procesar 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 entrada son correctos, el impuesto se calcula mediante la clase de negocio [impots]. El controlador devuelve al cliente la vista [formulaire.aspx] tal como había sido validada, además del impuesto calculado. Si los datos del formulario de entrada son incorrectos, el controlador devuelve la vista [erreurs.aspx] con la lista de errores y un enlace para regresar al formulario.
  • retorno: corresponde al regreso al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal como fue validada antes del error.

Además, sabemos que toda solicitud dirigida a la aplicación pasa por el controlador [global.asax], si existe. Por lo tanto, 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 administrar una sesión. Ya vimos 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 diagrama MVC anterior muestra que necesitaremos crear un objeto [impot] para calcular el impuesto. No es necesario crearlo varias veces; una vez es suficiente. 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 produjo un error; se registra 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, manejamos la posible excepción e insertamos una clave [erreur] en la aplicación para indicar si hubo 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 que nada, se verifica si la aplicación se ha podido inicializar 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 a realizar
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' ejecutamos la acción
        Select Case action
            Case "init"
                ' inicializar la aplicación
                initAppli()
            Case "calcul"
                ' cálculo de impuestos
                calculImpot()
            Case "retour"
                ' regreso al formulario
                retourFormulaire()
            Case "effacer"
                ' inicializar aplicación
                initAppli()
            Case Else
                ' acción desconocida = iniciar
                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 verifica la validez de los datos ingresados
        Dim erreurs As ArrayList = checkData()
        ' si hay errores, se avisa
        If erreurs.Count <> 0 Then
            ' se guardan los datos ingresados
            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
        ' 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 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 inicio 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 verificar que la aplicación se haya inicializado correctamente:


         ' antes que nada, se verifica 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 pudo inicializarse correctamente (no se pudo crear el objeto [impot] necesario para el cálculo), entonces 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 la acción que se le solicita ejecutar a través del parámetro [action]. Ya nos hemos encontrado con este modo de funcionamiento en numerosas ocasiones. El procesamiento de cada tipo de acción se delega a una función.

6.1.9.1. Las acciones init, borrar

Estas dos acciones deben mostrar el formulario de entrada en blanco. Recordemos que este (véase «vistas») tiene dos parámetros:

  • Context.Items("formulario"): diccionario de tipo [NameValueCollection] que contiene los valores de los campos HTML y [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 calcula

Esta acción debe calcular el impuesto a pagar a partir de los datos ingresados en el formulario y devolverlo ya prellenado con los valores ingresados y, además, con el monto del impuesto calculado. La función [calculImpot], encargada de esta tarea, comienza por verificar 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 ingresados resultan inválidos, el controlador muestra la vista [erreurs.aspx], tras haber establecido previamente en el contexto los valores que esta espera:

  • los mensajes de error se colocan en un objeto [ArrayList], el cual se coloca posteriormente 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 ingresados 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 es útil verificar que los campos de [rdMarie, txtEnfants, txtSalaire] estén presentes en la solicitud enviada por el cliente. Esto es innecesario si estamos seguros de que nuestro cliente es un navegador que ha recibido la vista [formulaire.aspx], la cual contiene dichos campos. Sin embargo, 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. Consultaremos a la aplicación sin enviar los campos que espera y veremos cómo reacciona. Esta es 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 le envíe cadenas de parámetros inesperadas. Debe comportarse correctamente en todos los casos.

En nuestro caso, verificamos que los campos [rdMarie, txtEnfants, txtSalaire] estuvieran presentes en la solicitud, pero no que esta pudiera contener otros. En esta aplicación, dichos campos se ignorarían. Sin embargo, siempre por motivos de seguridad, sería conveniente registrar este tipo de solicitud en un archivo de registro 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. Luego almacena en el contexto la información que espera la vista [formulaire.aspx]:

  • Context.Items("formulario"): diccionario de tipo [NameValueCollection] que contiene los valores de los campos HTML, [rdmarie,txtEnfants,txtSalaire], aquí [Request.Form)] y c.a.d. los valores ingresados previamente 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 entre todas las consultas, ¿no podrían producirse conflictos de acceso que provoquen 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 partes
            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 los ingresos imponibles 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 agregado 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, después de 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]? Dado que esta variable es local a un método, se almacena en una estructura de memoria llamada pila. Esta pila forma parte del contexto del hilo, el cual se guarda cuando este se interrumpe. Cuando el hilo 2 se inicia, su contexto se establece con una nueva pila y, por lo tanto, una nueva variable local [impot]. Cuando el hilo 2 sea interrumpido a su vez, su contexto también se guardará. Al reiniciarse el hilo 1, se restaura su contexto, incluida su pila. De este modo, 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 confirmaron 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 regresar a la vista [formulaire.aspx], prellenada con los valores ingresados previamente 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 ingresados 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). Con un navegador, solicitamos la URL [http://localhost/impots1/main.aspx]:

Image

Llenamos el formulario:

Image

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

Image

Luego ingresamos datos erróneos:

Image

Al presionar el botón [Calculer], se obtiene la siguiente respuesta:

Image

Al hacer clic en el enlace [Retour au formulaire], volvemos al formulario tal 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 otros clientes además de los navegadores. Si se envía a un navegador un formulario con parámetros que deben enviarse al servidor al validarlo, el navegador reenviará los valores de esos 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. El servidor debe saber qué hacer en ese caso. Otro ejemplo son las validaciones de entrada que se realizan 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 sentado que recibiremos 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 inválidos al servidor, con lo que la aplicación web correría el riesgo de comportarse de manera inesperada. Ilustraremos estos puntos utilizando el cliente [curl].

En primer lugar, solicitamos la URL [http://localhost/impots1/main.aspx]:

dos>curl --include --url http://localhost/impuestos1/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 envió 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/impuestos1/main.aspx?action=cálculo 

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


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

Podemos observar que la aplicación web devolvió la vista [erreurs] con tres mensajes de error por los tres parámetros que faltaban. Ahora enviemos parámetros incorrectos:

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 detectaron 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/impuestos1/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 debemos dejarnos engañar por el hecho de que estemos desarrollando 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 hacemos ninguna suposición sobre el cliente y verificamos que los parámetros esperados en la solicitud estén presentes y sean válidos
  • se elabora una respuesta dirigida 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 celulares. 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 celular enviará 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 le concierne al cliente. El bloque [Contrôleur] cambia ligeramente, pero debe tomar en cuenta un nuevo parámetro en la solicitud: el parámetro [client], que indica con qué tipo de cliente se está tratando. El bloque [vues] debe generar vistas para cada tipo de cliente. Podría ser interesante tomar 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 mediano plazo sea solo los navegadores. Si la aplicación debe 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 escritas de manera «estática» 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 llamada [IMPOTS] de una base de datos MySQL llamada [dbimpots]. El contenido de esta tabla será el siguiente:

Image

El propietario de la base de datos es el usuario [admimpots], con la contraseña [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 de Windows. Para acceder a ellas, las aplicaciones utilizan programas llamados 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 esfuerzo de estandarización en la interfaz I1. Si se utilizan bases de datos que emplean controladores «normalizados», la base B1 se proporcionará 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 esta manera, 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, etc. No hay límite. Cualquier fuente de datos puede ser objeto de un controlador OLE DB si así lo decide un desarrollador. El beneficio es, evidentemente, enorme: se cuenta con 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 DB
  3. las clases odbc.net, para acceder a las bases de datos de SGBD que ofrecen un controlador ODBC

La clase SGBD MySQL cuenta desde hace tiempo con un controlador ODBC. Este es el que utilizamos ahora. En Windows, elegimos 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 cualquier usuario de la máquina podrá acceder (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 usaremos para crear una nueva fuente de datos ODBC:

Image

El asistente solicita que se seleccione el controlador ODBC que se va a utilizar. Windows incluye 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 motor de búsqueda. En este caso, hemos instalado el controlador [MySQL ODBC 3.51]. Lo seleccionamos y hacemos [Terminer]:

Image

Se debe proporcionar cierta información:

Data Source Name
el nombre que 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 de la máquina que aloja el SGBD MySQL. En este caso, se trata de la máquina local. Podría ser una máquina remota. Esto permitiría que una aplicación de Windows accediera a una base de datos remota sin necesidad de ninguna codificación especial. Este es uno de los grandes beneficios de la fuente ODBC.
Database Name
Un SGBD MySQL puede administrar varias bases de datos. Aquí se especifica cuál se desea administrar: 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 podemos utilizarla. Repetimos [OK] tantas veces como sea necesario para salir del asistente ODBC.

Si el lector no cuenta con el SGBD mySQL, puede obtenerlo de forma gratuita en la URL [http://www.mysql.com]. A continuación, presentamos los pasos para crear una fuente de datos ODBC con Access. Los primeros pasos son idénticos a los descritos anteriormente. Se agrega 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

La información que se debe proporcionar es la siguiente:

Nom de la source de données
el nombre que 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
Base de données
El nombre completo del archivo ACCESS que se va a utilizar

6.2.3. Una nueva clase de acceso a datos

Volvamos a la estructura MVC de nuestra aplicación:

Image

En el diagrama 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 adaptarla 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
            ' verifica los datos obtenidos
...
        End Function

        ' verifica 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 obtendrá 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 registran 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 colocan en las tablas
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' Se liberan los 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 verifican 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

Veamos quién es el fabricante:


        ' constructor
        Public Sub New(ByVal DSNimpots As String)
            ' se registran 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 deben obtener. El constructor simplemente almacena 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). Analicemos su código:

  • los parámetros de la conexión a la fuente de datos ODBC están definidos, 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 try/catch para manejar 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()
  • ejecutamos el comando [select] en la conexión abierta. Obtenemos 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 hace dos cosas:
    • avanza una fila en la tabla. Al inicio, estamos posicionados antes de la primera fila
    • devuelve el valor booleano [true] si ha podido avanzar, y [false] en caso contrario; este último caso indica que ya se han procesado todas las líneas.

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


                 ' Se procesa la tabla recuperada
                While myReader.Read()
                    ' los datos de la fila actual se colocan 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:
                 ' Se liberan los recursos
                myReader.Close()
                impotsConn.Close()
  • El contenido de los tres objetos [ArrayList] se transfiere a tres tablas convencionales:

             ' 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 hayan transferido a las tres tablas, solo queda verificar su contenido mediante el método [checkData] de la clase base [impotsData]:
             ' Se verifican 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 ingresa tres datos por teclado: casado nbEnfants salario
             ' el programa muestra entonces 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"

             ' verificació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 inicio 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

                 ' verificación del número de argumentos en la línea ingresada
                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
                     ' Verificació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 le pide repetidamente al usuario que ingrese con el teclado los tres datos necesarios para calcular su impuesto:

  • su estado civil: o para casado, n para soltero
  • su 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 su vez, 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 se debe 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 incorpora el objeto a la aplicación
            Application("objImpot") = objImpot
            ' sin errores
            Application("erreur") = False
        Catch ex As Exception
            'se produjo un error; se registra en la aplicación
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

La fuente de datos del objeto [impot] ahora es 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 estática en el código, lo colocamos 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 para que quede disponible para futuras 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 que nada, se verifica si la aplicación se ha podido inicializar 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 a realizar
...

6.2.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 modificó el controlador [global.asax.vb] en dos lugares: 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 agregado un archivo [web.config]

El trabajo de modificación se realizó 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 incluir en el archivo de configuración el nombre de la clase de acceso a datos que se 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 datos. Cambiar la fuente de datos equivale entonces a:

  • crear la clase de acceso a esa 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 <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, 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. Sin embargo, para ilustrar esta presencia, nos aseguramos de que la fuente ODBC no esté disponible al detener 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 nuevamente 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 diagrama anterior, la clase [impotsData] se encarga de recuperar los datos. En esta ocasión, deberá hacerlo de una fuente OLEDB.

Creamos la clase [impotsOLEDB], que obtendrá los datos (límites, coeffr, coeffn) de 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 registran los tres datos
            Me.chaineConnexion = chaineConnexion
        End Sub

        Public Overrides Function getData() As Object()
            ' inicializa los tres arreglos límites, 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 generar 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
            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 fila
                    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 verifican 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 los tres arreglos
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Veamos quién es el fabricante:


         ' constructor
        Public Sub New(ByVal chaineConnexion As String)
             ' se registran 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 deben adquirir. El controlador simplemente la almacena. 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, se puede utilizar la herramienta [WebMatrix]. Se inicia esta herramienta. Ofrece una ventana que permite conectarse a una fuente de datos:

Gracias al ícono 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 íconos que representan la conexión:

Image

Ahora, creemos 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 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 es simplemente 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). Analicemos 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 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 esta 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. Manejamos una excepción que puede ocurrir 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 estas 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 cuenta con 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 fila
                    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 hayan transferido a las tres tablas, solo queda verificar su contenido mediante el método [checkData] de la clase base [impotsData]:
            ' se verifican 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 los tres arreglos
            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 ingresa tres datos por teclado: casado nbEnfants salario
             ' el programa muestra entonces 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"

             ' verificació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

                 ' verificación del número de argumentos en la línea ingresada
                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
                     ' Verificació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:

  • 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 generó a partir de la información obtenida con [WebMatrix].

Una vez inicializada, la aplicación le pide repetidamente al usuario que ingrese en el teclado los tres datos necesarios para calcular su impuesto:

  • su estado civil: o para casado, n para soltero
  • su 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 se debe 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 produjo un error; se registra en la aplicación
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

La fuente de datos del objeto [impot] ahora es 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. Esta 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 agregado 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 <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>,/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 presentará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 cual 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 monto del impuesto ya no aparece en esta vista. Ahora se encuentra en la vista [simulations.aspx]. Así, al iniciar, la página que se muestra al usuario es la siguiente:

Image

Además, la vista [formulaire] incluye un script de JavaScript que verifica la validez de los datos ingresados antes de enviarlos al servidor, como se muestra en 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(){
          // verificación de 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 verificado 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){
            // el modelo no se ha verificado
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//si
          // Todo está bien: 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 monto 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 de JavaScript [calculer] utiliza expresiones regulares para verificar la validez de los campos de los formularios [document.frmImpots.txtEnfants] y [document.frmImpots.txtSalaire]. Si los valores ingresados 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
        ' recuperamos la solicitud anterior del contexto
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' preparamos 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] se presenta visualmente de la siguiente manera:

Image

y corresponde al 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 deben 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 procesar 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 entrada 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 entrada son incorrectos, el controlador devuelve la vista [erreurs.aspx] con la lista de errores y un enlace para regresar al formulario.
  • retorno: corresponde al regreso al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal como fue 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 anteriormente 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 que nada, se verifica si la aplicación se ha podido inicializar correctamente
...
        ' se ejecuta la acción
        Select Case action
            Case "init"
                ' inicializar aplicación
                initAppli()
            Case "calcul"
                ' cálculo del impuesto
                calculImpot()
            Case "retour"
                ' regreso al formulario
                retourFormulaire()
            Case "effacer"
                ' inicializar aplicación
                initAppli()
            Case Else
                ' acción desconocida = inicialización
                initAppli()
        End Select
    End Sub

...
    Private Sub calculImpot()
        ' se guardan los datos ingresados
        Session.Item("formulaire") = Request.Form
        ' se verifica la validez de los datos ingresados
        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 agrega 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 agrega 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 colocan 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

Arriba solo hemos incluido lo necesario para entender los cambios que se encuentran únicamente 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 en el estado en que fue validado. Esto debe hacerse en todos los casos, ya que, independientemente de si la operación da como resultado la respuesta [erreurs.aspx] o la respuesta [simulations.aspx], se regresa al formulario mediante el enlace [Retour au formulaire]. Para restaurarlo correctamente, es necesario haber guardado previamente sus valores en la sesión.
  • Si los datos ingresados resultan correctos, la función agrega 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 uso futuro. También se coloca en el contexto actual, ya que es ahí donde la espera la vista [simulations.aspx]
  • La vista [simulations.aspx] se muestra una vez que se ha incorporado al 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 realizados con respecto a las versiones anteriores:

  1. se ha creado una nueva vista
  2. Se modificó el controlador [main.aspx.vb] en un punto: el procesamiento 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 <ruta-de-la-aplicación>. 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, en un caso concreto, los mecanismos que se utilizan habitualmente en el desarrollo web. Hemos utilizado sistemáticamente la arquitectura MVC por su valor educativo. Se podrían haber abordado estos mismos ejemplos de manera diferente y tal vez más sencilla sin esta arquitectura. Sin embargo, esta ofrece grandes ventajas en cuanto la aplicación se vuelve un poco compleja, 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 este objeto a [String]. El servidor asignaría a la cookie un tiempo de vida, por ejemplo, 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 y cuando aún sean válidas (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 administradas por [Response.Cookies] al enviarlas al cliente y por [Request.Cookies] al recibirlas en el servidor.

  • El mecanismo anterior puede volverse bastante pesado si hay un número importante 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 lo tanto, tal vez sea conveniente almacenarlas en el servidor en lugar de en el dispositivo del usuario, por ejemplo, en una base de datos. Para vincular las simulaciones a un usuario específico, la aplicación podría comenzar con una fase de identificación que solicite un nombre de usuario y una contraseña, los cuales a su vez se almacenan en una base de datos o en cualquier otro tipo de repositorio de datos.
  • También podríamos querer asegurar el 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 que se le ofrecen en la página que se le ha enviado

¿Qué sucede, 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, debe preverse. Puede gestionarse mediante el controlador de aplicación [global.asax], que procesa todas las solicitudes realizadas a la aplicación. De esta manera, puede verificar que el recurso solicitado sea efectivamente [main.aspx].

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

Image

Se ha producido una falla 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 verificar que la acción solicitada sea 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, verifica que la acción solicitada pertenezca efectivamente a la última página enviada a ese cliente
  • la información que vincula las páginas y las acciones autorizadas en ellas 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 específica 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.