1. Parte 1
O PDF do documento está disponível |AQUI|.
Os exemplos do documento estão disponíveis |AQUI|.
1.1. Introduction
Objetivos do artigo:
- escrever uma aplicação web de três camadas [interface utilisateur, métier, accès aux données]
- configurar a aplicação com o Spring IOC
- escrever diferentes versões alterando a implementação de uma ou outra das três camadas.
Ferramentas utilizadas:
- Visual Studio.net para o desenvolvimento — consulte o parágrafo 3.1 do anexo;
- Servidor web Cassini para execução — consulte o anexo, parágrafo 3.2;
- Nunit para testes unitários – ver anexo, parágrafo 3.4;
- Spring para integração e configuração das camadas da aplicação web — consulte o anexo, parágrafo 3.3;
Em uma escala iniciante-intermediário-avançado, este documento se enquadra na categoria [intermédiaire-avancé]. Sua compreensão requer diversos pré-requisitos. Alguns deles podem ser adquiridos em documentos que escrevi. Nesse caso, eu os cito. É evidente que se trata apenas de uma sugestão e que o leitor pode utilizar seus documentos favoritos.
- linguagem VB.net: [Introdução à linguagem VB.NET por meio de exemplos (2004)];
- programação web em VB.net: [Desenvolvimento web com ASP.NET 1.1 (2004)];
- uso do componente IoC do Spring: [Spring IoC para .NET (2005)];
- documentação Spring.net: [Spring.NET | Homepage ]
Este documento segue a linha condutora de um documento escrito para Java [Arquiteturas de três camadas e arquiteturas MVC com Struts, Spring e Java]. Estamos desenvolvendo em VB.NET o aplicativo web MVC de três camadas escrito em Java. A ideia que queremos transmitir aqui é que as plataformas de desenvolvimento Java e .NET são suficientemente semelhantes entre si para que as competências adquiridas em uma dessas duas áreas possam ser reutilizadas na outra.
Não parece existir uma solução de desenvolvimento MVC ASP.NET amplamente reconhecida. A solução a seguir retoma o método apresentado no documento [Desenvolvimento web com ASP.NET 1.1 (2004)]. Embora essa solução tenha o mérito de utilizar conceitos comuns no desenvolvimento J2EE, ela deve ser considerada apenas pelo que é: c.a.d, um método entre tantos outros de desenvolvimento MVC. Assim que um método de desenvolvimento MVC em ASP.NET for amplamente aceito, será necessário adotar este último. A versão .NET do Spring, atualmente em desenvolvimento, pode muito bem ser uma primeira solução.
1.2. A aplicação webarticles
Apresentamos aqui os elementos de um aplicativo web simplificado de comércio eletrônico. Ele permitirá que os usuários da web:
- consultar uma lista de artigos provenientes de um banco de dados
- colocar alguns deles em um carrinho virtual
- confirmar o carrinho. Essa confirmação terá como único efeito atualizar, no banco de dados, os estoques dos itens comprados.
As diferentes visualizações apresentadas ao usuário serão as seguintes:
- a visualização “LISTE”, que apresenta uma lista dos itens à venda ![]() | - a visualização [INFOS], que fornece informações adicionais sobre um produto: ![]() |
- as visualizações [PANIER] e [PANIERVIDE], que mostram o conteúdo do carrinho do cliente
![]() | ![]() |
- a visualização [ERREURS], que sinaliza qualquer erro no aplicativo

1.3. Arquitetura geral do aplicativo
Deseja-se construir um aplicativo com a seguinte estrutura de três camadas:
![]() |
- as três camadas são tornadas independentes graças ao uso de interfaces
- a integração das diferentes camadas é realizada pelo Spring
- cada camada possui namespaces separados: web (camada UI), domain (camada de negócios) e dao (camada de acesso aos dados).
A aplicação seguirá uma arquitetura MVC (Modelo - Visão - Controlador). Se retomarmos o esquema em camadas acima, a arquitetura MVC se integra a ele da seguinte forma:
![]() |
O processamento de uma solicitação de um cliente ocorre de acordo com as seguintes etapas:
- o cliente faz uma solicitação ao controlador. Esse controlador, neste caso, será uma página .aspx à qual será atribuída uma função específica. Ela recebe todas as solicitações dos clientes. É a porta de entrada do aplicativo. É o C de MVC.
- o controlador processa essa solicitação. Para isso, ele pode precisar da ajuda da camada de negócios, o que chamamos de modelo M na estrutura MVC.
- O controlador recebe uma resposta da camada de negócios. A solicitação do cliente foi processada. Isso pode resultar em várias respostas possíveis. Um exemplo clássico é
- uma página de erros, caso a solicitação não tenha podido ser processada corretamente
- uma página de confirmação, caso contrário
- o controlador escolhe a resposta (= visualização) a ser enviada ao cliente. Na maioria das vezes, trata-se de uma página contendo elementos dinâmicos. O controlador fornece esses elementos à visualização.
- A visualização é enviada ao cliente. É o V de MVC.
1.4. O modelo
Analisamos aqui o M de MVC. O modelo é composto pelos seguintes elementos:
- as classes de negócio
- as classes de acesso aos dados
- o banco de dados
1.4.1. O banco de dados
O banco de dados contém apenas uma tabela chamada ARTICLES. Ela foi gerada com os seguintes 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
);
/* restrições */
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);
/* chave primária */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
chave primária que identifica um item de forma exclusiva | |
nome do artigo | |
seu preço | |
estoque atual | |
o nível de estoque abaixo do qual deve ser feito um pedido de reabastecimento |
1.4.2. Os espaços de nomes do modelo
O modelo M é fornecido aqui na forma de dois espaços de nomes:
- istia.st.articles.dao: contém as classes de acesso aos dados da camada [dao]
- istia.st.articles.domain: contém as classes de negócios da camada [domain]
Cada um desses espaços de nomes será gerado em um arquivo “assembly” próprio:
contenu | rôle | |
- [IArticlesDao]: a interface de acesso à camada [dao]. Essa é a única interface que a camada [domain] vê. Ela não vê nenhuma outra. - [Article]: classe que define um artigo - [ArticlesDaoArrayList]: classe de implementação da interface [IArticlesDao] com uma classe [ArrayList] | camada de acesso aos dados - está inteiramente na camada [dao] da arquitetura de três camadas da aplicativo web | |
- [IArticlesDomain]: a interface de acesso à camada [domain]. Essa é a única interface que a camada web vê. Ela não vê nenhuma outra. - [AchatsArticles]: uma classe que implementa [IArticlesDomain] - [Achat]: classe que representa a compra de um cliente - [Panier]: classe que representa o conjunto de compras de um cliente | representa o modelo das compras na web — está inteiramente na camada [domain] da arquitetura Arquitetura de três camadas da aplicação web |
1.4.3. A camada [dao]
A camada [dao] contém os seguintes elementos:
-
[IArticlesDao]: a interface de acesso à camada [dao]
-
[Article]: classe que define um artigo
-
[ArticlesDaoArrayList]: classe de implementação da interface [IArticlesDao] com uma classe [ArrayList]
A estrutura do projeto [Visual Studio] da camada [dao] é a seguinte:

