1. Parte 1
El PDF del documento está disponible |AQUÍ|.
Los ejemplos del documento están disponibles |AQUÍ|.
1.1. Introduction
Objetivos del artículo:
- escribir una aplicación web de tres capas [interface utilisateur, métier, accès aux données]
- configurar la aplicación con Spring IOC
- escribir diferentes versiones cambiando la implementación de cualquiera de las tres capas.
Herramientas utilizadas:
- Visual Studio.net para el desarrollo —véase el apartado 3.1 del anexo—;
- Servidor web Cassini para la ejecución —véase el anexo, párrafo 3.2—;
- Nunit para las pruebas unitarias – véase el apartado 3.4 del anexo;
- Spring para la integración y configuración de las capas de la aplicación web —véase el apartado 3.3 del anexo—;
En una escala de principiante-intermedio-avanzado, este documento se encuentra en el nivel [intermédiaire-avancé]. Para comprenderlo se requieren varios requisitos previos. Algunos de ellos pueden adquirirse en documentos que he escrito. En ese caso, los menciono. Es evidente que esto es solo una sugerencia y que el lector puede utilizar sus documentos favoritos.
- lenguaje VB.net: [Introducción al lenguaje VB.NET a través de ejemplos (2004)];
- programación web en VB.net: [Desarrollo web con ASP.NET 1.1 (2004)];
- uso del componente IoC de Spring: [Spring IoC para .NET (2005);
- documentación Spring.net: [Spring.NET | Homepage ]
Este documento sigue la línea de un documento escrito para Java [Arquitecturas de tres capas y arquitecturas MVC con Struts, Spring y Java (2005)]. En VB.NET, desarrollamos la aplicación web MVC de tres capas escrita en Java. La idea que queremos transmitir aquí es que las plataformas de desarrollo Java y .NET son lo suficientemente similares entre sí como para que los conocimientos adquiridos en cualquiera de estos dos ámbitos puedan reutilizarse en el otro.
No parece existir una solución de desarrollo MVC ASP.NET ampliamente reconocida. La siguiente solución retoma el método presentado en el documento [Desarrollo web con ASP.NET 1.1 (2004)]. Si bien esta tiene el mérito de utilizar conceptos comunes en el desarrollo J2EE, no debe interpretarse más allá de lo que es: c.a.d, un método entre otros de desarrollo MVC. Tan pronto como un método de desarrollo MVC en ASP.NET sea ampliamente aceptado, habrá que adoptar este último. La versión .NET de Spring, actualmente en desarrollo, bien podría ser una primera solución.
1.2. La aplicación webarticles
Aquí presentamos los elementos de una aplicación web simplificada de comercio electrónico. Esta permitirá a los usuarios de la web:
- consultar una lista de artículos provenientes de una base de datos
- agregar algunos de ellos a un carrito de compras
- confirmar la canasta. Esta confirmación tendrá como único efecto actualizar, en la base de datos, los inventarios de los artículos comprados.
Las diferentes vistas que se le mostrarán al usuario serán las siguientes:
- la vista «LISTE», que muestra una lista de los artículos a la venta ![]() | - la vista [INFOS], que brinda información adicional sobre un producto: ![]() |
- las vistas [PANIER] y [PANIERVIDE], que muestran el contenido del carrito del cliente
![]() | ![]() |
- la vista [ERREURS], que notifica cualquier error de la aplicación

1.3. Arquitectura general de la aplicación
Queremos desarrollar una aplicación con la siguiente estructura de tres capas:
![]() |
- Las tres capas son independientes gracias al uso de interfaces
- La integración de las diferentes capas se lleva a cabo mediante Spring
- cada capa cuenta con espacios de nombres separados: web (capa UI), dominio (capa de negocio) y DAO (capa de acceso a datos).
La aplicación seguirá una arquitectura MVC (Modelo - Vista - Controlador). Si retomamos el esquema de capas anterior, la arquitectura MVC se integra en él de la siguiente manera:
![]() |
El procesamiento de una solicitud de un cliente se lleva a cabo siguiendo estos pasos:
- el cliente envía una solicitud al controlador. Este controlador será, en este caso, una página .aspx a la que se le asignará un papel específico. Esta página recibe todas las solicitudes de los clientes. Es la puerta de entrada de la aplicación. Es la C de MVC.
- El controlador procesa esta solicitud. Para ello, puede necesitar la ayuda de la capa de negocio, lo que se conoce como el modelo M en la estructura MVC.
- El controlador recibe una respuesta de la capa de negocio. La solicitud del cliente ha sido procesada. Esto puede dar lugar a varias respuestas posibles. Un ejemplo clásico es
- una página de errores si la solicitud no se pudo procesar correctamente
- una página de confirmación, en caso contrario
- el controlador elige la respuesta (= vista) que se enviará al cliente. Esta suele ser una página que contiene elementos dinámicos. El controlador proporciona estos elementos a la vista.
- La vista se envía al cliente. Es la V de MVC.
1.4. El modelo
Aquí analizamos la M de MVC. El modelo se compone de los siguientes elementos:
- las clases de negocio
- las clases de acceso a datos
- la base de datos
1.4.1. La base de datos
La base de datos contiene únicamente una tabla llamada ARTICLES. Esta se generó con los siguientes comandos SQL:
CREATE TABLE ARTICLES (
ID INTEGER NOT NULL,
NOM VARCHAR(20) NOT NULL,
PRIX NUMERIC(15,2) NOT NULL,
STOCKACTUEL INTEGER NOT NULL,
STOCKMINIMUM INTEGER NOT NULL
);
/* restricciones */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
/* clave primaria */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
clave primaria que identifica un artículo de manera única | |
nombre del artículo | |
su precio | |
su stock actual | |
el nivel de existencias por debajo del cual se debe realizar un pedido de reabastecimiento |
1.4.2. Los espacios de nombres del modelo
El modelo M se presenta aquí en forma de dos espacios de nombres:
- istia.st.articles.dao: contiene las clases de acceso a los datos de la capa [dao]
- istia.st.articles.domain: contiene las clases de negocio de la capa [domain]
Cada uno de estos espacios de nombres se generará dentro de un archivo de «ensamblaje» propio:
contenu | rôle | |
- [IArticlesDao]: la interfaz de acceso a la capa [dao]. Esta es la única interfaz que ve la capa [domain]. No ve ninguna otra. - [Article]: clase que define un artículo - [ArticlesDaoArrayList]: clase de implementación de la interfaz [IArticlesDao] con una clase [ArrayList] | capa de acceso a datos - se encuentra íntegramente en la capa [dao] de la arquitectura de tres capas de la aplicación web | |
- [IArticlesDomain]: la interfaz de acceso a la capa [domain]. Es la única interfaz que ve la capa web. No ve ninguna otra. - [AchatsArticles]: una clase que implementa [IArticlesDomain] - [Achat]: clase que representa la compra de un cliente - [Panier]: clase que representa el conjunto de compras de un cliente | representa el modelo de las compras en la web — se encuentra íntegramente en la capa [domain] de la arquitectura Arquitectura de tres capas de la aplicación web |
1.4.3. La capa [dao]
La capa [dao] contiene los siguientes elementos:
-
[IArticlesDao]: la interfaz de acceso a la capa [dao]
-
[Article]: clase que define un artículo
-
[ArticlesDaoArrayList]: clase de implementación de la interfaz [IArticlesDao] con una clase [ArrayList]
La estructura del proyecto [Visual Studio] de la capa [dao] es la siguiente:

Comentarios:
- El proyecto [dao] es del tipo [bibliothèque de classes]
- Las clases se han colocado en una estructura de árbol con raíz en la carpeta [istia]. Todas se encuentran en el espacio de nombres [istia.st.articles.dao].
1.4.3.1. La clase [Article]
La clase que define un artículo es la siguiente:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' campos privados
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' ID de artículo
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
If Value <= 0 Then
Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
End If
Me._id = Value
End Set
End Property
' nombre del artículo
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If Value Is Nothing OrElse Value.Trim.Equals("") Then
Throw New Exception("Le champ nom [" + Value + "] est invalide")
End If
Me._nom = Value
End Set
End Property
' precio del artículo
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If Value < 0 Then
Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
End If
Me._prix = Value
End Set
End Property
' stock actual del artículo
Public Property stockactuel() As Integer
Get
Return _stockactuel
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
End If
Me._stockactuel = Value
End Set
End Property
' stock mínimo del artículo
Public Property stockminimum() As Integer
Get
Return _stockminimum
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
End If
Me._stockminimum = Value
End Set
End Property
' fabricante predeterminado
Public Sub New()
End Sub
' fabricante con propiedades
Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
Me.id = id
Me.nom = nom
Me.prix = prix
Me.stockactuel = stockactuel
Me.stockminimum = stockminimum
End Sub
' método de identificación del artículo
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Esta clase ofrece:
- un constructor que permite establecer los 5 datos de un artículo: [id, nom, prix, stockactuel, stockminimum]
- propiedades públicas que permiten leer y escribir los 5 datos.
- una verificación de los datos ingresados en el artículo. En caso de datos erróneos, se lanza una excepción.
- un método toString que permite obtener el valor de un artículo en forma de cadena de caracteres. Esto suele ser útil para la depuración de una aplicación.
1.4.3.2. La interfaz [IArticlesDao]
La interfaz [IArticlesDao] se define de la siguiente manera:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' lista de todos los artículos
Function getAllArticles() As IList
' agrega un artículo
Function ajouteArticle(ByVal unArticle As Article) As Integer
' elimina un artículo
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modifica un artículo
Function modifieArticle(ByVal unArticle As Article) As Integer
' busca un artículo
Function getArticleById(ByVal idArticle As Integer) As Article
' elimina todos los artículos
Sub clearAllArticles()
' cambia el inventario de un artículo
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
La función de los distintos métodos de la interfaz es la siguiente:
devuelve todos los artículos de la fuente de datos | |
vacía la fuente de datos | |
devuelve el objeto [Article] identificado por su clave primaria | |
permite agregar un artículo a la fuente de datos | |
permite modificar un artículo de la fuente de datos | |
permite eliminar un artículo de la fuente de datos | |
permite modificar el inventario de un artículo de la fuente de datos |
La interfaz pone a disposición de los programas clientes una serie de métodos definidos únicamente por sus firmas. No se ocupa de la forma en que estos métodos se implementarán realmente. Esto aporta flexibilidad a una aplicación. El programa cliente realiza sus llamadas a una interfaz y no a una implementación específica de la misma.
![]() |
La elección de una implementación específica se realizará mediante un archivo de configuración de Spring. Para demostrar que, al probar la aplicación web, lo único que importa es la interfaz de acceso a los datos y no su clase de implementación, primero implementaremos la fuente de datos mediante un objeto simple [ArrayList]. Posteriormente, presentaremos una solución basada en SGBD.
1.4.3.3. La clase de implementación [ArticlesDaoArrayList]
La clase de implementación [ArticlesDaoArrayList] se define de la siguiente manera:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Class ArticlesDaoArrayList
Implements istia.st.articles.dao.IArticlesDao
Private articles As New ArrayList
Private Const nbArticles As Integer = 4
' fabricante por defecto
Public Sub New()
' se fabrican algunos artículos
For i As Integer = 1 To nbArticles
articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
Next
End Sub
' lista de todos los artículos
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
' se devuelve la lista de artículos
SyncLock Me
Return articles
End SyncLock
End Function
' eliminación de todos los artículos
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' se vacía la lista de artículos
SyncLock Me
articles.Clear()
End SyncLock
End Sub
' obtener un artículo identificado por su clave
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' se busca el artículo en la lista
SyncLock Me
Dim ipos As Integer = posArticle(articles, idArticle)
If ipos <> -1 Then
Return CType(articles(ipos), Article)
Else
Return Nothing
End If
End SyncLock
End Function
' agregar un artículo a la lista de artículos
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' se agrega el artículo a la lista de artículos
SyncLock Me
' se verifica que no exista ya
Dim ipos As Integer = posArticle(articles, unArticle.id)
If ipos <> -1 Then
Throw New Exception("L'article d'id [" + unArticle.id.ToString + "] existe déjà")
End If
' se agrega el artículo
articles.Add(unArticle)
' se devuelve el resultado
Return 1
End SyncLock
End Function
' modificar un artículo
Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
' se modifica un artículo
SyncLock Me
' se verifica que exista
Dim ipos As Integer = posArticle(articles, articleNouveau.id)
' en caso de que no exista
If ipos = -1 Then Return 0
' existe: lo modificamos
articles(ipos) = articleNouveau
' se devuelve el resultado
Return 1
End SyncLock
End Function
' eliminar un artículo identificado por su clave
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' eliminación de un artículo
SyncLock Me
' se verifica si existe
Dim ipos As Integer = posArticle(articles, idArticle)
' en caso de que no exista
If ipos = -1 Then Return 0
' existe: se elimina
articles.RemoveAt(ipos)
' se devuelve el resultado
Return 1
End SyncLock
End Function
' modificar el inventario de un artículo identificado por su clave
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' modificar el stock de un artículo
SyncLock Me
' se verifica si existe
Dim ipos As Integer = posArticle(articles, idArticle)
' en caso de que no exista
If ipos = -1 Then Return 0
' existe: se modifica su stock si es posible
Dim unArticle As Article = CType(articles(ipos), Article)
' solo se modifica el stock si es suficiente
If unArticle.stockactuel + mouvement >= 0 Then
unArticle.stockactuel += mouvement
Return 1
Else
Return 0
End If
End SyncLock
End Function
' buscar un artículo identificado por su clave
Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
' devuelve la posición del artículo [idArticle] en la lista o -1 si no se encuentra
Dim unArticle As Article
For i As Integer = 0 To listArticles.Count - 1
unArticle = CType(listArticles(i), Article)
If unArticle.id = idArticle Then
Return i
End If
Next
' no encontrado
Return -1
End Function
End Class
End Namespace
Comentarios:
- la fuente de datos se simula mediante el campo privado [articles] de tipo [ArrayList]
- El constructor de la clase crea por defecto 4 entradas en la fuente de datos.
- Todos los métodos de acceso a los datos se han sincronizado para evitar problemas de acceso concurrente a la fuente de datos. En un momento dado, solo un hilo tiene acceso a un método específico.
- El método [posArticle] permite conocer la posición [0..N] en la fuente [ArrayList] de un artículo identificado por su número. Si el artículo no existe, el método devuelve la posición -1. Este método es utilizado repetidamente por los demás métodos.
- El método [ajouteArticle] permite agregar un artículo a la lista de artículos. Devuelve el número de artículos insertados: 1. Si el artículo ya existía, se lanza una excepción.
- El método [modifieArticle] permite modificar un artículo existente. Devuelve el número de artículos modificados: 1 si el artículo existía, 0 en caso contrario.
- El método [supprimeArticle] permite eliminar un artículo existente. Devuelve el número de artículos eliminados: 1 si el artículo existía, 0 en caso contrario.
- El método [getAllArticles] proporciona la lista de todos los artículos
- El método [getArticleById] permite obtener un artículo identificado por su número. Se obtiene el valor [nothing] si el artículo no existe.
- El código no presenta ninguna dificultad real. Dejamos que el lector lo revise y lo comprenda.
1.4.3.4. Generación del ensamblado de la capa [dao]
El proyecto de Visual Studio está configurado para generar el ensamblado [webarticles-dao.dll]. Este se genera en la carpeta [bin] del proyecto:
![]() | ![]() |
1.4.3.5. Pruebas NUnit de la capa [dao]
En Java, las clases se prueban con el marco [Junit]. En .NET, el marco Nunit ofrece las mismas posibilidades de pruebas unitarias:

La estructura del proyecto de pruebas en Visual Studio es la siguiente:

Comentarios:
- el proyecto [tests] es del tipo [bibliothèque de classes]
- las pruebas [NUnit] requieren una referencia al ensamblado [nunit.framework.dll]
- La clase de prueba [NUnit] obtiene una instancia del objeto a probar a través de Spring. Por lo tanto, se encuentra
- en la carpeta [bin], los archivos de clases de Spring
- en [References], una referencia al ensamblaje [Spring-Core.dll] de la carpeta [bin]
- en [bin], un archivo de configuración para Spring
- la clase de prueba necesita el ensamblado [webarticles-dao.dll] de la capa [dao]. Este se ha colocado en la carpeta [bin] y su referencia se ha agregado a las referencias del proyecto.
Una clase de prueba [NUnit] requiere acceso a las clases del espacio de nombres [NUnit.Framework]. Por lo tanto, contiene la siguiente instrucción de importación:
El espacio de nombres [NUnit.Framework] se encuentra en el «ensamblado» [nunit.framework.dll], que debemos agregar a las referencias del proyecto:
![]() | ![]() |
El ensamblado [nunit.framework.dll] debe aparecer en la lista propuesta si se ha instalado [Nunit]. Basta con hacer doble clic en el ensamblado para agregarlo al proyecto:

La clase de prueba [NUnit] de la capa [dao] podría ser la siguiente:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesArrayList
' el objeto a probar
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' se obtiene una instancia del generador de objetos de Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' se solicita la instanciación del objeto articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' verificación visual
listArticles()
End Sub
<Test()> _
Public Sub testClearAllArticles()
' se eliminan todos los artículos
articlesDao.clearAllArticles()
' se solicitan todos los artículos
Dim articles As IList = articlesDao.getAllArticles
' verificación: debe haber 0
Assert.AreEqual(0, articles.Count)
End Sub
<Test()> _
Public Sub testAjouteArticle()
' eliminación de todos los artículos
articlesDao.clearAllArticles()
' verificación: la tabla de artículos debe estar vacía
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' se agregan dos artículos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificación: debe haber dos artículos
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' verificación visual
listArticles()
End Sub
<Test()> _
Public Sub testSupprimeArticle()
' eliminación de todos los artículos
articlesDao.clearAllArticles()
' verificación: la tabla de artículos debe estar vacía
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' se agregan dos artículos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificación: debe haber 2 artículos
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' se elimina el artículo 4
articlesDao.supprimeArticle(4)
' verificación: debe quedar 1 artículo
articles = articlesDao.getAllArticles
Assert.AreEqual(1, articles.Count)
' verificación visual
listArticles()
End Sub
<Test()> _
Public Sub testModifieArticle()
' eliminación de todos los artículos
articlesDao.clearAllArticles()
' verificación
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' agregar 2 artículos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificación
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' búsqueda del artículo 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' verificación
Assert.AreEqual(unArticle.nom, "article3")
' búsqueda del artículo 4
unArticle = articlesDao.getArticleById(4)
' verificación
Assert.AreEqual(unArticle.nom, "article4")
' modificación del artículo 4
articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
' verificación
unArticle = articlesDao.getArticleById(4)
Assert.AreEqual(unArticle.prix, 44, 0.000001)
' verificación visual
listArticles()
End Sub
<Test()> _
Public Sub testGetArticleById()
' eliminación de artículos
articlesDao.clearAllArticles()
' verificación
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' agregación de 2 artículos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificación
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' búsqueda del artículo 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' verificación
Assert.AreEqual(unArticle.nom, "article3")
' búsqueda del artículo 4
unArticle = articlesDao.getArticleById(4)
' verificación
Assert.AreEqual(unArticle.nom, "article4")
End Sub
' lista en pantalla
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
<Test()> _
Public Sub testArticleAbsent()
' eliminación de todos los artículos
articlesDao.clearAllArticles()
' búsqueda de artículo 1
Dim article As article = articlesDao.getArticleById(1)
' verificación
Assert.IsNull(article)
' modificación de un artículo inexistente
Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
' no se ha modificado ninguna línea
Assert.AreEqual(i, 0)
' Eliminación de un artículo inexistente
i = articlesDao.supprimeArticle(1)
' no se ha eliminado ninguna línea
Assert.AreEqual(0, i)
End Sub
<Test()> _
Public Sub testChangerStockArticle()
' eliminación de todos los artículos
articlesDao.clearAllArticles()
' agregación de un artículo
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' se agregó un artículo
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' creación de 100 hilos
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' se crea el hilo i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' se establece el nombre del hilo
taches(i).Name = "tache_" & i
' se inicia la ejecución del hilo i
taches(i).Start()
Next
' se espera a que terminen todos los hilos
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' verificaciones: el artículo 3 debe tener un stock de 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' se reduce el stock del artículo 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' verificación: su stock no debe haber cambiado
Assert.AreEqual(0, nbLignes)
' verificación visual
listArticles()
End Sub
Public Sub décrémente()
' hilo iniciado
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' el hilo reduce el inventario
articlesDao.changerStockArticle(3, -1)
' hilo finalizado
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
End Class
End Namespace
Comentarios:
- Queríamos escribir un programa de prueba para la interfaz [IArticlesDao] que fuera independiente de la clase de implementación de la misma. Por ello, utilizamos Spring para ocultar al programa de prueba el nombre de la clase de implementación.
- El método de atributo <Setup()> obtiene de Spring una referencia al objeto [articlesdao] que se va a probar. Este se define en el siguiente archivo [spring-config.xml]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>
Este archivo indica el nombre de la clase de implementación [istia.st.articles.dao.ArticlesDaoArrayList] de la interfaz [IArticlesDao] y dónde encontrarla: [ webarticles-dao.dll]. Dado que la instanciación no requiere parámetros, aquí no se define ninguno.
- La mayoría de las pruebas son fáciles de entender. Se invita al lector a leer los comentarios.
- El método [testChangerStockArticle] requiere algunas explicaciones. Crea 100 subprocesos encargados de reducir el inventario de un artículo determinado.
<Test()> _
Public Sub testChangerStockArticle()
' eliminación de todos los artículos
articlesDao.clearAllArticles()
' agregación de un artículo
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' se agrega un artículo
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' creación de 100 hilos
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' se crea el hilo i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' se establece el nombre del hilo
taches(i).Name = "tache_" & i
' se inicia la ejecución del hilo i
taches(i).Start()
Next
' se espera a que terminen todos los hilos
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' verificaciones: el artículo 3 debe tener un stock de 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' se reduce el stock del artículo 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' verificación: su stock no debe haber cambiado
Assert.AreEqual(0, nbLignes)
' verificación visual
listArticles()
End Sub
El objetivo aquí es probar los accesos simultáneos a la fuente de datos. El método encargado de actualizar el inventario es el siguiente:
Public Sub décrémente()
' se inicia el hilo
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' el hilo reduce el stock
articlesDao.changerStockArticle(3, -1)
' hilo finalizado
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
Disminuye en una unidad el inventario del artículo n.º 3. Si nos remitimos al código del método [testChangerStockArticle], vemos que:
- el stock del artículo n.º 3 se inicializa en 101
- los 100 hilos reducirán este stock en una unidad cada uno
- por lo tanto, debemos tener un stock de 1 al final de la ejecución de todos los hilos
Por otra parte, también en este método, se intenta que el stock del artículo n.º 4 pase a un valor negativo. Esto debe fallar.
Para probar la capa [dao], generamos DLL y [tests-webarticles-dao.dll] en la carpeta [bin] del proyecto [tests]:
![]() | ![]() |
Luego, con la aplicación [Nunit-Gui], cargamos este archivo DLL y ejecutamos las pruebas:

En la ventana de la izquierda, se ve la lista de los métodos probados. El color del punto que precede al nombre de cada método indica si el método se ha ejecutado correctamente (verde) o ha fallado (rojo). El lector que vea este documento en pantalla podrá observar que todas las pruebas se han superado. A partir de ahora, consideraremos que contamos con una capa [dao] operativa.
1.4.4. La capa [domain]
La capa [domain] contiene los siguientes elementos:
-
[IArticlesDomain]: la interfaz de acceso a la capa [domain]
-
[Achat]: clase que define una compra
-
[Panier]: clase que define una canasta de compras
-
[AchatsArticles]: clase de implementación de la interfaz [IArticlesDomain]
La estructura de la solución [Visual Studio] de la capa [domain] es la siguiente:

Comentarios:
- el proyecto [domain] es del tipo [bibliothèque de classes]
- Las clases se han colocado en una estructura de árbol con raíz en la carpeta [istia]. Todas se encuentran en el espacio de nombres [istia.st.articles.domain].
- La clase DLL de la capa [dao] se ha colocado en la carpeta [bin] del nuevo proyecto. Además, esta clase DLL se ha agregado como referencia al proyecto.
1.4.4.1. La interfaz [IArticlesDomain]
La interfaz [IArticlesDomain] desacopla la capa [métier] de la capa [web]. Esta última accede a la capa [métier/domain] a través de esta interfaz sin preocuparse por la clase que realmente la implementa. La interfaz define las siguientes acciones para acceder a la capa de negocio:
Imports Article = istia.st.articles.dao.Article
Namespace istia.st.articles.domain
Public Interface IArticlesDomain
' métodos
Sub acheter(ByVal panier As Panier)
Function getAllArticles() As IList
Function getArticleById(ByVal idArticle As Integer) As Article
ReadOnly Property erreurs() As ArrayList
End Interface
End Namespace
devuelve la lista de objetos [Article] de la fuente de datos asociada | |
devuelve el objeto [Article] identificado por [idArticle] | |
valida el carrito del cliente restando de los inventarios de los artículos comprados la cantidad adquirida; puede fallar si el inventario es insuficiente | |
devuelve la lista de errores que se han producido; queda vacía si no hay errores |
1.4.4.2. La clase [Achat]
La clase [Achat] representa una compra del cliente:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class Achat
' campos privados
Private _article As article
Private _qte As Integer
' constructor por defecto
Public Sub New()
End Sub
' constructor con parámetros
Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
' se hace a través de las propiedades
Me.article = unArticle
Me.qte = qte
End Sub
' artículo comprado
Public Property article() As article
Get
Return _article
End Get
Set(ByVal Value As article)
_article = Value
End Set
End Property
' cantidad comprada
Public Property qte() As Integer
Get
Return _qte
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Quantité [" + Value.ToString + "] invalide")
End If
_qte = Value
End Set
End Property
' total de la compra
Public ReadOnly Property totalAchat() As Double
Get
Return _qte * _article.prix
End Get
End Property
' identidad
Public Overrides Function ToString() As String
Return "[" + _article.ToString + "," + _qte.ToString + "]"
End Function
End Class
End Namespace
Comentarios:
- La clase [Achat] tiene las siguientes propiedades y métodos:
el artículo comprado | |
la cantidad comprada | |
el monto de la compra | |
cadena de identidad del objeto |
- cuenta con un constructor que permite inicializar las propiedades [article, qte] que definen una compra.
1.4.4.3. La clase [Panier]
La clase [Panier] representa el conjunto de compras del cliente:
Namespace istia.st.articles.domain
Public Class Panier
' campos privados
Private _achats As New ArrayList
Private _totalPanier As Double = 0
' fabricante predeterminado
Public Sub New()
End Sub
' lista de compras
Public ReadOnly Property achats() As ArrayList
Get
Return _achats
End Get
End Property
' total de compras
Public ReadOnly Property totalPanier() As Double
Get
Return _totalPanier
End Get
End Property
' métodos
Public Sub ajouter(ByVal unAchat As Achat)
' se verifica si la compra ya existe
Dim iAchat As Integer = posAchat(unAchat.article.id)
If iAchat <> -1 Then
' se encontró
Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
achatCourant.qte += unAchat.qte
Else
' no se ha encontrado
_achats.Add(unAchat)
End If
' se incrementa el total del carrito
_totalPanier += unAchat.totalAchat
End Sub
' eliminar un artículo
Public Sub enlever(ByVal idAchat As Integer)
' se busca el artículo
Dim iachat As Integer = posAchat(idAchat)
' si se encontró, se elimina
If iachat <> -1 Then
Dim achatCourant As Achat = CType(_achats(iachat), Achat)
' se elimina de la canasta
_achats.RemoveAt(iachat)
' se resta del total de la canasta
_totalPanier -= achatCourant.totalAchat
End If
End Sub
Private Function posAchat(ByVal idArticle As Integer) As Integer
' busca una compra en la lista de compras
' devuelve su posición en la lista o -1 si no se encuentra
Dim achatCourant As Achat
Dim trouvé As Boolean = False
Dim i As Integer = 0
While Not trouvé AndAlso i < _achats.Count
' compra actual
achatCourant = CType(_achats(i), Achat)
' comparación con el artículo buscado
If achatCourant.article.id = idArticle Then
Return i
End If
'compra siguiente
i += 1
End While
' no encontrado
Return -1
End Function
' función de identidad
Public Overrides Function ToString() As String
Return _achats.ToString
End Function
End Class
End Namespace
Comentarios:
- La clase [Panier] tiene las siguientes propiedades y métodos:
la lista de compras del cliente: lista de objetos de tipo [Achat] | |
Agrega una compra a la lista de compras | |
elimina la compra del artículo idAchat | |
el monto total de las compras en el carrito | |
devuelve la cadena de identidad de la canasta |
- El método [posAchat] es un método de utilidad que permite obtener la posición en la lista de compras de una compra identificada por el número del artículo comprado. La lista de compras se administra de tal manera que un artículo comprado varias veces ocupa solo una posición en la lista. De este modo, una compra puede identificarse mediante el número del artículo comprado. El método [posAchat] devuelve -1 si la compra buscada no existe.
- El método [ajouter] agrega una nueva compra a la lista de compras. Esto equivale a agregar una nueva entrada a la lista de compras si el artículo comprado aún no existía en la lista, o a incrementar la cantidad comprada si ya existía.
- El método [enlever] permite eliminar una compra identificada por un número de la lista de compras. Si la compra no existe, el método no genera ningún mensaje y no realiza ninguna acción.
- El monto de las compras [totalPanier] se mantiene a medida que se agregan y eliminan compras.
1.4.4.4. La clase [AchatsArticles]
La interfaz [IArticlesDomain] será implementada por la siguiente clase [AchatsArticles]:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class AchatsArticles
Implements IArticlesDomain
'campos privados
Private _articlesDao As IArticlesDao
Private _erreurs As ArrayList
' constructor
Public Sub New(ByVal articlesDao As IArticlesDao)
_articlesDao = articlesDao
End Sub
' lista de errores
Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
Get
Return _erreurs
End Get
End Property
' lista de artículos
Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
' lista de todos los artículos
Try
Return _articlesDao.getAllArticles
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' obtener un artículo identificado por su número
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
' un artículo específico
Try
Return _articlesDao.getArticleById(idArticle)
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' comprar un carrito
Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
' compra de un carrito: se deben restar las existencias de los artículos comprados
_erreurs = New ArrayList
Dim achat As achat
Dim achats As ArrayList = panier.achats
For i As Integer = achats.Count - 1 To 0 Step -1
' reducir el inventario del artículo i
achat = CType(achats(i), achat)
Try
If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
' No se pudo realizar la operación
_erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
Else
' La operación se realizó: se elimina la compra del carrito
panier.enlever(achat.article.id)
End If
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
Next
End Sub
End Class
End Namespace
Comentarios:
- esta clase implementa los cuatro métodos de la interfaz [IArticlesDomain]. Cuenta con dos campos privados:
el objeto de acceso a los datos | |
la lista de posibles errores. Se puede acceder a ella a través de la propiedad pública [erreurs] |
- Para crear una instancia de la clase, se debe proporcionar el objeto que permite el acceso a los datos:
- Los métodos [getAllArticles] y [getArticleById] se basan en los métodos del mismo nombre de la capa [dao]
- El método [acheter] valida la compra de un carrito. Esta validación consiste simplemente en restar de los inventarios los artículos comprados. La compra de un artículo solo es posible si el stock lo permite. Si no es así, la compra se rechaza: el artículo permanece en el carrito y se reporta un error en la lista [erreurs]. Una compra validada se retira del carrito y el stock del artículo correspondiente se reduce en la cantidad comprada.
1.4.4.5. Generación del ensamblado de la capa [domain]
El proyecto de Visual Studio está configurado para generar el ensamblado [webarticles-domain.dll]. Este se genera en la carpeta [bin] del proyecto:
![]() | ![]() |
1.4.4.6. Pruebas NUnit de la capa [domain]
La estructura del proyecto de prueba de Visual Studio es la siguiente:

Comentarios:
- el proyecto [tests] es del tipo [bibliothèque de classes]
- Las pruebas [NUnit] requieren una referencia al ensamblado [nunit.framework.dll]
- La clase de prueba [NUnit] obtiene una instancia del objeto a probar a través de Spring. Por lo tanto, se encuentra
- en la carpeta [bin], los archivos de clases de Spring
- en [References], una referencia al ensamblado [Spring-Core.dll] de la carpeta [bin]
- en [bin], un archivo de configuración para Spring
- la clase de prueba necesita el ensamblado [webarticles-dao.dll] de la capa [dao] y el ensamblado [webarticles-domain.dll] de la capa [domain]. Estos se han colocado en la carpeta [bin] y sus referencias se han agregado a las referencias del proyecto.
Una clase de prueba NUnit de la capa [domain] podría ser la siguiente:
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports istia.st.articles.domain
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesDomain
' el objeto a probar
Private articlesDomain As IArticlesDomain
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' se obtiene una instancia del generador de objetos de Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' se solicita la instanciación del objeto DAO de artículos
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
' y luego la del objeto «articlesdomain»
articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
End Sub
<Test()> _
Public Sub getAllArticles()
' verificación visual
listArticles()
End Sub
<Test()> _
Public Sub getArticleById()
' eliminación de los artículos
articlesDao.clearAllArticles()
' verificación
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' agregación de 2 artículos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificación
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' búsqueda del artículo 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' verificación
Assert.AreEqual(unArticle.nom, "article3")
' búsqueda del artículo 4
unArticle = articlesDao.getArticleById(4)
' verificación
Assert.AreEqual(unArticle.nom, "article4")
End Sub
<Test()> _
Public Sub acheterPanier()
' eliminación de artículos
articlesDao.clearAllArticles()
' verificación
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' agregar 2 artículos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificación
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' creación de un carrito con dos compras
Dim panier As New panier
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 10))
panier.ajouter(New Achat(New Article(4, "article4", 40, 40, 4), 10))
' verificaciones
Assert.AreEqual(700, panier.totalPanier, 0.000001)
Assert.AreEqual(2, panier.achats.Count)
' confirmación del carrito
articlesDomain.acheter(panier)
' verificaciones
Assert.AreEqual(0, articlesDomain.erreurs.Count)
Assert.AreEqual(0, panier.achats.Count)
' búsqueda del artículo 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' verificación
Assert.AreEqual(unArticle.stockactuel, 20)
' búsqueda de artículo 4
unArticle = articlesDao.getArticleById(4)
' verificación
Assert.AreEqual(unArticle.stockactuel, 30)
' nueva canasta
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
' confirmación del carrito
articlesDomain.acheter(panier)
' verificaciones
Assert.AreEqual(1, articlesDomain.erreurs.Count)
' búsqueda de artículo 3
unArticle = articlesDomain.getArticleById(3)
' verificación
Assert.AreEqual(unArticle.stockactuel, 20)
End Sub
<Test()> _
Public Sub testRetirerAchats()
' eliminación del contenido de ARTICLES
articlesDao.clearAllArticles()
' lee la tabla ARTICLES
Dim articles As IList = articlesDao.getAllArticles()
Assert.AreEqual(0, articles.Count)
' inserción
Dim article3 As New Article(3, "article3", 30, 30, 3)
articlesDao.ajouteArticle(article3)
Dim article4 As New Article(4, "article4", 40, 40, 4)
articlesDao.ajouteArticle(article4)
' lee la tabla ARTICLES
articles = articlesDomain.getAllArticles()
Assert.AreEqual(2, articles.Count)
' creación de un carrito con dos compras
Dim monPanier As New Panier
monPanier.ajouter(New Achat(article3, 10))
monPanier.ajouter(New Achat(article4, 10))
' verificaciones
Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
Assert.AreEqual(2, monPanier.achats.Count)
' agregar un artículo ya comprado
monPanier.ajouter(New Achat(article3, 10))
' verificaciones
' el total debe aumentarse a 1000
Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
' Sigue habiendo 2 artículos en el carrito
Assert.AreEqual(2, monPanier.achats.Count)
' La cantidad del artículo 3 debe haber cambiado a 20
Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
Assert.AreEqual(20, unAchat.qte)
' se retira el artículo 3 del carrito
monPanier.enlever(3)
' verificaciones
' el total debe haber cambiado a 400
Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
' Solo un artículo en el carrito
Assert.AreEqual(1, monPanier.achats.Count)
' debe ser el artículo n.º 4
Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
End Sub
' lista en pantalla
Private Sub listArticles()
Dim articles As IList = articlesDomain.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
End Namespace
Comentarios:
- Queríamos escribir un programa de prueba para la interfaz [IArticlesDomain] que fuera independiente de la clase de implementación de la misma. Por ello, utilizamos Spring para ocultar al programa de prueba el nombre de la clase de implementación.
- El método de atributo <Setup()> obtiene de Spring una referencia a los objetos [articlesdomain] y [articlesdao] que se van a probar. Estos se definen en el siguiente archivo [spring-config.xml]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
<object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
<constructor-arg index="0">
<ref object="articlesdao" />
</constructor-arg>
</object>
</objects>
Este archivo indica:
- (continuación)
- para el singleton [articlesdao], el nombre de la clase de implementación [istia.st.articles.dao.ArticlesDaoArrayList] y dónde encontrarla: [ webarticles-dao.dll]. Dado que la instanciación no requiere parámetros, aquí no se define ninguno.
- Para el singleton [articlesdomain], el nombre de la clase de implementación es [istia.st.articles.domain.AchatsArticles] y se encuentra en [ webarticles-domain.dll]. La clase [AchatsArticles] tiene un constructor con un parámetro: el singleton que administra el acceso a la capa [dao]. Aquí, este se define como el singleton [articlesdao] definido anteriormente.
- La clase de prueba obtiene una instancia de la clase a probar [articlesdomain], así como una instancia de la clase de acceso a datos [articlesdao]. Este último punto es controvertido. En teoría, la clase de prueba no debería necesitar tener acceso a la capa [dao], que ni siquiera se supone que conozca. En este caso, hemos hecho caso omiso de esta «ética» que, de haberse respetado, nos habría obligado a crear nuevos métodos en nuestra interfaz [IArticlesDomain].
Para probar la capa [domain], generamos DLL y [tests-webarticles-domain.dll] en la carpeta [bin] del proyecto [tests]:
![]() | ![]() |
Luego, con la aplicación [Nunit-Gui], cargamos este archivo DLL y ejecutamos las pruebas:

