6. Ejemplos
En este capítulo nos proponemos ilustrar lo visto anteriormente mediante una serie de ejemplos.
6.1. Ejemplo 1
6.1.1. El problema
Esta aplicación debe permitir a un usuario calcular sus impuestos. Nos situamos en el caso simplificado de un contribuyente que solo tiene que declarar su salario (cifras de 2004 correspondientes a los ingresos de 2003):
- se calcula el número de participaciones del empleado nbParts = nbEnfants/2 + 1 si no está casado, nbEnfants/2 + 2 si está casado, donde nbEnfants es el número de hijos que tiene.
- si tiene al menos tres hijos, tiene media parte más
- se calcula su base imponible R = 0,72 * S, donde S es su salario anual
- se calcula su coeficiente familiar QF = R / nbParts
- Se calcula su impuesto I. Consideremos la siguiente tabla:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14 753 | 0,1914 | 1322,92 |
23 888 | 0,2826 | 2668,39 |
38 868 | 0,3738 | 4846,98 |
47 932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Cada línea tiene 3 campos. Para calcular el impuesto I, se busca la primera línea en la que QF sea menor o igual que el campo 1. Por ejemplo, si QF = 5000, se encontrará la línea
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 introducción son correctos, el impuesto se calcula mediante la clase de negocio [impots]. El controlador devuelve al cliente la vista [formulaire.aspx] tal y como se había validado, además del impuesto calculado. Si los datos del formulario de introducción son incorrectos, el controlador devolverá la vista [erreurs.aspx] con la lista de errores y un enlace para volver al formulario.
- retorno: corresponde al retorno al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal y como se validó antes del error.
El controlador [main.aspx] no tiene conocimiento alguno sobre el cálculo de impuestos. Simplemente se encarga de gestionar el diálogo cliente-servidor y de ejecutar las acciones solicitadas por el cliente. Para la acción [calcul], se basará en la clase de negocio [impot].
6.1.3. La clase de negocio
La clase de impuestos se definirá de la siguiente manera:
' espacios de nombres importados
Imports System
' clase
Namespace st.istia.univangers.fr
Public Class impot
Private limites(), coeffR(), coeffN() As Decimal
' constructor
Public Sub New(ByRef source As impotsData)
' los datos necesarios para el cálculo del impuesto
' proceden de una fuente externa [source]
' se recogen; puede haber una excepción
Dim data() As Object = source.getData
limites = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' cálculo del impuesto
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' cálculo del número de participaciones
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' cálculo de la base imponible y del coeficiente familiar
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' cálculo del impuesto
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
End Namespace
Se crea un objeto de impuestos proporcionando a su constructor una fuente de datos de tipo [impotsData]. Esta clase dispone de un método público [getData] que permite obtener las tres tablas de datos necesarias para el cálculo del impuesto y que se han presentado anteriormente. Este método puede gestionar una excepción si no se han podido obtener los datos o si estos resultan ser incorrectos. Una vez creado el objeto [impot], se puede llamar repetidamente a su método «calcular», que calcula el impuesto del contribuyente a partir de su estado civil (casado o soltero), el número de hijos y su salario anual.
6.1.4. La clase de acceso a los datos
La clase [impotsData] es la clase que permite acceder a los datos. Se trata de una clase abstracta. Es necesario crear una clase derivada para cada nueva fuente de datos posible (tablas, archivos planos, bases de datos, consola, etc.). Su definición es la siguiente:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' método de acceso a los datos
Public MustOverride Function getData() As Object()
' método de verificación de datos
Protected Function checkData() As Integer
' Verifica los datos obtenidos
' debe haber datos
valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valide Then Return 1
' debe haber 3 tablas del mismo tamaño
If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
If Not valide Then Return 2
' las tablas no deben estar vacías
valide = limites.Length <> 0
If Not valide Then Return 3
' cada matriz debe contener elementos >=0 y en orden ascendente
valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valide Then Return 4
' todo está bien
Return 0
End Function
' comprueba la validez del contenido de una matriz
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
' la matriz debe tener sus primeros n elementos >=0 y en orden estrictamente ascendente
If tableau(0) < 0 Then Return False
For i As Integer = 1 To n - 1
If tableau(i) <= tableau(i - 1) Then Return False
Next
' correcto
Return True
End Function
End Class
End Namespace
La clase tiene los siguientes atributos protegidos:
tabla de límites de tramos impositivos | |
tabla de coeficientes aplicados a la base imponible | |
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. Tiene un método abstracto [getData] que las clases derivadas deberán implementar. La función de este método es:
- asignar valores a los tres arreglos «límites», «coeffr» y «coeffn»
- lanzar una excepción si no se han podido obtener los datos o si estos resultan inválidos.
La clase proporciona los métodos protegidos [checkData] y [check], que comprueban la validez de los atributos (limites, coeffr, coeffn). Esto exime a las clases derivadas de tener que implementarlos. Solo tendrán que utilizarlos.
La primera clase derivada que utilizaremos será la siguiente:
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsArray
Inherits impotsData
' constructor sin argumentos
Public Sub New()
' Inicialización de matrices con constantes
limites = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
checked = True
valide = True
End Sub
' constructor con tres matrices como argumentos
Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
' se almacenan los datos
Me.limites = limites
Me.coeffr = coeffr
Me.coeffn = coeffn
checked = False
End Sub
Public Overrides Function getData() As Object()
' se comprueban los datos, si procede
Dim erreur As Integer
If Not checked Then erreur = checkData() : checked = True
' si no son válidos, se lanza una excepción
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' en caso contrario, se devuelven los tres tableros
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Esta clase, denominada [impotsArray], tiene dos constructores:
- un constructor sin argumentos que inicializa los atributos (límites, coeffr, coeffn) de la clase base con matrices codificadas de forma «fija»
- un constructor que inicializa los atributos (límites, coeffr, coeffn) de la clase base con matrices que se le pasan como parámetros
El método [getData], que permitirá a las clases externas obtener las matrices (limites, coeffr, coeffn), se limita a comprobar la validez de las tres matrices mediante el método [checkData] de la clase base. Lanza una excepción si los datos no son válidos.
6.1.5. Pruebas de las clases de negocio y de acceso a datos
Es importante incluir en una aplicación web únicamente clases de negocio y de acceso a datos cuya corrección haya sido certificada. De este modo, la fase de depuración de la aplicación web podrá centrarse en la parte del controlador y las vistas. Un programa de pruebas podría ser el siguiente:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' programa interactivo de cálculo de impuestos
' el usuario introduce tres datos mediante el teclado: casado nbEnfants salario
' a continuación, el programa muestra el impuesto a pagar
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' creación de un objeto de impuestos
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' bucle infinito
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' se solicitan los parámetros para el cálculo del impuesto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' ¿Hay que hacer algo?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' Comprobación del número de argumentos en la línea introducida
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' comprobación de la validez de los parámetros
If Not erreur Then
' casado
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' salario
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' si los parámetros son correctos, se calcula el impuesto
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " euro(s)"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
End Namespace
La aplicación pide al usuario que introduzca mediante el teclado los tres datos necesarios para calcular sus impuestos:
- su estado civil: «o» para casado, «n» para soltero
- el número de hijos
- su salario anual
El cálculo de los impuestos se realiza mediante un objeto de tipo [impot] que se crea al iniciar la aplicación:
' creación de un objeto de impuesto
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
Como fuente de datos se utiliza un objeto de tipo [impotsArray]. Se utiliza el constructor sin argumentos de esta clase, que proporciona las tres matrices (límites, coeffr, coeffn) con valores fijos. La creación de un objeto [impot] puede, en teoría, provocar una excepción, ya que, para crearse, el objeto solicitará los datos (límites, coeffr, coeffn) a su fuente de datos, que se le ha pasado como parámetro, y esta obtención de datos puede lanzar una excepción. Resulta que, en este caso, el método de obtención de datos (código fijo) no puede provocar ninguna excepción. No obstante, hemos dejado la gestión de la misma para llamar la atención del lector sobre la posibilidad de que el objeto [impot] se construya incorrectamente.
A continuación se muestra un ejemplo de ejecución del programa anterior:
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
Compilamos todas las clases [impot, impotsData, impotsArray] en un ensamblado [impot.dll]:
dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
21/04/2004 09:21 5 632 impot.dll
Compilamos el programa de prueba:
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
21/04/2004 09:21 5 632 impot.dll
21/04/2004 09:23 4 608 testimpots1.exe
Podemos realizar las pruebas:
dos>testimpots1
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 60000
impôt=6872 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :
6.1.6. Las vistas de la aplicación web
La aplicación tendrá dos vistas: [formulaire.aspx] y [erreurs.aspx]. Ilustremos el funcionamiento de la aplicación mediante capturas de pantalla. La vista [formulaire.aspx] se muestra cuando se solicita por primera vez la URL [main.aspx]:

El usuario rellena el formulario:

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

Puede cometer un error al introducir los datos:

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

Puede utilizar el enlace [Retour au formulaire] anterior para volver a la vista [formulaire.aspx] tal y 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 la casilla [oui] debe marcarse, «» en caso contrario | |
lo mismo para la casilla [non] | |
valor que debe introducirse en el campo de entrada [txtEnfants] | |
valor que debe introducirse en el campo de entrada [txtSalaire] | |
valor que debe introducirse en el campo de entrada [txtImpot] |
La página tiene dos formularios, cada uno con un botón [submit]. El botón [Calculer] es el botón [submit] del siguiente formulario:
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
Se puede observar que los parámetros del formulario se enviarán al controlador con [action=calcul]. El botón [Effacer] es el botón [submit] del siguiente formulario:
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
Se observa que los parámetros del formulario se enviarán al controlador con [action=effacer]. En este caso, el formulario no tiene ningún parámetro. Lo único que importa es la acción.
Los campos de [formulaire.aspx] se calculan mediante [formulaire.aspx.vb]:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' campos de la página
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' se recupera la consulta anterior del contexto
Dim form As NameValueCollection = Context.Items("formulaire")
' se prepara la página para mostrarla
' botones de opción
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' el resto
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
txtImpot = CType(Context.Items("txtImpot"), String)
End Sub
End Class
El cálculo de los campos de [main.aspx] se realiza a partir de dos datos introducidos por el controlador en el contexto de la página:
- Context.Items («formulario»): diccionario de tipo NameValueCollection que contiene los valores de los campos HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot"): valor del impuesto
6.1.8. La vista [erreurs.aspx]
La vista [erreurs.aspx] es la que muestra los posibles errores que puedan producirse durante el funcionamiento de la aplicación. Su código de presentación es el siguiente:
<%@ page src="erreurs.aspx.vb" inherits="erreurs" AutoEventWireup="false"%>
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<%=erreursHTML%>
</ul>
<a href="<%=href%>">
<%=lien%>
</a>
</body>
</HTML>
La página tiene tres campos dinámicos:
código HTML de una lista de errores | |
URL de un enlace | |
Texto del enlace |
Estos campos los calcula la parte de control de la página en [erreurs.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class erreurs
Inherits System.Web.UI.Page
' parámetro de página
Protected erreursHTML As String = ""
Protected href As String
Protected lien As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' se recuperan los elementos del contexto
Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
href = context.Items("href").ToString
lien = context.Items("lien").ToString
' se genera el código HTML de la lista
Dim i As Integer
For i = 0 To erreurs.Count - 1
erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
End Sub
End Class
El controlador de la página recupera la información que el controlador de la aplicación ha colocado en el contexto de la página:
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 aplicación
El controlador [main.aspx] debe gestionar tres acciones:
- init: corresponde a la primera solicitud del cliente. El controlador muestra la vista [formulaire.aspx]
- calcul: corresponde a la solicitud de cálculo del impuesto. Si los datos del formulario de introducción son correctos, el impuesto se calcula mediante la clase de negocio [impots]. El controlador devuelve al cliente la vista [formulaire.aspx] tal y como se había validado, además del impuesto calculado. Si los datos del formulario de introducción son incorrectos, el controlador devuelve la vista [erreurs.aspx] con la lista de errores y un enlace para volver al formulario.
- retorno: corresponde al retorno al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal y como se validó antes del error.
Por otra parte, sabemos que toda solicitud dirigida a la aplicación pasa por el controlador [global.asax], si existe. Así pues, en la entrada de la aplicación tenemos una cadena de dos controladores:
- [global.asax], que, debido a la arquitectura de ASP.NET, recibe todas las solicitudes dirigidas a la aplicación
- [main.aspx], que, por decisión del desarrollador, también recibe todas las solicitudes dirigidas a la aplicación
La necesidad de [main.aspx] se debe a que tendremos que gestionar una sesión. Ya hemos visto que [global.asax] no era adecuado como controlador en este caso. Aquí podríamos prescindir por completo de [global.asax]. Sin embargo, lo utilizaremos para ejecutar código al iniciar la aplicación. El esquema MVC anterior muestra que necesitaremos crear un objeto [impot] para calcular el impuesto. No es necesario crearlo varias veces, con una vez basta. Por lo tanto, lo crearemos al iniciar la aplicación, durante el evento [Application_Start] gestionado por el controlador [global.asax]. El código de este es el siguiente:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' se crea un objeto «impost»
Dim objImpot As impot
Try
objImpot = New impot(New impotsArray)
' se inserta el objeto en la aplicación
Application("objImpot") = objImpot
' sin errores
Application("erreur") = False
Catch ex As Exception
': se ha producido un error; se anota en la aplicación
Application("erreur") = True
End Try
End Sub
End Class
Una vez creado, el objeto de tipo [impot] se incorpora a la aplicación. Es ahí donde las distintas consultas de los distintos clientes lo buscarán. Dado que la creación del objeto [impot] puede fallar, gestionamos la posible excepción e introducimos una clave [erreur] en la aplicación para indicar si se ha producido o no un error al crear el objeto [impot].
El código del controlador [main.aspx, main.aspx.vb] será el siguiente:
[main.aspx]
[main.aspx.vb]
Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
If CType(Application("erreur"), Boolean) Then
' se redirige a la página de errores
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' se recupera la acción que hay que realizar
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' se ejecuta la acción
Select Case action
Case "init"
' inicializar la aplicación
initAppli()
Case "calcul"
' cálculo de la renta
calculImpot()
Case "retour"
' volver al formulario
retourFormulaire()
Case "effacer"
' inicialización de la aplicación
initAppli()
Case Else
' acción desconocida = inicialización
initAppli()
End Select
End Sub
Private Sub initAppli()
' se muestra el formulario prellenado
Context.Items("formulaire") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' se inicializa el formulario
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("txtEnfants", "")
form.Set("txtSalaire", "")
Return form
End Function
Private Sub calculImpot()
' se comprueba la validez de los datos introducidos
Dim erreurs As ArrayList = checkData()
' si hay errores, se avisa
If erreurs.Count <> 0 Then
' se guardan los datos introducidos
Session.Item("formulaire") = Request.Form
' se prepara la página de errores
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' Si no hay errores, se calcula el impuesto
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' se muestra la página de resultados
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("formulaire") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub retourFormulaire()
' se muestra el formulario con los valores tomados de la sesión
Context.Items("formulaire") = Session.Item("formulaire")
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function checkData() As ArrayList
' al principio no hay errores
Dim erreurs As New ArrayList
Dim erreur As Boolean = False
' botón de opción «casado»
Try
Dim rdMarie As String = Request.Form("rdMarie").ToString
If rdMarie <> "oui" And rdMarie <> "non" Then
Throw New Exception
End If
Catch
erreurs.Add("Vous n'avez pas indiqué votre statut marital")
End Try
' Número de hijos
Try
Dim txtEnfants As String = Request.Form("txtEnfants").ToString
Dim nbEnfants As Integer = CType(txtEnfants, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' salario
Try
Dim txtSalaire As String = Request.Form("txtSalaire").ToString
Dim salaire As Integer = CType(txtSalaire, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' se muestra la lista de errores
Return erreurs
End Function
End Class
El controlador comienza por comprobar que la aplicación se ha inicializado correctamente:
' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
If CType(Application("erreur"), Boolean) Then
' se redirige a la página de errores
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
Si el controlador detecta que la aplicación no se ha podido inicializar correctamente (no se ha podido crear el objeto [impot] necesario para el cálculo), muestra la página de errores con los parámetros adecuados. En este caso, no es necesario incluir el enlace de retorno al formulario, ya que toda la aplicación resulta estar indisponible. Se inserta un mensaje de error general en [Context.Items("erreurs")], del tipo [ArrayList].
Si el controlador detecta que la aplicación está operativa, analiza entonces la acción que se le solicita que ejecute a través del parámetro [action]. Ya nos hemos encontrado en numerosas ocasiones con este modo de funcionamiento. El procesamiento de cada tipo de acción se delega a una función.
6.1.9.1. Las acciones init y borrar
Estas dos acciones deben mostrar el formulario de entrada vacío. Recordemos que este (véanse las vistas) tiene dos parámetros:
- Context.Items("formulario"): diccionario de tipo [NameValueCollection] que contiene los valores de los campos HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot"): valor del impuesto
La función [initAppli] inicializa estos dos parámetros para mostrar un formulario en blanco.
6.1.9.2. La acción «calcular»
Esta acción debe calcular el impuesto a pagar a partir de los datos introducidos en el formulario y devolverlo ya rellenado con los valores introducidos y, además, con el importe del impuesto calculado. La función [calculImpot], encargada de esta tarea, comienza por comprobar que los datos del formulario sean correctos:
- el campo [rdMarie] debe estar presente y tener el valor [oui] o [non]
- el campo [txtEnfants] debe estar presente y ser un número entero >=0
- el campo [txtSalaire] debe estar presente y ser un número entero >=0
Si los datos introducidos resultan inválidos, el controlador muestra la vista [erreurs.aspx] tras haber introducido previamente en el contexto los valores que esta espera:
- los mensajes de error se colocan en un objeto [ArrayList], objeto que a continuación se coloca en el contexto [Context.Items("erreurs")]
- la URL del enlace de retorno y el texto de dicho enlace también se colocan en el contexto.
Antes de pasar el control a la página [erreurs.aspx], que enviará la respuesta al cliente, los valores introducidos en el formulario (Request.Form) se almacenan en la sesión, asociados a la clave «formulario». Esto permitirá recuperarlos en una solicitud posterior.
Cabe preguntarse aquí si resulta útil comprobar que los campos [rdMarie, txtEnfants, txtSalaire] estén presentes en la solicitud enviada por el cliente. Es innecesario si estamos seguros de que nuestro cliente es un navegador que ha recibido la vista [formulaire.aspx], que contiene dichos campos. Nunca se puede estar seguro de ello. Más adelante mostraremos un ejemplo en el que el cliente es la aplicación [curl] que ya hemos visto. Realizaremos una consulta a la aplicación sin enviar los campos que espera y veremos cómo reacciona. Se trata de una regla que ya se ha mencionado en varias ocasiones y que recordamos aquí: una aplicación nunca debe hacer suposiciones sobre el tipo de cliente que la consulta. Por seguridad, debe considerar que puede ser consultada por una aplicación programada que pueda enviarle cadenas de parámetros inesperadas. Debe comportarse correctamente en todos los casos.
En nuestro caso, hemos comprobado que los campos [rdMarie, txtEnfants, txtSalaire] estaban presentes en la consulta, pero no que esta pudiera contener otros. En esta aplicación, se ignorarían. No obstante, siempre por motivos de seguridad, sería interesante registrar este tipo de solicitud en un archivo de registros y enviar una alerta al administrador de la aplicación para que este sepa que la aplicación está recibiendo solicitudes «extrañas». Al analizarlas en el archivo de registro, podría detectar un posible ataque a la aplicación y tomar entonces las medidas necesarias para protegerla.
Si los datos esperados son correctos, el controlador inicia el cálculo del impuesto con el objeto [impot] almacenado en la aplicación. A continuación, almacena en el contexto los dos datos que espera la vista [formulaire.aspx]:
- Context.Items («formulario»): diccionario de tipo [NameValueCollection] que contiene los valores de los campos HTML, [rdmarie,txtEnfants,txtSalaire], en este caso [Request.Form)] y c.a.d. los valores introducidos anteriormente en el formulario
- Context.Items("txtImpot"): valor del impuesto que se acaba de obtener
Es posible que al lector atento le haya surgido una duda al leer lo anterior: dado que el objeto [impot] creado al iniciar la aplicación es compartido por todas las consultas, ¿no podrían producirse conflictos de acceso que provocaran la corrupción de los datos del objeto [impot]? Para responder a esta pregunta, debemos volver al código de la clase [impot]. Las consultas utilizan el método [impot].calculerImpot para obtener el impuesto a pagar. Por lo tanto, es este código el que debemos examinar:
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' cálculo del número de participaciones
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' cálculo de la base imponible y del coeficiente familiar
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' cálculo del impuesto
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
Return impot
End Function
Supongamos que un hilo está ejecutando el método anterior y se interrumpe. Entonces, otro hilo ejecuta el método. ¿Cuáles son los riesgos? Para averiguarlo, hemos añadido el siguiente código:
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
' se espera 10 segundos
Thread.Sleep(10000)
Return impot
El hilo 1, tras calcular el valor [impot1] de la variable local [impot], se interrumpe. A continuación, se ejecuta el hilo 2 y calcula un nuevo valor, [impot2], para esa misma variable [impot] antes de ser interrumpido. El hilo 1 recupera el control. ¿Qué encuentra en la variable local [impot]? Al ser una variable local de un método, se almacena en una estructura de memoria denominada pila. Esta pila forma parte del contexto del hilo, que se guarda cuando este se interrumpe. Cuando el hilo 2 se inicia, su contexto se establece con una nueva pila y, por lo tanto, con una nueva variable local [impot]. Cuando el hilo 2 sea interrumpido a su vez, su contexto también se guardará. Cuando se reinicia el hilo 1, se restaura su contexto, incluida su pila. Así, recupera su variable local [impot] y no la del hilo 2. Por lo tanto, nos encontramos en una situación en la que no hay conflictos de acceso entre las solicitudes. Las pruebas realizadas con la pausa de 10 segundos mencionada anteriormente han confirmado que las solicitudes simultáneas obtenían efectivamente el resultado esperado.
6.1.9.3. La acción de retorno
Esta acción corresponde a la activación del enlace [Retour vers le formulaire] de la vista [erreurs.aspx] para volver a la vista [formulaire.aspx], que aparece prellenada con los valores introducidos anteriormente y guardados en la sesión. La función [retourFormulaire] recupera esta información. Se inicializan los dos parámetros que espera la vista [formulaire.aspx]:
- Context.Items("formulario") con los valores introducidos anteriormente y guardados en la sesión
- Context.Items("txtImpot") con la cadena vacía
6.1.10. Prueba de la aplicación web
Todos los archivos anteriores se colocan en una carpeta <ruta-de-la-aplicación>.

En esta carpeta se crea una subcarpeta [bin] en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb]. A continuación se recuerda el comando de compilación necesario:
dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 09:21 5 632 impot.dll
El archivo [impot.dll] anterior debe colocarse en <ruta-de-la-aplicación>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<ruta-de-la-aplicación>,/impots1). Desde un navegador, solicitamos la URL [http://localhost/impots1/main.aspx]:

Rellenamos el formulario:

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

A continuación, introducimos datos erróneos:

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

Al utilizar el enlace [Retour au formulaire], volvemos al formulario tal y 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 clientes distintos de los navegadores. Si se envía a un navegador un formulario con parámetros que deben enviarse al validarlo, el navegador devolverá los valores de dichos parámetros al servidor. Otro cliente podría no hacerlo y, en ese caso, el servidor recibiría una solicitud en la que faltarían algunos parámetros. Debe saber qué hacer en ese caso. Otro ejemplo es el de las validaciones de entrada realizadas del lado del cliente. Si el formulario contiene datos que deben validarse, esta validación puede realizarse del lado del cliente mediante scripts incluidos en el documento que contiene el formulario. El navegador solo enviará el formulario si todos los datos validados del lado del cliente son válidos. Por lo tanto, en el lado del servidor, podríamos sentir la tentación de dar por hecho que vamos a recibir datos ya validados y no querer realizar esta validación por segunda vez. Esto sería un error. De hecho, un cliente que no sea un navegador podría enviar datos no válidos al servidor, con lo que la aplicación web correría el riesgo de comportarse de forma inesperada. Vamos a ilustrar estos puntos utilizando el cliente [curl].
En primer lugar, solicitamos la URL [http://localhost/impots1/main.aspx]:
dos>curl --include --url http://localhost/impots1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:18:10 GMT
Set-Cookie: ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 982
Connection: Close
<html>
<head>
<title>Impôt</title>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" >Oui <INPUT type="radio" value="non" name="rdMarie" checked>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value=""></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value=""></TD>
</TR>
<TR>
<TD>Impôt à payer :
</TD>
<TD></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
El servidor nos ha enviado el código HTML del formulario. En los encabezados HTTP tenemos la cookie de sesión. La utilizaremos en las siguientes solicitudes para mantener la sesión. Solicitemos la acción [calcul] sin proporcionar parámetros:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:22:42 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
</ul>
<a href="main.aspx?action=retour">
Retour au formulaire
</a>
</body>
</HTML>
Podemos observar que la aplicación web ha devuelto la vista [erreurs] con tres mensajes de error correspondientes a los tres parámetros que faltan. Enviemos ahora unos parámetros erróneos:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=xx --data txtEnfants=xx --data txtSalaire=xx --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:25:50 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
</ul>
<a href="main.aspx?action=retour">
Retour au formulaire
</a>
</body>
</HTML>
Los tres errores se han detectado correctamente. Ahora enviemos parámetros válidos:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=oui --data txtEnfants=2 --data txtSalaire=60000 --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:28:24 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1000
Connection: Close
<html>
<head>
<title>Impôt</title>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" checked>Oui <INPUT type="radio" value="non" name="rdMarie" >Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="2"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="60000"></TD>
</TR>
<TR>
<TD>Impôt à payer :
</TD>
<TD>4300 euro(s)</TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
Hemos calculado correctamente el impuesto a pagar: 4300 euros. De este ejemplo aprenderemos que no hay que dejarse engañar por el hecho de que estemos escribiendo una aplicación web destinada a clientes que son navegadores. Una aplicación web es un servicio TCP/IP y este protocolo de red no permite determinar la naturaleza de la aplicación cliente de un servicio. Por lo tanto, no podemos saber si el cliente de una aplicación web es un navegador o no. Por lo tanto, seguimos dos reglas:
- al recibir una solicitud de un cliente, no se hace ninguna suposición sobre el cliente y se comprueba que los parámetros esperados en la solicitud estén presentes y sean válidos
- se elabora una respuesta destinada a los navegadores, es decir, por lo general, documentos HTML
Una aplicación web puede diseñarse para atender simultáneamente a diferentes clientes, por ejemplo, navegadores y teléfonos móviles. En ese caso, se puede incluir en cada solicitud un nuevo parámetro que indique el tipo de cliente. Así, un navegador solicitará el cálculo del impuesto mediante una solicitud a la URL http://machine/impots/main.aspx?client=navegador&action=cálculo, mientras que el teléfono móvil realizará una solicitud a la URL http://machine/impots/main.aspx?client=mobile&action=calcul. La estructura MVC facilita la programación de una aplicación de este tipo. Queda de la siguiente manera:

El bloque [Classes métier, Classes d'accès aux données] no cambia. De hecho, es una parte que no afecta al cliente. El bloque [Contrôleur] cambia ligeramente, pero debe tener en cuenta un nuevo parámetro en la solicitud: el parámetro [client], que indica a qué tipo de cliente se dirige. El bloque [vues] debe generar vistas para cada tipo de cliente. Podría resultar interesante tener en cuenta, desde el diseño de la aplicación, la presencia del parámetro [client] en la consulta, aunque el objetivo a corto o medio plazo se limite únicamente a los navegadores. Si la aplicación tiene que gestionar posteriormente un nuevo tipo de cliente, solo habrá que escribir las vistas adaptadas a este.
6.2. Ejemplo 2
6.2.1. El problema
Aquí nos proponemos abordar el mismo problema que antes, pero modificando la fuente de datos del objeto [impot] creado por la aplicación web. En la versión anterior, la fuente de datos utilizada proporcionaba los valores de tablas «fijas» en el código. En esta ocasión, la nueva fuente de datos los obtendrá de una fuente de datos ODBC asociada a una base de datos MySQL.
6.2.2. La fuente de datos ODBC
Los datos se encontrarán en una tabla denominada [IMPOTS] de una base de datos MySQL denominada [dbimpots]. El contenido de esta tabla será el siguiente:

El propietario de la base de datos es el usuario [admimpots], cuya contraseña es [mdpimpots]. Asociamos una fuente de datos ODBC a esta base de datos. Antes de hacerlo, recordemos primero las diferentes formas de acceder a una base de datos con la plataforma .NET.
Existen numerosas bases de datos para las plataformas Windows. Para acceder a ellas, las aplicaciones utilizan programas denominados controladores (drivers).

En el esquema anterior, el controlador presenta dos interfaces:
- la interfaz I1, que se presenta a la aplicación
- la interfaz I2 hacia la base de datos
Para evitar que una aplicación escrita para una base de datos B1 tenga que reescribirse si se migra a una base de datos B2 diferente, se ha llevado a cabo un trabajo de normalización en la interfaz I1. Si se utilizan bases de datos que emplean controladores «normalizados», la base B1 se suministrará con un controlador P1, la base B2 con un controlador P2, y la interfaz I1 de ambos controladores será idéntica. Por lo tanto, no será necesario reescribir la aplicación. De este modo, por ejemplo, se podrá migrar una base de datos ACCESS a una base de datos MySQL sin modificar la aplicación.
Existen dos tipos de controladores estandarizados:
- los controladores ODBC (Open DataBase Connectivity)
- los controladores OLE y DB (Object Linking and Embedding DataBase)
Los controladores ODBC permiten el acceso a bases de datos. Las fuentes de datos para los controladores OLE y DB son más variadas: bases de datos, sistemas de mensajería, directorios... No hay ningún límite. Cualquier fuente de datos puede ser objeto de un controlador OLE DB si así lo decide el desarrollador. El interés es, evidentemente, enorme: se dispone de un acceso uniforme a una gran variedad de datos.
La plataforma .NET 1.1 incluye tres tipos de clases de acceso a datos:
- 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 y DB
- las clases odbc.net, para acceder a las bases de datos de SGBD, que ofrecen un controlador ODBC
La clase SGBD MySQL dispone desde hace tiempo de un controlador ODBC. Este es el que utilizamos ahora. En Windows, seleccionamos la opción [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Dependiendo de la versión de Windows, esta ruta puede variar ligeramente. Obtenemos la siguiente aplicación, que nos permitirá crear nuestra fuente ODBC:

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

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

Hay que introducir una serie de datos:
el nombre con el que se identificará la fuente de datos ODBC. Cualquier aplicación de Windows podrá acceder a la fuente mediante este nombre | |
un texto arbitrario que describa la fuente de datos | |
El nombre del equipo que aloja el SGBD MySQL. En este caso se trata del equipo local. Podría ser un equipo remoto. Esto permitiría a una aplicación de Windows acceder a una base de datos remota sin necesidad de ninguna codificación específica. Este es uno de los grandes ventajas de la fuente ODBC. | |
Un SGBD MySQL puede gestionar varias bases de datos. Aquí se especifica cuál se quiere gestionar: 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 ya podemos utilizarla. Repetimos [OK] tantas veces como sea necesario para salir del asistente ODBC.
Si el lector no dispone de SGBD mySQL, puede descargarlo gratuitamente en la URL [http://www.mysql.com]. A continuación se describe el procedimiento para crear una fuente ODBC con Access. Los primeros pasos son idénticos a los descritos anteriormente. Se añade una nueva fuente de datos del sistema:

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

Los datos que hay que proporcionar son los siguientes:
el nombre que designará la fuente de datos ODBC. Cualquier aplicación de Windows podrá acceder a la fuente a través de este nombre | |
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 los datos
Volvamos a la estructura MVC de nuestra aplicación:

En el esquema anterior, la clase [impotsData] se encarga de recuperar los datos. En este caso, deberá hacerlo de la base de datos MySQL [dbimpots]. Sabemos, desde la versión anterior de esta aplicación, que [impotsData] es una clase abstracta de la que hay que derivar cada vez que se quiera adaptar a una nueva fuente de datos. Recordemos la estructura de esta clase abstracta:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' método de acceso a los datos
Public MustOverride Function getData() As Object()
' método de verificación de datos
Protected Function checkData() As Integer
' comprueba los datos obtenidos
...
End Function
' comprueba la validez del contenido de una tabla
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
...
End Function
End Class
End Namespace
La clase que deriva de [impotsData] debe implementar dos métodos:
- un constructor, si el constructor sin argumentos de [impotsData] no le conviene
- el método [getData], que devuelve los tres arreglos (límites, coeffr, coeffn)
Creamos la clase [impotsODBC], que recuperará los datos (límites, coeffr, coeffn) de una fuente ODBC a la que le daremos el nombre:
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' variables de instancia
Protected DSNimpots As String
' constructor
Public Sub New(ByVal DSNimpots As String)
' se anotan los tres datos
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' inicializa las tres tablas «limites», «coeffr» y «coeffn» a partir de
' del contenido de la tabla [impots] de la base de datos ODBC DSNimpots
' «límites», «coeffr» y «coeffn» son las tres columnas de esta tabla
' puede generar diversas excepciones
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' la consulta SELECT
Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
' tablas para recuperar los datos
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' se intenta acceder a la base de datos
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' se crea un objeto de comando
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' se ejecuta la consulta
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Análisis de la tabla recuperada
While myReader.Read()
' Los datos de la fila actual se introducen en las tablas
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' Liberación de recursos
myReader.Close()
impotsConn.Close()
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' Las tablas dinámicas se transfieren a tablas estáticas
Me.limites = New Decimal(aLimites.Count - 1) {}
Me.coeffr = New Decimal(aLimites.Count - 1) {}
Me.coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = Decimal.Parse(aLimites(i).ToString())
coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
Next i
' Se comprueban los datos obtenidos
Dim erreur As Integer = checkData()
' si los datos no son válidos, se lanza una excepción
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' de lo contrario, se devuelven las tres tablas
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Echemos un vistazo al fabricante:
' constructor
Public Sub New(ByVal DSNimpots As String)
' se anotan los tres datos
Me.DSNimpots = DSNimpots
End Sub
Recibe como parámetro el nombre de la fuente ODBC en la que se encuentran los datos que se van a adquirir. El constructor se limita a almacenar este nombre. El método [getData] se encarga de leer los datos de la tabla [impots] y de colocarlos en tres matrices (límites, coeffr, coeffn). Comentaremos su código:
- se definen los parámetros de conexión a la fuente de datos ODBC, pero esta no está abierta
' cadena de conexión a la base de datos
Dim connectString As String = "DSN=" + DSNimpots + ";"
' se crea un objeto de conexión a la base de datos; esta conexión no está abierta
Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
- Se definen tres objetos [ArrayList] para recuperar los datos de la tabla [impots]:
' tablas para recuperar los datos
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
- Todo el código de acceso a la base de datos está rodeado por un bloque «try/catch» para gestionar un posible error de acceso. Se abre la conexión con la base de datos:
' Se intenta acceder a la base de datos
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
- Se ejecuta el comando [select] en la conexión abierta. Se obtiene un objeto [OdbcDataReader] que nos permitirá recorrer las filas de la tabla resultante de la consulta SELECT:
' se crea un objeto de comando
Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
' se ejecuta la consulta
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
- Recorremos la tabla de resultados, línea por línea. Para ello, utilizamos el método [Read] del objeto [OdbcDataReader] obtenido anteriormente. Este método realiza dos acciones:
- avanza una línea en la tabla. Al principio, nos encontramos situados antes de la primera línea
- devuelve el valor booleano [true] si ha podido avanzar, y [false] en caso contrario; este último caso indica que se han procesado todas las líneas.
Las columnas de la línea actual del objeto [OdbcDataReader] se obtienen mediante OdbcDataReader. Se obtiene un objeto que representa el valor de la columna. Recorremos toda la tabla para introducir su contenido en los tres objetos [ArrayList]:
' Se procesa la tabla recuperada
While myReader.Read()
' Los datos de la fila actual se introducen en las tablas
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- Una vez hecho esto, liberamos los recursos asociados a la conexión:
- El contenido de los tres objetos [ArrayList] se transfiere a tres matrices clásicas:
' Las tablas dinámicas se transfieren a tablas estáticas
limites = New Decimal(aLimites.Count - 1) {}
coeffr = New Decimal(aLimites.Count - 1) {}
coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = CType(aLimites(i), Decimal)
coeffR(i) = CType(aCoeffR(i), Decimal)
coeffN(i) = CType(aCoeffN(i), Decimal)
Next i
- Una vez que los datos de la tabla [impots] se han transferido a las tres tablas, solo queda comprobar su contenido mediante el método [checkData] de la clase base [impotsData]:
' Se comprueban los datos obtenidos
Dim erreur As Integer = checkData()
' si los datos no son válidos, se lanza una excepción
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' de lo contrario, se devuelven las tres tablas
Return New Object() {limites, coeffr, coeffn}
6.2.4. Pruebas de la clase de acceso a datos
Un programa de prueba podría ser el siguiente:
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' página de prueba
Module testimpots
Sub Main(ByVal arguments() As String)
' programa interactivo de cálculo de impuestos
' el usuario introduce tres datos mediante el teclado: casado nbEnfants salario
' a continuación, el programa muestra el impuesto a pagar
Const syntaxe1 As String = "pg DSNimpots"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' comprobación de los parámetros del programa
If arguments.Length <> 1 Then
' mensaje de error
Console.Error.WriteLine(syntaxe1)
' fin
Environment.Exit(1)
End If
' se recuperan los argumentos
Dim DSNimpots As String = arguments(0)
' creación de un objeto de impuesto
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' bucle infinito
While True
' al principio no hay errores
Dim erreur As Boolean = False
' se solicitan los parámetros para el cálculo del impuesto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' ¿Hay que hacer algo?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' Comprobación del número de argumentos en la línea introducida
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' Comprobación de la validez de los parámetros
' casado
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' salario
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' los parámetros son correctos: se calcula el impuesto
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
La aplicación se inicia con un parámetro:
- DSNimpots: nombre de la fuente de datos ODBC que se va a utilizar
El cálculo del impuesto se realiza mediante un objeto de tipo [impot] creado al iniciar la aplicación:
' creación de un objeto de impuesto
Dim objImpôt As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
Una vez iniciada, la aplicación solicita repetidamente al usuario que introduzca mediante el teclado los tres datos necesarios para calcular sus impuestos:
- su estado civil: «o» para casado, «n» para soltero
- el número de hijos
- su salario anual
Se compilan todas las clases:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:32 3 210 testimpots.vb
A continuación, se compila el programa de prueba:
dir>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:34 6 144 testimpots.exe
01/04/2004 19:32 3 210 testimpots.vb
El programa de prueba se ejecuta primero con la fuente de datos ODBC MySQL:
dos>testimpots odbc-mysql-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Se cambia la fuente ODBC por una fuente de Access:
dos>testimpots odbc-access-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 F
6.2.5. Las vistas de la aplicación web
Son las mismas que las de la aplicación anterior: formulaire.aspx y erreurs.aspx
6.2.6. Los controladores de la aplicación [global.asax, main.aspx]
Solo hay que modificar el controlador [global.asax]. De hecho, este se encarga de crear el objeto [impot] al iniciar la aplicación. El constructor de este objeto tiene como único parámetro el objeto de tipo [impotsData], encargado de recuperar los datos. Por lo tanto, este parámetro cambia para cada nuevo tipo de fuente de datos. El controlador [global.asax.vb] queda así:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' se crea un objeto de impuesto
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' se introduce el objeto en la aplicación
Application("objImpot") = objImpot
' sin errores
Application("erreur") = False
Catch ex As Exception
'se ha producido un error; se anota en la aplicación
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
La fuente de datos del objeto [impot] es ahora un objeto [impotODBC]. Este último tiene como parámetro el nombre DSN de la fuente de datos ODBC que se va a utilizar. En lugar de escribir este nombre de forma fija en el código, se incluye en el archivo de configuración [web.config] de la aplicación:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
Se sabe que el valor de una clave C de la sección <appSettings> del archivo [web.config] se obtiene en el código de la aplicación mediante [ConfigurationSettings.AppSettings(C)].
Para conocer la causa de la excepción, se registra el mensaje de la misma en la aplicación, de modo que quede disponible para las consultas. El controlador [main.aspx.vb] incluirá este mensaje en su lista de errores:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
If CType(Application("erreur"), Boolean) Then
' se redirige a la página de errores
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...(" + Application("message").ToString + ")")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' se recupera la acción que hay que realizar
...
6.2.7. Resumen de los cambios
La aplicación está lista para ser probada. A continuación, enumeramos los cambios introducidos con respecto a la versión anterior:
- se ha creado una nueva clase de acceso a datos
- se ha modificado el controlador [global.asax.vb] en dos puntos: la creación del objeto [impot] y el registro en la aplicación del mensaje relacionado con una posible excepción
- se ha modificado el controlador [main.aspx.vb] en un punto para mostrar el mensaje de excepción anterior
- Se ha añadido un archivo [web.config]
El trabajo de modificación se llevó a cabo principalmente en 1, c.a.d, fuera de la aplicación web. Esto fue posible gracias a la arquitectura MVC de la aplicación, que separa el controlador de las clases de negocio. Ahí radica todo el interés de esta arquitectura. Se podría demostrar que, con un archivo de configuración adecuado, se habría podido evitar cualquier modificación del controlador de la aplicación. Es posible introducir en el archivo el nombre de la clase de acceso a datos que se va a instanciar dinámicamente, así como los diversos parámetros necesarios para dicha instanciación. Con esta información, [global.asax] puede instanciar el objeto de acceso a los datos. Cambiar de fuente de datos equivale entonces a:
- crear la clase de acceso a dicha fuente si aún no existe
- modificar el archivo [web.config] para permitir la creación dinámica de una instancia de esta clase en [global.asax]
6.2.8. Prueba de la aplicación web
Todos los archivos anteriores se colocan en una carpeta <application-path>.

En esta carpeta se crea una subcarpeta [bin] en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. A continuación se recuerda el comando de compilación necesario:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:32 3 210 testimpots.vb
El archivo [impot.dll] anterior debe colocarse en <ruta-de-la-aplicación>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<ruta-de-la-aplicación>,/impots2). Las pruebas arrojan los mismos resultados que en la versión anterior, ya que la presencia de la base de datos es transparente para el usuario. No obstante, para ilustrar esta presencia, nos aseguramos de que la fuente ODBC no esté disponible deteniendo los archivos SGBD y MySQL, y solicitamos la URL [http://localhost/impots2/main.aspx]. Obtenemos la siguiente respuesta:

6.3. Ejemplo 3
6.3.1. El problema
Aquí nos proponemos abordar el mismo problema modificando de nuevo la fuente de datos del objeto [impot] creado por la aplicación web. En esta ocasión, la nueva fuente de datos será una base de datos ACCESS a la que se accederá mediante un controlador OLEDB. Nuestro objetivo es mostrar otra forma de acceder a una base de datos.
6.3.2. La fuente de datos OLEDB
Los datos se encontrarán en una tabla llamada [IMPOTS] de una base de datos ACCESS. El contenido de esta tabla será el siguiente:

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

- En el esquema anterior, la clase [impotsData] se encarga de recuperar los datos. En esta ocasión, deberá hacerlo a partir de una fuente OLEDB.
Creamos la clase [impotsOLEDB], que irá a buscar los datos (límites, coeffr, coeffn) en una fuente ODBC a la que le daremos el nombre:
Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb
Namespace st.istia.univangers.fr
Public Class impotsOLEDB
Inherits impotsData
' variables de instancia
Protected chaineConnexion As String
' constructor
Public Sub New(ByVal chaineConnexion As String)
' se anotan los tres datos
Me.chaineConnexion = chaineConnexion
End Sub
Public Overrides Function getData() As Object()
' inicializa los tres arrays «limites», «coeffr» y «coeffn» a partir de
' del contenido de la tabla [impots] de la base de datos OLEDB [chaineConnexion]
' «límites», «coeffr» y «coeffn» son las tres columnas de esta tabla
' puede lanzar diversas excepciones
' se crea un objeto DataAdapter para leer los datos de la fuente OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
' se crea una imagen en memoria del resultado de la consulta SELECT
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' se recupera el contenido de la tabla «impots»
Dim lignesImpots As DataRowCollection = contenu.Rows
' se dimensionan las tablas de recepción
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' se transfiere el contenido de la tabla «impots» a los arrays
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' línea i de la tabla
ligne = lignesImpots.Item(i)
' se recupera el contenido de la línea
limites(i) = CType(ligne.Item(0), Decimal)
coeffr(i) = CType(ligne.Item(1), Decimal)
coeffn(i) = CType(ligne.Item(2), Decimal)
Next
Catch
Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
End Try
' se comprueban los datos obtenidos
Dim erreur As Integer = checkData()
' si los datos no son válidos, se lanza una excepción
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' en caso contrario, se devuelven los tres arrays
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Echemos un vistazo al fabricante:
' constructor
Public Sub New(ByVal chaineConnexion As String)
' se anotan los tres datos
Me.chaineConnexion = chaineConnexion
End Sub
Recibe como parámetro la cadena de conexión de la fuente OLEDB, en la que se encuentran los datos que se van a adquirir. El controlador se limita a almacenarla. Una cadena de conexión contiene todos los parámetros necesarios para que el controlador OLEDB se conecte a la fuente OLEDB. Por lo general, es bastante compleja. Para averiguar cuál es la cadena de conexión de las bases de datos ACCESS, podemos utilizar la herramienta [WebMatrix]. Iniciamos esta herramienta. Nos muestra una ventana que permite conectarnos a una fuente de datos:
![]() ![]() | ![]() ![]() |
Gracias al icono señalado por la flecha anterior, es posible establecer una conexión con dos tipos de bases de datos de Microsoft: SQL Server y ACCESS. Seleccionemos ACCESS:

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

Ahora, vamos a crear 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 del ratón 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 no es más que el nombre del archivo ACCESS. Aprovecharemos este dato para generar la cadena de conexión a nuestra fuente de datos OLEDB.
Volvamos ahora a nuestra clase [impotsOLEDB]. El método [getData] se encarga de leer los datos de la tabla [impots] y de colocarlos en tres matrices (limites, coeffr, coeffn). Comentaremos su código:
- definimos el objeto [DataAdapter], que nos permitirá transferir a la memoria el resultado de una consulta SELECT de SQL. Para ello, definimos la consulta [select] que se va a ejecutar y la asociamos al objeto [DataAdapter]. El constructor de este último también requiere la cadena de conexión que deberá utilizar para conectarse a la fuente OLEDB
' se crea un objeto DataAdapter para leer los datos de la fuente OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
- Se ejecuta el comando [select] mediante el método [Fill] del objeto [DataAdapter]. El resultado de [select] se inserta en un objeto [DataTable] creado para la ocasión. Un objeto [DataTable] es la representación en memoria de una tabla de base de datos, c.a.d, un conjunto de filas y columnas. Gestionamos una excepción que puede producirse si, por ejemplo, la cadena de conexión es incorrecta.
' se crea una imagen en memoria del resultado de la consulta
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
- En [contenu], tenemos la tabla [impots] traída por [select]. Un objeto [DataTable] es una tabla, es decir, un conjunto de filas. Se puede acceder a ellas a través de la propiedad [rows] de [datatable]:
' se recupera el contenido de la tabla «impots»
Dim lignesImpots As DataRowCollection = contenu.Rows
- Cada elemento de la colección [lignesImpots] es un objeto de tipo [DataRow] que representa una fila de la tabla. Esta tiene columnas a las que se puede acceder a través del objeto [DataRow] mediante su propiedad [Item]. [DataRow].[Item(i)] es la columna n.º i de la fila [DataRow]. Al recorrer la colección de filas (la colección DataRows de lignesImpots) y la colección de columnas de cada fila, se puede obtener la tabla completa:
' se dimensionan las tablas de recepción
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' se transfiere el contenido de la tabla «impots» a los arrays
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' línea i de la tabla
ligne = lignesImpots.Item(i)
' se recupera el contenido de la línea
limites(i) = CType(ligne.Item(0), Decimal)
coeffr(i) = CType(ligne.Item(1), Decimal)
coeffn(i) = CType(ligne.Item(2), Decimal)
Next
Catch
Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
End Try
- Una vez que los datos de la tabla [impots] se han transferido a las tres tablas, solo queda comprobar su contenido mediante el método [checkData] de la clase base [impotsData]:
' se comprueban los datos obtenidos
Dim erreur As Integer = checkData()
' si los datos no son válidos, se lanza una excepción
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' en caso contrario, se devuelven los tres arrays
Return New Object() {limites, coeffr, coeffn}
6.3.4. Pruebas de la clase de acceso a datos
Un programa de prueba podría ser el siguiente:
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' página de prueba
Module testimpots
Sub Main(ByVal arguments() As String)
' programa interactivo de cálculo de impuestos
' el usuario introduce tres datos mediante el teclado: casado nbEnfants salario
' a continuación, el programa muestra el impuesto a pagar
Const syntaxe1 As String = "pg bdACCESS"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' comprobación de los parámetros del programa
If arguments.Length <> 1 Then
' mensaje de error
Console.Error.WriteLine(syntaxe1)
' fin
Environment.Exit(1)
End If
' se recuperan los argumentos
Dim chemin As String = arguments(0)
' se prepara la cadena de conexión
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' creación de un objeto de impuestos
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' bucle infinito
While True
' al principio no hay errores
Dim erreur As Boolean = False
' Se solicitan los parámetros para el cálculo del impuesto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' ¿Hay que hacer algo?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' Comprobación del número de argumentos en la línea introducida
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' Comprobación de la validez de los parámetros
' casado
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' salario
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' los parámetros son correctos: se calcula el impuesto
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
La aplicación se ejecuta con un parámetro:
- bdACCESS: nombre del archivo ACCESS que se va a procesar
El cálculo del impuesto se realiza mediante un objeto de tipo [impot] creado al iniciar la aplicación:
' se recuperan los argumentos
Dim chemin As String = arguments(0)
' se prepara la cadena de conexión
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' creación de un objeto de impuestos
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
La cadena de conexión a la fuente OLEDB se ha creado a partir de la información obtenida con [WebMatrix].
Una vez inicializada, la aplicación solicita repetidamente al usuario que introduzca mediante el teclado los tres datos necesarios para calcular sus impuestos:
- su estado civil: «o» para casado, «n» para soltero
- el número de hijos
- su salario anual
Se compilan todas las clases:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
El archivo [impots.mdb] se coloca en la carpeta de la aplicación de prueba y esta se ejecuta de la siguiente manera:
dos>testimpots impots.mdb
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Se puede ejecutar la aplicación con un archivo ACCESS incorrecto:
dos>testimpots xx
L'erreur suivante s'est produite : Erreur d'accès à la base de données (Ficher 'D:\data\serge\devel\aspnet\poly\chap5\impots\3\xx' introuvable.)
6.3.5. Las vistas de la aplicación web
Son las mismas que las de la aplicación anterior: formulaire.aspx y erreurs.aspx
6.3.6. Los controladores de la aplicación [global.asax, main.aspx]
Solo hay que modificar el controlador [global.asax]. De hecho, este se encarga de crear el objeto [impot] al iniciar la aplicación. El constructor de este objeto tiene como único parámetro el objeto de tipo [impotsData], encargado de recuperar los datos. Por lo tanto, este parámetro cambia al cambiar las fuentes de datos. El controlador [global.asax.vb] queda así:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' se crea un objeto de impuesto
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' se inserta el objeto en la aplicación
Application("objImpot") = objImpot
' sin errores
Application("erreur") = False
Catch ex As Exception
'se ha producido un error; se anota en la aplicación
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
La fuente de datos del objeto [impot] es ahora un objeto [impotOLEDB]. Este último tiene como parámetro la cadena de conexión de la fuente de datos OLEDB que se va a utilizar. Dicha cadena se encuentra en el archivo de configuración [web.config] de la aplicación:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion"
value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots2\impots.mdb" />
</appSettings>
</configuration>
El controlador [main.aspx] no cambia.
6.3.7. Resumen de los cambios
La aplicación está lista para ser probada. Enumeremos los cambios introducidos con respecto a la versión anterior:
- 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 añadido un archivo [web.config]
6.3.8. Prueba de la aplicación web
Todos los archivos anteriores se colocan en una carpeta <ruta-de-la-aplicación>.

En esta carpeta se crea una subcarpeta [bin] en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. A continuación se recuerda el comando de compilación necesario:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
El archivo [impot.dll] generado por este comando debe colocarse en <application-path>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<application-path>,/impots3). Las pruebas arrojan los mismos resultados que en la versión anterior.
6.4. Ejemplo 4
6.4.1. El problema
Ahora nos proponemos transformar nuestra aplicación en una aplicación de simulación de cálculos de impuestos. Un usuario podrá realizar cálculos sucesivos de impuestos y estos se le mostrarán en una nueva vista similar a esta:

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 que acabamos de mostrar una captura de pantalla. La clase de acceso a los datos será la clase [impotsODBC] del ejemplo 2.
6.4.3. Las vistas de la aplicación web
La vista [erreurs.aspx] no cambia. La vista [formulaire.aspx] cambia ligeramente. De hecho, el importe del impuesto ya no aparece en esta vista. Ahora se encuentra en la vista [simulations.aspx]. Así, al iniciar la aplicación, la página que se muestra al usuario es la siguiente:

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

El código de presentación es el siguiente:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
<script language="javascript">
function calculer(){
// Se comprueban los parámetros antes de enviarlos al servidor
with(document.frmImpots){
//Número de hijos
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// no se ha comprobado el modelo
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
txtEnfants.focus();
return;
}//si
//salario
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// No se ha comprobado el modelo
alert("Le salaire n'a pas été donné ou est incorrect");
txtSalaire.focus();
return;
}//si
// Todo correcto: se envía el formulario al servidor
submit();
}//con
}//calcular
</script>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="button" value="Calculer" onclick="calculer()">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
Los campos dinámicos de la página son los mismos que en las versiones anteriores. El campo dinámico del importe del impuesto ha desaparecido. El botón [Calculer] ya no es un botón de tipo [submit]. Es de tipo [button] y, al hacer clic en él, se ejecuta la función JavaScript [calculer()]:
<INPUT type="button" value="Calculer" onclick="calculer()">
Se le ha asignado al formulario el nombre [frmImpots] para poder hacer referencia a él en el script [calculer]:
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
La función JavaScript [calculer] utiliza expresiones regulares para comprobar la validez de los campos de los formularios [document.frmImpots.txtEnfants] y [document.frmImpots.txtSalaire]. Si los valores introducidos son correctos, [document.frmImpots.submit()] los envía al servidor.
La página de presentación obtiene sus campos dinámicos de su controlador [formulaire.aspx.vb] de la siguiente manera:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' campos de la página
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' se recupera la solicitud anterior del contexto
Dim form As NameValueCollection = Context.Items("formulaire")
' se prepara la página para mostrarla
' botones de opción
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' el resto
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
End Sub
End Class
El controlador [formulaire.aspx.vb] es idéntico a las versiones anteriores, salvo que ya no tiene que recuperar el campo [txtImpot] del contexto, ya que dicho campo ha desaparecido de la página.
La vista [simulations.aspx] tiene el siguiente aspecto:

y se corresponde con el siguiente código de presentación:
<%@ page src="simulations.aspx.vb" inherits="simulations" autoeventwireup="false" %>
<HTML>
<HEAD>
<title>simulations</title>
</HEAD>
<body>
<P>Résultats des simulations</P>
<HR width="100%" SIZE="1">
<table>
<tr>
<th>
Marié</th>
<th>
Enfants</th>
<th>
Salaire annuel (euro)</th>
<th>
Impôt à payer (euro)</th>
</tr>
<%=simulationsHTML%>
</table>
<p></p>
<a href="<%=href%>">
<%=lien%>
</a>
</body>
</HTML>
Este código presenta tres campos dinámicos:
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 van a mostrar. Cada elemento es una matriz de 4 cadenas de caracteres que representan la información (estado civil, hijos, salario, impuestos) de la simulación. | |
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 gestionar tres acciones:
- init: corresponde a la primera solicitud del cliente. El controlador muestra la vista [formulaire.aspx]
- calcul: corresponde a la solicitud de cálculo del impuesto. Si los datos del formulario de introducción son correctos, el impuesto se calcula mediante la clase de negocio [impotsODBC]. El controlador devuelve al cliente la vista [simulations.aspx] con el resultado de la simulación actual más todas las anteriores. Si los datos del formulario de introducción son incorrectos, el controlador devuelve la vista [erreurs.aspx] con la lista de errores y un enlace para volver al formulario.
- retorno: corresponde al retorno al formulario tras un error. El controlador muestra la vista [formulaire.aspx] tal y como estaba validada antes del error.
En esta nueva versión, solo ha cambiado la acción [calcul]. De hecho, si los datos son válidos, debe conducir a la vista [simulations.aspx], mientras que antes conducía a la vista [formulaire.aspx]. El controlador [main.aspx.vb] queda así:
Imports System
...
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' antes de nada, se comprueba si la aplicación se ha inicializado correctamente
...
' se ejecuta la acción
Select Case action
Case "init"
' inicializar la aplicación
initAppli()
Case "calcul"
' cálculo del impuesto
calculImpot()
Case "retour"
' volver al formulario
retourFormulaire()
Case "effacer"
' inicialización de la aplicación
initAppli()
Case Else
' acción desconocida = inicialización
initAppli()
End Select
End Sub
...
Private Sub calculImpot()
' se guardan los datos introducidos
Session.Item("formulaire") = Request.Form
' se comprueba la validez de los datos introducidos
Dim erreurs As ArrayList = checkData()
' si hay errores, se avisa
If erreurs.Count <> 0 Then
' se prepara la página de errores
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' aquí no hay errores: se calcula el impuesto
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' se añade el resultado a las simulaciones existentes
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' se añade la simulación actual
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
impot.ToString}
simulations.Add(simulation)
' se incluyen las simulaciones en la sesión y el contexto
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' se muestra la página de resultados
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
Server.Transfer("simulations.aspx", True)
End Sub
...
End Class
En lo anterior solo hemos conservado lo necesario para comprender las modificaciones que se encuentran exclusivamente en la función [calculImpots]:
- en primer lugar, la función guarda el formulario [Request.Form] en la sesión, con el fin de poder regenerar el formulario tal y como quedó validado. Esto hay que hacerlo en todos los casos, ya que, tanto si la operación da como resultado la respuesta [erreurs.aspx] como si da como resultado la respuesta [simulations.aspx], se vuelve al formulario mediante el enlace [Retour au formulaire]. Para recuperarlo correctamente, es necesario haber guardado previamente sus valores en la sesión.
- Si los datos introducidos son correctos, la función añade la simulación actual (casado, hijos, salario, impuestos) a la lista de simulaciones. Esta se encuentra en la sesión asociada a la clave «simulations».
- La lista de simulaciones se vuelve a guardar en la sesión para su uso futuro. También se coloca en el contexto actual, ya que es allí donde la espera la vista [simulations.aspx]
- La vista [simulations.aspx] se muestra una vez que se ha introducido en el contexto el resto de la información que espera
6.4.5. Resumen de los cambios
La aplicación está lista para ser probada. Enumeremos los cambios introducidos con respecto a las versiones anteriores:
- se ha creado una nueva vista
- Se ha modificado el controlador [main.aspx.vb] en un punto: el tratamiento de la acción [calcul]
6.4.6. Prueba de la aplicación web
Se invita al lector a realizar las pruebas. Recordemos el procedimiento. Todos los archivos de la aplicación se encuentran en una carpeta <application-path>. En esta carpeta se crea una subcarpeta [bin], en la que se coloca el ensamblado [impot.dll], resultado de la compilación de los archivos de las clases de negocio: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. El archivo [impot.dll] generado por este comando debe colocarse en <ruta-de-la-aplicación>\bin para que la aplicación web pueda acceder a él. El servidor Cassini se inicia con los parámetros (<ruta-de-la-aplicación>,/impots4).
6.5. Conclusion
Los ejemplos anteriores han mostrado, a partir de un caso concreto, los mecanismos que se utilizan habitualmente en el desarrollo web. Hemos utilizado sistemáticamente la arquitectura MVC por su interés didáctico. Se podrían haber abordado estos mismos ejemplos de otra manera y quizá de forma más sencilla sin esta arquitectura. Sin embargo, esta aporta grandes ventajas en cuanto la aplicación se vuelve un poco compleja y cuenta con múltiples páginas.
Podríamos continuar con nuestros ejemplos de diferentes maneras. He aquí algunas de ellas:
- el usuario podría querer conservar sus simulaciones a lo largo del tiempo. Realizaría simulaciones un día D y podría recuperarlas el día D+3, por ejemplo. Una posible solución a este problema es el uso de cookies. Sabemos que el token de sesión entre el servidor y un cliente se transmite mediante este mecanismo. También podríamos utilizarlo para transmitir las simulaciones entre el cliente y el servidor.
- Al mismo tiempo que el servidor envía la página con el resultado de las simulaciones, envía en sus encabezados HTTP una cookie que contiene una cadena de caracteres que representa las simulaciones. Dado que estas se encuentran en un objeto [ArrayList], es necesario realizar una transformación de dicho objeto a [String]. El servidor asignaría a la cookie un periodo de validez, por ejemplo, de 30 días.
- El navegador del cliente almacena las cookies recibidas en un archivo y las reenvía cada vez que realiza una solicitud al servidor que se las envió, siempre que sigan siendo válidas (que no se haya superado su vida útil). Para las simulaciones, el servidor recibirá una cadena de caracteres [String] que deberá transformar en el objeto [ArrayList].
Las cookies son gestionadas por [Response.Cookies] al enviarlas al cliente y por [Request.Cookies] al recibirlas en el servidor.
- El mecanismo anterior puede resultar bastante pesado si hay un gran número de simulaciones. Además, es frecuente que un usuario borre periódicamente sus cookies, eliminándolas todas, aunque por lo demás autorice a su navegador a utilizarlas. Por lo tanto, tarde o temprano se perderá la cookie de las simulaciones. Por ello, puede ser conveniente almacenarlas en el servidor en lugar de en el cliente, por ejemplo, en una base de datos. Para vincular las simulaciones a un usuario concreto, la aplicación podría comenzar con una fase de identificación en la que se solicite un nombre de usuario y una contraseña, almacenados a su vez en una base de datos o en cualquier otro tipo de repositorio de datos.
- También podríamos querer garantizar la seguridad del funcionamiento de nuestra aplicación. Actualmente, esta parte de dos supuestos:
- el usuario siempre pasa por el controlador [main.aspx]
- y, en ese caso, siempre utiliza las acciones propuestas en la página que se le ha enviado
¿Qué ocurre, por ejemplo, si el usuario solicita directamente la URL [http://localhost/impots4/formulaire.aspx]? Este caso es poco probable, ya que el usuario desconoce la existencia de esta URL. Sin embargo, hay que tenerlo en cuenta. Puede gestionarse mediante el controlador de aplicación [global.asax], que supervisa todas las solicitudes realizadas a la aplicación. De este modo, puede verificar que el recurso solicitado es efectivamente [main.aspx].
Un caso más probable es que un usuario no utilice las acciones presentes en la página que el servidor le ha enviado. Por ejemplo, ¿qué ocurre si el usuario solicita directamente la URL [http://localhost/impots4/main.aspx?action=retour] sin haber rellenado previamente el formulario? Probémoslo. Obtenemos la siguiente respuesta:

Se ha producido un fallo en el servidor. Es normal. Para la acción [retour], el controlador espera encontrar en la sesión un objeto [NameValueCollection] que represente los valores del formulario que debe mostrar. No los encuentra. El mecanismo del controlador permite ofrecer una solución elegante a este problema. Para cada solicitud, el controlador [main.aspx] puede comprobar que la acción solicitada es, efectivamente, una de las acciones de la página enviada previamente al usuario. Se puede utilizar el siguiente mecanismo:
- el controlador, antes de enviar su respuesta al cliente, almacena en la sesión de este una información que identifica dicha página
- cuando recibe una nueva solicitud del cliente, comprueba que la acción solicitada pertenece efectivamente a la última página enviada a dicho cliente
- la información que vincula las páginas y las acciones autorizadas en dichas páginas puede introducirse en el archivo de configuración [web.config] de la aplicación.
- La experiencia demuestra que los controladores de aplicaciones tienen una amplia base común y que es posible crear un controlador genérico, cuya especialización para una aplicación concreta se realiza mediante un archivo de configuración. Este es el enfoque que sigue, por ejemplo, la herramienta [Struts] en el ámbito de la programación web en Java.