Comentários:
- o projeto [dao] é do tipo [bibliothèque de classes]
- as classes foram organizadas em uma árvore cuja raiz é a pasta [istia]. Todas elas estão no espaço de nomes [istia.st.articles.dao].
1.4.3.1. A classe [Article]
A classe que define um artigo é a seguinte:
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 do artigo
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
' nome do artigo
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
' preço do artigo
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
' estoque atual do artigo
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
' estoque mínimo do artigo
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 padrão
Public Sub New()
End Sub
' fabricante com propriedades
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 identificação do artigo
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Esta classe oferece:
- um construtor que permite definir as 5 informações de um artigo: [id, nom, prix, stockactuel, stockminimum]
- propriedades públicas que permitem ler e gravar as 5 informações.
- uma verificação dos dados inseridos no artigo. Em caso de dados incorretos, é lançada uma exceção.
- um método toString que permite obter o valor de um artigo na forma de uma sequência de caracteres. Isso costuma ser útil para a depuração de um aplicativo.
1.4.3.2. A interface [IArticlesDao]
A interface [IArticlesDao] é definida da seguinte forma:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' lista de todos os itens
Function getAllArticles() As IList
' adiciona um artigo
Function ajouteArticle(ByVal unArticle As Article) As Integer
' exclui um artigo
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' altera um artigo
Function modifieArticle(ByVal unArticle As Article) As Integer
' pesquisa um artigo
Function getArticleById(ByVal idArticle As Integer) As Article
' exclui todos os itens
Sub clearAllArticles()
' altera o estoque de um artigo
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
A função dos diferentes métodos da interface é a seguinte:
retorna todos os itens da fonte de dados | |
esvazia a fonte de dados | |
retorna o objeto [Article] identificado por sua chave primária | |
permite adicionar um item à fonte de dados | |
permite modificar um item da fonte de dados | |
permite excluir um item da fonte de dados | |
permite modificar o estoque de um item da fonte de dados |
A interface disponibiliza aos programas clientes uma série de métodos definidos apenas por suas assinaturas. Ela não se preocupa com a forma como esses métodos serão efetivamente implementados. Isso proporciona flexibilidade à aplicação. O programa cliente faz suas chamadas por meio de uma interface e não de uma implementação específica dela.
![]() |
A escolha de uma implementação específica será feita por meio de um arquivo de configuração do Spring. Para demonstrar que, ao testar a aplicação web, apenas a interface de acesso aos dados é relevante — e não sua classe de implementação —, vamos, em primeiro lugar, implementar a fonte de dados por meio de um objeto simples [ArrayList]. Posteriormente, apresentaremos uma solução baseada em SGBD.
1.4.3.3. A classe de implementação [ArticlesDaoArrayList]
A classe de implementação [ArticlesDaoArrayList] é definida da seguinte forma:
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 padrão
Public Sub New()
' fabricamos alguns itens
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 os itens
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
' retorna a lista de itens
SyncLock Me
Return articles
End SyncLock
End Function
' exclusão de todos os itens
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' esvazia a lista de itens
SyncLock Me
articles.Clear()
End SyncLock
End Sub
' obter um item identificado por sua chave
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' procurando o item na 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
' adicionar um item à lista de itens
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' adicionando o item à lista de itens
SyncLock Me
' verifica-se se ele já não existe
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
' adiciona-se o item
articles.Add(unArticle)
' retornar o resultado
Return 1
End SyncLock
End Function
' editar um artigo
Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
' modifica-se um artigo
SyncLock Me
' verifica-se se ele existe
Dim ipos As Integer = posArticle(articles, articleNouveau.id)
' caso ele não exista
If ipos = -1 Then Return 0
' ele existe — modifica-se
articles(ipos) = articleNouveau
' retorna-se o resultado
Return 1
End SyncLock
End Function
' excluir um artigo identificado por sua chave
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' exclusão de um item
SyncLock Me
' verifica-se se ele existe
Dim ipos As Integer = posArticle(articles, idArticle)
' caso não exista
If ipos = -1 Then Return 0
' ele existe — excluí-lo
articles.RemoveAt(ipos)
' retornar o resultado
Return 1
End SyncLock
End Function
' alterar o estoque de um artigo identificado por sua chave
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' alterar o estoque de um item
SyncLock Me
' verifica-se se ele existe
Dim ipos As Integer = posArticle(articles, idArticle)
' caso não exista
If ipos = -1 Then Return 0
' existe — altera-se o estoque, se possível
Dim unArticle As Article = CType(articles(ipos), Article)
' só se altera o estoque se ele for suficiente
If unArticle.stockactuel + mouvement >= 0 Then
unArticle.stockactuel += mouvement
Return 1
Else
Return 0
End If
End SyncLock
End Function
' procurar um item identificado por sua chave
Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
' retorna a posição do item [idArticle] na lista ou -1 se não for encontrado
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
' não encontrado
Return -1
End Function
End Class
End Namespace
Comentários:
- a fonte de dados é simulada pelo campo privado [articles], do tipo [ArrayList]
- O construtor da classe cria, por padrão, 4 itens na fonte de dados.
- Todos os métodos de acesso aos dados foram sincronizados para evitar problemas de acesso simultâneo à fonte de dados. A qualquer momento, apenas um thread tem acesso a um determinado método.
- O método [posArticle] permite determinar a posição [0..N] na fonte [ArrayList] de um item identificado por seu número. Se o artigo não existir, o método retorna a posição -1. Esse método é utilizado repetidamente pelos demais métodos.
- O método [ajouteArticle] permite adicionar um artigo à lista de artigos. Ele retorna o número de artigos inseridos: 1. Se o artigo já existisse, uma exceção é lançada.
- O método [modifieArticle] permite modificar um artigo existente. Ele retorna o número de artigos modificados: 1 se o artigo existia, 0 caso contrário.
- O método [supprimeArticle] permite excluir um artigo existente. Ele retorna o número de artigos excluídos: 1 se o artigo existia, 0 caso contrário.
- O método [getAllArticles] fornece a lista de todos os artigos
- O método [getArticleById] permite obter um artigo identificado por seu número. Recebe-se o valor [nothing] se o artigo não existir.
- O código não apresenta nenhuma dificuldade real. Deixamos que o leitor o examine e compreenda.
1.4.3.4. Geração do assembly da camada [dao]
O projeto do Visual Studio está configurado para gerar o assembly [webarticles-dao.dll]. Ele é gerado na pasta [bin] do projeto:
![]() | ![]() |
1.4.3.5. Testes NUnit da camada [dao]
Em Java, as classes são testadas com o framework [Junit]. No .NET, o framework Nunit oferece as mesmas possibilidades de testes unitários:

A estrutura do projeto de teste no Visual Studio é a seguinte:

Comentários:
- o projeto [tests] é do tipo [bibliothèque de classes]
- os testes [NUnit] exigem uma referência ao assembly [nunit.framework.dll]
- a classe de teste [NUnit] obtém uma instância do objeto a ser testado por meio do Spring. Por isso, encontramos
- na pasta [bin], os arquivos de classes do Spring
- em [References], uma referência ao assembly [Spring-Core.dll] da pasta [bin]
- em [bin], um arquivo de configuração para o Spring
- a classe de teste precisa do assembly [webarticles-dao.dll] da camada [dao]. Este foi colocado na pasta [bin] e sua referência adicionada às referências do projeto.
Uma classe de teste [NUnit] requer acesso às classes do namespace [NUnit.Framework]. Portanto, nela encontra-se a seguinte instrução de importação:
O namespace [NUnit.Framework] está localizado no “assembly” [nunit.framework.dll], que deve ser adicionado às referências do projeto:
![]() | ![]() |
O assembly [nunit.framework.dll] deve constar na lista sugerida caso a instalação do [Nunit] tenha sido realizada. Basta clicar duas vezes no assembly para adicioná-lo ao projeto:

A classe de teste [NUnit] da camada [dao] poderia ser a seguinte:
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
' o objeto a ser testado
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' recupera-se uma instância do criador de objetos do Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' solicita-se a instanciação do objeto articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' verificação visual
listArticles()
End Sub
<Test()> _
Public Sub testClearAllArticles()
' excluímos todos os artigos
articlesDao.clearAllArticles()
' solicita-se todos os artigos
Dim articles As IList = articlesDao.getAllArticles
' verificação: deve haver 0
Assert.AreEqual(0, articles.Count)
End Sub
<Test()> _
Public Sub testAjouteArticle()
' exclusão de todos os itens
articlesDao.clearAllArticles()
' verificação: a tabela de itens deve estar vazia
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adicionamos dois itens
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação: deve haver dois itens
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' verificação visual
listArticles()
End Sub
<Test()> _
Public Sub testSupprimeArticle()
' exclusão de todos os itens
articlesDao.clearAllArticles()
' verificação: a tabela de itens deve estar vazia
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adicionamos dois itens
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação: deve haver 2 itens
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' exclusão do item 4
articlesDao.supprimeArticle(4)
' verificação: deve restar 1 item
articles = articlesDao.getAllArticles
Assert.AreEqual(1, articles.Count)
' verificação visual
listArticles()
End Sub
<Test()> _
Public Sub testModifieArticle()
' exclusão de todos os itens
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 itens
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' busca pelo item 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.nom, "article3")
' busca do item 4
unArticle = articlesDao.getArticleById(4)
' verificação
Assert.AreEqual(unArticle.nom, "article4")
' alteração do artigo 4
articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
' verificação
unArticle = articlesDao.getArticleById(4)
Assert.AreEqual(unArticle.prix, 44, 0.000001)
' verificação visual
listArticles()
End Sub
<Test()> _
Public Sub testGetArticleById()
' exclusão de itens
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 itens
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' busca do item 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.nom, "article3")
' busca do artigo 4
unArticle = articlesDao.getArticleById(4)
' verificação
Assert.AreEqual(unArticle.nom, "article4")
End Sub
' listagem na tela
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()
' exclusão de todos os itens
articlesDao.clearAllArticles()
' busca de item 1
Dim article As article = articlesDao.getArticleById(1)
' verificação
Assert.IsNull(article)
' alteração de um item inexistente
Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
' não foi necessário modificar nenhuma linha
Assert.AreEqual(i, 0)
' exclusão de item inexistente
i = articlesDao.supprimeArticle(1)
' não foi necessário excluir nenhuma linha
Assert.AreEqual(0, i)
End Sub
<Test()> _
Public Sub testChangerStockArticle()
' exclusão de todos os itens
articlesDao.clearAllArticles()
' adição de um artigo
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' adição de um artigo
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' criação de 100 threads
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' cria-se o tópico i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' define-se o nome do thread
taches(i).Name = "tache_" & i
' inicia-se a execução do thread i
taches(i).Start()
Next
' aguarda-se o término de todos os threads
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' verificações — o item 3 deve ter um estoque de 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' diminui-se o estoque do artigo 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' verificação: seu estoque não deve ter mudado
Assert.AreEqual(0, nbLignes)
' verificação visual
listArticles()
End Sub
Public Sub décrémente()
' thread iniciado
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' thread reduz o estoque
articlesDao.changerStockArticle(3, -1)
' thread concluído
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
End Class
End Namespace
Comentários:
- quisemos escrever um programa de teste para a interface [IArticlesDao] que fosse independente da classe de implementação da mesma. Por isso, utilizamos o Spring para ocultar do programa de teste o nome da classe de implementação.
- O método de atributo <Setup()> obtém do Spring uma referência ao objeto [articlesdao] a ser testado. Este está definido no seguinte arquivo [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 arquivo indica o nome da classe de implementação [istia.st.articles.dao.ArticlesDaoArrayList] da interface [IArticlesDao] e onde encontrá-la: [ webarticles-dao.dll]. Como a instanciação não requer parâmetros, nenhum deles é definido aqui.
- A maioria dos testes é fácil de entender. Recomenda-se ao leitor que leia os comentários.
- O método [testChangerStockArticle] requer algumas explicações. Ele cria 100 threads encarregadas de diminuir o estoque de um determinado item.
<Test()> _
Public Sub testChangerStockArticle()
' exclusão de todos os itens
articlesDao.clearAllArticles()
' adição de um item
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' adição de um item
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' criação de 100 threads
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' criamos o tópico i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' define-se o nome do thread
taches(i).Name = "tache_" & i
' inicia-se a execução do thread i
taches(i).Start()
Next
' aguarda-se o término de todos os threads
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' verificações — o item 3 deve ter um estoque de 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' diminui-se o estoque do artigo 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' verificação: seu estoque não deve ter mudado
Assert.AreEqual(0, nbLignes)
' verificação visual
listArticles()
End Sub
O objetivo aqui é testar acessos simultâneos à fonte de dados. O método responsável por atualizar o estoque é o seguinte:
Public Sub décrémente()
' thread iniciado
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' o thread reduz o estoque
articlesDao.changerStockArticle(3, -1)
' thread concluído
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
Ela diminui em uma unidade o estoque do artigo nº 3. Se consultarmos o código do método [testChangerStockArticle], vemos que:
- o estoque do artigo nº 3 é inicializado em 101
- os 100 threads irão diminuir esse estoque em uma unidade cada
- portanto, devemos ter um estoque igual a 1 ao final da execução de todos os threads
Além disso, ainda nesse método, tentamos fazer com que o estoque do artigo nº 4 assuma um valor negativo. A tentativa deve falhar.
Para testar a camada [dao], geramos o DLL [tests-webarticles-dao.dll] na pasta [bin] do projeto [tests]:
![]() | ![]() |
Em seguida, com o auxílio do aplicativo [Nunit-Gui], carregamos esse DLL e executamos os testes:

Na janela à esquerda, vemos a lista dos métodos testados. A cor do ponto que precede o nome de cada método indica se o método foi aprovado (verde) ou reprovado (vermelho). O leitor que estiver visualizando este documento na tela poderá ver que todos os testes foram bem-sucedidos. A partir de agora, consideraremos que temos uma camada [dao] operacional.
1.4.4. A camada [domain]
A camada [domain] contém os seguintes elementos:
-
[IArticlesDomain]: a interface de acesso à camada [domain]
-
[Achat]: classe que define uma compra
-
[Panier]: classe que define um carrinho de compras
-
[AchatsArticles]: classe de implementação da interface [IArticlesDomain]
A estrutura da solução [Visual Studio] da camada [domain] é a seguinte:

Comentários:
- o projeto [domain] é do tipo [bibliothèque de classes]
- as classes foram organizadas em uma árvore cuja raiz é a pasta [istia]. Todas elas estão no espaço de nomes [istia.st.articles.domain].
- A DLL da camada [dao] foi colocada na pasta [bin] do novo projeto. Além disso, essa DLL foi adicionada como referência ao projeto.
1.4.4.1. A interface [IArticlesDomain]
A interface [IArticlesDomain] desacopla a camada [métier] da camada [web]. Esta última acessa a camada [métier/domain] por meio dessa interface, sem se preocupar com a classe que a implementa efetivamente. A interface define as seguintes ações para o acesso à camada de negócios:
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
retorna a lista de objetos [Article] da fonte de dados associada | |
retorna o objeto [Article] identificado por [idArticle] | |
valida o carrinho do cliente, deduzindo dos estoques dos itens comprados a quantidade adquirida — pode falhar se o estoque for insuficiente | |
retorna a lista de erros ocorridos — vazia se não houver erros |
1.4.4.2. A classe [Achat]
A classe [Achat] representa uma compra do cliente:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class Achat
' campos privados
Private _article As article
Private _qte As Integer
' construtor padrão
Public Sub New()
End Sub
' construtor com parâmetros
Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
' acessando por meio das propriedades
Me.article = unArticle
Me.qte = qte
End Sub
' item comprado
Public Property article() As article
Get
Return _article
End Get
Set(ByVal Value As article)
_article = Value
End Set
End Property
' quantidade 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 da compra
Public ReadOnly Property totalAchat() As Double
Get
Return _qte * _article.prix
End Get
End Property
' identidade
Public Overrides Function ToString() As String
Return "[" + _article.ToString + "," + _qte.ToString + "]"
End Function
End Class
End Namespace
Comentários:
- a classe [Achat] possui as seguintes propriedades e métodos:
o item comprado | |
a quantidade comprada | |
o valor da compra | |
cadeia de identidade do objeto |
- ela possui um construtor que permite inicializar as propriedades [article, qte] que definem uma compra.
1.4.4.3. A classe [Panier]
A classe [Panier] representa o conjunto de compras do cliente:
Namespace istia.st.articles.domain
Public Class Panier
' campos privados
Private _achats As New ArrayList
Private _totalPanier As Double = 0
' fabricante padrão
Public Sub New()
End Sub
' lista de compras
Public ReadOnly Property achats() As ArrayList
Get
Return _achats
End Get
End Property
' total das compras
Public ReadOnly Property totalPanier() As Double
Get
Return _totalPanier
End Get
End Property
' métodos
Public Sub ajouter(ByVal unAchat As Achat)
' verifica se a compra já existe
Dim iAchat As Integer = posAchat(unAchat.article.id)
If iAchat <> -1 Then
' foi encontrada
Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
achatCourant.qte += unAchat.qte
Else
' não foi encontrada
_achats.Add(unAchat)
End If
' incrementa-se o total do carrinho
_totalPanier += unAchat.totalAchat
End Sub
' remover um item
Public Sub enlever(ByVal idAchat As Integer)
' procura-se o item
Dim iachat As Integer = posAchat(idAchat)
' se for encontrado, remove-se
If iachat <> -1 Then
Dim achatCourant As Achat = CType(_achats(iachat), Achat)
' retira do carrinho
_achats.RemoveAt(iachat)
' diminuir o total do carrinho
_totalPanier -= achatCourant.totalAchat
End If
End Sub
Private Function posAchat(ByVal idArticle As Integer) As Integer
' procura uma compra na lista de compras
' retorna sua posição na lista ou -1 se não for encontrado
Dim achatCourant As Achat
Dim trouvé As Boolean = False
Dim i As Integer = 0
While Not trouvé AndAlso i < _achats.Count
' compra atual
achatCourant = CType(_achats(i), Achat)
' comparação com o item procurado
If achatCourant.article.id = idArticle Then
Return i
End If
'compra seguinte
i += 1
End While
' não encontrado
Return -1
End Function
' função de identidade
Public Overrides Function ToString() As String
Return _achats.ToString
End Function
End Class
End Namespace
Comentários:
- a classe [Panier] possui as seguintes propriedades e métodos:
lista de compras do cliente — lista de objetos do tipo [Achat] | |
adiciona uma compra à lista de compras | |
remove a compra do item idAchat | |
o valor total das compras no carrinho | |
retorna a string de identificação do carrinho |
- o método [posAchat] é um método utilitário que permite obter a posição na lista de compras de uma compra identificada pelo número do item adquirido. A lista de compras é gerenciada de forma que um item comprado várias vezes ocupe apenas uma posição na lista. Assim, uma compra pode ser identificada pelo número do item comprado. O método [posAchat] retorna -1 se a compra procurada não existir.
- O método [ajouter] adiciona uma nova compra à lista de compras. Isso equivale a adicionar uma nova entrada à lista de compras, caso o artigo comprado ainda não existisse na lista, ou a incrementar a quantidade comprada, caso já existisse.
- O método [enlever] permite remover uma compra identificada por um número da lista de compras. Se a compra não existir, o método não exibe nenhuma mensagem e não realiza nenhuma ação.
- O valor total das compras ([totalPanier]) é mantido à medida que compras são adicionadas ou removidas.
1.4.4.4. A classe [AchatsArticles]
A interface [IArticlesDomain] será implementada pela seguinte classe [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
' construtor
Public Sub New(ByVal articlesDao As IArticlesDao)
_articlesDao = articlesDao
End Sub
' lista de erros
Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
Get
Return _erreurs
End Get
End Property
' lista de itens
Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
' lista de todos os itens
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
' obter um item identificado pelo seu número
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
' um item 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 um carrinho
Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
' compra de um carrinho — os estoques dos itens comprados devem ser reduzidos
_erreurs = New ArrayList
Dim achat As achat
Dim achats As ArrayList = panier.achats
For i As Integer = achats.Count - 1 To 0 Step -1
' reduzir o estoque do item i
achat = CType(achats(i), achat)
Try
If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
' não foi possível realizar a operação
_erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
Else
' a operação foi concluída — a compra é removida do carrinho
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
Comentários:
- esta classe implementa os quatro métodos da interface [IArticlesDomain]. Ela possui dois campos privados:
o objeto de acesso aos dados | |
a lista de possíveis erros. Ela é acessível por meio da propriedade pública [erreurs] |
- Para criar uma instância da classe, é necessário fornecer o objeto que permite o acesso aos dados:
- os métodos [getAllArticles] e [getArticleById] baseiam-se nos métodos de mesmo nome da camada [dao]
- O método [acheter] valida a compra de um carrinho. Essa validação consiste simplesmente em reduzir os estoques dos itens comprados. A compra de um artigo só é possível se o estoque permitir. Caso contrário, a compra é recusada: o artigo permanece no carrinho e um erro é sinalizado na lista [erreurs]. Uma compra validada é removida do carrinho e o estoque do artigo correspondente é reduzido pela quantidade comprada.
1.4.4.5. Geração do assembly da camada [domain]
O projeto do Visual Studio está configurado para gerar o assembly [webarticles-domain.dll]. Ele é gerado na pasta [bin] do projeto:
![]() | ![]() |
1.4.4.6. Testes NUnit da camada [domain]
A estrutura do projeto de teste do Visual Studio é a seguinte:

Comentários:
- o projeto [tests] é do tipo [bibliothèque de classes]
- os testes [NUnit] exigem uma referência ao assembly [nunit.framework.dll]
- a classe de teste [NUnit] obtém uma instância do objeto a ser testado por meio do Spring. Por isso, encontramos
- na pasta [bin], os arquivos de classes do Spring
- em [References], uma referência ao assembly [Spring-Core.dll] da pasta [bin]
- em [bin], um arquivo de configuração para o Spring
- a classe de teste precisa do assembly [webarticles-dao.dll] da camada [dao] e do assembly [webarticles-domain.dll] da camada [domain]. Estes foram colocados na pasta [bin] e suas referências foram adicionadas às referências do projeto.
Uma classe de teste NUnit da camada [domain] poderia ser a seguinte:
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
' o objeto a ser testado
Private articlesDomain As IArticlesDomain
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' recuperando uma instância do criador de objetos do Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' solicita-se a instanciação do objeto `articles` do DAO
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
' e, em seguida, do objeto `articlesdomain`
articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
End Sub
<Test()> _
Public Sub getAllArticles()
' verificação visual
listArticles()
End Sub
<Test()> _
Public Sub getArticleById()
' exclusão dos artigos
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 itens
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' busca do item 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.nom, "article3")
' busca do artigo 4
unArticle = articlesDao.getArticleById(4)
' verificação
Assert.AreEqual(unArticle.nom, "article4")
End Sub
<Test()> _
Public Sub acheterPanier()
' exclusão de itens
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 itens
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' criação de um carrinho com duas 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))
' verificações
Assert.AreEqual(700, panier.totalPanier, 0.000001)
Assert.AreEqual(2, panier.achats.Count)
' confirmação do carrinho
articlesDomain.acheter(panier)
' verificações
Assert.AreEqual(0, articlesDomain.erreurs.Count)
Assert.AreEqual(0, panier.achats.Count)
' busca do item 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.stockactuel, 20)
' busca de artigo 4
unArticle = articlesDao.getArticleById(4)
' verificação
Assert.AreEqual(unArticle.stockactuel, 30)
' nova cesta
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
' confirmação do carrinho
articlesDomain.acheter(panier)
' verificações
Assert.AreEqual(1, articlesDomain.erreurs.Count)
' busca de item 3
unArticle = articlesDomain.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.stockactuel, 20)
End Sub
<Test()> _
Public Sub testRetirerAchats()
' exclusão do conteúdo de ARTICLES
articlesDao.clearAllArticles()
' lê a tabela ARTICLES
Dim articles As IList = articlesDao.getAllArticles()
Assert.AreEqual(0, articles.Count)
' inserção
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)
' lê a tabela ARTICLES
articles = articlesDomain.getAllArticles()
Assert.AreEqual(2, articles.Count)
' criação de um carrinho com duas compras
Dim monPanier As New Panier
monPanier.ajouter(New Achat(article3, 10))
monPanier.ajouter(New Achat(article4, 10))
' verificações
Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
Assert.AreEqual(2, monPanier.achats.Count)
' adicionar um item já comprado
monPanier.ajouter(New Achat(article3, 10))
' verificações
' o total deve ser alterado para 1000
Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
' ainda há 2 itens no carrinho
Assert.AreEqual(2, monPanier.achats.Count)
' a quantidade do item 3 deve ter sido alterada para 20
Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
Assert.AreEqual(20, unAchat.qte)
' retiramos o item 3 do carrinho
monPanier.enlever(3)
' verificações
' o total deve ter passado para 400
Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
' apenas 1 item no carrinho
Assert.AreEqual(1, monPanier.achats.Count)
' deve ser o item nº 4
Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
End Sub
' listagem na tela
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
Comentários:
- quisemos escrever um programa de teste para a interface [IArticlesDomain] que fosse independente da classe de implementação da mesma. Por isso, utilizamos o Spring para ocultar do programa de teste o nome da classe de implementação.
- O método de atributo <Setup()> obtém do Spring uma referência aos objetos [articlesdomain] e [articlesdao] a serem testados. Estes estão definidos no seguinte arquivo [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 arquivo indica:
- (continuação)
- para o singleton [articlesdao], o nome da classe de implementação [istia.st.articles.dao.ArticlesDaoArrayList] e onde encontrá-la: [ webarticles-dao.dll]. Como a instanciação não requer parâmetros, nenhum deles é definido aqui.
- para o singleton [articlesdomain], o nome da classe de implementação [istia.st.articles.domain.AchatsArticles] e onde encontrá-la: [ webarticles-domain.dll]. A classe [AchatsArticles] possui um construtor com um parâmetro: o singleton que gerencia o acesso à camada [dao]. Aqui, ele é definido como sendo o singleton [articlesdao] definido anteriormente.
- A classe de teste obtém uma instância da classe a ser testada, [articlesdomain], bem como uma instância da classe de acesso aos dados, [articlesdao]. Este último ponto é controverso. Teoricamente, a classe de teste não deveria precisar ter acesso à camada [dao], da qual ela nem mesmo deveria ter conhecimento. Aqui, ignoramos essa “ética” que, para ser respeitada, nos obrigaria a criar novos métodos em nossa interface [IArticlesDomain].
Para testar a camada [domain], geramos a DLL e a [tests-webarticles-domain.dll] na pasta [bin] do projeto [tests]:
![]() | ![]() |
Em seguida, com o auxílio do aplicativo [Nunit-Gui], carregamos esse DLL e executamos os testes:

O leitor que visualizar este documento na tela poderá constatar que todos os testes foram bem-sucedidos. A partir de então, consideraremos que temos uma camada [domain] operacional.
1.4.5. Conclusão
Vale lembrar que queremos construir a seguinte aplicação web de três camadas:
![]() |
O modelo M da nossa aplicação MVC já está escrito e testado. Ele nos é fornecido em dois arquivos: DLL e [webarticles-dao.dll, webarticles-domain.dll]. Podemos passar para a última camada, a camada [web], que contém o controlador C e as visualizações V. Consideraremos, em primeiro lugar, um método apresentado no documento [Développement WEB avec ASP.NET 1.1 ]
- o controlador C é implementado por dois arquivos [global.asax, main.aspx]
- as visualizações V são implementadas por páginas aspx
1.5. A camada [web]
A arquitetura MVC do aplicativo web será a seguinte:
![]() |
as classes de negócio [domain], as classes de acesso aos dados [dao] e a fonte de dados | |
as páginas ASPX | |
todas as solicitações dos clientes HTTP passam pelos dois controladores a seguir: global.asax: gerencia os eventos relacionados ao início da aplicação main.aspx: processa individualmente a solicitação de cada cliente |
1.5.1. As vistas
As visualizações correspondem às apresentadas no início deste documento:
liste.aspx | As visualizações estão reunidas na pasta [vues] do aplicativo ![]() | |
infos.aspx | ||
panier.aspx | ||
paniervide.aspx | ||
erreurs.aspx |
1.5.2. Os controladores
Conforme mencionado, o controlador será composto por dois elementos:
- [global.asax,global.asax.vb]: utilizado principalmente para inicializar o aplicativo e inserir no contexto do mesmo todos os dados a serem compartilhados entre os diferentes clientes
- [main.aspx, main.aspx.vb]: o controlador propriamente dito, responsável por processar as solicitações HTTP dos clientes.
As diferentes solicitações dos clientes serão encaminhadas ao controlador [main.aspx] e conterão um parâmetro chamado [action], especificando a ação solicitada pelo cliente:
solicitação | significado | ação do controlador | respostas possíveis |
o cliente deseja a lista dos artigos | - solicita a lista de itens à camada de negócios | - [LISTE] - [ERREURS] | |
o cliente solicita informações sobre um dos artigos exibidos na visualização [LISTE] | - solicita o artigo à camada de negócios | - [INFOS] - [ERREURS] | |
o cliente compra um item | - solicita o artigo à camada de negócios e o adiciona ao carrinho do cliente | - [INFOS] se houver erro na quantidade - [LISTE] se não houver erro | |
o cliente deseja excluir um compra do carrinho | - recupera o carrinho da sessão e a modifica | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
o cliente deseja visualizar seu carrinho | - recupera o carrinho da sessão | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
o cliente concluiu suas compras e passa para a fase de pagamento | - atualiza os estoques no banco de dados dos itens comprados - esvazia o carrinho do cliente dos itens cuja compra foi confirmada | - [LISTE] - [ERREURS] |
1.5.3. Configuração do aplicativo
Procuraremos configurar o aplicativo de forma a torná-lo o mais flexível possível em relação a alterações como:
- a alteração dos URL das diferentes visualizações
- a alteração das classes que implementam as interfaces [IArticlesDao] e [IArticlesDomain]
- a alteração do SGBD, do banco de dados e da tabela de artigos
1.5.3.1. As alterações no URL
Os nomes das visões URL serão inseridos no arquivo de configuração do aplicativo [web.config], juntamente com alguns outros 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. A alteração das classes de implementação das interfaces
De acordo com o princípio das arquiteturas de três camadas, as camadas devem ser independentes umas das outras. Essa independência é obtida da seguinte maneira:
- as camadas se comunicam entre si por meio de interfaces e não por meio de classes concretas
- o código de uma camada nunca instancia por conta própria a classe de outra camada para utilizá-la. Ele simplesmente solicita a uma ferramenta externa, neste caso o Spring, uma instância de implementação da interface da camada que deseja utilizar. Para isso, sabemos que ele não precisa conhecer o nome da classe de implementação, mas apenas o nome do singleton do Spring do qual deseja uma referência.
Em nossa aplicação, o Spring será configurado no arquivo [web.config] da aplicação web da seguinte maneira:
<?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 acessar a camada [métier], uma classe da camada [web] poderá solicitar o singleton [articlesDomain]. O Spring, então, instanciará um objeto do tipo [istia.st.articles.domain.AchatsArticles]. Para essa instanciação, ele precisa de um objeto do tipo [articlesDao], ou seja, de um objeto do tipo [istia.st.articles.dao.ArticlesDaoArrayList]. O Spring então instanciará tal objeto. Ao final da operação, a camada [web], que solicitou o singleton [articlesDomain], possui toda a cadeia que a conecta à fonte de dados:
![]() |
1.5.3.3. Alterações relacionadas ao SGBD ou ao banco de dados
Este ponto será ignorado aqui, uma vez que estamos em um aplicativo de teste sem o SGBD. Abordaremos posteriormente a implementação de uma camada [dao] baseada em um SGBD.
1.5.4. A biblioteca de tags <asp:>
Consideremos a visualização [ERREURS] que exibe uma lista de erros:

A visualização [ERREURS] é responsável por exibir uma lista de erros que o controlador [main.aspx] colocou no contexto da solicitação sob o nome [context.Items("erreurs")]. Existem várias maneiras de escrever uma página desse tipo. Aqui, estamos interessados apenas na parte de exibição dos erros.
Vale lembrar que uma página ASPX possui uma parte de apresentação HTML e uma parte de código .NET que prepara os dados que a parte de apresentação deve exibir. Essas duas partes podem estar em um mesmo arquivo [aspx] (solução WebMatrix) ou em dois arquivos: [aspx] para a apresentação e [aspx.vb] para o código. Esta última solução é a utilizada pelo Visual Studio. Para complicar ainda mais as coisas, a parte de apresentação HTML também pode conter código .NET, o que tende a confundir a separação entre [contrôleur] e [présentation] na visualização. Essa solução é geralmente fortemente desaconselhada. Remover todo o código da parte [présentation] exigiu a criação de bibliotecas de tags. Elas “ocultam” o código sob a aparência de tags análogas às tags HTML. Apresentamos duas soluções possíveis para a página [ERREURS].
Nossa primeira solução utiliza o código .NET na parte [présentation] da página. A página ASPX recupera a lista de erros presente na solicitação em sua parte controladora [erreurs.aspx.vb]:
Protected erreurs As ArrayList
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' recuperamos os erros
erreurs = CType(context.Items("erreurs"), ArrayList)
End Sub
e, em seguida, exibe na seção [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>
A segunda solução utiliza a tag <asp:repeater> da biblioteca de tags <asp:> do ASP.NET. Se criarmos uma página ASPX graficamente, essa tag estará disponível na forma de um componente de servidor que pode ser arrastado para o formulário de design. Se criarmos o código ASPX manualmente, podemos nos referir a ela como biblioteca de tags.
Com a biblioteca de tags <asp:>, o código ASPX da visualização [ERREURS] anterior passa a ser o seguinte:
<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>
A tag
serve para repetir um padrão HTML nos diferentes elementos de uma fonte de dados. Seus diferentes elementos são os seguintes:
o padrão HTML a ser exibido antes que os elementos da fonte de dados sejam exibidos | |
o padrão HTML a ser repetido para cada um dos elementos da fonte de dados. A expressão [<%# Container.DataItem %>] serve para exibir o valor do elemento atual da fonte de dados | |
o padrão HTML a ser exibido após a exibição dos elementos da fonte de dados |
A fonte de dados é vinculada à tag geralmente na seção [contrôleur] da 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
..
' vinculando os erros a rptErreurs
With rptErreurs
.DataSource = context.Items("erreurs")
.DataBind()
End With
End Sub
Essa ligação também pode ser feita no momento da criação da página, caso a fonte de dados já seja conhecida, como, por exemplo, um banco de dados existente.
Usaremos em nossas visualizações outra tag: <asp:datagrid>, que permite exibir uma fonte de dados na forma de uma tabela.
1.5.5. Estrutura da solução do Visual Studio para o aplicativo [webarticles]
Uma aplicação web é um quebra-cabeça com muitos elementos. Adotar uma arquitetura MVC geralmente aumenta o número desses elementos. A estrutura da aplicação [webarticles] sob [Visual Studio] é a seguinte:
![]() | ![]() | ![]() |
![]() |
Observações:
- o projeto [web] é do tipo [bibliothèque de classes] e não do tipo [Application web ASP.NET], como seria de se esperar. O tipo [Application web ASP.NET] exige a presença do servidor web IIS na máquina de desenvolvimento ou em uma máquina remota. O servidor IIS não vem instalado por padrão nas máquinas Windows XP Família. No entanto, muitos PC são vendidos com essa versão. Para permitir que os leitores que possuem o Windows XP possam implementar o aplicativo em estudo, utilizaremos o servidor Web Cassini (ver anexo), disponível gratuitamente na Microsoft, e substituiremos o projeto [Application web ASP.NET] por um projeto [bibliothèque de classes]. Isso acarreta algumas desvantagens, que são explicadas nos anexos.
- Os arquivos DLL utilizados pela aplicação são os seguintes:
reúne as classes da camada de acesso aos dados | |
agrupa as classes da camada de negócios | |
contém as classes do Spring que nos permitem integrar as camadas web, domain e DAO | |
classes de logs — utilizadas pelo Spring |
Essas DLL são colocadas na pasta [bin] e adicionadas às referências do projeto.
1.5.6. As visualizações ASPX
Conforme recomendado anteriormente, utilizaremos a biblioteca de tags <asp:> em nossas visualizações ASPX.
1.5.6.1. O componente de usuário [entete.ascx]
Para conferir certa uniformidade às diferentes visualizações, elas compartilharão o mesmo cabeçalho, que exibe o nome do aplicativo junto com o menu:
![]() | ![]() |
O menu é dinâmico e definido pelo controlador. Este insere na solicitação enviada à página ASPX um atributo-chave “actions” cujo valor associado é uma matriz de elementos do tipo Hashtable(). Cada elemento dessa matriz é um dicionário destinado a gerar uma opção do menu do cabeçalho. Cada dicionário possui duas chaves:
- href: a URL associada à opção do menu
- link: o texto do menu
Transformaremos o cabeçalho em um controle de usuário. Um controle de usuário encapsula uma parte da página (apresentação e código associado) em um componente reutilizável posteriormente em outras páginas. Aqui, queremos reutilizar o componente [entete] nas outras visualizações do aplicativo. O código de apresentação ficará em [entete.ascx] e o código de controle associado em [entete.ascx.vb]. O código de apresentação utilizará um componente <asp:repeater> para exibir a tabela de opções do menu:
![]() |
n° | type | nom | rôle |
1 | repetidor | rptMenu fonte de dados: uma tabela de dicionários com duas chaves: href, link | exibir as opções do menu |
O código de apresentação da página será o seguinte:
Comentários:
- o componente [repeater] é definido nas linhas 6 a 14
- cada elemento da fonte de dados associada ao repetidor é um dicionário com duas chaves: href — linha 9 e link — linha 10
O código de controle associado será o seguinte:
Comentários:
- o componente do tipo [EnteteWebArticles] possui uma propriedade pública [actions] somente para gravação — linha 7
- essa propriedade permite associar ao componente <asp:repeater> denominado [rptMenu] — linha 10 — a tabela de opções calculada pelo controlador da aplicação — linhas 11-12.
As demais visualizações da aplicação utilizarão o cabeçalho definido por [entete.ascx]. A página [erreurs.aspx], por exemplo, incluirá o cabeçalho por meio do seguinte código:
Comentários:
- A linha 1 declara que a tag <WA:entete> deverá ser associada ao componente definido pelo arquivo [entete.ascx]. Os atributos [TagPrefix] e [TagName] são livres.
- Feito isso, a inserção do componente no código de apresentação da página é realizada na linha 9. Na execução, essa tag terá o efeito de incluir, no código da página ASPX que a contém, o código da página [entete.ascx]. O código de controle [erreurs.aspx.vb] se encarregará de inicializar esse componente. Ele pode fazer isso da seguinte maneira:
Comentários:
- a linha 6 cria um objeto do tipo [EnteteWebArticles], que é o tipo do componente criado
- a linha 11 inicializa a propriedade [actions] desse objeto
1.5.6.2. A visualização [liste.aspx]
1.5.6.2.1. Introduction
Essa visualização exibe a lista de itens disponíveis para venda:
![]() | ![]() |
Ela é exibida após uma solicitação /main?action=liste ou /main?action=validationpanier. Os elementos da solicitação do controlador são os seguintes:
objeto Hashtable() — a tabela de opções do menu | |
ArrayList de objetos do tipo [Article] | |
objeto String — mensagem a ser exibida na parte inferior da página |
Cada link [Infos] da tabela HTML de artigos possui um URL no formato [?action=infos&id=ID], em que ID é o campo id doartigo exibido.
1.5.6.2.2. Componentes da página
![]() |
n° | type | nom | rôle |
componente de usuário | cabeçalho | exibir o cabeçalho | |
DataGrid | DataGridArticles 3 - coluna relacionada: cabeçalho: Nome, campo: nome 4 - coluna relacionada: cabeçalho: Preço, campo: preço 5 - coluna de hipertexto: texto: Informações, campo URL: id, formato URL: /webarticles/main.aspx?action=infos&id={0} | exibir os artigos à venda | |
rótulo | lblMessage | exibir uma mensagem |
Vamos relembrar como definir essas propriedades:
- no Visual Studio, selecione o [DataGrid] para acessar sua folha de propriedades:

- utilize o link [Mise en forme automatique] acima para gerenciar o formato da tabela exibida
- e o link [Générateur de propriétés] para gerenciar seu conteúdo
1.5.6.2.3. Código de apresentação [liste.aspx]
Comentários:
- a linha 9 define o cabeçalho da página
- as linhas 12 a 24 definem as características do [DataGrid]
- a linha 26 define o rótulo [lblMessage]
1.5.6.2.4. Código do controlador [liste.aspx.vb]
Comentários:
- os componentes da página aparecem nas linhas 13 a 15. Vale ressaltar que foi necessário criar um objeto [EnteteWebArticles] com um operador [new], embora isso não tenha sido necessário com os outros componentes. Sem essa criação explícita, ocorria um erro de execução indicando que o objeto [entete] não fazia referência a nada. Esse ponto mereceria ser aprofundado. No entanto, isso não foi feito.
- a tabela de opções do menu do cabeçalho é obtida do contexto para inicializar o componente [entete] da página — linha 20
- a lista de itens é obtida do contexto — linha 22
- para inicializar o componente [DataGridArticles] — linhas 24-27
- O componente [lblMessage] é inicializado com uma mensagem inserida no contexto — linha 29
1.5.6.3. A visualização [infos.aspx]
1.5.6.3.1. Introduction
Essa visualização exibe informações sobre um item e também permite sua compra:

Ela é exibida após uma solicitação /main?action=infos&id=ID ou uma solicitação /main?action=compra&id=ID quando a quantidade comprada está incorreta. Os elementos da solicitação do controlador são os seguintes:
objeto Hashtable() — a tabela de opções do menu | |
objeto do tipo [Article] — item a ser exibido | |
objeto String — mensagem a ser exibida em caso de erro na quantidade | |
objeto String — valor a ser exibido no campo de entrada [Qte] |
Os campos [msg] e [qte] são utilizados em caso de erro de entrada na quantidade:

Esta página contém um formulário que é enviado pelo botão [Acheter]. O destino do URL, que é o POST, é o [?action=achat&id=ID], onde ID é o ID do item comprado.
1.5.6.3.2. Os componentes da página
![]() |
n° | type | nom | rôle |
componente de usuário | cabeçalho | exibir o cabeçalho | |
literal | litID | exibir o número do artigo | |
DataGrid | DataGridArticle 3 - coluna relacionada: cabeçalho: Nome, campo: nome 4 - coluna relacionada: título: Preço, campo: preço 5 - coluna relacionada: cabeçalho: Estoque atual, campo: stockActuel 6 - coluna relacionada: título: Estoque mínimo, campo: stockMinimum | exibir um item | |
HTML Enviar | enviar o formulário | ||
HTML Entrada runat=server | txtQte | inserir a quantidade comprada | |
rótulo | lblMsgQte | possível mensagem de erro |
1.5.6.3.3. O código de apresentação [infos.aspx]
Comentários:
- o cabeçalho está incluído na página — linha 9
- o literal [litId] está definido na linha 10
- o DataGrid e o [DataGridArticles] são definidos nas linhas 12 a 34
- o formulário é definido nas linhas 36 a 46. Ele é do tipo POST.
- O destino do POST é fornecido por uma variável [strAction] — linha 36. Essa variável deverá ser definida pelo controlador.
- O campo de entrada da quantidade comprada está definido na linha 41. Trata-se de um componente HTML do servidor (runat=server). No código, o acesso a ele é feito por meio de um objeto.
- A linha 42 define o rótulo [lblMsgQte], que conterá uma eventual mensagem de erro sobre a quantidade inserida
1.5.6.3.4. O código de controle [infos.aspx.vb]
Comentários:
- os componentes da página são definidos nas linhas 10 a 14
- a classe define uma propriedade pública [strAction] que serve para definir o destino do POST do formulário — linhas 17 a 25
- o artigo a ser exibido é recuperado do contexto da aplicação — linha 30
- a tabela de opções do menu do cabeçalho é obtida do contexto para inicializar o componente [entete] da página — linha 32
- linhas 33-39: o componente [DataGridArticle] é vinculado a uma fonte de dados do tipo [ArrayList], que contém apenas o item recuperado na linha 30
- os componentes [lblMsgQte, txtQte] são inicializados com informações obtidas do contexto — linhas 42-45
- a propriedade [straction] também é inicializada com uma informação obtida do contexto — linha 47. Essa variável serve para gerar o atributo [action] do formulário HTML presente na página:
1.5.6.4. A visualização [panier.aspx]
1.5.6.4.1. Introduction
Esta visualização exibe o conteúdo do carrinho:

Ela é exibida após uma solicitação /main?action=carrinho ou /main?action=retirarcompra&id=ID. Os elementos da solicitação do controlador são os seguintes:
objeto Hashtable() — a tabela de opções do menu | |
objeto do tipo [Panier] — o carrinho a ser exibido |
Cada link [Retirer] da tabela HTML de compras do carrinho possui um URL na forma [?action=retirerachat&id=ID], onde ID é o campo [id] do item que se deseja remover do carrinho.
1.5.6.4.2. Os componentes da página
![]() |
n° | type | nom | rôle |
componente de usuário | cabeçalho | exibir o cabeçalho | |
DataGrid | DataGridAchats 3 - coluna relacionada - cabeçalho: Artigo, campo: nome 4 - coluna relacionada - cabeçalho: Qtd., campo: qtd. 5 - coluna relacionada - título: Preço, campo: preço 6 - coluna relacionada - título: Total, campo: total, formatação {0:C} 7 - coluna de hipertexto - Texto: Retirar, URL: id, formato da URL: /webarticles/main.aspx?action=retirerachat&id={0} | exibir a lista de itens comprados | |
rótulo | lblTotal | exibir o valor a pagar |
1.5.6.4.3. O código de apresentação [panier.aspx]
Comentários
- a linha 9 inclui o cabeçalho
- nas linhas 12 a 27, o componente [DataGridAchats] é definido
- na linha 29, o componente [lblTotal] é definido
1.5.6.4.4. O código de controle [panier.aspx.vb]
Comentários:
- os componentes da página são declarados nas linhas 11 a 13
- a inicialização do componente [entete] é idêntica à encontrada nas páginas já analisadas — linha 17
- o carrinho a ser exibido é recuperado da sessão — linha 19
- a exibição desse carrinho por meio do componente [DataGridAchats] apresenta problemas. A dificuldade decorre da inicialização do componente. Recordemos suas colunas:
- coluna [Article] associada ao campo [nom] da fonte de dados
- coluna [Qté] associada ao campo [qte] da fonte de dados
- coluna [Prix] associada ao campo [prix] da fonte de dados
- coluna [Total] associada ao campo [total] da fonte de dados
A fonte de dados de que dispomos é o carrinho de compras e sua lista de compras. Esta última será a fonte de dados do [DataGrid]. Apenas os objetos [Achat], que irão alimentar as linhas do [DataGrid], não possuem as propriedades [nom, qte, prix, total] esperadas pelo [DataGrid]. Por isso, cria-se aqui, especialmente para o [DataGrid], uma fonte de dados cujos elementos possuem as características esperadas pelo [DataGrid]. Esses elementos serão do tipo [LigneAchat], uma classe criada para a ocasião e derivada da classe [Achat] — linhas 36-66
- com a classe [LigneAchat] definida, a fonte de dados de [DataGridAchats] é construída a partir do carrinho encontrado na sessão — linhas 20-30
- o valor das compras é exibido por meio da propriedade [totalPanier] da classe [Panier] — linha 32
1.5.6.5. A visualização [paniervide.aspx]
1.5.6.5.1. Introduction
Essa visualização exibe a informação indicando que o carrinho está vazio:

Ela é exibida após uma solicitação /main?action=carrinho ou /main?action=retirarcompra&id=ID. Os elementos da solicitação do controlador são os seguintes:
objeto Hashtable() — a tabela de opções do menu |
1.5.6.5.2. Os componentes da página
![]() |
n° | type | nom | rôle |
componente de usuário | cabeçalho | exibir o cabeçalho |
1.5.6.5.3. O código de apresentação [paniervide.aspx]
Comentários:
- o cabeçalho está incluído na linha 9
1.5.6.5.4. O código de controle [paniervide.aspx.vb]
Comentários:
- basta inicializar o único componente dinâmico da página — linha 10
1.5.6.6. A visualização [erreurs.aspx]
1.5.6.6.1. Introduction
Esta tela é exibida em caso de erros:

Ela é exibida após qualquer solicitação que resulte em erro, exceto no caso da ação de compra com quantidade incorreta, que é tratada pela visualização [INFOS]. Os elementos da solicitação do controlador são os seguintes:
objeto Hashtable() — a tabela de opções do menu | |
ArrayList de objetos [String] representando as mensagens de erro a serem exibidas |
1.5.6.6.2. Os componentes da página
![]() |
n° | type | nom | rôle |
componente de usuário | cabeçalho | exibir o cabeçalho | |
repetidor | rptErreurs | exibir a lista de erros |
1.5.6.6.3. O código de apresentação [erreurs.aspx]
Comentários:
- o cabeçalho está definido na linha 9
- O componente [rptErreurs] está definido nas linhas 13 a 19. Seu conteúdo provém de uma fonte de dados do tipo [ArrayList], composta por objetos [String].
1.5.6.6.4. O código de controle [erreurs.aspx.vb]
Comentários:
- o componente [entete] é inicializado como de costume, nas linhas 9 e 14
- o componente [rptErreurs] é inicializado com a lista de erros do tipo [ArrayList] encontrada no contexto — linhas 16 a 19
1.5.7. Os controladores global.asax, main.aspx
Resta escrever o núcleo do nosso aplicativo web, o controlador. Sua função consiste em:
- recuperar a solicitação do cliente,
- processar a ação solicitada pelo cliente com o auxílio das classes de negócio,
- enviar a vista apropriada como resposta.
1.5.7.1. O controlador [global.asax.vb]
Quando a aplicação recebe sua primeira solicitação, a rotina [Application_Start] do arquivo [global.asax.vb] é executada. Isso ocorrerá apenas uma vez. O procedimento [Application_Start] tem como objetivo inicializar os objetos necessários para a aplicação web, que serão compartilhados em modo somente leitura por todas as threads de clientes. Esses objetos compartilhados podem ser colocados em dois locais:
- nos campos privados do controlador
- o contexto de execução da aplicação (Application)
O método [Application_Start] da aplicação [global.asax.vb] realizará as seguintes ações:
- verificará a presença, no arquivo [web.config], dos parâmetros necessários para o bom funcionamento da aplicação. Esses parâmetros foram descritos no parágrafo 1.5.3.
- inserirá no contexto do aplicativo a lista de eventuais erros na forma de um objeto [ArrayList erreurs]. Essa lista estará vazia se não houver erros, mas existirá de qualquer maneira.
- Se houver erros, o método [Application_Start] é encerrado nesse ponto. Caso contrário, ele solicita a referência de um singleton do tipo [IArticlesDomain], que será o objeto de negócio que o controlador utilizará para suas necessidades. Conforme explicado na seção 1.5.3.2, o controlador solicitará esse singleton ao framework Spring. Essa operação de instanciação pode gerar diversos erros. Se for esse o caso, eles serão, mais uma vez, armazenados no objeto [erreurs] do contexto da aplicação.
O controlador [global.asax.vb] possui um procedimento [Session_Start] executado sempre que um novo cliente chega. Nesse procedimento, será criado um carrinho vazio para o cliente. Esse carrinho será mantido ao longo das solicitações desse cliente específico. O código poderia ser o seguinte:
Comentários:
- os parâmetros esperados em [web.config] são definidos em uma tabela — linha 18
- eles são pesquisados em [web.config]. Se estiverem presentes, são armazenados no contexto da aplicação; caso contrário, um erro é registrado na lista de erros [erreurs] — linhas 21-33
- Se não houver erros, solicita-se ao Spring uma referência ao singleton [articlesDomain], que gerencia o acesso à camada [domain] da aplicação — linhas 35-47. Eventuais erros são registrados em [erreurs].
- Os erros são registrados no contexto da aplicação — linha 49
- a rotina é encerrada caso tenham ocorrido erros — linha 51
- cria-se uma matriz com três dicionários. Cada um deles possui duas chaves: href e link. Essa matriz representa as três opções possíveis do menu — linhas 52-71
- essa matriz é armazenada no contexto da aplicação — linha 73
- a cada novo cliente, o procedimento [Session_Start] é executado. Nele, cria-se um carrinho vazio na sessão do cliente — linhas 78-81
1.5.7.2. O controlador [main.aspx.vb]
O controlador [main.aspx.vb] recebe todas as solicitações dos clientes. Na verdade, todas elas seguem o formato [/webarticles/main.aspx?action=XX]. Uma solicitação será processada da seguinte maneira:
- o objeto [erreurs] do contexto da aplicação será verificado. Se não estiver vazio, isso significa que ocorreram erros durante a inicialização da aplicação e que ela não pode funcionar. Nesse caso, será enviada, como resposta, a visualização [ERREURS].
- o parâmetro [action] da solicitação será recuperado e verificado. Se ele não corresponder a uma ação conhecida, a visualização [ERREURS] será enviada com uma mensagem de erro apropriada.
- Se o parâmetro [action] for válido, a solicitação do cliente é encaminhada a um procedimento específico da ação para processamento:
método | solicitação | processamento | respostas possíveis |
GET /main?action=liste | - solicitar a lista de artigos à classe de negócios - exibi-lo | [LISTE] ou [ERREURS] | |
GET /main?action=infos&id=ID | - solicitar o artigo com id=ID à classe de negócios - exibi-lo | [INFOS] ou [ERREURS] | |
POST /main?action=compra&id=ID - a quantidade comprada faz parte dos parâmetros enviados | - solicitar o item com id=ID à classe de negócios - incluí-lo no carrinho na a sessão do cliente | [LISTE] ou [INFOS] ou [ERREURS] | |
GET /main?action=retirerachat&id=ID | - remover o item com id=ID da lista de compras do carrinho de sessão do cliente | [PANIER] | |
GET /main?action=carrinho | - exibir o carrinho da sessão do cliente | [PANIER] ou [PANIERVIDE] | |
GET /main?action=validationpanier | - diminuir na base de dados os estoques de todos os itens presentes no carrinho de sessão do cliente | [LISTE] ou [ERREURS] |
A estrutura do controlador [main.aspx.vb] poderia ser a seguinte:
Comentários:
- a classe possui dois campos privados que serão compartilhados entre os métodos — linhas 15-16:
- articlesDomain: o singleton de acesso à camada [domain]
- opções: a matriz de dicionários das opções do menu
- o procedimento [Page_Load]:
- inicializará os dois campos privados da classe
- recuperará o parâmetro [action] da solicitação e executará o método de processamento dessa ação.
1.5.7.3. O método [Page_Load]
Este evento é o primeiro a ocorrer na página. O código é o seguinte:
Comentários:
- a cada carregamento da página, verifica-se se a inicialização do aplicativo realizada por [global.asax] ocorreu corretamente.
- Para isso, recuperamos, no contexto do aplicativo, a lista de erros inserida ali por [global.asax] — linha 4
- Se essa lista não estiver vazia, exibe-se a visualização [ERREURS] — linhas 6-10
- recuperamos o singleton [articlesDomain] inserido pelo [global.asax] no contexto da aplicação e o armazenamos no campo privado [articlesDomain] para que esteja disponível para os diversos métodos da classe — linha 12
- faz-se um procedimento semelhante com a matriz de opções do menu — linha 14
- recuperamos o parâmetro [action] da solicitação — linha 16
- executamos o método correspondente à ação solicitada. Uma ação não prevista é considerada como a ação [liste] — linhas 16-36
1.5.7.4. Processamento da ação [liste]
Trata-se de exibir a lista de itens:

O código é o seguinte:
Comentários:
- eventuais erros são registrados em um [ArrayList] — linha 4
- a lista de itens é solicitada ao singleton [articlesDomain] — linhas 5 a 12
- se houver erros, envia-se a visualização [ERREURS] — linhas 13 a 19
- caso contrário, envia-se a visualização [LISTE] — linhas 20 a 24
1.5.7.5. Processamento da ação [infos]
O cliente solicitou informações sobre um determinado artigo:

O código é o seguinte:
Comentários:
- os eventuais erros são colocados em um [ArrayList] – linha 4
- o identificador do artigo solicitado é recuperado na consulta — linha 6
- esse identificador é verificado. Ele deve estar presente e deve ser um número inteiro. Caso contrário, a visualização [ERREURS] é enviada com a mensagem de erro apropriada — linhas 7-27
- após a verificação do identificador, o artigo é solicitado ao singleton [articlesDomain]. Caso ocorra uma exceção, a visualização [ERREURS] é enviada — linhas 29-39
- Se o item não for encontrado, a visualização [ERREURS] é enviada — linhas 41-48
- Se o artigo tiver sido encontrado, ele é inserido na sessão do usuário e, em seguida, exibido na visualização [INFOS] — linhas 50-56
1.5.7.6. Processamento da ação [achat]
O cliente comprou o artigo exibido na visualização [INFOS].

O código é o seguinte:
Comentários:
- o item adicionado à sessão é recuperado — linha 5
- se ele não estiver lá (a sessão pode ter expirado), é exibida a visualização [LISTE] — linhas 7-10
- a quantidade comprada é recuperada na consulta — linha 12
- sua validade é verificada — linhas 13-29
- em caso de invalidade, dependendo da situação, é exibida a visualização [LISTE] — linha 16 ou a visualização [INFOS] — linhas 24-28
- se tudo estiver normal, a compra é registrada no carrinho — linhas 31-32
- em seguida, a visualização [LISTE] é enviada — linha 34
1.5.7.7. Processamento da ação [panier]
O cliente fez várias compras e solicita visualizar o carrinho:

O código é o seguinte:
Comentários:
- recuperamos o carrinho da sessão — linha 4. Aqui não verificamos se realmente recuperamos algo. Seria necessário fazer isso, pois a sessão pode ter expirado.
- Se o carrinho estiver vazio, enviamos a visualização [PANIERVIDE] — linhas 6 a 10
- caso contrário, enviamos a visualização [PANIER] — linhas 11 a 14
1.5.7.8. Processamento da ação [retirerachat]
O cliente deseja remover um item de compra do carrinho:

O código é o seguinte:
Comentários:
- recuperamos o carrinho da sessão — linha 4. Aqui não verificamos se realmente recuperamos algo. Seria necessário fazer isso, pois a sessão pode ter expirado.
- Recupera-se na solicitação o identificador [id] do item a ser removido — linha 8.
- A compra correspondente é removida do carrinho — linha 10
- A validade do identificador do item comprado não foi verificada aqui. Se ele tiver um tipo inválido, ocorrerá uma exceção que será tratada nas linhas 11-13. Se for válido, mas inexistente, o método [panier.enlever] — linha 10 — não faz nada.
- Exibe-se o novo carrinho - linha 16
1.5.7.9. Processamento da ação [validerpanier]
O cliente deseja confirmar seu carrinho:

O código é o seguinte:
Comentários:
- recuperamos o carrinho da sessão — linha 6. Aqui não verificamos se realmente recuperamos algo. Seria necessário fazer isso, pois a sessão pode ter expirado.
- Caso a sessão tenha expirado, teremos um ponteiro [nothing] para o carrinho e o método [acheter] — linha 9 — lançará uma exceção e a visualização [ERREURS] será enviada. No entanto, a mensagem de erro não será muito clara para o usuário.
- Nas linhas 8 a 16, tenta-se validar o carrinho recuperado da sessão. Algumas compras podem não ser validadas se a quantidade solicitada exceder o estoque do item solicitado. Esses casos são registrados pelo método [acheter] em uma lista de erros que é recuperada na linha 11.
- Se houver erros, a visualização [ERREURS] é enviada — linhas 18-23
- caso contrário, é enviada a visualização [LISTE] — linhas 25-26
1.6. Conclusion
Desenvolvemos aqui uma aplicação seguindo um modelo MVC. Não parece existir (abril de 2005) nenhum “framework” profissional de desenvolvimento MVC em ASP.NET, como os que existem em Java (Struts, Spring, ...). O projeto [Spring.net] deve propor um em breve. Enquanto se aguarda essa novidade, o método anterior permite um desenvolvimento MVC viável para aplicativos de médio porte.