El lector que visualice este documento en pantalla podrá ver que todas las pruebas se han realizado con éxito. A partir de ahora, consideraremos que contamos con una capa [domain] operativa.
1.4.5. Conclusión
Recordemos que queremos construir la siguiente aplicación web de tres capas:
![]() |
El modelo M de nuestra aplicación MVC ya está escrito y probado. Se nos proporciona en dos archivos: DLL y [webarticles-dao.dll, webarticles-domain.dll]. Podemos pasar a la última capa, la capa [web], que contiene el controlador C y las vistas V. En primer lugar, consideraremos un método presentado en el documento [Développement WEB avec ASP.NET 1.1 ]
- el controlador C está implementado en dos archivos: [global.asax, main.aspx]
- las vistas V están implementadas mediante páginas aspx
1.5. La capa [web]
La arquitectura MVC de la aplicación web será la siguiente:
![]() |
las clases de negocio [domain], las clases de acceso a datos [dao] y la fuente de datos | |
las páginas ASPX | |
todas las solicitudes de los clientes HTTP pasan por los siguientes dos controladores: global.asax: gestiona los eventos relacionados con el inicio inicial de la aplicación main.aspx: procesa individualmente la solicitud de cada cliente |
1.5.1. Las vistas
Las vistas son las mismas que se presentaron al inicio del documento:
liste.aspx | Las vistas se encuentran en la carpeta [vues] de la aplicación ![]() | |
infos.aspx | ||
panier.aspx | ||
paniervide.aspx | ||
erreurs.aspx |
1.5.2. Los controladores
Como se ha indicado, el controlador estará compuesto por dos elementos:
- [global.asax,global.asax.vb]: se utiliza principalmente para inicializar la aplicación e incorporar al contexto de esta todos los datos que se compartirán entre los distintos clientes
- [main.aspx, main.aspx.vb]: el controlador propiamente dicho, el que procesa las solicitudes HTTP de los clientes.
Las diferentes solicitudes de los clientes se dirigirán al controlador [main.aspx] y contendrán un parámetro llamado [action] que especifica la acción solicitada por el cliente:
solicitud | significado | acción del controlador | respuestas posibles |
El cliente quiere la lista de artículos | - solicita la lista de artículos a la capa de negocio | - [LISTE] - [ERREURS] | |
El cliente solicita información sobre uno de los artículos que aparecen en la vista [LISTE] | - solicita el artículo a la capa de negocio | - [INFOS] - [ERREURS] | |
el cliente compra un artículo | - solicita el artículo a la capa de negocio y lo agrega a la canasta del cliente | - [INFOS] si hay un error de cantidad - [LISTE] si no hay error | |
el cliente quiere eliminar un compra de su carrito | - recupera el carrito de la sesión y la modifica | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
El cliente quiere ver su carrito | - recupera el carrito de la sesión | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
El cliente ha terminado sus compras y pasa a la etapa de pago | - actualiza en la base de datos los inventarios de los artículos comprados - vacía el carrito del cliente de los artículos cuya compra se ha validado | - [LISTE] - [ERREURS] |
1.5.3. Configuración de la aplicación
Buscaremos configurar la aplicación de manera que sea lo más flexible posible ante cambios tales como:
- el cambio de las clases URL de las distintas vistas
- el cambio de las clases que implementan las interfaces [IArticlesDao] y [IArticlesDomain]
- el cambio del SGBD, de la base de datos, de la tabla de artículos
1.5.3.1. Los cambios en URL
Los nombres de las vistas URL se colocarán en el archivo de configuración de la aplicación [web.config] junto con algunos otros parámetros:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
..
<appSettings>
<add key="urlMain" value="/webarticles/main.aspx"/>
<add key="urlInfos" value="vues/infos.aspx"/>
<add key="urlErreurs" value="vues/erreurs.aspx"/>
<add key="urlListe" value="vues/liste.aspx"/>
<add key="urlPanier" value="vues/panier.aspx"/>
<add key="urlPanierVide" value="vues/paniervide.aspx"/>
</appSettings>
</configuration>
1.5.3.2. El cambio de las clases de implementación de las interfaces
De acuerdo con el principio de las arquitecturas de tres capas, las capas deben ser independientes unas de otras. Esta independencia se logra de la siguiente manera:
- las capas se comunican entre sí a través de interfaces y no mediante clases concretas
- el código de una capa nunca instancia por sí mismo la clase de otra capa para utilizarla. Simplemente solicita a una herramienta externa, en este caso Spring, una instancia de implementación de la interfaz de la capa que desea utilizar. Para ello, sabemos que no necesita conocer el nombre de la clase de implementación, sino solo el nombre del singleton de Spring del cual desea una referencia.
En nuestra aplicación, Spring se configurará en el archivo [web.config] de la aplicación web de la siguiente manera:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
<resource uri="config://spring/objects" />
</context>
<objects>
<object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
<object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
<constructor-arg index="0">
<ref object="articlesDao" />
</constructor-arg>
</object>
</objects>
</spring>
<appSettings>
<add key="urlMain" value="/webarticles/main.aspx"/>
<add key="urlInfos" value="vues/infos.aspx"/>
<add key="urlErreurs" value="vues/erreurs.aspx"/>
<add key="urlListe" value="vues/liste.aspx"/>
<add key="urlPanier" value="vues/panier.aspx"/>
<add key="urlPanierVide" value="vues/paniervide.aspx"/>
</appSettings>
</configuration>
Para tener acceso a la capa [métier], una clase de la capa [web] podrá solicitar el singleton [articlesDomain]. Spring creará entonces un objeto de tipo [istia.st.articles.domain.AchatsArticles]. Para esta instanciación, necesita un objeto de tipo [articlesDao], es decir, un objeto de tipo [istia.st.articles.dao.ArticlesDaoArrayList]. Spring creará entonces un objeto de ese tipo. Al final de la operación, la capa [web], que solicitó el singleton [articlesDomain], cuenta con toda la cadena que la conecta con la fuente de datos:
![]() |
1.5.3.3. Los cambios relacionados con el SGBD o con la base de datos
Este punto se omitirá aquí, ya que nos encontramos en una aplicación de prueba sin SGBD. En una segunda etapa abordaremos la implementación de una capa [dao] basada en un SGBD.
1.5.4. La biblioteca de etiquetas <asp:>
Consideremos la vista [ERREURS] que muestra una lista de errores:

La vista [ERREURS] se encarga de mostrar una lista de errores que el controlador [main.aspx] ha colocado en el contexto de la solicitud con el nombre [context.Items("erreurs")]. Hay varias formas de escribir una página de este tipo. Aquí nos enfocamos únicamente en la parte de visualización de los errores.
Recordemos que una página ASPX tiene una parte de presentación HTML y una parte de código .NET que prepara los datos que la parte de presentación debe mostrar. Estas dos partes pueden estar en un mismo archivo [aspx] (solución WebMatrix) o en dos archivos: [aspx] para la presentación y [aspx.vb] para el código. Esta última solución es la que utiliza Visual Studio. Para complicar aún más las cosas, la parte de presentación HTML también puede contener código .NET, lo que tiende a difuminar la separación entre [contrôleur] y [présentation] a simple vista. Por lo general, se desaconseja encarecidamente esta solución. Para eliminar todo el código de la sección [présentation] fue necesario crear bibliotecas de etiquetas. Estas «ocultan» el código bajo la apariencia de etiquetas similares a las de HTML. Presentamos dos posibles soluciones para la página [ERREURS].
Nuestra primera solución utiliza el código .NET en la sección [présentation] de la página. La página ASPX recupera la lista de errores presente en la solicitud en su sección de controlador [erreurs.aspx.vb]:
Protected erreurs As ArrayList
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' se recuperan los errores
erreurs = CType(context.Items("erreurs"), ArrayList)
End Sub
y luego lo muestra en la sección [présentation, erreurs.aspx]:
<h2>Les erreurs suivantes se sont produites :</h2>
<ul>
<%
for i as integer=0 to erreurs.count-1
response.write("<li>" & erreurs(i).ToString & "</li>")
next
%>
</ul>
La segunda solución utiliza la etiqueta <asp:repeater> de la biblioteca de etiquetas <asp:> de ASP.NET. Si se crea una página ASPX de manera gráfica, esta etiqueta está disponible como un componente de servidor que se coloca en el formulario de diseño. Si se escribe el código ASPX manualmente, se puede hablar de una biblioteca de etiquetas.
Con la biblioteca de etiquetas <asp:>, el código ASPX de la vista [ERREURS] anterior queda así:
<asp:Repeater id="rptErreurs" runat="server">
<HeaderTemplate>
<h3>Les erreurs suivantes se sont produites :
</h3>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
La etiqueta
sirve para repetir un patrón HTML en los distintos elementos de una fuente de datos. Sus distintos elementos son los siguientes:
el patrón HTML que se mostrará antes de que se muestren los elementos de la fuente de datos | |
el motivo HTML que se repetirá para cada uno de los elementos de la fuente de datos. La expresión [<%# Container.DataItem %>] sirve para mostrar el valor del elemento actual de la fuente de datos | |
el patrón HTML que se mostrará después de que se hayan mostrado los elementos de la fuente de datos |
La fuente de datos se vincula a la etiqueta, generalmente en la sección [contrôleur] de la página:
Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
..
' se vinculan los errores a rptErreurs
With rptErreurs
.DataSource = context.Items("erreurs")
.DataBind()
End With
End Sub
Esta vinculación también se puede realizar al momento del diseño de la página si ya se conoce la fuente de datos, por ejemplo, una base de datos existente.
En nuestras vistas utilizaremos otra etiqueta: <asp:datagrid>, que permite mostrar una fuente de datos en forma de tabla.
1.5.5. Estructura de la solución de Visual Studio de la aplicación [webarticles]
Una aplicación web es un rompecabezas con muchos elementos. Dotarla de una arquitectura MVC suele aumentar el número de estos. La estructura de la aplicación [webarticles] en [Visual Studio] es la siguiente:
![]() | ![]() | ![]() |
![]() |
Comentarios:
- el proyecto [web] es del tipo [bibliothèque de classes] y no del tipo [Application web ASP.NET], como cabría esperar lógicamente. El tipo [Application web ASP.NET] requiere que el servidor web IIS esté presente en la máquina de desarrollo o en una máquina remota. El servidor IIS no viene instalado de serie en las computadoras con Windows XP Familiar. Sin embargo, muchas computadoras PC se venden con esta versión. Para que los lectores que cuenten con Windows XP puedan implementar la aplicación analizada, utilizaremos el servidor web Cassini (ver anexo), disponible de forma gratuita en Microsoft, y reemplazaremos el proyecto [Application web ASP.NET] por un proyecto [bibliothèque de classes]. Esto conlleva algunos inconvenientes que se explican en los anexos.
- Los archivos DLL utilizados por la aplicación son los siguientes:
reúne las clases de la capa de acceso a datos | |
agrupa las clases de la capa de negocio | |
contiene las clases de Spring que nos permiten integrar las capas web, de dominio y DAO | |
clases de registro: utilizadas por Spring |
Estos DLL se colocan en la carpeta [bin] y se agregan a las referencias del proyecto.
1.5.6. Las vistas ASPX
Tal como se recomendó anteriormente, utilizaremos la biblioteca de etiquetas <asp:> en nuestras vistas ASPX.
1.5.6.1. El componente de usuario [entete.ascx]
Para dar cierta uniformidad a las diferentes vistas, estas compartirán un mismo encabezado, el que muestra el nombre de la aplicación junto con el menú:
![]() | ![]() |
El menú es dinámico y lo define el controlador. Este incluye en la solicitud enviada a la página ASPX un atributo de clave «actions» cuyo valor asociado es una matriz de elementos de tipo Hashtable(). Cada elemento de esta matriz es un diccionario destinado a generar una opción del menú del encabezado. Cada diccionario tiene dos claves:
- href: el enlace URL asociado a la opción del menú
- enlace: el texto del menú
Convertiremos el encabezado en un control de usuario. Un control de usuario encapsula una parte de la página (presentación y código asociado) en un componente que luego se puede reutilizar en otras páginas. En este caso, queremos reutilizar el componente [entete] en las demás vistas de la aplicación. El código de presentación estará en [entete.ascx] y el código de control asociado en [entete.ascx.vb]. El código de presentación utilizará un componente <asp:repeater> para mostrar la tabla de opciones del menú:
![]() |
n° | type | nom | rôle |
1 | repetidor | rptMenu fuente de datos: una tabla de diccionarios con dos claves: href, enlace | Mostrar las opciones del menú |
El código de presentación de la página será el siguiente:
Comentarios:
- el componente [repeater] se define en las líneas 6 a 14
- cada elemento de la fuente de datos asociada al repetidor es un diccionario con dos claves: href —línea 9— y enlace —línea 10—
El código de control asociado será el siguiente:
Comentarios:
- El componente de tipo [EnteteWebArticles] tiene una propiedad pública [actions] de solo escritura —línea 7
- esta propiedad permite asociar al componente <asp:repeater> denominado [rptMenu] —línea 10— la tabla de opciones calculada por el controlador de la aplicación —líneas 11-12—.
Las demás vistas de la aplicación utilizarán el encabezado definido por [entete.ascx]. La página [erreurs.aspx], por ejemplo, incluirá el encabezado mediante el siguiente código:
Comentarios:
- La línea 1 indica que la etiqueta <WA:entete> deberá asociarse al componente definido por el archivo [entete.ascx]. Los atributos [TagPrefix] y [TagName] pueden definirse libremente.
- Una vez hecho esto, la inserción del componente en el código de presentación de la página se realiza con la línea 9. Al ejecutarse, esta etiqueta tendrá como efecto incluir en el código de la página ASPX —que la contiene— el código de la página [entete.ascx]. El código de control [erreurs.aspx.vb] se encargará de inicializar este componente. Puede hacerlo de la siguiente manera:
Comentarios:
- la línea 6 crea un objeto de tipo [EnteteWebArticles], que es el tipo del componente creado
- la línea 11 inicializa la propiedad [actions] de este objeto
1.5.6.2. La vista [liste.aspx]
1.5.6.2.1. Introduction
Esta vista muestra la lista de artículos disponibles para la venta:
![]() | ![]() |
Se muestra tras una solicitud /main?action=liste o /main?action=validationpanier. Los elementos de la solicitud del controlador son los siguientes:
objeto Hashtable() — la tabla de opciones del menú | |
ArrayList de objetos del tipo [Article] | |
objeto String: mensaje que se mostrará al pie de página |
Cada enlace [Infos] de la tabla HTML de artículos tiene un URL con el formato [?action=infos&id=ID], donde ID es el campo id delartículo que se muestra.
1.5.6.2.2. Componentes de la página
![]() |
n° | type | nom | rôle |
componente de usuario | encabezado | mostrar el encabezado | |
DataGrid | DataGridArticles 3 - columna relacionada: encabezado: Nombre, campo: nombre 4 - columna relacionada: encabezado: Precio, campo: precio 5 - columna de hipervínculo: texto: Información, campo URL: id, formato URL: /webarticles/main.aspx?action=infos&id={0} | mostrar los artículos en venta | |
etiqueta | lblMessage | Mostrar un mensaje |
Recordemos cómo proceder para definir estas propiedades:
- en Visual Studio, seleccionamos el [DataGrid] para acceder a su hoja de propiedades:

- utilizamos el enlace [Mise en forme automatique] anterior para administrar el formato de la tabla que se muestra
- y el enlace [Générateur de propriétés] para gestionar su contenido
1.5.6.2.3. Código de presentación [liste.aspx]
Comentarios:
- la línea 9 define el encabezado de la página
- las líneas 12 a 24 definen las características de [DataGrid]
- la línea 26 define la etiqueta [lblMessage]
1.5.6.2.4. Código del controlador [liste.aspx.vb]
Comentarios:
- Los componentes de la página aparecen en las líneas 13 a 15. Cabe señalar que fue necesario crear un objeto [EnteteWebArticles] con un operador [new], mientras que esto no fue necesario con los demás componentes. Sin esta creación explícita, se producía un error de ejecución que indicaba que el objeto [entete] no hacía referencia a nada. Este punto merecería un análisis más profundo. No se ha hecho.
- La tabla de opciones del menú de la cabecera se toma del contexto para inicializar el componente [entete] de la página —línea 20
- la lista de artículos se toma del contexto —línea 22
- para inicializar el componente [DataGridArticles] —líneas 24-27
- El componente [lblMessage] se inicializa con un mensaje incluido en el contexto —línea 29
1.5.6.3. La vista [infos.aspx]
1.5.6.3.1. Introduction
Esta vista muestra información sobre un artículo y también permite su compra:

Se muestra tras una solicitud /main?action=infos&id=ID o una solicitud /main?action=compra&id=ID cuando la cantidad comprada es incorrecta. Los elementos de la solicitud del controlador son los siguientes:
objeto Hashtable() — la tabla de opciones del menú | |
objeto de tipo [Article]: artículo a mostrar | |
objeto String: mensaje que se mostrará en caso de error en la cantidad | |
objeto String: valor que se debe mostrar en el campo de entrada [Qte] |
Los campos [msg] y [qte] se utilizan en caso de error de entrada en la cantidad:

Esta página contiene un formulario que se envía mediante el botón [Acheter]. El destino de URL desde POST es [?action=achat&id=ID], donde ID es el ID del artículo comprado.
1.5.6.3.2. Los componentes de la página
![]() |
n° | type | nom | rôle |
componente de usuario | encabezado | mostrar el encabezado | |
literal | litID | mostrar el número de artículo | |
DataGrid | DataGridArticle 3 - columna relacionada: encabezado: Nombre, campo: nombre 4 - columna relacionada: encabezado: Precio, campo: precio 5 - columna relacionada: encabezado: Stock actual, campo: stockActuel 6 - columna relacionada: encabezado: Stock mínimo, campo: stockMinimum | Mostrar un artículo | |
HTML Enviar | Enviar el formulario | ||
HTML Entrada runat=servidor | txtQte | ingresar la cantidad comprada | |
etiqueta | lblMsgQte | posible mensaje de error |
1.5.6.3.3. El código de presentación [infos.aspx]
Comentarios:
- el encabezado está incluido en la página — línea 9
- El literal [litId] se define en la línea 10
- DataGrid y [DataGridArticles] se definen en las líneas 12 a 34
- El formulario se define en las líneas 36 a 46. Es de tipo POST.
- El destino de POST lo proporciona una variable [strAction] —línea 36—. Esta variable deberá ser definida por el controlador.
- El campo de entrada de la cantidad comprada se define en la línea 41. Es un componente HTML de servidor (runat=server). En cuanto al código, se accede a él a través de un objeto.
- La línea 42 define la etiqueta [lblMsgQte], que contendrá un posible mensaje de error sobre la cantidad ingresada
1.5.6.3.4. El código de control [infos.aspx.vb]
Comentarios:
- Los componentes de la página se definen en las líneas 10 a 14
- La clase define una propiedad pública [strAction] que sirve para definir el destino del POST del formulario —líneas 17 a 25
- El artículo que se mostrará se obtiene del contexto de la aplicación —línea 30
- La tabla de opciones del menú de la cabecera se toma del contexto para inicializar el componente [entete] de la página —línea 32
- líneas 33-39: el componente [DataGridArticle] se vincula a una fuente de datos de tipo [ArrayList] que contiene únicamente el artículo recuperado en la línea 30
- los componentes [lblMsgQte, txtQte] se inicializan con información tomada del contexto —líneas 42-45
- la propiedad [straction] también se inicializa con información tomada del contexto —línea 47—. Esta variable sirve para generar el atributo [action] del formulario HTML presente en la página:
1.5.6.4. La vista [panier.aspx]
1.5.6.4.1. Introduction
Esta vista muestra el contenido del carrito:

Se muestra tras una solicitud de tipo /main?action=carrito o /main?action=retirarcompra&id=ID. Los elementos de la solicitud del controlador son los siguientes:
objeto Hashtable() — la tabla de opciones del menú | |
objeto de tipo [Panier]: la canasta que se va a mostrar |
Cada enlace [Retirer] de la tabla HTML de las compras del carrito tiene un URL con el formato [?action=retirerachat&id=ID], donde ID es el campo [id] del artículo que queremos eliminar del carrito.
1.5.6.4.2. Los componentes de la página
![]() |
n° | type | nom | rôle |
componente de usuario | encabezado | mostrar el encabezado | |
DataGrid | DataGridAchats 3- columna relacionada - encabezado: Artículo, campo: nombre 4 - columna relacionada - encabezado: Cantidad, campo: cant 5 - columna relacionada - encabezado: Precio, campo: precio 6 - columna relacionada - encabezado: Total, campo: total, formato {0:C} 7 - columna de hipervínculo - Texto: Retirar, URL: id, formato de URL: /webarticles/main.aspx?action=retirerachat&id={0} | Mostrar la lista de artículos comprados | |
etiqueta | lblTotal | Mostrar el monto a pagar |
1.5.6.4.3. El código de presentación [panier.aspx]
Comentarios
- la línea 9 incluye el encabezado
- En las líneas 12 a 27, se define el componente [DataGridAchats]
- En la línea 29, se define el componente [lblTotal]
1.5.6.4.4. El código de control [panier.aspx.vb]
Comentarios:
- los componentes de la página se declaran en las líneas 11-13
- La inicialización del componente [entete] es idéntica a la que se encuentra en las páginas ya analizadas —línea 17
- La canasta que se va a mostrar se recupera de la sesión —línea 19
- La visualización de esta canasta mediante el componente [DataGridAchats] presenta problemas. La dificultad radica en la inicialización del componente. Recordemos sus columnas:
- columna [Article] asociada al campo [nom] de la fuente de datos
- columna [Qté] asociada al campo [qte] de la fuente de datos
- columna [Prix] asociada al campo [prix] de la fuente de datos
- columna [Total] asociada al campo [total] de la fuente de datos
La fuente de datos de la que disponemos es el carrito de compras y su lista de compras. Esta última será la fuente de datos de [DataGrid]. Solo los objetos [Achat] que alimentarán las filas del [DataGrid] no tienen las propiedades [nom, qte, prix, total] que espera el [DataGrid]. Por lo tanto, aquí se crea, especialmente para el [DataGrid], una fuente de datos cuyos elementos tienen las características esperadas por el [DataGrid]. Estos elementos serán del tipo [LigneAchat], una clase creada para esta ocasión y derivada de la clase [Achat] —líneas 36-66—
- Una vez definida la clase [LigneAchat), la fuente de datos de [DataGridAchats] se construye a partir del carrito encontrado en la sesión —líneas 20-30
- El monto de las compras se muestra gracias a la propiedad [totalPanier] de la clase [Panier] —línea 32
1.5.6.5. La vista [paniervide.aspx]
1.5.6.5.1. Introduction
Esta vista muestra la información que indica que el carrito está vacío:

Se muestra tras una solicitud /main?action=carrito o /main?action=retirarcompra&id=ID. Los elementos de la solicitud del controlador son los siguientes:
objeto Hashtable() — la tabla de opciones del menú |
1.5.6.5.2. Los componentes de la página
![]() |
n° | type | nom | rôle |
componente de usuario | encabezado | mostrar el encabezado |
1.5.6.5.3. El código de presentación [paniervide.aspx]
Comentarios:
- el encabezado está incluido en la línea 9
1.5.6.5.4. El código de control [paniervide.aspx.vb]
Comentarios:
- Simplemente se inicializa el único componente dinámico de la página —línea 10
1.5.6.6. La vista [erreurs.aspx]
1.5.6.6.1. Introduction
Esta vista se muestra en caso de errores:

Se muestra tras cualquier solicitud que genere un error, excepto en el caso de la acción de compra con una cantidad incorrecta, que se procesa mediante la vista [INFOS]. Los elementos de la solicitud del controlador son los siguientes:
objeto Hashtable() — la tabla de opciones del menú | |
ArrayList de objetos [String] que representan los mensajes de error que se deben mostrar |
1.5.6.6.2. Los componentes de la página
![]() |
n° | type | nom | rôle |
componente de usuario | encabezado | mostrar el encabezado | |
repetidor | rptErreurs | mostrar la lista de errores |
1.5.6.6.3. El código de presentación [erreurs.aspx]
Comentarios:
- El encabezado se define en la línea 9
- El componente [rptErreurs] se define en las líneas 13 a 19. Su contenido proviene de una fuente de datos que será del tipo [ArrayList] de objetos [String].
1.5.6.6.4. El código de control [erreurs.aspx.vb]
Comentarios:
- el componente [entete] se inicializa como de costumbre, en las líneas 9 y 14
- el componente [rptErreurs] se inicializa con la lista de errores de tipo [ArrayList] encontrada en el contexto —líneas 16-19
1.5.7. Los controladores global.asax, main.aspx
Aún queda por escribir el núcleo de nuestra aplicación web: el controlador. Su función consiste en:
- recibir la solicitud del cliente,
- procesar la acción solicitada por este utilizando las clases de negocio,
- enviar como respuesta la vista correspondiente.
1.5.7.1. El controlador [global.asax.vb]
Cuando la aplicación recibe su primera solicitud, se ejecuta el procedimiento [Application_Start] del archivo [global.asax.vb]. Esto ocurrirá solo una vez. El procedimiento [Application_Start] tiene como objetivo inicializar los objetos necesarios para la aplicación web, los cuales serán compartidos en modo de solo lectura por todos los hilos de los clientes. Estos objetos compartidos pueden ubicarse en dos lugares:
- los campos privados del controlador
- el contexto de ejecución de la aplicación (Application)
El método [Application_Start] de la aplicación [global.asax.vb] realizará las siguientes acciones:
- verificará la presencia, en el archivo [web.config], de los parámetros necesarios para el correcto funcionamiento de la aplicación. Estos se describieron en el párrafo 1.5.3.
- insertará en el contexto de la aplicación la lista de posibles errores en forma de un objeto [ArrayList erreurs]. Esta lista estará vacía si no hay errores, pero existirá de todos modos.
- Si se han producido errores, el método [Application_Start] se detiene ahí. De lo contrario, solicita la referencia de un singleton de tipo [IArticlesDomain], que será el objeto de negocio que el controlador utilizará para sus necesidades. Como se explicó en 1.5.3.2, el controlador solicitará este singleton al marco Spring. Esta operación de instanciación puede generar diversos errores. Si ese es el caso, estos se almacenarán, una vez más, en el objeto [erreurs] del contexto de la aplicación.
El controlador [global.asax.vb] cuenta con un procedimiento [Session_Start] que se ejecuta cada vez que llega un nuevo cliente. En este procedimiento, se creará un carrito vacío para el cliente. Este carrito se mantendrá a lo largo de las solicitudes de ese cliente en particular. El código podría ser el siguiente:
Comentarios:
- los parámetros esperados en [web.config] se definen en una tabla —línea 18
- se buscan en [web.config]. Si están presentes, se almacenan en el contexto de la aplicación; de lo contrario, se registra un error en la lista de errores [erreurs] —líneas 21-33
- Si no hay errores, se solicita a Spring una referencia del singleton [articlesDomain] que administra el acceso a la capa [domain] de la aplicación —líneas 35-47. Los posibles errores se registran en [erreurs].
- Los errores se registran en el contexto de la aplicación —línea 49
- Se sale del procedimiento si se han producido errores —línea 51
- Se crea una matriz de tres diccionarios. Cada uno de ellos tiene dos claves: href y enlace. Esta matriz representa las tres opciones posibles del menú —líneas 52-71
- Este array se almacena en el contexto de la aplicación —línea 73
- Cada vez que llega un nuevo cliente, se ejecuta el procedimiento [Session_Start]. En él se crea un carrito vacío en la sesión del cliente —líneas 78-81
1.5.7.2. El controlador [main.aspx.vb]
El controlador [main.aspx.vb] procesa todas las solicitudes de los clientes. De hecho, todas estas tienen el formato [/webarticles/main.aspx?action=XX]. Una solicitud se procesará de la siguiente manera:
- se verificará el objeto [erreurs] del contexto de la aplicación. Si no está vacío, significa que se produjeron errores durante la inicialización de la aplicación y que esta no puede funcionar. En ese caso, se enviará, como respuesta, la vista [ERREURS].
- Se recuperará y verificará el parámetro [action] de la solicitud. Si no corresponde a una acción conocida, se enviará la vista [ERREURS] con un mensaje de error adecuado.
- Si el parámetro [action] es válido, la solicitud del cliente se pasa a un procedimiento específico de la acción para su procesamiento:
método | solicitud | procesamiento | respuestas posibles |
GET /main?action=lista | - solicitar la lista de artículos a la clase de negocio - mostrarlo | [LISTE] o [ERREURS] | |
GET /main?action=infos&id=ID | - solicitar el artículo con id=ID a la clase de negocio - mostrarlo | [INFOS] o [ERREURS] | |
POST /main?action=compra&id=ID - la cantidad comprada forma parte de los parámetros enviados | - solicitar el artículo con id=ID a la clase de negocio - incluirlo en el carrito en la sesión del cliente | [LISTE] o [INFOS] o [ERREURS] | |
GET /main?action=retirarcompra&id=ID | - eliminar el artículo con id=ID de la lista de compras del carrito de la sesión del cliente | [PANIER] | |
GET /main?action=carrito | - Mostrar la canasta de la sesión del cliente | [PANIER] o [PANIERVIDE] | |
GET /main?action=validacióncarrito | - Reducir en la base de datos las existencias de todos los artículos que se encuentran en el carrito de sesión del cliente | [LISTE] o [ERREURS] |
El esqueleto del controlador [main.aspx.vb] podría ser el siguiente:
Comentarios:
- la clase tiene dos campos privados que se compartirán entre los métodos —líneas 15-16—:
- articlesDomain: el singleton de acceso a la capa [domain]
- opciones: la matriz de diccionarios de las opciones del menú
- el procedimiento [Page_Load]:
- inicializará los dos campos privados de la clase
- recuperará el parámetro [action] de la consulta y ejecutará el método de procesamiento de esta acción.
1.5.7.3. El método [Page_Load]
Este evento es el primero en ocurrir en la página. El código es el siguiente:
Comentarios:
- cada vez que se carga la página, se verifica que la inicialización de la aplicación realizada por [global.asax] se haya llevado a cabo correctamente.
- Para ello, se recupera del contexto de la aplicación la lista de errores que [global.asax] ha colocado allí (línea 4)
- Si esta lista no está vacía, se muestra la vista [ERREURS] —líneas 6-10
- Se recupera el singleton [articlesDomain] colocado por [global.asax] en el contexto de la aplicación y se almacena en el campo privado [articlesDomain] para que esté disponible para los distintos métodos de la clase —línea 12
- Se realiza un proceso similar con la tabla de opciones del menú —línea 14
- se recupera el parámetro [action] de la solicitud —línea 16
- se ejecuta el método correspondiente a la acción solicitada. Una acción no prevista se considera como la acción [liste] —líneas 16-36
1.5.7.4. Procesamiento de la acción [liste]
Se trata de mostrar la lista de artículos:

El código es el siguiente:
Comentarios:
- los posibles errores se colocan en un [ArrayList] - línea 4
- se solicita la lista de artículos al singleton [articlesDomain] — líneas 5-12
- Si hubo errores, se envía la vista [ERREURS] —líneas 13-19
- de lo contrario, se envía la vista [LISTE] —líneas 20-24
1.5.7.5. Procesamiento de la acción [infos]
El cliente solicitó información sobre un artículo específico:

El código es el siguiente:
Comentarios:
- los posibles errores se colocan en un [ArrayList] - línea 4
- El identificador del artículo solicitado se obtiene de la consulta —línea 6
- Se verifica este identificador. Debe estar presente y debe ser un número entero. Si no es así, se envía la vista [ERREURS] con el mensaje de error correspondiente —líneas 7-27
- Una vez verificado el identificador, se solicita el artículo al singleton [articlesDomain]. Si se produce una excepción, se envía la vista [ERREURS] —líneas 29-39
- Si no se encontró el artículo, se envía la vista [ERREURS] —líneas 41-48
- Si se ha encontrado el artículo, se coloca en la sesión del usuario y luego se muestra en la vista [INFOS] — líneas 50-56
1.5.7.6. Procesamiento de la acción [achat]
El cliente compró el artículo que se muestra en la vista [INFOS].

El código es el siguiente:
Comentarios:
- se recupera el artículo agregado a la sesión —línea 5
- si no está ahí (la sesión pudo haber caducado), se muestra la vista [LISTE] —líneas 7-10
- se recupera la cantidad comprada en la consulta —línea 12
- se verifica su validez —líneas 13-29—
- en caso de invalidez, según el caso, se envía la vista [LISTE] —línea 16— o la vista [INFOS] —líneas 24-28—
- si todo está en orden, la compra se registra en el carrito —líneas 31-32
- luego se envía la vista [LISTE] —línea 34
1.5.7.7. Procesamiento de la acción [panier]
El cliente ha realizado varias compras y solicita ver el carrito:

El código es el siguiente:
Comentarios:
- se recupera el carrito de compras de la sesión —línea 4—. Aquí no se verifica si realmente se recuperó algo. Habría que hacerlo, ya que la sesión podría haber caducado.
- Si el carrito está vacío, se envía la vista [PANIERVIDE] —líneas 6-10
- de lo contrario, se envía la vista [PANIER] —líneas 11-14
1.5.7.8. Procesamiento de la acción [retirerachat]
El cliente desea eliminar un artículo de su carrito:

El código es el siguiente:
Comentarios:
- se recupera el carrito de la sesión —línea 4—. Aquí no se verifica si realmente se recuperó algo. Sería necesario hacerlo, ya que la sesión podría haber caducado.
- Se recupera en la consulta el identificador [id] del artículo que se va a eliminar —línea 8.
- Se elimina la compra correspondiente del carrito —línea 10—
- Aquí no se ha verificado la validez del identificador del artículo comprado. Si este tiene un tipo inválido, se producirá una excepción que se manejará en las líneas 11-13. Si es válido pero no existe, el método [panier.enlever] —línea 10— no hace nada.
- Se muestra el nuevo carrito —línea 16
1.5.7.9. Procesamiento de la acción [validerpanier]
El cliente desea validar su carrito:

El código es el siguiente:
Comentarios:
- se recupera el carrito de la sesión —línea 6—. Aquí no se verifica si realmente se recuperó algo. Habría que hacerlo, ya que la sesión podría haber caducado.
- En caso de que la sesión haya caducado, tendremos un puntero [nothing] para el carrito y el método [acheter] —línea 9— lanzará una excepción y se enviará la vista [ERREURS]. Sin embargo, el mensaje de error no será muy claro para el usuario.
- En las líneas 8 a 16, se intenta validar el carrito recuperado de la sesión. Algunas compras pueden no validarse si la cantidad solicitada excede el stock del artículo solicitado. Estos casos son almacenados por el método [acheter] en una lista de errores que se recupera en la línea 11.
- Si hay errores, se envía la vista [ERREURS] —líneas 18-23—
- de lo contrario, se envía la vista [LISTE] —líneas 25-26
1.6. Conclusion
Aquí hemos desarrollado una aplicación basada en un modelo MVC. Al parecer (abril de 2005) no existen «frameworks» profesionales de desarrollo MVC en ASP.NET como los que existen en Java (Struts, Spring, ...). Se espera que el proyecto [Spring.net] ofrezca uno en breve. Mientras tanto, el método anterior permite un desarrollo viable en MVC para aplicaciones de tamaño mediano.































