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 3 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 — ver o parágrafo 3.1 do anexo;
- Servidor web Cassini para a execução — ver anexo, parágrafo 3.2;
- Nunit para os testes unitários – ver anexo, parágrafo 3.4;
- Spring para a integração e configuração das camadas da aplicação web — ver anexo, parágrafo 3.3;
Numa escala iniciante-intermédio-avançado, este documento situa-se na parte [intermédiaire-avancé]. A sua compreensão requer vários pré-requisitos. Alguns deles podem ser adquiridos em documentos que escrevi. Nesse caso, faço referência aos mesmos. É evidente que se trata apenas de uma sugestão e que o leitor pode utilizar os seus documentos preferidos.
- linguagem VB.net: [Introdução ao VB.NET através de exemplos (2004)];
- programação web em VB.net: [Desenvolvimento Web com ASP.NET 1.1 (2004)];
- utilização 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 (2005)]. Estamos a construir em VB.NET a aplicação web MVC de três camadas, escrita em Java. A ideia que pretendemos transmitir aqui é que as plataformas de desenvolvimento Java e .NET são suficientemente semelhantes entre si para que as competências adquiridas numa destas duas áreas possam ser reutilizadas na outra.
Não parece existir uma solução de desenvolvimento MVC ASP.NET amplamente reconhecida. A solução que se segue retoma o método apresentado no documento [Desenvolvimento Web com ASP.NET 1.1 (2004)]. Embora esta solução tenha o mérito de utilizar conceitos comuns no desenvolvimento J2EE, não deve, no entanto, ser considerada mais do que aquilo que é: c.a.d, um método entre outros de desenvolvimento MVC. Assim que um método de desenvolvimento MVC em ASP.NET for amplamente aceite, será então necessário adotar este último. A versão .NET do Spring, atualmente em desenvolvimento, poderá muito bem ser uma primeira solução.
1.2. A aplicação webarticles
Apresentamos aqui os elementos de uma aplicação web simplificada de comércio eletrónico. Esta permitirá aos clientes da web:
- consultar uma lista de artigos provenientes de uma base de dados
- colocar alguns deles num cesto de compras eletrónico
- confirmar o cesto. Esta confirmação terá como único efeito atualizar, na base de dados, os stocks dos artigos comprados.
As diferentes vistas apresentadas ao utilizador serão as seguintes:
- a vista «LISTE», que apresenta uma lista dos artigos à venda ![]() | - a vista [INFOS], que fornece informações adicionais sobre um produto: ![]() |
- as vistas [PANIER] e [PANIERVIDE], que apresentam o conteúdo do cesto de compras do cliente
![]() | ![]() |
- a vista [ERREURS], que sinaliza qualquer erro da aplicação

