Skip to content

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.

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

Image

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:

  1. 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.
  1. 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.
  2. 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
  3. 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.
  4. 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:

  1. as classes de negócio
  2. as classes de acesso aos dados
  3. 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);
id
chave primária que identifica um item de forma exclusiva
nom
nome do artigo
prix
seu preço
stockactuel
estoque atual
stockminimum
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:

assembly
contenu
rôle
webarticles-dao
- [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
webarticles-domain
- [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:

Image

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:

  1. um construtor que permite definir as 5 informações de um artigo: [id, nom, prix, stockactuel, stockminimum]
  2. propriedades públicas que permitem ler e gravar as 5 informações.
  3. uma verificação dos dados inseridos no artigo. Em caso de dados incorretos, é lançada uma exceção.
  4. 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:

getAllArticles
retorna todos os itens da fonte de dados
clearAllArticles
esvazia a fonte de dados
getArticleById
retorna o objeto [Article] identificado por sua chave primária
ajouteArticle
permite adicionar um item à fonte de dados
modifieArticle
permite modificar um item da fonte de dados
supprimerArticle
permite excluir um item da fonte de dados
changerStockArticle
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:

Image

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

Image

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:

Imports NUnit.Framework

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:

Image

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:

Image

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:

Image

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
Function getAllArticles() As IList
retorna a lista de objetos [Article] da fonte de dados associada
Function getArticleById(ByVal idArticle As Integer) As Article
retorna o objeto [Article] identificado por [idArticle]
acheter(ByVal panier As Panier)
valida o carrinho do cliente, deduzindo dos estoques dos itens comprados a quantidade adquirida — pode falhar se o estoque for insuficiente
ReadOnly Property erreurs() As ArrayList
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:
Public Property article() As article
o item comprado
Public Property qte() As Integer
a quantidade comprada
Public ReadOnly Property totalAchat() As Double
o valor da compra
Public Overrides Function ToString() As String
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:
ReadOnly Property achats() As ArrayList
lista de compras do cliente — lista de objetos do tipo [Achat]
ajouter(ByVal unAchat As Achat)
adiciona uma compra à lista de compras
enlever(ByVal idAchat As Integer)
remove a compra do item idAchat
ReadOnly Property totalPanier() As Double
o valor total das compras no carrinho
Function ToString() As String
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:
_articlesDao As IArticlesDao
o objeto de acesso aos dados
_erreurs As ArrayList
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:
Sub New(ByVal articlesDao As IArticlesDao)
  • 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:

Image

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:

Image

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:

M=modèle
as classes de negócio [domain], as classes de acesso aos dados [dao] e a fonte de dados
V=vues
as páginas ASPX
C=contrôleur
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
liste.aspx
As visualizações estão reunidas na pasta [vues] do aplicativo
INFOS
infos.aspx
PANIER
panier.aspx
PANIERVIDE
paniervide.aspx
ERREURS
erreurs.aspx

1.5.2. Os controladores

Conforme mencionado, o controlador será composto por dois elementos:

  1. [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
  2. [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
action=liste
o cliente deseja a lista dos
artigos
- solicita a lista de itens à camada
de negócios
- [LISTE]
- [ERREURS]
action=infos
o cliente solicita
informações sobre um dos
artigos exibidos na visualização
[LISTE]
- solicita o artigo à camada de negócios
- [INFOS]
- [ERREURS]
action=achat
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
action=retirerachat
o cliente deseja excluir um
compra do carrinho
- recupera o carrinho da sessão
e a modifica
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
o cliente deseja visualizar seu
carrinho
- recupera o carrinho da sessão
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
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:

  1. a alteração dos URL das diferentes visualizações
  2. a alteração das classes que implementam as interfaces [IArticlesDao] e [IArticlesDomain]
  3. 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:

Image

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

        <asp:Repeater id="rptErreurs" runat="server">

serve para repetir um padrão HTML nos diferentes elementos de uma fonte de dados. Seus diferentes elementos são os seguintes:

HeaderTemplate
o padrão HTML a ser exibido antes que os elementos da fonte de dados sejam exibidos
ItemTemplate
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
FooterTemplate
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:
webarticles-dao.dll
reúne as classes da camada de acesso aos dados
webarticles-domain.dll
agrupa as classes da camada de negócios
Spring.Core.dll
contém as classes do Spring que nos permitem integrar as camadas web, domain e DAO
log4net.dll
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:

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:

<%@ Control codebehind="entete.ascx.vb" Language="vb" autoeventwireup="false" inherits="istia.st.articles.web.EnteteWebArticles" %>
        <table>
            <tr>
                <td>
                    <h2>Magasin virtuel</h2></td>
                <asp:Repeater id="rptMenu" runat="server">
                    <ItemTemplate>
                        <td>
                            |<a href='<%# Container.DataItem("href") %>'>
                                <%# Container.DataItem("link") %>
                            </a>
                        </td>
                    </ItemTemplate>
                </asp:Repeater>
            </tr>
        </table>
        <hr>

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:

Namespace istia.st.articles.web
    Public Class EnteteWebArticles
        Inherits System.Web.UI.UserControl

        Protected WithEvents rptMenu As System.Web.UI.WebControls.Repeater

        Public WriteOnly Property actions() As Hashtable()
            Set(ByVal Value As Hashtable())
                ' associa-se a tabela de ações ao seu componente
                With rptMenu
                    .DataSource = Value
                    .DataBind()
                End With
            End Set
        End Property
    End Class
End Namespace

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:

<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>Les erreurs suivantes se sont produites :
        </h3>    

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:
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' componentes da página
...
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
             ' vincula-se as opções do menu ao rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
...
        End Sub
    End Class

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:

actions
objeto Hashtable() — a tabela de opções do menu
listarticles
ArrayList de objetos do tipo [Article]
message
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
type
nom
rôle
1
componente de usuário
cabeçalho
exibir o cabeçalho
2
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
6
rótulo
lblMessage
exibir uma mensagem

Vamos relembrar como definir essas propriedades:

  • no Visual Studio, selecione o [DataGrid] para acessar sua folha de propriedades:

Image

  • 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]
<%@ Page codebehind="liste.aspx.vb" inherits="istia.st.articles.web.ListeWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Liste des articles</h2>
        <P>
            <asp:DataGrid id="DataGridArticles" runat="server" ForeColor="Black" BackColor="LightGoldenrodYellow"
                BorderColor="Tan" CellPadding="2" BorderWidth="1px" GridLines="None" AutoGenerateColumns="False">
                <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                <FooterStyle BackColor="Tan"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Nom"></asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Infos" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=infos&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Center" ForeColor="DarkSlateBlue" BackColor="PaleGoldenrod"></PagerStyle>
            </asp:DataGrid></P>
        <P>
            <asp:Label id="lblMessage" runat="server" BackColor="#FFC080"></asp:Label></P>
    </body>
</HTML>

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]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao

Namespace istia.st.articles.web

     ' gerencia a página de exibição da lista de itens
    Public Class ListeWebarticles
        Inherits System.Web.UI.Page

         ' componentes da página
        Protected WithEvents lblMessage As System.Web.UI.WebControls.Label
        Protected WithEvents DataGridArticles As System.Web.UI.WebControls.DataGrid
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' prepara-se a visualização [liste] a partir das informações do contexto
             ' vincula-se as opções do menu ao rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' recuperam-se os artigos em uma tabela de dados
            Dim articles As ArrayList = CType(context.Items("articles"), ArrayList)
             ' eles são vinculados ao componente [DataGrid] da página
            With DataGridArticles
                .DataSource = articles
                .DataBind()
            End With
             ' exibe a mensagem
            lblMessage.Text = context.Items("message").ToString
        End Sub
    End Class
End Namespace

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:

Image

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:

actions
objeto Hashtable() — a tabela de opções do menu
article
objeto do tipo [Article] — item a ser exibido
msg
objeto String — mensagem a ser exibida em caso de erro na quantidade
qte
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:

Image

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
type
nom
rôle
1
componente de usuário
cabeçalho
exibir o cabeçalho
2
literal
litID
exibir o número do artigo
3 à 6
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
7
HTML Enviar
 
enviar o formulário
8
HTML Entrada
runat=server
txtQte
inserir a quantidade comprada
9
rótulo
lblMsgQte
possível mensagem de erro
1.5.6.3.3. O código de apresentação [infos.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="infos.aspx.vb" inherits="istia.st.articles.web.InfosWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Article d'id [<asp:Literal id="litId" runat="server"></asp:Literal>]</h2>
        <P>
            <asp:DataGrid id="DataGridArticle" runat="server" BackColor="White" BorderColor="#E7E7FF" CellPadding="3"
                BorderWidth="1px" BorderStyle="None" GridLines="Horizontal" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="#F7F7F7" BackColor="#738A9C"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="#F7F7F7"></AlternatingItemStyle>
                <ItemStyle HorizontalAlign="Center" ForeColor="#4A3C8C" BackColor="#E7E7FF"></ItemStyle>
                <HeaderStyle Font-Bold="True" HorizontalAlign="Center" ForeColor="#F7F7F7" BackColor="#4A3C8C"></HeaderStyle>
                <FooterStyle ForeColor="#4A3C8C" BackColor="#B5C7DE"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Nom">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix" DataFormatString="{0:C}">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="stockactuel" HeaderText="Stock actuel">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="stockminimum" HeaderText="Stock minimum">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="#4A3C8C" BackColor="#E7E7FF" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <HR width="100%" SIZE="1">
        <form method="post" action="<%=strAction%>">
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qté</td>
                    <td><INPUT type="text" maxLength="3" size="3" id="txtQte" runat="server"></td>
                    <td><asp:Label id="lblMsgQte" runat="server" />
                    </td>
                </tr>
            </table>
        </form>
    </body>
</HTML>

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]
Imports istia.st.articles.dao
Imports System
Imports System.Collections

Namespace istia.st.articles.web

     ' gerencia a página de informações sobre um artigo
    Public Class InfosWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents lblMsgQte As System.Web.UI.WebControls.Label
        Protected WithEvents litId As System.Web.UI.WebControls.Literal
        Protected WithEvents txtQte As System.Web.UI.HtmlControls.HtmlInputText
        Protected WithEvents DataGridArticle As System.Web.UI.WebControls.DataGrid
        Protected WithEvents entete As New EnteteWebArticles

         ' o URL, para onde o formulário será enviado
        Private _strAction As String
        Public Property strAction() As String
            Get
                Return _strAction
            End Get
            Set(ByVal Value As String)
                _strAction = Value
            End Set
        End Property

         ' exibição da página
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' recupera-se as informações da consulta
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' vinculamos as opções do menu ao rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' vinculamos o artigo ao [DataGrid]
            Dim articles As New ArrayList
            articles.Add(unArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
             ' o ID do rótulo
            litId.Text = unArticle.id.ToString
             ' a mensagem de erro
            lblMsgQte.Text = context.Items("msg").ToString
             ' a quantidade anterior
            txtQte.Value = context.Items("qte").ToString
             ' a ação URL
            strAction = "?action=achat&id=" + unArticle.id.ToString
        End Sub
End Namespace

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:
        <form method="post" action="<%=strAction%>">
....
        </form>

1.5.6.4. A visualização [panier.aspx]

1.5.6.4.1. Introduction

Esta visualização exibe o conteúdo do carrinho:

Image

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:

actions
objeto Hashtable() — a tabela de opções do menu
panier
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
type
nom
rôle
1
componente de usuário
cabeçalho
exibir o cabeçalho
2
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
8
rótulo
lblTotal
exibir o valor a pagar
1.5.6.4.3. O código de apresentação [panier.aspx]
<%@ Page codebehind="panier.aspx.vb" inherits="istia.st.articles.web.PanierWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contenu de votre panier</h2>
        <P>
            <asp:DataGrid id="DataGridAchats" runat="server" BorderWidth="1px" GridLines="Vertical" CellPadding="4"
                BackColor="White" BorderStyle="None" BorderColor="#DEDFDE" ForeColor="Black" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#CE5D5A"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="White"></AlternatingItemStyle>
                <ItemStyle BackColor="#F7F7DE"></ItemStyle>
                <HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#6B696B"></HeaderStyle>
                <FooterStyle BackColor="#CCCC99"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Article"></asp:BoundColumn>
                    <asp:BoundColumn DataField="qte" HeaderText="Qt&#233;"></asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix"></asp:BoundColumn>
                    <asp:BoundColumn DataField="totalAchat" HeaderText="Total" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Retirer" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=retirerachat&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="Black" BackColor="#F7F7DE" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <P>Total de la commande :
            <asp:Label id="lblTotal" runat="server"></asp:Label>&nbsp;euros</P>
    </body>
</HTML>

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]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao
Imports istia.st.articles.domain

Namespace istia.st.articles.web
     ' gerencia a página de exibição do carrinho
    Public Class PanierWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents DataGridAchats As System.Web.UI.WebControls.DataGrid
        Protected WithEvents lblTotal As System.Web.UI.WebControls.Label
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' vincula as opções do menu ao rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' recupera o carrinho
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' transfere as compras para uma tabela de linhas de compra
            Dim achats(unPanier.achats.Count - 1) As LigneAchat
             ' altera-se o tipo dos elementos do ArrayList
            For i As Integer = 0 To achats.Length - 1
                achats(i) = New LigneAchat(CType(unPanier.achats(i), Achat))
            Next
             ' vincula-se os dados aos componentes [DataGrid] da página
            With DataGridAchats
                .DataSource = achats
                .DataBind()
            End With
             ' exibe-se o total a pagar
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

         ' linha de compra criada a partir de um objeto de compra
        Private Class LigneAchat
            Inherits Achat

             ' o construtor recebe uma compra
            Public Sub New(ByVal unAchat As Achat)
                Me.article = unAchat.article
                Me.qte = unAchat.qte
            End Sub

             ' id: retorna o ID do item comprado
            Public ReadOnly Property id() As Integer
                Get
                    Return article.id
                End Get
            End Property

             ' nome: nome do item comprado
            Public ReadOnly Property nom() As String
                Get
                    Return article.nom
                End Get
            End Property

             ' preço do item comprado
            Public ReadOnly Property prix() As Double
                Get
                    Return article.prix
                End Get
            End Property

        End Class
    End Class
End Namespace

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:

Image

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:

actions
objeto Hashtable() — a tabela de opções do menu
1.5.6.5.2. Os componentes da página
type
nom
rôle
1
componente de usuário
cabeçalho
exibir o cabeçalho
1.5.6.5.3. O código de apresentação [paniervide.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<%@ Page codebehind="paniervide.aspx.vb" inherits="istia.st.articles.web.PaniervideWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contenu de votre panier</h2>
        <P>Votre panier est vide</P>
    </body>
</HTML>

Comentários:

  • o cabeçalho está incluído na linha 9
1.5.6.5.4. O código de controle [paniervide.aspx.vb]
Namespace istia.st.articles.web
     ' gerencia a página de exibição de um carrinho vazio
    Public Class PaniervideWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' prepara a visualização [paniervide] a partir das informações do contexto
             ' vincula as opções do menu ao rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

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:

Image

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:

actions
objeto Hashtable() — a tabela de opções do menu
erreurs
ArrayList de objetos [String] representando as mensagens de erro a serem exibidas
1.5.6.6.2. Os componentes da página
type
nom
rôle
1
componente de usuário
cabeçalho
exibir o cabeçalho
2
repetidor
rptErreurs
exibir a lista de erros
1.5.6.6.3. O código de apresentação [erreurs.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="erreurs.aspx.vb" inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>Les erreurs suivantes se sont produites :
        </h3>
        <ul>
            <asp:Repeater id="rptErreurs" runat="server">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
            </asp:Repeater></ul>
    </body>
</HTML>

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]
Namespace istia.st.articles.web

     ' gerencia a página de erros
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' componentes da página
        Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' prepara-se a visualização [erreurs] a partir das informações do contexto
             ' vinculamos as opções do menu ao rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' vinculando os erros a rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

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:

Imports System
Imports System.Web
Imports System.Web.SessionState
Imports System.Configuration
Imports istia.st.articles.domain
Imports System.Collections
Imports Spring.Context

Namespace istia.st.articles.web

    Public Class GlobalWebArticles
        Inherits System.Web.HttpApplication

         ' inicialização do aplicativo
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

             ' dados locais
            Dim parameters() As String = {"urlMain", "urlErreurs", "urlInfos", "urlListe", "urlPanier", "urlPanierVide"}
            Dim erreurs As New ArrayList

             ' recuperam-se os parâmetros de inicialização do aplicativo
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                 ' leitura no arquivo de configuração
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                     ' registra-se o erro
                    erreurs.Add("Paramètre [" + parameters(i) + "] absent dans le fichier [web.config]")
                Else
                     ' o parâmetro é armazenado no aplicativo
                    Application.Item(parameters(i)) = param
                End If
            Next
             ' houve erros?
            If erreurs.Count = 0 Then
                 ' cria-se um objeto IArticlesDomain para acesso à camada de negócios
                Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(contexte.GetObject("articlesDomain"), IArticlesDomain)
                     ' o objeto é armazenado na aplicação
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                     ' o erro é armazenado
                    erreurs.Add("Erreur lors de la construction de l'objet d'accès à la couche métier [" + ex.ToString + "]")
                End Try
            End If
             ' os erros são inseridos no aplicativo
            Application.Item("erreurs") = erreurs
             ' o processo é encerrado caso tenham ocorrido erros
            If erreurs.Count <> 0 Then Return
             ' cria-se uma tabela de opções de menu
            Dim options As New Hashtable
             ' recuperamos o URL do controlador
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim uneOption As Hashtable
             ' lista de itens
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=liste")
            uneOption.Add("lien", "Liste des articles")
            options.Add("liste", uneOption)
             ' carrinho
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=panier")
            uneOption.Add("lien", "Voir le panier")
            options.Add("panier", uneOption)
             ' confirmação do carrinho
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=validationpanier")
            uneOption.Add("lien", "Valider le panier")
            options.Add("validationpanier", uneOption)
             ' inserimos as opções do menu no aplicativo
            Application.Item("options") = options
            Return
        End Sub

         ' inicialização da sessão
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
             ' criamos um carrinho para o cliente
            Session.Item("panier") = New Panier
        End Sub
    End Class
End Namespace

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
doListe
GET /main?action=liste
- solicitar a lista de artigos
à classe de negócios
- exibi-lo
[LISTE] ou [ERREURS]
doInfos
GET /main?action=infos&id=ID
- solicitar o artigo com id=ID à
classe de negócios
- exibi-lo
[INFOS] ou [ERREURS]
doAchat
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]
doRetirerAchat
GET /main?action=retirerachat&id=ID
- remover o item com id=ID da
lista de compras do carrinho de
sessão do cliente
[PANIER]
doPanier
GET /main?action=carrinho
- exibir o carrinho da
sessão do cliente
[PANIER] ou [PANIERVIDE]
doValidationPanier
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:

Imports System.Collections
Imports System
Imports System.Data

Imports istia.st.articles.dao
Imports istia.st.articles.domain

Namespace istia.st.articles.web

     ' classe controladora do aplicativo web
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

         ' campos privados
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

         ' carregamento da página
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

         ' métodos de processamento de ações

         ' lista de artigos
        Public Sub doListe()
...
        End Sub

         ' informações sobre um artigo
        Public Sub doInfos()
...
        End Sub

         ' compra de um artigo
        Public Sub doAchat()
...
        End Sub

         ' cancelamento de uma compra
        Public Sub doRetirerAchat()
...
        End Sub

         ' visualização do carrinho
        Public Sub doPanier()
...
        End Sub

         ' compra do carrinho
        Public Sub doValidationPanier()
...
        End Sub

    End Class

End Namespace

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:

         ' carregamento da página
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' verifica-se se o aplicativo iniciou corretamente
            Dim erreurs As ArrayList = CType(Application.Item("erreurs"), ArrayList)
             ' se houver erros, é exibida a tela [erreurs]
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("urlErreurs"), String))
            End If
             ' recupera-se o objeto de acesso à classe de negócios
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
             ' assim como as opções do menu
            options = CType(Application.Item("options"), Hashtable)
             ' recuperamos a ação a ser executada
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "liste"
            End If
             ' executa-se a ação
            Select Case action
                Case "liste"
                    doListe()
                Case "infos"
                    doInfos()
                Case "achat"
                    doAchat()
                Case "panier"
                    doPanier()
                Case "retirerachat"
                    doRetirerAchat()
                Case "validationpanier"
                    doValidationPanier()
                Case Else
                    doListe()
            End Select
        End Sub

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:

Image

O código é o seguinte:

         ' lista de itens
        Public Sub doListe()
             ' gerenciamento de erros
            Dim erreurs As New ArrayList
             ' solicita-se a lista de itens
            Dim articles As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                 ' registra-se o erro
                erreurs.Add(ex.ToString)
            End Try
             ' erros?
            If erreurs.Count <> 0 Then
                 ' exibição da visualização [erreurs]
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
            End If
             ' visualização da vista [liste]
            context.Items("articles") = articles
            context.Items("options") = New Hashtable() {CType(options("panier"), Hashtable)}
            If context.Items("message") Is Nothing Then context.Items("message") = ""
            Server.Transfer(CType(Application.Item("urlListe"), String))
        End Sub

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:

Image

O código é o seguinte:

         ' informações sobre um artigo
        Public Sub doInfos()
             ' gestão de erros
            Dim erreurs As New ArrayList
             ' recuperação do ID do artigo solicitado
            Dim strId As String = Request.QueryString("id")
             ' há alguma coisa?
            If strId Is Nothing Then
                 ' anormal — enviamos a página de erros
                erreurs.Add("action incorrecte (action=infos, id=rien)")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' temos um número inteiro?
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                 ' anormal — enviamos a página de erros
                erreurs.Add("action incorrecte (action=infos, id[" + strId + "] invalide)")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End Try
             ' estamos solicitando o item com o ID de chave
            Dim unArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                 ' problema de acesso aos dados
                erreurs.Add("Problème d'accès aux données (" + ex.ToString + ")")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End Try
             ' O item foi recuperado?
            If unArticle Is Nothing Then
                 ' o item não existe
                erreurs.Add("L'article d'id=" + id.ToString + " n'existe pas")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' o item foi encontrado — ele é inserido na sessão atual
            Session.Item("article") = unArticle
             ' preparando a exibição
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
            context.Items("msg") = ""
            context.Items("qte") = ""
            Server.Transfer(CType(Application.Item("urlInfos"), String))
        End Sub

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

