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

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:
tabla de límites de tramos impositivos | |
tabla de coeficientes aplicados a los ingresos imponibles | |
tabla de coeficientes aplicados al número de participaciones | |
valor booleano que indica si se han verificado los datos (límites, coeffr, coeffn) | |
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>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]:

El usuario completa el formulario:

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

Puede cometer un error al ingresar los datos:

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

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

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:
"checked" si se debe marcar la casilla [oui], "" en caso contrario | |
lo mismo para la casilla [non] | |
valor que se debe ingresar en el campo de entrada [txtEnfants] | |
valor que se debe ingresar en el campo de entrada [txtSalaire] | |
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:
código HTML de una lista de errores | |
URL de un enlace | |
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:
objeto ArrayList que contiene la lista de mensajes de error que se deben mostrar | |
URL de un enlace | |
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:

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]
[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]
[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>.

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]:

Llenamos el formulario:

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

Luego ingresamos datos erróneos:

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

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

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

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:

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:

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).

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:
- las clases SQL y Server.NET, para acceder a las bases de datos de Microsoft SQL Server
- las clases Ole Db.NET, para acceder a las bases de datos de SGBD que ofrecen un controlador OLE DB
- 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:

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:

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]:

Se debe proporcionar cierta información:
el nombre que identificará la fuente de datos ODBC. Cualquier aplicación de Windows podrá acceder a la fuente mediante este nombre | |
un texto arbitrario que describa la fuente de datos | |
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. | |
Un SGBD MySQL puede administrar varias bases de datos. Aquí se especifica cuál se desea administrar: dbimpots | |
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 | |
la contraseña de este usuario. En este caso: mdpimpots | |
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]:

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:

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

La información que se debe proporcionar es la siguiente:
el nombre que identificará la fuente de datos ODBC. Cualquier aplicación de Windows podrá acceder a la fuente mediante este nombre | |
un texto arbitrario que describa la fuente de datos | |
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:

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:
- 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:
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:
- se ha creado una nueva clase de acceso a datos
- 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
- se ha modificado el controlador [main.aspx.vb] en un punto para mostrar el mensaje de excepción anterior
- 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>.

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:

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:

6.3.3. La clase de acceso a los datos
Volvamos a la estructura MVC de nuestra aplicación:

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

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:

Ahora, creemos un nuevo archivo .aspx mediante [Files/New File]:

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

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

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

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
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:
- se ha creado una nueva clase de acceso a datos
- se ha modificado el controlador [global.asax.vb] en un punto: creación del objeto [impot]
- 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>.

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:

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

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:

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:

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:

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:
código HTML de una lista de simulaciones en forma de filas de tabla HTML | |
URL de un enlace | |
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:
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. | |
URL de un enlace | |
Texto del enlace |
6.4.4. Los controladores [global.asax, main.aspx]
Recordemos el esquema MVC de nuestra 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 [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:
- se ha creado una nueva vista
- 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:

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.