1.3. Arquitetura geral da aplicação
Pretende-se construir uma aplicação com a seguinte estrutura de três camadas:
![]() |
- as três camadas são tornadas independentes através da utilização de interfaces
- A integração das diferentes camadas é realizada pelo Spring
- cada camada está sujeita a espaços de nomes separados: web (camada UI), domain (camada de negócio) e dao (camada de acesso aos dados).
A aplicação seguirá uma arquitetura MVC (Modelo - Vista - Controlador). Se retomarmos o esquema em camadas acima, a arquitetura MVC integra-se nele da seguinte forma:
![]() |
O processamento de um pedido de um cliente decorre de acordo com as seguintes etapas:
- o cliente envia um pedido ao controlador. Este controlador será, neste caso, uma página .aspx à qual será atribuída uma função específica. Esta página recebe todos os pedidos dos clientes. É a porta de entrada da aplicação. É o C de MVC.
- o controlador processa essa solicitação. Para tal, pode necessitar da ajuda da camada de negócio, a que se chama modelo M na estrutura MVC.
- O controlador recebe uma resposta da camada de negócio. O pedido do cliente foi processado. Este pode dar origem a 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 (= vista) a enviar ao cliente. Esta é, na maioria das vezes, uma página que contém elementos dinâmicos. O controlador fornece esses elementos à vista.
- A vista é enviada ao cliente. É o V de MVC.
1.4. O modelo
Analisamos aqui o M de MVC. O modelo é aqui constituído pelos seguintes elementos:
- as classes de negócio
- as classes de acesso aos dados
- a base de dados
1.4.1. A base de dados
A base de dados contém apenas uma tabela denominada ARTICLES. Esta 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 artigo de forma única | |
nome do artigo | |
o seu preço | |
stock atual | |
o nível de stock abaixo do qual deve ser efetuada uma encomenda de reabastecimento |
1.4.2. Os espaços de nomes do modelo
O modelo M é aqui fornecido sob a 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ócio da camada [domain]
Cada um destes espaços de nomes será gerado num ficheiro «assembly» que lhe é próprio:
contenu | rôle | |
- [IArticlesDao]: a interface de acesso à camada [dao]. Esta é a única interface que a camada [domain] vê. Não vê nenhuma outra. - [Article]: classe que define um artigo - [ArticlesDaoArrayList]: classe de implementação da da interface [IArticlesDao] com uma classe [ArrayList] | camada de acesso aos dados - encontra-se inteiramente na camada [dao] da arquitetura de três camadas da aplicação web | |
- [IArticlesDomain]: a interface de acesso à camada [domain]. É a única interface que a camada web vê. 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 - encontra-se inteiramente na camada [domain] da arquitetura Arquitetura de 3 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 colocadas numa árvore cuja raiz é a pasta [istia]. Estão todas 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
' stock 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
' stock 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 por predefiniçã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 escrever 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 cadeia de caracteres. Isto é frequentemente útil para a depuração de uma aplicação.
1.4.3.2. A interface [IArticlesDao]
A interface [IArticlesDao] está definida da seguinte forma:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' lista de todos os artigos
Function getAllArticles() As IList
' adiciona um artigo
Function ajouteArticle(ByVal unArticle As Article) As Integer
' elimina um artigo
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' altera um artigo
Function modifieArticle(ByVal unArticle As Article) As Integer
' procura um artigo
Function getArticleById(ByVal idArticle As Integer) As Article
' elimina todos os artigos
Sub clearAllArticles()
' altera o stock 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 artigos da fonte de dados | |
esvazia a fonte de dados | |
retorna o objeto [Article] identificado pela sua chave primária | |
permite adicionar um artigo à fonte de dados | |
permite modificar um artigo da fonte de dados | |
permite eliminar um artigo da fonte de dados | |
permite alterar o stock de um artigo da fonte de dados |
A interface disponibiliza aos programas clientes um certo número de métodos definidos apenas pelas suas assinaturas. Não se ocupa da forma como esses métodos serão efetivamente implementados. Isto confere flexibilidade a uma aplicação. O programa cliente efetua as suas chamadas a uma interface e não a uma implementação específica da mesma.
![]() |
A escolha de uma implementação específica será feita através de um ficheiro de configuração do Spring. Para demonstrar que, para testar a aplicação web, apenas a interface de acesso aos dados é relevante e não a sua classe de implementação, vamos, em primeiro lugar, implementar a fonte de dados através 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 por predefinição
Public Sub New()
' fabricamos alguns artigos
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 artigos
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
' retorna a lista de artigos
SyncLock Me
Return articles
End SyncLock
End Function
' eliminação de todos os artigos
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' esvazia a lista de artigos
SyncLock Me
articles.Clear()
End SyncLock
End Sub
' obter um artigo identificado pela sua chave
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' procurar o artigo 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 artigo à lista de artigos
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' o artigo é adicionado à lista de artigos
SyncLock Me
' verifica-se se 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 artigo
articles.Add(unArticle)
' retorna o resultado
Return 1
End SyncLock
End Function
' alterar um artigo
Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
' modifica-se um artigo
SyncLock Me
' verifica-se se existe
Dim ipos As Integer = posArticle(articles, articleNouveau.id)
' no caso de não existir
If ipos = -1 Then Return 0
' existe - altera-se
articles(ipos) = articleNouveau
' retorna-se o resultado
Return 1
End SyncLock
End Function
' eliminar um artigo identificado pela sua chave
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' eliminação de um artigo
SyncLock Me
' verifica-se se existe
Dim ipos As Integer = posArticle(articles, idArticle)
' no caso de não existir
If ipos = -1 Then Return 0
' existe - elimina-se
articles.RemoveAt(ipos)
' retornar o resultado
Return 1
End SyncLock
End Function
' alterar o stock de um artigo identificado pela sua chave
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' alterar o stock de um artigo
SyncLock Me
' verifica-se se existe
Dim ipos As Integer = posArticle(articles, idArticle)
' no caso de não existir
If ipos = -1 Then Return 0
' existe - altera-se o seu stock, se for possível
Dim unArticle As Article = CType(articles(ipos), Article)
' só se altera o stock se for suficiente
If unArticle.stockactuel + mouvement >= 0 Then
unArticle.stockactuel += mouvement
Return 1
Else
Return 0
End If
End SyncLock
End Function
' procurar um artigo identificado pela sua chave
Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
' retorna a posição do artigo [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 predefiniçã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. Em 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 artigo identificado pelo seu n.º. Se o artigo não existir, o método devolve a posição -1. Este método é utilizado repetidamente pelos outros métodos.
- O método [ajouteArticle] permite adicionar um artigo à lista de artigos. Retorna o número de artigos inseridos: 1. Se o artigo já existisse, é lançada uma exceção.
- O método [modifieArticle] permite modificar um artigo existente. Devolve o número de artigos modificados: 1 se o artigo existisse, 0 caso contrário.
- O método [supprimeArticle] permite eliminar um artigo existente. Retorna o número de artigos eliminados: 1 se o artigo existisse, 0 caso contrário.
- O método [getAllArticles] fornece a lista de todos os artigos
- O método [getArticleById] permite obter um artigo identificado pelo seu n.º. Obtém-se o valor [nothing] se o artigo não existir.
- O código não apresenta grandes dificuldades. Deixamos que o leitor o analise 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]. Este é 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]. Em .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] requerem uma referência ao assembly [nunit.framework.dll]
- A classe de teste [NUnit] obtém uma instância do objeto a testar através do Spring. Por isso, encontramos
- na pasta [bin], os ficheiros de classes do Spring
- em [References], uma referência ao assembly [Spring-Core.dll] da pasta [bin]
- em [bin], um ficheiro de configuração para o Spring
- a classe de teste necessita do assembly [webarticles-dao.dll] da camada [dao]. Este foi colocado na pasta [bin] e a sua referência foi adicionada às referências do projeto.
Uma classe de teste [NUnit] necessita de acesso às classes do espaço de nomes [NUnit.Framework]. Por isso, encontra-se aí a seguinte instrução de importação:
O espaço de nomes [NUnit.Framework] encontra-se no «assembly» [nunit.framework.dll], que deve ser adicionado às referências do projeto:
![]() | ![]() |
O assembly [nunit.framework.dll] deve constar da lista apresentada se a instalação do [Nunit] tiver sido efetuada. Basta clicar duas vezes no assembly para o adicionar 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 testar
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' recupera-se uma instância do criador de objetos 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()
' eliminam-se 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()
' eliminação de todos os artigos
articlesDao.clearAllArticles()
' verificação: a tabela de artigos deve estar vazia
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adicionam-se dois artigos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação: deve haver dois artigos
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' verificação visual
listArticles()
End Sub
<Test()> _
Public Sub testSupprimeArticle()
' eliminação de todos os artigos
articlesDao.clearAllArticles()
' verificação: a tabela de artigos deve estar vazia
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adicionam-se dois artigos
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' verificação: deve haver 2 artigos
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' elimina-se o artigo 4
articlesDao.supprimeArticle(4)
' verificação: deve restar 1 artigo
articles = articlesDao.getAllArticles
Assert.AreEqual(1, articles.Count)
' verificação visual
listArticles()
End Sub
<Test()> _
Public Sub testModifieArticle()
' eliminação de todos os artigos
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 artigos
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)
' pesquisa do artigo 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.nom, "article3")
' pesquisa do artigo 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()
' eliminação de artigos
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 artigos
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)
' pesquisa do artigo 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.nom, "article3")
' pesquisa do artigo 4
unArticle = articlesDao.getArticleById(4)
' verificação
Assert.AreEqual(unArticle.nom, "article4")
End Sub
' lista no ecrã
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()
' eliminação de todos os artigos
articlesDao.clearAllArticles()
' pesquisa de artigo 1
Dim article As article = articlesDao.getArticleById(1)
' verificação
Assert.IsNull(article)
' alteração de um artigo inexistente
Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
' não foi necessário alterar nenhuma linha
Assert.AreEqual(i, 0)
' eliminação de artigo inexistente
i = articlesDao.supprimeArticle(1)
' não foi necessário eliminar nenhuma linha
Assert.AreEqual(0, i)
End Sub
<Test()> _
Public Sub testChangerStockArticle()
' eliminação de todos os artigos
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 fim de todos os threads
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' verificações — o artigo 3 deve ter um stock de 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' diminui-se o stock do artigo 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' verificação: o seu stock 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 diminui o stock
articlesDao.changerStockArticle(3, -1)
' thread concluído
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
End Class
End Namespace
Comentários:
- pretendíamos escrever um programa de teste para a interface [IArticlesDao] que fosse independente da classe de implementação da mesma. Por isso, utilizámos 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 ficheiro [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 ficheiro indica o nome da classe de implementação [istia.st.articles.dao.ArticlesDaoArrayList] da interface [IArticlesDao] e onde a encontrar: [ webarticles-dao.dll]. Como a instanciação não requer parâmetros, nenhum é definido aqui.
- A maioria dos testes é fácil de compreender. Recomenda-se ao leitor que leia os comentários.
- O método [testChangerStockArticle] requer algumas explicações. Cria 100 threads encarregadas de diminuir o stock de um determinado artigo.
<Test()> _
Public Sub testChangerStockArticle()
' eliminação de todos os artigos
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 fim de todos os threads
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' verificações — o artigo 3 deve ter um stock de 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' diminui-se o stock do artigo 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' verificação: o seu stock não deve ter mudado
Assert.AreEqual(0, nbLignes)
' verificação visual
listArticles()
End Sub
O objetivo aqui é testar os acessos simultâneos à fonte de dados. O método responsável pela atualização do stock é o seguinte:
Public Sub décrémente()
' thread iniciado
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' o thread diminui o stock
articlesDao.changerStockArticle(3, -1)
' thread concluído
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
Diminui em uma unidade o stock do artigo n.º 3. Se consultarmos o código do método [testChangerStockArticle], verificamos que:
- o stock do artigo n.º 3 é inicializado em 101
- os 100 threads irão diminuir esse stock em uma unidade cada
- devemos, portanto, ter um stock de 1 no final da execução de todos os threads
Além disso, ainda neste método, tentamos fazer com que o stock 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, utilizando a aplicação [Nunit-Gui], carregamos este ficheiro DLL e executamos os testes:

Na janela da 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 bem-sucedido (verde) ou falhou (vermelho). O leitor que visualizar este documento no ecrã poderá ver que todos os testes foram bem-sucedidos. Posteriormente, 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 cesto 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 colocadas numa árvore cuja raiz é a pasta [istia]. Estão todas 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, esta DLL foi adicionada como referência ao projeto.
1.4.4.1. A interface [IArticlesDomain]
A interface [IArticlesDomain] separa a camada [métier] da camada [web]. Esta última acede à camada [métier/domain] através desta interface, sem se preocupar com a classe que a implementa efetivamente. A interface define as seguintes ações para o acesso à camada de negócio:
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 stocks dos artigos comprados a quantidade adquirida — pode falhar se o stock for insuficiente | |
retorna a lista de erros que ocorreram — 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 por predefinição
Public Sub New()
End Sub
' construtor com parâmetros
Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
' através das propriedades
Me.article = unArticle
Me.qte = qte
End Sub
' artigo 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 artigo adquirido | |
a quantidade comprada | |
o valor da compra | |
cadeia de identificação do objeto |
- 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 predefinido
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 produto
Public Sub enlever(ByVal idAchat As Integer)
' procura-se o produto
Dim iachat As Integer = posAchat(idAchat)
' se tiver sido encontrado, remove-se
If iachat <> -1 Then
Dim achatCourant As Achat = CType(_achats(iachat), Achat)
' retira-se do cesto
_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 a 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 artigo 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 | |
retira a compra do artigo idAchat | |
o valor total das compras no cesto | |
retorna a cadeia 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.º do artigo adquirido. A lista de compras é gerida de forma a que um artigo comprado várias vezes ocupe apenas uma posição na lista. Assim, uma compra pode ser identificada pelo número do artigo comprado. O método [posAchat] devolve -1 se a compra procurada não existir.
- O método [ajouter] adiciona uma nova compra à lista de compras. Isto equivale a adicionar uma nova entrada à lista de compras, caso o artigo adquirido ainda não existisse na lista, ou a aumentar a quantidade adquirida, 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 emite qualquer mensagem e não realiza nenhuma ação.
- O valor das compras [totalPanier] é mantido à medida que as compras são adicionadas e 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 artigos
Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
' lista de todos os artigos
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 artigo identificado pelo seu n.º
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
' um artigo 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 cesto
Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
' compra de um cesto - os stocks dos artigos 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 stock do artigo 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 cesto
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]. Possui dois campos privados:
o objeto de acesso aos dados | |
a lista de eventuais erros. É acessível através 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 com o mesmo nome da camada [dao]
- O método [acheter] valida a compra de um cesto de compras. Esta validação consiste simplesmente em reduzir os stocks dos artigos comprados. A compra de um artigo só é possível se o seu stock o permitir. Caso contrário, a compra é recusada: o artigo permanece no cesto e é sinalizado um erro na lista [erreurs]. Uma compra validada é removida do cesto e o stock do artigo correspondente é reduzido na 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]. Este é 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] requerem uma referência ao assembly [nunit.framework.dll]
- A classe de teste [NUnit] obtém uma instância do objeto a testar através do Spring. Por isso, encontramos
- na pasta [bin], os ficheiros de classes do Spring
- em [References], uma referência ao assembly [Spring-Core.dll] da pasta [bin]
- em [bin], um ficheiro de configuração para o Spring
- a classe de teste necessita do assembly [webarticles-dao.dll] da camada [dao] e do assembly [webarticles-domain.dll] da camada [domain]. Estes foram colocados na pasta [bin] e as respetivas 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 testar
Private articlesDomain As IArticlesDomain
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' recuperamos uma instância do criador de objetos Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' solicita-se a instanciação do objeto DAO «articles»
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()
' eliminação dos artigos
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 artigos
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)
' pesquisa do artigo 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.nom, "article3")
' pesquisa do artigo 4
unArticle = articlesDao.getArticleById(4)
' verificação
Assert.AreEqual(unArticle.nom, "article4")
End Sub
<Test()> _
Public Sub acheterPanier()
' eliminação de artigos
articlesDao.clearAllArticles()
' verificação
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' adição de 2 artigos
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 cesto de compras 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 cesto de compras
articlesDomain.acheter(panier)
' verificações
Assert.AreEqual(0, articlesDomain.erreurs.Count)
Assert.AreEqual(0, panier.achats.Count)
' pesquisa do artigo 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.stockactuel, 20)
' pesquisa de artigo 4
unArticle = articlesDao.getArticleById(4)
' verificação
Assert.AreEqual(unArticle.stockactuel, 30)
' novo cesto de compras
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
' confirmação do cesto de compras
articlesDomain.acheter(panier)
' verificações
Assert.AreEqual(1, articlesDomain.erreurs.Count)
' pesquisa de artigo 3
unArticle = articlesDomain.getArticleById(3)
' verificação
Assert.AreEqual(unArticle.stockactuel, 20)
End Sub
<Test()> _
Public Sub testRetirerAchats()
' eliminaçã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 cesto 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 artigo 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)
' continuam a existir 2 artigos no cesto
Assert.AreEqual(2, monPanier.achats.Count)
' a quantidade do artigo 3 deve ter passado para 20
Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
Assert.AreEqual(20, unAchat.qte)
' retira-se o artigo 3 do cesto
monPanier.enlever(3)
' verificações
' o total deve ter passado para 400
Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
' apenas 1 artigo no cesto
Assert.AreEqual(1, monPanier.achats.Count)
' deve ser o artigo n.º 4
Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
End Sub
' lista no ecrã
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:
- pretendíamos escrever um programa de teste para a interface [IArticlesDomain] que fosse independente da classe de implementação da mesma. Por isso, utilizámos 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 testar. Estes estão definidos no seguinte ficheiro [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 ficheiro indica:
- (continuação)
- para o singleton [articlesdao], o nome da classe de implementação [istia.st.articles.dao.ArticlesDaoArrayList] e onde a encontrar: [ webarticles-dao.dll]. Como a instanciação não requer parâmetros, nenhum é definido aqui.
- para o singleton [articlesdomain], o nome da classe de implementação [istia.st.articles.domain.AchatsArticles] e onde a encontrar: [ webarticles-domain.dll]. A classe [AchatsArticles] tem um construtor com um parâmetro: o singleton que gere o acesso à camada [dao]. Aqui, este é definido como sendo o singleton [articlesdao] definido anteriormente.
- A classe de teste obtém uma instância da classe a testar [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 de ter acesso à camada [dao], que nem sequer deveria conhecer. Aqui, ignorámos esta «ética» que, para ser respeitada, nos teria obrigado a criar novos métodos na 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, utilizando a aplicação [Nunit-Gui], carregamos este DLL e executamos os testes:

O leitor que visualizar este documento no ecrã poderá verificar que todos os testes foram bem-sucedidos. Posteriormente, consideraremos que dispomos de uma camada [domain] operacional.
1.4.5. Conclusão
Recorde-se que pretendemos construir a seguinte aplicação web de três camadas:
![]() |
O modelo M da nossa aplicação MVC já está escrito e testado. É-nos fornecido em dois ficheiros: 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 vistas V. Vamos considerar, em primeiro lugar, um método apresentado no documento [Développement WEB avec ASP.NET 1.1 ]
- o controlador C é implementado por dois ficheiros [global.asax, main.aspx]
- as vistas V são implementadas por páginas aspx
1.5. A camada [web]
A arquitetura MVC da aplicação 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 seguintes: global.asax: gere os eventos relacionados com o arranque inicial da aplicação main.aspx: processa individualmente a solicitação de cada cliente |
1.5.1. As vistas
As vistas correspondem às que foram apresentadas no início do documento:
liste.aspx | As vistas estão reunidas na pasta [vues] da aplicação ![]() | |
infos.aspx | ||
panier.aspx | ||
paniervide.aspx | ||
erreurs.aspx |
1.5.2. Os controladores
Conforme referido, o controlador será composto por dois elementos:
- [global.asax,global.asax.vb]: utilizado principalmente para inicializar a aplicação e colocar no contexto da mesma todos os dados a partilhar entre os diferentes clientes
- [main.aspx, main.aspx.vb]: o verdadeiro controlador, responsável por processar as solicitações HTTP dos clientes.
As diferentes solicitações dos clientes serão encaminhadas para o controlador [main.aspx] e conterão um parâmetro denominado [action] que especifica a ação solicitada pelo cliente:
solicitação | significado | ação do controlador | respostas possíveis |
o cliente pretende a lista dos artigos | - solicita a lista de artigos à camada de negócio | - [LISTE] - [ERREURS] | |
O cliente solicita informações sobre um dos artigos apresentados na vista [LISTE] | - solicita o artigo à camada de negócios | - [INFOS] - [ERREURS] | |
o cliente compra um artigo | - solicita o artigo à camada de negócio e adiciona-o ao cesto de compras do cliente | - [INFOS] se houver erro na quantidade - [LISTE] se não houver erro | |
o cliente pretende eliminar um compra do seu carrinho | - recupera o carrinho da sessão e altera-o | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
o cliente pretende visualizar o seu cesto | - recupera o carrinho da sessão | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
o cliente concluiu as suas compras e passa para a fase de pagamento | - atualiza os stocks na base de dados dos artigos comprados - esvazia o cesto de compras do cliente dos artigos cuja compra foi validada | - [LISTE] - [ERREURS] |
1.5.3. Configuração da aplicação
Procuraremos configurar a aplicação de forma a torná-la o mais flexível possível face a alterações como:
- a alteração dos URL das diferentes vistas
- a alteração das classes que implementam as interfaces [IArticlesDao] e [IArticlesDomain]
- a alteração do SGBD, da base de dados e da tabela de artigos
1.5.3.1. As alterações ao URL
Os nomes das vistas URL serão colocados no ficheiro de configuração da aplicação [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. Esta independência é obtida da seguinte forma:
- as camadas comunicam entre si através de interfaces e não de classes concretas
- o código de uma camada nunca instancia por si próprio a classe de outra camada para a utilizar. Limita-se a solicitar a uma ferramenta externa, neste caso o Spring, uma instância de implementação da interface da camada que pretende utilizar. Para tal, sabemos que não precisa de conhecer o nome da classe de implementação, mas apenas o nome do singleton do Spring de que pretende uma referência.
Na nossa aplicação, o Spring será configurado no ficheiro [web.config] da aplicação web da seguinte forma:
<?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 aceder à camada [métier], uma classe da camada [web] poderá solicitar o singleton [articlesDomain]. O Spring irá então instanciar um objeto do tipo [istia.st.articles.domain.AchatsArticles]. Para esta instanciação, necessita de um objeto do tipo [articlesDao], ou seja, de um objeto do tipo [istia.st.articles.dao.ArticlesDaoArrayList]. O Spring irá então instanciar esse objeto. No final da operação, a camada [web], que solicitou o singleton [articlesDomain], dispõe de toda a cadeia que a liga à fonte de dados:
![]() |
1.5.3.3. Alterações relacionadas com o SGBD ou com a base de dados
Este ponto será ignorado aqui, uma vez que nos encontramos numa aplicação de teste sem o SGBD. Abordaremos, numa segunda fase, a implementação de uma camada [dao] baseada num SGBD.
1.5.4. A biblioteca de tags <asp:>
Consideremos a vista [ERREURS] que apresenta uma lista de erros:

A vista [ERREURS] é responsável por apresentar uma lista de erros que o controlador [main.aspx] colocou no contexto da solicitação com o nome [context.Items("erreurs")]. Existem várias formas de escrever uma página deste tipo. Aqui, estamos interessados apenas na parte relativa à exibição dos erros.
Recorde-se que uma página ASPX tem uma parte de apresentação HTML e uma parte de código .NET que prepara os dados que a parte de apresentação deve apresentar. Estas duas partes podem estar num único ficheiro [aspx] (solução WebMatrix) ou em dois ficheiros: [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. Esta solução é, em geral, fortemente desaconselhada. A remoção de todo o código da parte [présentation] exigiu a criação de bibliotecas de tags. Estas «ocultam» o código sob a forma de tags semelhantes às tags HTML. Apresentamos duas soluções possíveis para a página [ERREURS].
A nossa primeira solução utiliza código .NET na parte [présentation] da página. A página ASPX recupera a lista de erros presente no pedido na 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
...
' recuperam-se os erros
erreurs = CType(context.Items("erreurs"), ArrayList)
End Sub
e, em seguida, apresenta-o na secçã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 baliza <asp:repeater> da biblioteca de balizas <asp:> do ASP.NET. Se criarmos uma página ASPX graficamente, esta baliza está disponível sob a forma de um componente de servidor que se arrasta para o formulário de design. Se criarmos o código ASPX manualmente, podemos referir-nos a uma biblioteca de balizas.
Com a biblioteca de tags <asp:>, o código ASPX da vista [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 baliza
serve para repetir um padrão HTML nos diferentes elementos de uma fonte de dados. Os seus diferentes elementos são os seguintes:
o padrão HTML a apresentar antes de os elementos da fonte de dados serem apresentados | |
o padrão HTML a repetir para cada um dos elementos da fonte de dados. A expressão [<%# Container.DataItem %>] serve para apresentar o valor do elemento atual da fonte de dados | |
o padrão HTML a ser apresentado após a apresentação dos elementos da fonte de dados |
A fonte de dados está ligada à baliza, geralmente na parte [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
..
' associa-se os erros a rptErreurs
With rptErreurs
.DataSource = context.Items("erreurs")
.DataBind()
End With
End Sub
Esta ligação também pode ser feita na fase de conceção da página, caso a fonte de dados já seja conhecida, por exemplo, uma base de dados existente.
Nas nossas vistas, utilizaremos outra baliza: <asp:datagrid>, que permite apresentar uma fonte de dados sob a forma de uma tabela.
1.5.5. Estrutura da solução do Visual Studio da aplicação [webarticles]
Uma aplicação web é um puzzle com inúmeros elementos. Dotá-la de uma arquitetura MVC aumenta, geralmente, o número desses elementos. A estrutura da aplicação [webarticles] no âmbito de [Visual Studio] é a seguinte:
![]() | ![]() | ![]() |
![]() |
Comentários:
- o projeto [web] é do tipo [bibliothèque de classes] e não do tipo [Application web ASP.NET], como seria lógico esperar. O tipo [Application web ASP.NET] requer a presença do servidor web IIS na máquina de desenvolvimento ou numa máquina remota. O servidor IIS não existe de série nas máquinas Windows XP Família. No entanto, muitos PC são vendidos com esta versão. Para permitir que os leitores que dispõem do Windows XP possam implementar a aplicação 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]. Isto acarreta alguns inconvenientes, que são explicados nos anexos.
- Os ficheiros 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 Spring que nos permitem integrar as camadas web, domain e DAO | |
classes de registos — utilizadas pelo Spring |
Estas DLL são colocadas na pasta [bin] e adicionadas às referências do projeto.
1.5.6. As vistas ASPX
Tal como recomendado anteriormente, utilizaremos a biblioteca de tags <asp:> nas nossas vistas ASPX.
1.5.6.1. O componente de utilizador [entete.ascx]
Para conferir uma certa uniformidade às diferentes vistas, estas partilharão um mesmo cabeçalho, aquele que exibe o nome da aplicação juntamente 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 desta matriz é um dicionário destinado a gerar uma opção do menu do cabeçalho. Cada dicionário tem duas chaves:
- href: o URL associado à opção do menu
- link: o texto do menu
Vamos transformar o cabeçalho num controlo de utilizador. Um controlo de utilizador encapsula uma parte da página (apresentação e código associado) num componente reutilizável noutras páginas. Neste caso, pretendemos reutilizar o componente [entete] nas outras vistas da aplicação. O código de apresentação estará em [entete.ascx] e o código de controlo associado em [entete.ascx.vb]. O código de apresentação utilizará um componente <asp:repeater> para apresentar 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 controlo associado será o seguinte:
Comentários:
- o componente do tipo [EnteteWebArticles] tem uma propriedade pública [actions] apenas para escrita — linha 7
- esta 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 outras vistas da aplicação utilizarão o cabeçalho definido por [entete.ascx]. A página [erreurs.aspx], por exemplo, incluirá o cabeçalho através do seguinte código:
Comentários:
- A linha 1 declara que a baliza <WA:entete> deverá ser associada ao componente definido pelo ficheiro [entete.ascx]. Os atributos [TagPrefix] e [TagName] são livres.
- Feito isto, a inserção do componente no código de apresentação da página é efetuada na linha 9. Durante a execução, esta baliza terá como efeito incluir no código da página ASPX, que a contém, o código da página [entete.ascx]. O código de controlo [erreurs.aspx.vb] encarregar-se-á de inicializar este componente. Pode fazê-lo da seguinte forma:
Comentários:
- a linha 6 cria um objeto do tipo [EnteteWebArticles], que é o tipo do componente criado
- a linha 11 inicializa a propriedade [actions] deste objeto
1.5.6.2. A vista [liste.aspx]
1.5.6.2.1. Introduction
Esta vista apresenta a lista de artigos disponíveis para venda:
![]() | ![]() |
É apresentada na sequência de uma consulta /main?action=liste ou /main?action=validationpanier. Os elementos da consulta 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 apresentar na parte inferior da página |
Cada ligação [Infos] da tabela HTML dos artigos tem um URL com o formato [?action=infos&id=ID], em que ID é o campo id doartigo apresentado.
1.5.6.2.2. Componentes da página
![]() |
n° | type | nom | rôle |
componente do utilizador | cabeçalho | exibir o cabeçalho | |
DataGrid | DataGridArticles 3 - coluna relacionada: cabeçalho: Nome, campo: nome 4 - coluna associada: 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 | Mostrar uma mensagem |
Recorde-se como proceder para definir estas propriedades:
- no Visual Studio, seleciona-se o [DataGrid] para aceder à sua folha de propriedades:

- utilize-se o link [Mise en forme automatique] acima para gerir o formato da tabela apresentada
- e o link [Générateur de propriétés] para gerir o 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-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-15. De notar que foi necessário criar um objeto [EnteteWebArticles] com um operador [new], ao passo que isso não foi necessário com os outros componentes. Sem esta criação explícita, ocorria um erro de execução indicando que o objeto [entete] não fazia referência a nada. Este ponto mereceria ser aprofundado. Não o foi.
- 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 artigos é 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 vista [infos.aspx]
1.5.6.3.1. Introduction
Esta vista apresenta informações sobre um artigo e permite também a sua compra:

É apresentada na sequência de uma solicitação /main?action=infos&id=ID ou de uma solicitação /main?action=achat&id=ID quando a quantidade comprada está errada. Os elementos da solicitação do controlador são os seguintes:
objeto Hashtable() — a tabela de opções do menu | |
objeto do tipo [Article] — artigo a apresentar | |
objeto String - mensagem a apresentar em caso de erro na quantidade | |
objeto String - valor a apresentar no campo de introdução de dados [Qte] |
Os campos [msg] e [qte] são utilizados em caso de erro de introdução de dados relativos à quantidade:

Esta página contém um formulário que é enviado através do botão [Acheter]. O destino do URL do POST é o [?action=achat&id=ID], sendo que o ID é o ID do artigo adquirido.
1.5.6.3.2. Os componentes da página
![]() |
n° | type | nom | rôle |
componente do utilizador | cabeçalho | exibir o cabeçalho | |
literal | litID | Mostrar o n.º do artigo | |
DataGrid | DataGridArticle 3 - coluna associada: cabeçalho: Nome, campo: nome 4 - coluna relacionada: título: Preço, campo: preço 5 - coluna associada: cabeçalho: Stock atual, campo: stockActuel 6 - coluna relacionada: título: Stock mínimo, campo: stockMinimum | exibir um artigo | |
HTML Enviar | Enviar o formulário | ||
HTML Entrada runat=server | txtQte | introduzir a quantidade comprada | |
rótulo | lblMsgQte | eventual 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] estão definidos nas linhas 12 a 34
- o formulário é definido nas linhas 36-46. É do tipo POST.
- O destino do POST é fornecido por uma variável [strAction] — linha 36. Esta variável deverá ser definida pelo controlador.
- O campo de introdução da quantidade comprada está definido na linha 41. Trata-se de um componente HTML do servidor (runat=server). No código, acede-se a ele através de um objeto.
- A linha 42 define o rótulo [lblMsgQte], que conterá uma eventual mensagem de erro relativa à quantidade introduzida
1.5.6.3.4. O código de controlo [infos.aspx.vb]
Comentários:
- os componentes da página são definidos nas linhas 10-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 apresentar é 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] é associado a uma fonte de dados do tipo [ArrayList] que contém apenas o artigo recuperado na linha 30
- os componentes [lblMsgQte, txtQte] são inicializados com informações obtidas do contexto — linhas 42-45
- a propriedade [straction] é igualmente inicializada com uma informação retirada do contexto — linha 47. Esta variável serve para gerar o atributo [action] do formulário HTML presente na página:
1.5.6.4. A vista [panier.aspx]
1.5.6.4.1. Introduction
Esta vista apresenta o conteúdo do cesto:

É apresentada na sequência de uma solicitação /main?action=panier ou /main?action=retirerachat&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 apresentar |
Cada ligação [Retirer] da tabela HTML das compras do carrinho tem um URL com o formato [?action=retirerachat&id=ID], em que ID é o campo [id] do artigo que se pretende remover do cesto.
1.5.6.4.2. Os componentes da página
![]() |
n° | type | nom | rôle |
componente do utilizador | cabeçalho | exibir o cabeçalho | |
DataGrid | DataGridAchats 3 - coluna associada - cabeçalho: Artigo, campo: nome 4 - coluna associada - cabeçalho: Qtd., campo: qtd. 5 - coluna associada - título: Preço, campo: preço 6 - coluna associada - 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 artigos comprados | |
rótulo | lblTotal | Mostrar o montante 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-27, o componente [DataGridAchats] está definido
- na linha 29, o componente [lblTotal] é definido
1.5.6.4.4. O código de controlo [panier.aspx.vb]
Comentários:
- os componentes da página são declarados nas linhas 11-13
- A inicialização do componente [entete] é idêntica à encontrada nas páginas já analisadas — linha 17
- o carrinho a apresentar é recuperado da sessão — linha 19
- a exibição deste cesto de compras com o componente [DataGridAchats] apresenta problemas. A dificuldade reside na inicialização do componente. Recorde-se as colunas deste:
- 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 cesto de compras e a respetiva 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, especificamente para o [DataGrid], uma fonte de dados cujos elementos possuem as características esperadas pelo [DataGrid]. Estes elementos serão do tipo [LigneAchat], uma classe criada para o efeito e derivada da classe [Achat] — linhas 36-66
- uma vez definida a classe [LigneAchat), a fonte de dados de [DataGridAchats] é construída a partir do cesto de compras encontrado na sessão — linhas 20-30
- o valor das compras é apresentado através da propriedade [totalPanier] da classe [Panier] — linha 32
1.5.6.5. A vista [paniervide.aspx]
1.5.6.5.1. Introduction
Esta vista apresenta a informação que indica que o carrinho está vazio:

É apresentada na sequência de uma solicitação /main?action=cesto 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 do utilizador | 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 controlo [paniervide.aspx.vb]
Comentários:
- limita-se a inicializar o único componente dinâmico da página — linha 10
1.5.6.6. A vista [erreurs.aspx]
1.5.6.6.1. Introduction
Esta página é apresentada em caso de erros:

É apresentada na sequência de qualquer pedido que resulte num erro, exceto no caso da ação de compra com uma quantidade errada, que é tratada pela vista [INFOS]. Os elementos do pedido do controlador são os seguintes:
objeto Hashtable() — a tabela de opções do menu | |
ArrayList de objetos [String] que representam as mensagens de erro a apresentar |
1.5.6.6.2. Os componentes da página
![]() |
n° | type | nom | rôle |
componente do utilizador | cabeçalho | exibir o cabeçalho | |
repetidor | rptErreurs | Mostrar 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-19. O seu conteúdo provém de uma fonte de dados que será do tipo [ArrayList] de objetos [String].
1.5.6.6.4. O código de controlo [erreurs.aspx.vb]
Comentários:
- o componente [entete] é inicializado como habitualmente, nas linhas 9 e 14
- o componente [rptErreurs] é inicializado com a lista de erros do tipo [ArrayList] encontrada no contexto — linhas 16-19
1.5.7. Os controladores global.asax, main.aspx
Resta escrever o núcleo da nossa aplicação web, o controlador. A sua função consiste em:
- recuperar o pedido do cliente,
- processar a ação solicitada pelo cliente com a ajuda das classes de negócio,
- enviar, em resposta, a vista adequada.
1.5.7.1. O controlador [global.asax.vb]
Quando a aplicação recebe a sua primeira solicitação, é executado o procedimento [Application_Start] do ficheiro [global.asax.vb]. Esta será a única vez. O procedimento [Application_Start] tem como objetivo inicializar os objetos necessários à aplicação web e que serão partilhados em modo de leitura apenas por todos os threads de cliente. Estes objetos partilhados 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 ficheiro [web.config], dos parâmetros necessários ao bom funcionamento da aplicação. Estes foram descritos no parágrafo 1.5.3.
- inserirá no contexto da aplicação a lista de eventuais erros sob a forma de um objeto [ArrayList erreurs]. Esta lista estará vazia se não houver erros, mas existirá na mesma.
- Se tiverem ocorrido erros, o método [Application_Start] termina aqui. Caso contrário, solicita a referência de um singleton do tipo [IArticlesDomain], que será o objeto de negócio que o controlador utilizará para as suas necessidades. Tal como explicado em 1.5.3.2, o controlador solicitará esse singleton ao framework Spring. Esta operação de instanciação pode dar origem a vários erros. Se for esse o caso, estes serão, mais uma vez, armazenados no objeto [erreurs] do contexto da aplicação.
O controlador [global.asax.vb] dispõe de um procedimento [Session_Start] executado sempre que um novo cliente chega. Neste procedimento, será criado um cesto vazio para o cliente. Este cesto será mantido ao longo das solicitações desse cliente específico. O código poderá ser o seguinte:
Comentários:
- os parâmetros esperados em [web.config] estão definidos numa tabela — linha 18
- são pesquisados em [web.config]. Se estiverem presentes, são armazenados no contexto da aplicação; caso contrário, é registado um erro na lista de erros [erreurs] — linhas 21-33
- Se não houver erros, solicita-se ao Spring uma referência ao singleton [articlesDomain], que gere o acesso à camada [domain] da aplicação — linhas 35-47. Os eventuais erros são registados em [erreurs].
- Os erros são registados no contexto da aplicação — linha 49
- a rotina é encerrada caso tenham ocorrido erros — linha 51
- É criado um array com três dicionários. Cada um deles tem duas chaves: href e link. Este array representa as três opções de menu possíveis — linhas 52-71
- esta matriz é armazenada no contexto da aplicação — linha 73
- a cada novo cliente, é executado o procedimento [Session_Start]. Nele, é criado um cesto 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. Com efeito, todas elas têm o formato [/webarticles/main.aspx?action=XX]. Uma solicitação será processada da seguinte forma:
- 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 esta não pode funcionar. Nesse caso, será enviada, como resposta, a vista [ERREURS].
- o parâmetro [action] da solicitação será recuperado e verificado. Se não corresponder a uma ação conhecida, a vista [ERREURS] é enviada com uma mensagem de erro adequada.
- Se o parâmetro [action] for válido, a solicitação do cliente é encaminhada para um procedimento específico da ação para processamento:
método | pedido | processamento | respostas possíveis |
GET /main?action=liste | - solicitar a lista de artigos à classe de negócio - exibi-lo | [LISTE] ou [ERREURS] | |
GET /main?action=infos&id=ID | - solicitar o artigo com id=ID à classe de negócio - exibi-lo | [INFOS] ou [ERREURS] | |
POST /main?action=compra&id=ID - a quantidade comprada faz parte dos parâmetros enviados | - solicitar o artigo com id=ID à classe de negócio - incluí-lo no cesto de compras na a sessão do cliente | [LISTE] ou [INFOS] ou [ERREURS] | |
GET /main?action=retirerachat&id=ID | - retirar o artigo com id=ID da lista de compras do carrinho de sessão do cliente | [PANIER] | |
GET /main?action=cesto | - exibir o cesto de compras da sessão do cliente | [PANIER] ou [PANIERVIDE] | |
GET /main?action=validationpanier | - Reduzir na base de dados os stocks de todos os artigos 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 tem dois campos privados que serão partilhados entre os métodos — linhas 15-16:
- articlesDomain: o singleton de acesso à camada [domain]
- opções: o array 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 da aplicação efetuada por [global.asax] decorreu corretamente.
- Para tal, recupera-se, no contexto da aplicação, a lista de erros colocada ali por [global.asax] — linha 4
- Se essa lista não estiver vazia, exibe-se a vista [ERREURS] — linhas 6-10
- recuperamos o singleton [articlesDomain] inserido pelo [global.asax] no contexto da aplicação e armazenamo-lo no campo privado [articlesDomain] para que esteja disponível para os diferentes métodos da classe — linha 12
- faz-se um procedimento semelhante com a matriz de opções do menu - linha 14
- recupera-se o parâmetro [action] da solicitação - linha 16
- executa-se 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 apresentar a lista de artigos:

O código é o seguinte:
Comentários:
- as eventuais erros são colocados num [ArrayList] - linha 4
- a lista de artigos é solicitada ao singleton [articlesDomain] — linhas 5-12
- Se tiverem ocorrido erros, envia-se a vista [ERREURS] - linhas 13-19
- caso contrário, envia-se a vista [LISTE] - linhas 20-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:
- as eventuais erros são colocados num [ArrayList] - linha 4
- o identificador do artigo solicitado é recuperado na consulta - linha 6
- esse identificador é verificado. Deve estar presente e deve ser um número inteiro. Se não for esse o caso, a vista [ERREURS] é enviada com a mensagem de erro apropriada — linhas 7-27
- assim que o identificador for verificado, o artigo é solicitado ao singleton [articlesDomain]. Se ocorrer uma exceção, é enviada a vista [ERREURS] - linhas 29-39
- Se o artigo não tiver sido encontrado, é enviada a vista [ERREURS] — linhas 41-48
- Se o artigo tiver sido encontrado, é colocado na sessão do utilizador e, em seguida, apresentado na vista [INFOS] — linhas 50-56
1.5.7.6. Processamento da ação [achat]
O cliente comprou o artigo apresentado na vista [INFOS].

O código é o seguinte:
Comentários:
- o artigo adicionado à sessão é recuperado — linha 5
- se não estiver presente (a sessão pode ter expirado), é apresentada a vista [LISTE] — linhas 7-10
- a quantidade comprada é recuperada na consulta — linha 12
- a sua validade é verificada — linhas 13-29
- em caso de invalidade, consoante o caso, é enviada a vista [LISTE] — linha 16 ou a vista [INFOS] — linhas 24-28
- se tudo estiver normal, a compra é registada no cesto - linhas 31-32
- em seguida, é enviada a vista [LISTE] — linha 34
1.5.7.7. Processamento da ação [panier]
O cliente efetuou várias compras e solicita ver o cesto de compras:

O código é o seguinte:
Comentários:
- recuperamos o carrinho da sessão — linha 4. Aqui não verificamos se recuperámos efetivamente algo. Seria necessário fazê-lo, pois a sessão pode ter expirado.
- Se o carrinho estiver vazio, envia-se a vista [PANIERVIDE] — linhas 6-10
- caso contrário, enviamos a vista [PANIER] — linhas 11-14
1.5.7.8. Processamento da ação [retirerachat]
O cliente pretende retirar um artigo do seu cesto:

O código é o seguinte:
Comentários:
- recuperamos o carrinho da sessão — linha 4. Aqui não verificamos se recuperámos efetivamente algo. Seria necessário fazê-lo, pois a sessão pode ter expirado.
- Recupera-se na requisição o identificador [id] do artigo a remover — linha 8.
- A compra correspondente é removida do carrinho - linha 10
- A validade do identificador do artigo comprado não foi verificada aqui. Se este 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.
- Apresenta-se o novo cesto de compras - linha 16
1.5.7.9. Processamento da ação [validerpanier]
O cliente pretende confirmar o seu cesto de compras:

O código é o seguinte:
Comentários:
- recuperamos o carrinho da sessão — linha 6. Aqui não verificamos se recuperámos efetivamente algo. Seria necessário fazê-lo, 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 vista [ERREURS] será enviada. No entanto, a mensagem de erro será pouco clara para o utilizador.
- Nas linhas 8-16, tenta-se validar o carrinho recuperado da sessão. Algumas compras podem não ser validadas se a quantidade solicitada exceder o stock do artigo em questão. Estes casos são registados pelo método [acheter] numa lista de erros que é recuperada na linha 11.
- Se houver erros, é enviada a vista [ERREURS] — linhas 18-23
- caso contrário, é enviada a vista [LISTE] — linhas 25-26
1.6. Conclusion
Desenvolvemos aqui uma aplicação de acordo com um modelo MVC. Não parece existir (abril de 2005) nenhum «framework» profissional de desenvolvimento MVC em ASP.NET, tal como existe em Java (Struts, Spring, ...). O projeto [Spring.net] deverá propor um em breve. Enquanto se aguarda essa novidade, o método anterior permite um desenvolvimento MVC viável para aplicações de média dimensão.