Image

O código é o seguinte:

         ' compra de um item
        Public Sub doAchat()
             ' compra de um artigo
             ' recuperamos o item que estava na sessão
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' Temos alguma coisa?
            If unArticle Is Nothing Then
                 ' não é normal — exibe-se a lista de itens
                doListe()
            End If
             ' recuperamos a quantidade lançada
            Dim strQte As String = Request.Form("txtQte")
             ' tem alguma coisa?
            If strQte Is Nothing Then
                 ' Não está certo — estamos enviando a lista de itens
                doListe()
            End If
             ' temos um número inteiro?
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                 ' anormal — enviamos a página de informações com a mensagem de erro
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                context.Items("msg") = "Quantité incorrecte"
                context.Items("qte") = strQte
                Server.Transfer(CType(Application.Item("urlInfos"), String))
            End Try
             ' tudo certo — colocamos a compra no carrinho do cliente
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            unPanier.ajouter(New Achat(unArticle, qte))
             ' exibimos a lista de itens
            doListe()
        End Sub

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:

Image

O código é o seguinte:

         ' visualização do carrinho
        Public Sub doPanier()
             '- recupera-se o carrinho da sessão
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' carrinho vazio?
            If unPanier.achats.Count = 0 Then
                 ' exibição do carrinho vazio
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlPanierVide"), String))
            End If
             ' exibição do carrinho com itens
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("validationpanier"), Hashtable)}
            Server.Transfer(CType(Application.Item("urlPanier"), String))
        End Sub

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:

Image

O código é o seguinte:

         ' exclusão de um item da compra
        Public Sub doRetirerAchat()
             'recuperação do carrinho na sessão
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' remoção do item
            Try
                 ' recuperação do ID do item removido
                Dim idArticle As Integer = Integer.Parse(Request.QueryString("id"))
                 ' remoção do carrinho
                unPanier.enlever(idArticle)
            Catch ex As Exception
                 ' exibe-se a lista de itens
                doListe()
            End Try
             ' exibe o carrinho
            doPanier()
        End Sub

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:

Image

O código é o seguinte:

         ' compra do carrinho
        Public Sub doValidationPanier()
             ' inicialmente, sem erros
            Dim erreurs As ArrayList
             'o carrinho é recuperado da sessão
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' tentando confirmar o carrinho
            Try
                articlesDomain.acheter(unPanier)
                 ' registram-se os eventuais erros de compra
                erreurs = articlesDomain.erreurs
            Catch ex As Exception
                 ' registra-se o erro
                erreurs = New ArrayList
                erreurs.Add(String.Format("Erreur lors de la validation du panier [{0}]", ex.Message))
            End Try
             ' se houver erros, exibe-se a página de erros
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("panier"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' tudo correu bem — exibe-se a lista de itens com uma mensagem de sucesso
            context.Items("message") = "Votre panier a été validé"
            doListe()
        End Sub

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.