7. Componentes do Servidor ASP - 1
7.1. Introdução
Neste capítulo, descrevemos a tecnologia recomendada no ASP.NET para a construção da interface do utilizador. Sabemos que existem duas fases distintas no processamento de uma página .aspx pelo servidor web:
- Primeiro, o controlador da página é executado. Este consiste em código localizado na própria página .aspx (solução WebMatrix) ou num ficheiro separado (solução Visual Studio.NET).
- Em seguida, o código de apresentação da página .aspx é executado e convertido em código HTML enviado ao cliente.

O ASP.NET oferece três bibliotecas de tags para escrever o código de apresentação da página:
- Etiquetas HTML clássicas. É isto que temos vindo a utilizar até agora.
- tags HTML do lado do servidor
- Etiquetas Web Forms
Independentemente da biblioteca de tags utilizada, a função do controlador da página permanece a mesma. Este deve calcular o valor dos parâmetros dinâmicos que aparecem no código de apresentação. Até agora, estes parâmetros dinâmicos eram simples: eram objetos do tipo [String]. Assim, se no código de apresentação tivermos uma tag <%=name%>:
- o controlador da página declara uma variável [name] do tipo [String] e calcula o seu valor
- quando o controlador de página termina o seu trabalho e o código de apresentação é executado para gerar a resposta HTML, a tag <%=name%> é substituída pelo valor calculado pelo código do controlador
Sabemos que a separação controlador/visualização é arbitrária e que o código do controlador e o código da visualização podem ser misturados na mesma página. Já explicámos por que razão esta abordagem é desaconselhada e continuaremos a aderir à separação controlador/visualização.
As tags [server HTML] e [WebForms] permitem-lhe incluir objetos no código de apresentação que são mais complexos do que o simples objeto [String]. Isto pode, por vezes, ser muito útil. Tomemos o exemplo de um formulário com uma lista. Esta lista deve ser apresentada ao cliente com código HTML semelhante a este:
<select name="uneListe" size="3">
<option value="opt1">option1</option>
<option value="opt2">option2</option>
<option value="opt3" selected>option3</option>
</select>
O conteúdo da lista e a opção a ser selecionada são elementos dinâmicos e, por isso, devem ser gerados pelo controlador da página. Já nos deparámos com este problema e resolvemo-lo incluindo a tag
Esta tag será substituída pelo valor [String] da variável [uneListeHTML]. Este valor, calculado pelo controlador, deve ser o código HTML da lista, ou seja, "<select name=..>...</select>". Isto não é particularmente difícil de fazer e parece uma solução elegante que evita colocar código de geração diretamente na camada de apresentação da página. Aqui, haveria um loop com testes a serem inseridos nele, o que o tornaria bastante «desorganizado». No entanto, este método tem uma desvantagem. A separação entre controlo e apresentação numa página também serve para delinear duas áreas de responsabilidade:
- a do programador .NET, que lida com o controlador da página,
- a do designer gráfico, que lida com a parte de apresentação da página
Aqui, vemos que a geração do código HTML para a lista foi transferida para o controlador. O designer gráfico pode querer modificar este código HTML para alterar a aparência «visual» da lista. Seria forçado a trabalhar na secção [controlador] e, assim, sair da sua área de especialização, com o risco associado de introduzir inadvertidamente erros no código.
As bibliotecas de tags de servidor resolvem este problema. Elas fornecem um objeto que representa uma lista HTML. Por exemplo, a biblioteca [WebForms] oferece a seguinte tag:
Esta tag representa um objeto [ListBox] que pode ser manipulado pelo controlador da página. Este objeto possui propriedades para representar as várias opções na lista HTML e para designar a opção selecionada. O controlador da página atribuirá, portanto, os valores apropriados a estas propriedades. Quando a camada de apresentação é executada, a tag
será substituída pelo código HTML que representa o objeto [uneListe], ou seja, o código "<select ..>...</select>". Por enquanto, não há diferença fundamental em relação ao método anterior, a não ser uma abordagem de codificação orientada a objetos, o que é interessante. Voltemos ao nosso designer gráfico, que precisa de modificar o «aspecto» da lista. As tags de servidor têm atributos de estilo (BackColor, Bordercolor, BorderWidth, ...) que permitem definir a aparência visual do objeto HTML correspondente. Assim, podemos escrever:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
A vantagem é que o designer gráfico trabalha diretamente no código de apresentação para efetuar estas alterações. Esta é uma vantagem clara em relação ao método anterior. As bibliotecas de tags do lado do servidor simplificam, assim, a construção da camada de apresentação das páginas web — aquilo a que nos referimos nos capítulos anteriores como a interface do utilizador. O objetivo deste capítulo é apresentá-las. Veremos que, por vezes, oferecem objetos complexos, como calendários ou tabelas ligadas a fontes de dados. São extensíveis, o que significa que o utilizador pode criar a sua própria biblioteca de tags. Podem, assim, criar uma tag que gere um banner numa página. Todas as páginas que utilizem esta tag terão então o mesmo banner.
O HTML gerado para uma tag adapta-se ao tipo de navegador do cliente. Quando o navegador envia um pedido ao servidor web, inclui um cabeçalho [User-Agent: xx] entre os seus cabeçalhos HTTP, onde [xx] identifica o cliente. Aqui está um exemplo:
Com esta informação, o servidor web pode determinar as capacidades do cliente, nomeadamente o tipo de código HTML que este consegue processar. Ao longo do tempo, surgiram de facto várias versões da linguagem HTML. Os navegadores modernos suportam as versões mais recentes da linguagem, ao passo que os navegadores mais antigos não o fazem. Com base no cabeçalho HTTP [User-Agent:] enviado pelo cliente, o servidor enviará de volta uma versão HTML que o cliente consiga compreender. Esta é uma abordagem útil e prática, pois significa que o programador não precisa de se preocupar com o tipo específico de navegador do cliente utilizado pela sua aplicação.
Por fim, IDEs avançadas, como o Visual Studio.NET, o WebMatrix, etc., permitem um design da interface web ao «estilo Windows». Embora estas ferramentas não sejam essenciais, prestam uma ajuda significativa ao programador. O programador concebe a interface web utilizando componentes gráficos que coloca na interface. Tem acesso direto às propriedades de cada componente da interface, que pode configurar conforme desejar. Estas propriedades são traduzidas para o código de apresentação HTML da interface como atributos da tag <asp:> do componente. A vantagem para o programador é que não precisa de memorizar nem a lista nem a sintaxe dos atributos de cada tag. Esta é uma vantagem significativa quando não se está totalmente familiarizado com as bibliotecas de tags de servidor oferecidas pelo ASP.NET. Uma vez dominada esta sintaxe, alguns programadores podem preferir codificar as tags diretamente no código de apresentação da página, sem passar pela fase de design gráfico. Um IDE deixa então de ser necessário; basta um simples editor de texto. Dependendo do seu fluxo de trabalho, o foco passa a ser nos componentes (usando um IDE) ou nas tags (usando um editor de texto). Estes dois termos são equivalentes. Um componente é o objeto que será manipulado pelo código de controlo da página. O IDE dá-nos acesso às suas propriedades durante a fase de design. Os valores atribuídos a estas propriedades são imediatamente traduzidos para os atributos da tag do componente no código de apresentação. Durante a fase de execução, o código de controlo da página irá manipular o componente e atribuir valores a algumas das suas propriedades. O código de apresentação irá gerar o código HTML do componente utilizando, por um lado, os atributos definidos durante o design para a tag de servidor correspondente e, por outro lado, os valores das propriedades do componente calculados pelo código de controlo.
7.2. O contexto de execução dos exemplos
Iremos ilustrar o design de interfaces web baseadas em componentes de servidor utilizando programas cujo contexto de execução será, na sua maioria, o seguinte:
- a aplicação web consistirá numa única página P contendo um formulário F,
- o cliente fará a sua primeira solicitação diretamente a esta página P. Isto envolverá solicitar o URL da página P utilizando um navegador. Trata-se, portanto, de uma solicitação GET que será feita a este URL P. O servidor entregará a página P e, consequentemente, o formulário F que ela contém,
- o utilizador irá preenchê-lo e enviá-lo, ou seja, irá realizar uma ação que obriga o navegador a enviar o formulário F para o servidor. A operação POST do navegador será sempre direcionada para a página P. O servidor irá novamente entregar a página P com o formulário F, cujo conteúdo poderá ter sido modificado pela ação do utilizador.
- Em seguida, os passos 2 e 3 serão retomados.
Este é um processo de execução muito específico, fora do qual certos conceitos discutidos abaixo deixam de funcionar. Já não estamos num contexto de arquitetura MVC, em que uma aplicação multipáginas é controlada por uma página específica a que chamámos de «controlador da aplicação». Neste tipo de arquitetura, os POSTs de formulários têm como destino o controlador e não os próprios formulários. No entanto, veremos que a construção de um formulário com componentes do lado do servidor implica que este formulário seja enviado para si próprio.
7.3. O Componente Label
7.3.1. Utilização
A tag <asp:label> permite inserir texto dinâmico no código de apresentação de uma página. Portanto, não faz mais do que a tag <%=variable%> usada até agora. Estudar esta primeira tag irá ajudar-nos a compreender como funcionam as tags de servidor. Iremos criar uma página com uma secção de controlo [form1.aspx.vb] e uma secção de apresentação [form1.aspx]. O objetivo é apresentar a hora:

Este assunto já foi abordado no Capítulo 2, e o leitor é encorajado a consultá-lo se desejar saber como foi tratado. O código de apresentação [form1.aspx] é o seguinte:
<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<asp:Label Runat="server" ID="lblHeure" />
</body>
</HTML>
Apresentamos a tag <asp:label>. Nas bibliotecas de tags, o atributo [runat="server"] é obrigatório. O atributo ID identifica o componente. O controlador deve referenciá-lo utilizando este identificador. O código do controlador [form1.aspx.vb] é o seguinte:
Imports System.Web.UI.WebControls
Public Class form1
Inherits System.Web.UI.Page
Protected lblHeure As Label
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' saves the current query in request.txt of the page folder
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' sets time in lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
End Class
O controlador deve atribuir um valor ao objeto [lblHeure] do tipo [System.Web.UI.WebControls.Label]. Todos os objetos exibidos pelas tags <asp:> pertencem ao namespace [System.Web.UI.WebControls]. Portanto, podemos importar sistematicamente este namespace:
Imports System.Web.UI.WebControls
O objeto [Label] possui várias propriedades, incluindo a propriedade [Text], que representa o texto que será exibido pela tag <asp:label> correspondente. Aqui, definimos esta propriedade para a hora atual. Fazemos isto no procedimento [Form_Load] do controlador, que é sempre executado. No procedimento [Form_Init], que também é sempre executado, mas antes do procedimento [Form_Load], armazenamos o pedido do cliente num ficheiro [request.txt] na pasta da aplicação. Teremos a oportunidade de examinar este ficheiro para compreender certos aspetos do funcionamento das páginas que utilizam tags de servidor.
O objeto [Label] possui muitas propriedades, métodos e eventos. Recomenda-se ao leitor que consulte a documentação sobre a classe [Label] para explorá-los. Este será o caso ao longo do resto do livro. Para cada tag, apresentamos apenas as poucas propriedades de que precisamos.
7.3.2. Testes
Colocamos os ficheiros (form1.aspx, form1.aspx.vb) numa pasta <application-path> e iniciamos o Cassini com os parâmetros (<application-path>,/form1). Em seguida, solicitamos o URL [http://localhost/form1/form1.aspx]. Obtemos o seguinte resultado:

O código HTML recebido pelo navegador é o seguinte:
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<span id="lblHeure">Il est 19:39:37</span>
</body>
</HTML>
Podemos ver que a tag do servidor
<asp:Label Runat="server" ID="lblHeure" />
foi convertida no seguinte código HTML:
É a propriedade [Text] do objeto [lblHeure] que foi colocada entre as tags <span> e </span>. O pedido feito pelo cliente e guardado em [request.txt] é o seguinte:
GET /form1/form1.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Nada fora do comum.
7.3.3. Construir a aplicação com o WebMatrix
Construímos manualmente o código de apresentação para a página [form1.aspx]. Este método é útil se conhecer as tags. Um simples editor de texto é então suficiente para criar a interface do utilizador. Para começar, é frequentemente necessária uma ferramenta de design gráfico combinada com geração automática de código, porque não conhece a sintaxe das tags de que necessita. Vamos agora criar a mesma aplicação utilizando a ferramenta WebMatrix. Assim que o WebMatrix for iniciado, selecionamos a opção [Ficheiro/Novo Ficheiro]:

Criamos uma página ASP.NET chamada [form2.aspx]. Após confirmar o assistente anterior, vemos a janela de design da página [form2.aspx]:


Note que o WebMatrix coloca tanto o código de controlo da página como o código de apresentação num único ficheiro, neste caso [form2.aspx]. O separador [All] exibe o conteúdo deste ficheiro de texto. Já podemos ver que não está vazio:

A desvantagem deste tipo de ferramenta é que muitas vezes gera código desnecessário. É o que acontece aqui, onde o WebMatrix gerou uma tag HTML <form> mesmo que não tenhamos intenção de criar um formulário... Além disso, podemos ver que o documento não possui uma tag <title>. Vamos corrigir ambas as questões imediatamente para obter a seguinte versão atualizada:

O que chamamos de código do controlador será inserido entre as tags <script> e </script>, garantindo pelo menos uma separação visual entre os dois tipos de código: controlo e apresentação. Voltamos ao separador [Design] para projetar a nossa interface. Uma lista de componentes está disponível numa janela de ferramentas à esquerda da janela de design:

A janela de ferramentas dá acesso a dois tipos de componentes:
- componentes [WebControls], que se traduzem em tags <asp:>
- Componentes [HTML Elements], que se traduzem em tags HTML padrão. No entanto, pode adicionar o atributo [runat="server"] aos atributos de uma tag HTML. Neste caso, a tag HTML e os seus atributos ficam acessíveis ao controlador através de um objeto cujas propriedades correspondem às da tag HTML que representa. Anteriormente, referimo-nos a estas tags como tags HTML do lado do servidor.
Clique duas vezes no componente [Label] na lista [WebControls]. No separador [Design], obtém o seguinte resultado:

No separador [All], o código passou a ser o seguinte:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>
Primeiro, irá notar que a tag <script> desapareceu. Foi gerada uma tag <asp:label>. Tem um nome [Label1] e um valor [Label]. Vamos voltar ao separador [Design] para alterar estes dois valores. Clique uma vez no componente [Label] para abrir a janela de propriedades no canto inferior direito:

O leitor é convidado a rever as propriedades do objeto [Label]. Duas delas são de interesse aqui:
- Texto: este é o texto que o rótulo deve exibir — definimos a cadeia de caracteres como vazia (ou seja, nada)
- ID: este é o seu identificador — definimo-lo como lblHeure
O separador [Design] tem agora este aspeto:

e o código para [All] passa a ser:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
O layout da página está concluído. Ainda precisamos de escrever o código de controlo responsável por definir a hora na propriedade [Text] de [lblHeure]. Adicionamos o seguinte código no separador [All]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' sets time in lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
</script>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
Note que no código do controlador, o objeto [lblHeure] não é declarado como era anteriormente:
Protected lblHeure As New System.Web.UI.WebControls.Label
Na verdade, todos os componentes de servidor <asp:> na camada de apresentação são declarados implicitamente no código do controlador. Declará-los explicitamente causa um erro de compilação, indicando que o objeto já está declarado. Estamos prontos para executar o código. Selecionamos a opção [View/Start] ou pressionamos o atalho [F5]. O Cassini é iniciado automaticamente com os seguintes parâmetros:

Aceitamos estes valores. O navegador predefinido do sistema abre-se automaticamente para solicitar o URL [http://localhost/form2.aspx]. Obtemos o seguinte resultado:

A partir de agora, utilizaremos principalmente a ferramenta WebMatrix para facilitar o desenvolvimento e o teste dos pequenos programas que iremos escrever.
7.4. O Componente Literal
7.4.1. Utilização
A tag <asp:literal> permite inserir texto dinâmico no código de apresentação de uma página, de forma semelhante à tag <asp:label>. O seu principal atributo é [Text], que representa o texto que será inserido tal como está no fluxo HTML da página. Esta tag é suficiente se não pretender formatar o texto que deseja inserir no fluxo HTML. De facto, enquanto a classe [Label] permite a formatação utilizando atributos como [BorderColor, BorderWidth, Font, ...], a classe [Literal] não possui nenhum destes atributos. O leitor pode reproduzir o exemplo anterior na íntegra, substituindo o componente [Label] por um componente [Literal].
7.5. O componente Button
7.5.1. Utilização
A tag <asp:Button> permite-lhe inserir um botão do tipo [Submit] num formulário, o que traz consigo um tratamento de eventos semelhante ao encontrado nas aplicações Windows. Este é o ponto que queremos explorar mais a fundo aqui. Criamos a seguinte página [form3.aspx]:

Esta página, criada com o WebMatrix, tem três componentes:
N.º | nome | tipo | propriedades | função |
1 | Botão | texto=Botão1 | botão de envio | |
2 | Botão | text=Botão2 | botão de envio | |
3 | Rótulo | text= | mensagem informativa |
O código gerado pelo WebMatrix para esta secção é o seguinte:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Vemos os componentes [Button] e [Label] utilizados no design gráfico da página dentro das tags <asp:>. Repare na tag <form runat="server">, que foi gerada automaticamente. Esta é uma tag HTML do lado do servidor — ou seja, uma tag HTML padrão representada por um objeto que pode ser manipulado pelo controlador. O código HTML para a tag <form> será gerado com base no valor que o controlador atribui a este objeto.
Vamos adicionar o procedimento [Page_Init] à secção do controlador do código, que lida com o evento [Init] da página. Colocaremos aí o código que guarda o pedido do cliente no ficheiro [request.txt]. Precisaremos disso para compreender como funcionam os botões.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' saves the current request in request.txt of the page folder
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
Note que não incluímos a cláusula [Handles MyBase.Init] após a declaração do procedimento [Page_Init]. Isto porque o evento [Init] do objeto [Page] tem um manipulador padrão chamado [Page_Init]. Se usarmos este nome de manipulador, a cláusula [Handles Page.Init] torna-se desnecessária. No entanto, incluí-la não causa um erro.
7.5.2. Teste
Executamos a aplicação no WebMatrix premindo [F5]. Obtemos a seguinte página:

O código HTML recebido pelo navegador é o seguinte:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
Tenha em atenção os seguintes pontos:
- a tag <form runat="server"> foi convertida numa tag HTML
Foram definidos dois atributos: [method="post"] e [action="form3.aspx"]. Podemos atribuir valores diferentes a estes atributos? Tentaremos esclarecer este ponto um pouco mais adiante. Note aqui que o formulário será enviado para o URL [form3.aspx]. Foram também definidos dois outros atributos [name, id]. Na maioria das vezes, estes são ignorados. No entanto, se a página contiver código JavaScript do lado do navegador, o atributo [name] da tag <form> é útil.
- As tags <asp:button> tornaram-se tags de botão [submit] HTML. Clicar em qualquer um destes botões irá, portanto, desencadear um "post" do formulário [_ctl10] para o URL [form3.aspx].
- A tag <asp:label> tornou-se uma tag HTML <span>
- Foi gerado um campo oculto [__VIEWSTATE] com um valor estranho:
Este campo representa, de forma codificada, o estado do formulário enviado ao cliente. Este estado representa o valor de todos os seus componentes. Uma vez que [__VIEWSTATE] faz parte do formulário, o seu valor será enviado juntamente com o resto para o servidor. Isto permitirá ao servidor saber qual o componente do formulário que alterou o seu valor e, se necessário, tomar decisões. Estas decisões assumirão a forma de eventos, tais como «a caixa de texto com tal e tal nome alterou o seu valor».
7.5.3. Pedidos do cliente
Assim que a página [form3.aspx] tiver sido carregada no navegador, vamos solicitá-la novamente e examinar o pedido enviado pelo navegador para a recuperar. Recorde-se que a nossa aplicação armazena isto no ficheiro [request.txt] dentro da pasta da aplicação:
GET /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Esta é uma solicitação GET padrão. Agora, vamos clicar no botão [Button1] na página do navegador. Nada parece acontecer. No entanto, sabemos que o formulário foi enviado. O código HTML da página confirma isso. Isso é confirmado pelo novo conteúdo de [request.txt]:
POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1
O primeiro cabeçalho HTTP indica claramente que o cliente enviou um pedido POST para o URL [/form3.aspx]. A última linha mostra os valores enviados:
- o valor do campo oculto __VIEWSTATE
- o valor do botão que foi clicado
Se clicarmos em [Button2], os valores enviados pelo navegador são os seguintes:
O valor do campo oculto é sempre o mesmo, mas é o valor de [Button2] que foi enviado. O servidor pode, portanto, determinar qual o botão que foi utilizado. Utilizará esta informação para desencadear um evento que pode ser tratado pela página assim que esta for carregada.
7.5.4. Tratamento do evento Click de um objeto Button
Vamos rever como funciona uma página .aspx. Trata-se de um objeto derivado da classe [Page]. Vamos chamar a classe derivada de [aPage]. Quando o servidor recebe um pedido para essa página, um objeto do tipo [unePage] é instanciado através da operação new unePage(...). O servidor gera então dois eventos chamados [Init] e [Load], nessa ordem. O objeto [unePage] pode lidar com estes eventos fornecendo os manipuladores de eventos [Page_Init] e [Page_Load]. Outros eventos serão gerados mais tarde. Teremos a oportunidade de revisá-los. Se a solicitação do cliente for um POST, o servidor irá gerar o evento [Click] para o botão que desencadeou este POST. Se a classe [unePage] tiver fornecido um manipulador para este evento, ele será chamado. Vamos explorar este mecanismo com o WebMatrix. No separador [Design] do [form3.aspx], clique duas vezes no botão [Button1]. Seremos então automaticamente direcionados para o separador [Code], dentro do corpo de um procedimento chamado [Button1_Click]. Para compreender melhor, vamos ao separador [All] e veja todo o código. Foram feitas as seguintes alterações:
<%@ Page Language="VB" %>
<script runat="server">
...
Sub Button1_Click(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
<form runat="server">
...
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
</form>
</body>
</html>
Foi adicionado um novo atributo [onclick="Button1_Click"] à tag <asp:Button> para [Button1]. Este atributo especifica o procedimento responsável por tratar o evento [Click] no objeto [Button1], neste caso o procedimento [Button1_Click]. Resta apenas escrevê-lo:
Sub Button1_Click(sender As Object, e As EventArgs)
' click on button 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
O procedimento exibe uma mensagem informativa no rótulo [lblInfo]. Procedemos da mesma forma para o botão [Button2] para obter a seguinte nova página [form3.aspx]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' sauve la requte courante dans request.txt du dossier de la page
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub Button1_Click(sender As Object, e As EventArgs)
' clic sur bouton 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' clic sur bouton 2
lblInfo.Text="Vous avez cliqué sur [Bouton2]"
End Sub
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Premimos [F5] para executar o código e obtemos a seguinte página:

Se analisarmos o código HTML recebido, veremos que não sofreu alterações em relação à versão anterior da página:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
Se clicarmos no [Botão1], obtemos a seguinte resposta:

O código HTML recebido para esta resposta é o seguinte:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
</p>
</form>
</body>
</html>
Podemos ver que o campo oculto [__VIEWSTATE] alterou o seu valor. Isto reflete a alteração no valor do componente [lblInfo].
7.5.5. Eventos no ciclo de vida de uma aplicação ASP.NET
A documentação do ASP.NET lista os eventos gerados pelo servidor durante o ciclo de vida de uma aplicação:
- Quando a aplicação recebe a sua primeira solicitação, o evento [Start] no objeto [HttpApplication] da aplicação é gerado. Este evento pode ser tratado pelo procedimento [Application_Start] no ficheiro [global.asax] da aplicação.
Em seguida, teremos uma série de eventos que se repetirão para cada pedido recebido:
- se a solicitação não enviou um token de sessão, uma nova sessão é iniciada e um evento [Start] no objeto [Session] associado à solicitação é gerado. Este evento pode ser tratado pelo procedimento [Session_Start] no ficheiro [global.asax] da aplicação.
- O servidor gera o evento [BeginRequest] no objeto [HttpApplication]. Este pode ser tratado pelo procedimento [Application_BeginRequest] no ficheiro [global.asax] da aplicação.
- O servidor carrega a página solicitada pela solicitação. Ele instanciará um objeto [Page] e, em seguida, gerará dois eventos neste objeto: [Init] e, depois, [Load]. Estes dois eventos podem ser tratados pelos procedimentos [Page_Init] e [Page_Load] da página.
- Com base nos valores enviados recebidos, o servidor irá gerar outros eventos: [TextChanged] para um componente [TextBox] cujo valor tenha mudado, [CheckedChanged] para um botão de opção cujo valor tenha mudado, [SelectedIndexChanged] para uma lista cujo item selecionado tenha mudado, ... Teremos a oportunidade de mencionar os principais eventos para cada um dos componentes do servidor que iremos apresentar. Cada evento E num objeto denominado O pode ser tratado por um procedimento denominado O_E.
- A ordem em que os eventos anteriores são tratados não é garantida. Por conseguinte, os manipuladores não devem fazer quaisquer suposições sobre esta ordem. No entanto, podemos ter a certeza de que o evento [Click] do botão que desencadeou o POST é processado em último lugar.
- Assim que a página estiver pronta, o servidor enviá-la-á ao cliente. Antes de o fazer, gera o evento [PreRender], que pode ser tratado pelo procedimento [Page_PreRender] da página.
- Assim que a resposta HTML for enviada ao cliente, a página será descarregada da memória. Nesse momento, serão gerados dois eventos: [Unload] e [Disposed]. A página pode utilizar estes eventos para libertar recursos.
A aplicação também pode receber eventos fora de um pedido do cliente:
- O evento [End] num objeto [Session] da aplicação ocorre quando uma sessão termina. Isto pode acontecer quer mediante um pedido explícito do código de uma página, quer porque o tempo de vida da sessão expirou. O procedimento [Session_End] no ficheiro [global.asax] trata deste evento. Normalmente, liberta os recursos obtidos em [Session_Start].
- O evento [End] no objeto [HttpApplication] da aplicação ocorre quando a aplicação termina. Isto acontece, por exemplo, quando o servidor web é desligado. O procedimento [Application_End] no ficheiro [global.asax] trata deste evento. É normalmente utilizado para libertar recursos adquiridos em [Application_Start].
Os seguintes pontos devem ser tidos em conta:
- O modelo de eventos anterior baseia-se em trocas HTTP padrão cliente-servidor. Isto é claramente evidente ao examinar os cabeçalhos HTTP trocados.
- O processamento dos eventos anteriores ocorre sempre no lado do servidor. Clicar num botão pode, naturalmente, ser tratado por um script JavaScript do lado do servidor. No entanto, este não é um evento do servidor, e estamos a lidar com uma tecnologia independente do ASP.NET.
Quando é que os eventos são processados, seja no lado do servidor (eventos relacionados com componentes do servidor) ou no lado do navegador por scripts JavaScript?
Tomemos o exemplo de uma lista suspensa. Quando o utilizador altera o item selecionado na mesma, o evento (alteração do item selecionado) pode ou não ser processado e, se for processado, pode ser processado em momentos diferentes.
- Se quiser processá-lo imediatamente, existem duas soluções:
- Pode ser tratado pelo navegador utilizando um script JavaScript. O servidor não intervém neste caso. Para que isto seja possível, a página deve poder ser reconstruída utilizando valores presentes na página.
- Pode ser processado pelo servidor. Para isso, existe apenas uma solução: o formulário deve ser enviado ao servidor para processamento. Temos, portanto, uma operação [submit]. Veremos que, neste caso, utilizamos um componente de servidor chamado [DropDownList] e definimos o seu atributo [AutoPostBack] como [true]. Isto significa que, se o item selecionado na lista suspensa mudar, o formulário deve ser imediatamente enviado ao servidor. Neste caso, o servidor gera código HTML para o objeto [DropDownList] que está associado a uma função JavaScript responsável por executar um [submit] assim que ocorrer o evento «item selecionado alterado». Este [submit] enviará o formulário para o servidor, e campos ocultos serão incluídos na publicação para indicar que o [post] resultou de uma alteração de seleção na lista suspensa. O servidor irá então gerar o evento [SelectedIndexChanged], que a página pode processar.
- Se pretender processar a seleção, mas não imediatamente, defina o atributo [AutoPostBack] do componente de servidor [DropDownList] como [false]. Neste caso, o servidor gera o código HTML padrão para uma lista <select> para o objeto [DropDownList], sem uma função JavaScript associada. Nada acontece quando o utilizador altera a seleção na lista suspensa. No entanto, quando o utilizador enviar o formulário utilizando um botão [submit], por exemplo, o servidor será capaz de reconhecer que ocorreu uma alteração na seleção. Vimos, de facto, que o formulário enviado para o servidor contém um campo oculto [__VIEWSTATE] que representa, de forma codificada, o estado de todos os elementos no formulário enviado. Quando o servidor recebe o novo formulário enviado pelo cliente, pode verificar se o item selecionado na lista suspensa mudou. Se for o caso, irá acionar o evento [SelectedIndexChanged], que a página pode então tratar. Para distinguir este mecanismo do anterior, alguns autores afirmam que o evento de «alteração de seleção» é «armazenado em cache» quando ocorre no navegador. Só será processado pelo servidor quando o navegador enviar o formulário para ele, frequentemente após um clique num botão [submit].
- Por fim, se não desejar tratar o evento, defina o atributo [AutoPostBack] do componente de servidor [DropDownList] como [false] e não escreva o manipulador para o seu evento [SelectedIndexChanged].
Uma vez compreendido o mecanismo de tratamento de eventos, o programador não irá conceber uma aplicação web como uma aplicação Windows. De facto, enquanto uma alteração de seleção numa caixa combinada dentro de uma aplicação Windows pode ser utilizada para atualizar imediatamente a aparência do formulário que a contém, é mais provável que se hesite em tratar este evento imediatamente numa aplicação web se isso envolver um «envio» do formulário para o servidor — e, consequentemente, uma viagem de ida e volta entre o cliente e o servidor. É por isso que a propriedade [AutoPostBack] dos componentes do lado do servidor está definida como [false] por predefinição. Além disso, o mecanismo [AutoPostBack], que depende de scripts JavaScript gerados automaticamente pelo servidor web no formulário enviado ao cliente, só pode ser utilizado se se tiver a certeza de que o navegador do cliente tem a execução de scripts JavaScript ativada. Os formulários são, portanto, frequentemente construídos da seguinte forma:
- os componentes do lado do servidor do formulário têm a sua propriedade [AutoPostBack] definida como [false]
- o formulário tem um ou mais botões responsáveis por executar a operação [POST]
- no código do controlador da página, escreve-se manipuladores apenas para os eventos que se pretende tratar, na maioria das vezes o evento [Click] num dos botões.
7.6. O componente TextBox
7.6.1. Utilização
A tag <asp:TextBox> permite-lhe inserir um campo de entrada no código de apresentação de uma página. Criamos uma página [form4.aspx] para obter o seguinte layout:

1234Esta página, criada com o WebMatrix, tem os seguintes componentes:
N.º | Nome | tipo | propriedades | função |
1 | Caixa de Texto | AutoPostback=true Texto= | campo de entrada | |
2 | Caixa de Texto | AutoPostback=false Texto= | campo de entrada | |
3 | Rótulo | text= | Mensagem informativa sobre o conteúdo de [TextBox1] | |
3 | Rótulo | text= | Mensagem informativa sobre o conteúdo de [TextBox2] |
O código gerado pelo WebMatrix para esta secção é o seguinte:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
No separador [Design], clique duas vezes no componente [TextBox1]. A estrutura do manipulador de eventos [TextChanged] para este objeto é então gerada (no separador [All]):
<%@ Page Language="VB" %>
<script runat="server">
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
...
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
....
</form>
</body>
</html>
O atributo [OnTextChanged="TextBox1_TextChanged"] foi adicionado à tag <asp:TextBox id="TextBox1"> para especificar o manipulador do evento [TextChanged] em [TextBox1]. Estamos agora a escrever o procedimento [TextBox1_Changed].
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' text change
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
No procedimento, escrevemos uma mensagem no rótulo [lblInfo1] indicando o evento e mostrando o conteúdo de [TextBox1]. Fazemos o mesmo para [TextBox2]. Também incluímos a hora para acompanhar melhor o tratamento do evento. O código final para [form4.aspx] é o seguinte:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' sauve la requte courante dans request.txt du dossier de la page
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' changement de texte
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
' changement de texte
lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
End Sub
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Adicionámos o procedimento [Page_Init] para armazenar o pedido do cliente, tal como no exemplo anterior.
7.6.2. Teste
Iniciamos a aplicação no WebMatrix premindo [F5]. Obtemos a seguinte página:

O código HTML recebido pelo navegador é o seguinte:
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
Existem muitos elementos neste código que foram gerados automaticamente pelo servidor. Observe os seguintes pontos:
- Existem três campos ocultos: [__VIEWSTATE], que já vimos, [__EventTarget] e [__EventArgument]. Os dois últimos campos são utilizados para tratar o evento «change» do navegador no campo de entrada [TextBox1]
- As tags de servidor <asp:textbox> geraram as tags HTML <input type="text" ...> que correspondem aos campos de entrada
- A tag de servidor <asp:textbox id="TextBox1" AutoPostBack="true" ...> gerou uma tag <input type="text" ...> com um atributo [onchange="__doPostBack('TextBox1','')"]. Este atributo especifica que, se o conteúdo de [TextBox1] for alterado, a função JavaScript [_doPostBack(...)] deve ser executada. É o seguinte:
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
O que faz a função acima? Atribui um valor a cada um dos dois campos ocultos [__EventTarget] e [__EventArgument] e, em seguida, submete o formulário. O formulário é, portanto, enviado para o servidor. Este é o efeito [AutoPostBack]. O evento «change» do navegador aciona a execução do código «__doPostBack('TextBox1','')». Podemos deduzir que, no formulário enviado, o campo oculto [__EventTarget] terá o valor 'TextBox1' e o campo oculto [__EventArgument] terá o valor ''. Isto permite ao servidor identificar o componente que desencadeou o POST.
- A tag de servidor <asp:textbox id="TextBox2"...> gerou uma tag padrão <input type="text" ...> porque o seu atributo [AutoPostBack] não estava definido como [true].
- A tag <form> indica que o formulário será enviado para [form4.aspx]:
Vamos realizar o nosso primeiro teste. Digite algum texto no primeiro campo de entrada:

depois mova o cursor para o segundo campo de entrada. Imediatamente, aparece uma nova página:

O que aconteceu? Quando o cursor saiu do primeiro campo de entrada, o navegador verificou se o seu conteúdo tinha mudado. E tinha. Assim, do lado do navegador, isso desencadeou o evento [change] no campo HTML [TextBox1]. Vimos então que uma função JavaScript foi executada e enviou o formulário para [form4.aspx]. Esta página foi então recarregada pelo servidor. Os valores enviados pelo formulário permitiram ao servidor reconhecer que o conteúdo da tag [TextBox1] do lado do servidor tinha mudado. O procedimento [TextBox1_Changed] foi, portanto, executado no lado do servidor. Colocou uma mensagem no rótulo [lblInfo1]. Assim que este procedimento terminou, [form4.aspx] foi reenviado para o navegador. É por isso que agora temos texto em [lblInfo1]. Dito isto, pode parecer surpreendente que haja algo no campo de entrada [TextBox1]. De facto, nenhum procedimento do lado do servidor atribui um valor a este campo. Este é um mecanismo geral dos formulários web ASP.NET: o servidor devolve o formulário no estado em que o recebeu. Para tal, reatribui aos componentes o valor que lhes foi enviado pelo cliente. Para alguns componentes, o cliente não envia qualquer valor. É o caso, por exemplo, dos componentes <asp:label>, que são renderizados como tags HTML <span>. Recorde-se que o formulário tem um campo oculto [__VIEWSTATE] que representa o estado do formulário quando este é enviado ao cliente. Este estado é a soma dos estados de todos os componentes do formulário, incluindo quaisquer componentes <asp:label>. Uma vez que o campo oculto [__VIEWSTATE] é enviado pelo navegador do cliente, o servidor consegue restaurar o estado anterior de todos os componentes do formulário. Resta apenas modificar aqueles cujos valores foram alterados pela solicitação POST.
Vamos agora ver em [request.txt] a solicitação feita pelo navegador:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Podemos ver claramente o pedido POST e os parâmetros que estão a ser enviados. Voltemos ao nosso navegador e introduzamos algum texto no segundo campo de entrada:

Voltemos ao campo de entrada n.º 1 para introduzir um novo texto: desta vez, nada acontece. Porquê? Porque o campo de entrada [TextBox2] não tem a propriedade [AutoPostBack] definida como [true], pelo que a tag <input type="text"...> gerada para ele não processa o evento [Change], conforme mostrado no seu código HTML:
Portanto, nenhum evento ocorre quando sai do campo de entrada n.º 2. Agora, vamos introduzir um novo texto no campo n.º 1:

Vamos sair do campo de entrada n.º 1. Imediatamente, o evento [Change] neste campo é detetado e o formulário é enviado para o servidor, que devolve a seguinte página:

O que aconteceu? Primeiro, o navegador enviou o formulário. Isto reflete-se no pedido do cliente armazenado em [request.txt]:
POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte
O servidor começa por restaurar os componentes aos seus valores anteriores utilizando o campo oculto [__VIEWSTATE] que o cliente lhe enviou. Utilizando os campos enviados [TextBox1] e [TextBox2], atribui os valores que foram enviados aos componentes [TextBox1] e [TextBox2]. É através deste mecanismo que o cliente recupera o formulário tal como foi enviado. Em seguida, novamente utilizando os campos enviados [__VIEWSTATE], [TextBox1] e [TextBox2], o servidor deteta que os valores dos campos de entrada [TextBox1] e [TextBox2] foram alterados. Por conseguinte, aciona os eventos [TextChanged] para estes dois objetos. Os procedimentos [TextBox1_TextChanged] e [TextBox2_TextChanged] serão executados, e os rótulos [labelInfo1] e [labelInfo2] receberão novos valores. Em seguida, a página [form4.aspx] modificada é enviada de volta ao cliente.
Agora, vamos modificar o campo de entrada n.º 1 novamente:

Quando movemos o cursor para fora do campo 1, o evento [Change] ocorre no navegador. Em seguida, ocorre a sequência de eventos já explicada (envio do navegador para o servidor, ..., envio da resposta do servidor). Recebemos a seguinte resposta:

Devido ao carimbo de data/hora exibido para cada mensagem, podemos ver que apenas o procedimento [TextBox1_Changed] foi executado no servidor. O procedimento [TextBox2_TextChanged] não foi executado porque o valor de [TextBox2] não mudou. Por fim, vamos inserir um novo texto no campo n.º 2:

Em seguida, coloque o cursor no campo n.º 1 e volte a movê-lo para o campo n.º 2. A página não muda. Porquê? Como não estamos a alterar o valor do campo n.º 1, o evento [Change] do navegador não ocorre quando saímos desse campo. Consequentemente, o formulário não é enviado para o servidor. Por isso, nada muda na página. É o facto de o conteúdo de [lblInfo2] não mudar que nos mostra que não há POST. Se houvesse, o servidor detectaria que o conteúdo de [TextBox2] mudou e deveria refletir isso em [lblInfo2].
A lição deste exemplo é que não faz sentido definir a propriedade [AutoPostBack] de um [TextBox] como [true]. Na maioria das vezes, isto causa uma ida e volta desnecessária entre o cliente e o servidor.
7.6.3. O papel do campo __VIEWSTATE
Vimos que o servidor coloca sistematicamente um campo oculto chamado __VIEWSTATE no formulário que gera. Observámos que este campo representa o estado do formulário e que, se este campo oculto for devolvido ao servidor, este pode reconstruir o valor anterior do formulário. O estado de um formulário é a soma dos estados dos seus componentes. Cada componente tem uma propriedade [EnableViewState] com um valor booleano que indica se o estado do componente deve ou não ser colocado no campo oculto [__VIEWSTATE]. Por predefinição, esta propriedade está definida como [true], o que significa que o estado de todos os componentes de um formulário é colocado em [__VIEWSTATE]. Por vezes, isto não é desejável.
Vamos realizar alguns testes para compreender melhor o papel da propriedade [EnableViewState]. Vamos definir esta propriedade como [false] para ambos os campos de entrada:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...
Agora vamos executar a aplicação e digitar algum texto no campo n.º 1 antes de passar para o campo n.º 2. É então enviada uma solicitação POST para o servidor, e obtemos a seguinte resposta:

Digite algum texto no campo #2, modifique o texto no campo #1 e, em seguida, volte ao campo #2 (nessa ordem). Uma nova solicitação POST é enviada ao servidor e recebemos a seguinte resposta:

Por enquanto, tudo está como antes. Agora vamos modificar o conteúdo do campo n.º 1 e, em seguida, passar para o campo n.º 2. É enviada uma nova solicitação POST. A resposta do servidor é a seguinte:

Desta vez, há uma alteração. O servidor detetou um evento [TextChanged] no campo n.º 2 porque o valor de [lblInfo2] foi modificado. No entanto, não houve qualquer alteração efetiva. Isto deve-se à propriedade [EnableViewState=false] de [TextBox2]. Isto faz com que o servidor não armazene o estado anterior de [TextBox2] no campo [__VIEWSTATE] do formulário. Isto equivale a atribuir a cadeia vazia como estado anterior. Quando ocorreu o POST desencadeado pela alteração no conteúdo de [TextBox1], o servidor comparou o valor atual de [TextBox2], que era [two], com o seu valor anterior (a cadeia vazia). Concluiu que [TextBox2] tinha alterado o seu valor e gerou o evento [TextChanged] para [TextBox2]. Podemos verificar este comportamento inserindo uma string vazia em [TextBox2]. Com base no que acabou de ser explicado, o servidor não deveria gerar o evento [TextChanged] para [TextBox2]. Vamos experimentar:

Foi exatamente isso que aconteceu. A hora e o conteúdo de [lblInfo2] mostram que o procedimento [TextBox2_TextChanged] não foi executado. Tendo isso em mente, vamos examinar a propriedade [EnableViewState] dos quatro componentes do formulário:
Queremos preservar o estado deste componente para que o servidor saiba se ele mudou ou não | |
o mesmo | |
Não queremos preservar o estado deste componente. Queremos que o texto seja recalculado a cada novo POST. Se não for recalculado, deve ficar vazio. Tudo isto é conseguido com [EnableViewState=false] | |
o mesmo |
A nossa página inicial tem agora este aspeto:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
....
<asp:Label id="lblInfo1" runat="server" enableviewstate="False"></asp:Label>
...
<asp:Label id="lblInfo2" runat="server" enableviewstate="False"></asp:Label>
...
Vamos executar a mesma série de testes de antes. Onde obtivemos o ecrã
versão 1

, obtemos agora:
versão 2

Nesta etapa, alterámos o conteúdo do campo 1 sem alterar o do campo 2, garantindo que o procedimento [TextBox2_TextChanged] não fosse executado no lado do servidor, o que significou que o campo [lblInfo2] não recebeu um novo valor. Por isso, é apresentado com o seu valor anterior. Na versão 1 [EnableViewState=true], este valor anterior era o valor introduzido. Na versão 2 [EnableViewState=false], este valor anterior é a cadeia de caracteres vazia.
Por vezes, não é necessário preservar o estado anterior dos componentes. Em vez de definir [EnableViewState=false] para cada um deles, pode especificar que a página não deve manter o seu estado. Isto é feito na diretiva [Page] do código de apresentação:
Neste caso, independentemente do valor da sua propriedade [EnableViewState], o estado de um componente não é armazenado no campo oculto [__VIEWSTATE]. Tudo se comporta então como se o seu estado anterior fosse a cadeia vazia.
Vamos agora utilizar o cliente [curl] para destacar outros mecanismos. Primeiro, solicitamos o URL [http://localhost/form4.aspx]:
dos>curl --include --url http://localhost/form4.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
Recebemos o código HTML para [form4.aspx] do servidor. Não é diferente daquele que o navegador recebeu. Lembre-se aqui do pedido feito pelo navegador quando enviou o formulário:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Vamos realizar o mesmo POST, mas sem enviar o campo [__VIEWSTATE]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=premier+texte --data TextBox2=
...................
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
..............
Tenha em atenção os seguintes pontos:
- O servidor detetou um evento [TextChanged] no [TextBox1], uma vez que este gerou o texto [lblInfo1]. A ausência de [__VIEWSTATE] não impediu isso. Na sua ausência, assume-se que o valor anterior de um campo de entrada é a cadeia vazia.
- Conseguiu restaurar o texto enviado para [TextBox1] no atributo [value] da tag [TextBox1], de modo que o campo [TextBox1] reaparece com o valor introduzido. Para fazer isto, não precisa de [__VIEWSTATE], mas apenas do valor enviado para [TextBox1]
Agora, vamos fazer o mesmo pedido novamente sem alterar nada. Obtemos a seguinte nova resposta:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=premier+texte --data TextBox2=
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
Na ausência de [__VIEWSTATE], o servidor não conseguiu verificar que o valor do campo [TextBox1] não tinha sido alterado. Por isso, age como se o valor anterior fosse a cadeia vazia. Consequentemente, gerou aqui o evento [TextChanged] em [TextBox1]. Vamos executar o mesmo pedido novamente, desta vez com o campo [TextBox1] vazio e [TextBox2] preenchido:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=second+texte --data TextBox1=
......
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
......
Na ausência de [__VIEWSTATE], o valor anterior de [TextBox1] foi tratado como uma cadeia vazia. Uma vez que o valor enviado de [TextBox1] também era uma cadeia vazia, o evento [TextChanged] em [TextBox1] não foi gerado. O procedimento [TextBox1_TextChanged] não foi executado e, por isso, o campo [lblInfo1] não recebeu um novo valor. Sabemos que, neste caso, o componente mantém o seu valor antigo. No entanto, não é esse o caso aqui; [lblInfo1] perdeu o seu valor anterior. Isto deve-se ao facto de este valor ser recuperado de [__VIEWSTATE]. Uma vez que este campo está em falta, foi atribuída a cadeia de caracteres vazia a [lblInfo1]. No que diz respeito a [TextBox2], o servidor comparou o seu valor enviado [segundo texto] com o seu valor anterior. Na ausência de [__VIEWSTATE], este valor anterior é igual à string vazia. Uma vez que o valor enviado de [TextBox2] é diferente da string vazia, o evento [TextChanged] em [TextBox2] foi acionado. O procedimento [TextBox2_TextChanged] foi executado e o campo [lblInfo2] recebeu um novo valor.
Pode-se questionar se os parâmetros [__EVENTTARGET] e [__EVENTARGUMENT] são realmente úteis. Ao não enviar estes parâmetros, o servidor não saberá qual o evento que desencadeou o [submit]. Vamos experimentar:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=second+texte --data TextBox1=premier+texte
..............................
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
.....................
Podemos ver que nenhum evento [TextChanged] foi tratado. Além disso, os campos enviados [TextBox1] e [TextBox2] não recuperam os seus valores enviados. Na verdade, tudo se comporta como se tivesse sido feita uma solicitação GET. Tudo volta ao normal se o campo [__EVENTTARGET] for incluído nos campos enviados, mesmo que não tenha valor:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=second+texte --data TextBox1=premier+texte
.......
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
........
7.6.4. Outras propriedades do componente TextBox
O componente de servidor [TextBox] também permite gerar as tags HTML <input type="password"..> e <textarea>..</textarea>, ou seja, as tags correspondentes, respetivamente, a um campo de palavra-passe e a um campo de entrada multilinha. Esta geração é controlada pela propriedade [TextMode] do componente [TextBox]. Tem três valores possíveis:
valor | Tag HTML gerada |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Iremos examinar a utilização destas propriedades com o seguinte exemplo [form5.aspx]:

N.º | nome | tipo | propriedades | função |
1 | Botão | Botão [submit] - utilizado para adicionar o conteúdo de [TextBox1] ao de [TextBox2] | ||
2 | Caixa de Texto | Modo de Texto=Senha Texto= | campo de entrada protegido por palavra-passe | |
3 | Caixa de Texto | Modo de texto=Multilinha Texto= | combina as entradas feitas em [TextBox1] |
A propriedade [EnableViewState] da página está definida como [false]. No lado do servidor, tratamos o evento de clique no botão [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' the contents of [textBox1] are added to those of [TextBox2]
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
Para compreender este código, é necessário recordar como é processado o pedido POST de um formulário. Os procedimentos [Page_Init] e [Page_Load] são executados em primeiro lugar. Seguem-se todos os procedimentos de eventos armazenados em cache. Por fim, é executado o procedimento que trata do evento que desencadeou o [POST] — neste caso, o procedimento [btnAjouter_Click]. Quando os manipuladores de eventos são executados, todos os componentes da página que têm um valor na solicitação POST assumiram esse valor. Os demais voltaram ao seu valor anterior se a propriedade [EnableViewState] estava definida como [true], ou ao seu valor de tempo de design se a propriedade [EnableViewState] estava definida como [false]. Aqui, os valores dos campos [TextBox1] e [TextBox2] farão parte do POST enviado pelo cliente. Portanto, no código anterior, [textbox1.text] terá o valor enviado pelo cliente, e o mesmo se aplica a [textbox2.text]. O procedimento [btnAjouter_Click] define o valor do campo [TextBox2] como o valor enviado para [TextBox2] somado ao valor enviado para [TextBox1], mais o caractere de quebra de linha [ControlChars.CrLf] definido no namespace [Microsoft.VisualBasic]. Não é necessário importar este namespace, uma vez que o servidor web o importa por predefinição.
O código final para [form5.aspx] é o seguinte:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' on ajoute le contenu de [textBox1] à celui de [TextBox2]
textbox2.text+=textbox1.text+controlchars.crlf
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
<asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
</p>
</form>
</body>
</html>
Um pouco antes, apresentámos uma captura de ecrã de uma execução.
7.7. O componente DropDownList
A tag <asp:DropDownList> permite inserir uma lista suspensa no código de apresentação de uma página. Criamos uma página [form6.aspx] para produzir o seguinte layout:

N.º | Nome | tipo | propriedades | função |
1 | Lista suspensa | AutoPostback=true EnableViewState=true | lista suspensa | |
2 | Rótulo | EnableViewState=false | mensagem informativa |
O código de apresentação gerado é o seguinte:
Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
Por enquanto, a lista suspensa não contém itens. Iremos preenchê-la no procedimento [Page_Load]. Para tal, precisamos de conhecer algumas das propriedades e métodos da classe [DropDownList]:
Uma coleção do tipo [ListItemCollection] que contém os itens da lista suspensa. Os membros desta coleção são do tipo [ListItem]. | |
O número de itens na coleção [Items] | |
Elemento número i na lista - do tipo [ListItem] | |
para adicionar um novo elemento [ListItem] à coleção [Items] | |
para remover todos os itens da coleção [Items] | |
para remover o item número i da coleção [Items] | |
O primeiro [ListItem] na coleção [Items] cuja propriedade [Selected] é verdadeira | |
Índice do elemento [SelectedItem] na coleção [Items] |
Os itens na coleção [Items] da classe [DropDownList] são do tipo [ListItem]. Cada [ListItem] gera uma tag HTML <option>:
Descrevemos algumas propriedades e métodos da classe [ListItem]:
construtor - cria um elemento [ListItem] com as propriedades [text] e [value]. Um elemento ListItem(T,V) irá gerar a tag HTML <option value="V">T</option>. A classe [ListItem] permite-nos, portanto, descrever os elementos de uma lista HTML | |
booleano. Se for verdadeiro, a opção correspondente na lista HTML terá o atributo [selected="selected"]. Este atributo indica ao navegador que o elemento correspondente deve aparecer selecionado na lista HTML | |
o texto T da opção HTML <option value="V" [selected="selected"]>T</option> | |
o valor V do atributo [Value] da opção HTML <option value="V" [selected="selected"]>T</option> |
Temos informações suficientes para escrever o código para preencher a lista suspensa [DropDownList1] no procedimento [Page_Load] da página:
Sub Page_Load(sender As Object, e As EventArgs)
' fill in the combo if it's the 1st time you've been called
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Assim que o componente [DropDownList1] for inicializado, o seu HTML será o seguinte:
<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">
<option value="1">un</option>
<option value="2">deux</option>
<option value="3">trois</option>
<option value="4">quatre</option>
</select>
Sabemos que o procedimento [Page_Load] é executado sempre que a página [form6.aspx] é carregada. É chamado pela primeira vez através de um pedido GET e, depois, através de um pedido POST sempre que o utilizador seleciona um novo item da lista suspensa. O código para preencher esta lista deve ser executado sempre em [Page_Load]? A resposta depende do atributo [EnableViewState] do componente [DropDownList1]. Se este atributo estiver definido como true, sabemos que o estado do componente [DropDownList1] será preservado entre pedidos no campo oculto [__VIEWSTATE]. Este estado inclui duas coisas:
- a lista de todos os valores na lista suspensa
- o valor do item selecionado nesta lista
Pode parecer tentador definir a propriedade [EnableViewState] do componente [DropDownList1] como [true] para não ter de recalcular os valores a colocar na lista. O problema, no entanto, é que, uma vez que o procedimento [Page_Load] é executado sempre que a página [form6.aspx] é solicitada, estes valores continuarão a ser calculados. O objeto [Page], do qual [form6.aspx] é uma instância, possui um atributo [IsPostBack] com um valor booleano. Se este atributo for verdadeiro, significa que a página foi solicitada via POST. Se for falso, significa que a página foi solicitada via GET. No nosso sistema de ida e volta cliente-servidor, o cliente solicita sempre a mesma página [form6.aspx] ao servidor. Na primeira vez, solicita-a com um GET; nas vezes seguintes, com um POST. Concluímos que a propriedade [IsPostBack] pode ser utilizada para detetar o primeiro pedido GET do cliente. Geramos os valores da lista suspensa apenas durante este primeiro pedido. Para pedidos subsequentes, estes valores serão gerados pelo mecanismo [VIEWSTATE]. Noutras situações, o conteúdo de uma lista pode variar de uma solicitação para outra e, por isso, deve ser recalculado para cada uma. Neste caso, definiremos o atributo [EnableViewState] da lista como [false] para evitar o cálculo duplo desnecessário do conteúdo da lista, a menos que precisemos de saber os itens selecionados anteriormente na lista, uma vez que esta informação é armazenada no [VIEWSTATE].
O atributo [AutoPostBack] da lista [DropDownList1] foi definido como true. Isto significa que o navegador enviará o formulário assim que detetar o evento «item selecionado alterado» na lista suspensa. O servidor, por sua vez, irá detetar — utilizando [VIEWSTATE] e os valores enviados — que o item selecionado no componente [DropDownList1] foi alterado. Em seguida, irá acionar o evento [SelectedIndexChanged] neste componente. Iremos tratá-lo com o seguinte procedimento:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' selection change
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
Quando este procedimento é executado, o objeto [DropDownList1] recuperou os seus elementos [ListItem] através de [VIEWSTATE]. Além disso, um desses elementos [ListItem] tem o seu atributo [Selected] definido como verdadeiro — aquele cujo valor foi enviado pelo navegador. Existem várias formas de aceder a este elemento:
é o primeiro [ListItem] da lista com o seu atributo [Selected] definido como true | |
corresponde à parte [text] da tag HTML para o elemento <option value="...">text</option> selecionado pelo utilizador | |
corresponde à parte [value] da tag HTML do elemento <option value="...">text</option> selecionado pelo utilizador | |
O índice na coleção [DropDownList1.Items] do primeiro elemento [ListItem] cujo atributo [Selected] é verdadeiro |
O código final para [form6.aspx] é o seguinte:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' on remplit le combo si c'est la 1ère fois qu'on est appelé
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' changement de sélection
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
7.8. O componente ListBox
A tag <asp:ListBox> permite inserir uma lista no código de apresentação de uma página. Criamos [form7.aspx] para obter o seguinte layout:

N.º | nome | tipo | propriedades | função |
1 | Caixa de Texto | EnableViewState=false | campo de entrada | |
2 | Botão | Botão [submit] que transfere o conteúdo de txtSaisie para a Lista 1, caso não esteja vazio. | ||
3 | Caixa de Lista | EnableViewState=true SelectionMode=Single | lista de valores para seleção única | |
4 | ListBox | EnableViewState=true SelectionMode=Multiple | lista de valores para seleção múltipla | |
5 | Botão | Botão [submit] que transfere o item selecionado da [lista 1] para a [lista 2] | ||
6 | Botão | Botão [submit] que transfere os itens selecionados da [lista 2] para a [lista 1] |
O código de apresentação gerado é o seguinte:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Tapez un texte pour l'inclure dans Liste 1 :
<asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
</p>
<p>
<table>
<tbody>
<tr>
<td>
<p align="center">
Liste 1
</p>
</td>
<td>
</td>
<td>
<p align="center">
Liste 2
</p>
</td>
</tr>
<tr>
<td>
<asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
</td>
<td>
<p>
<asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
</p>
<p>
<asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
</p>
</td>
<td>
<p>
<asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
</p>
</td>
</tr>
<tr>
<td>
<p align="center">
<asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
<td>
</td>
<td>
<p align="center">
<asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
</tr>
</tbody>
</table>
</p>
</form>
</body>
</html>
A classe [ListBox] deriva da mesma classe [ListControl] que a classe [DropDownList] discutida anteriormente. Inclui todas as propriedades e métodos observados na [DropDownList], uma vez que estes pertenciam, na verdade, à [ListControl]. Surge uma nova propriedade:
define o modo de seleção da lista HTML <select> que será gerada a partir do componente. Se SelectionMode=Single, então apenas um único item pode ser selecionado. Se SelectionMode=Multiple, podem ser selecionados vários itens. Para conseguir isto, o atributo [multiple="multiple"] será gerado na tag <select> da lista HTML. |
Vamos tratar dos eventos. Um clique no botão [Add] será tratado pelo seguinte procedimento [btnAdd_Click]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' added to list 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' raz txtSaisie
txtSaisie.text=""
End Sub
Se o texto introduzido em [txtSaisie] não for uma cadeia vazia ou em branco, é adicionado um novo item à lista [ListBox1]. Sabemos que precisamos de adicionar um item do tipo [ListItem]. Anteriormente, utilizámos o construtor [ListItem(T as String, V as String)] para realizar uma tarefa semelhante. Esse item gera a tag HTML [<option value="V">T</option>]. Aqui, usamos o construtor [ListItem(T as String)], que gera a tag HTML [<option value="T">T</option>], ou seja, o texto [T] da opção é usado para formar o valor da opção. Assim que o conteúdo de [txtSaisie] for adicionado à lista [ListBox1], o campo [txtSaisie] é limpo.
Os cliques nos botões [Clear] serão tratados pelos seguintes procedimentos:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' raz list 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' raz list 2
ListBox2.Items.Clear
End Sub
Os cliques nos botões para transferência entre listas são tratados pelos seguintes procedimentos:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' transfer the item selected in list 1 to list 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' transfer of item selected in list 2 to list 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' transfer items selected in l1 to l2
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i=l1.items.count-1 to 0 step -1
' selected?
if l1.items(i).selected then
' more selected
l1.items(i).selected=false
' transfer to l2
l2.items.add(l1.items(i))
' deletion in l1
l1.items.removeAt(i)
end if
next
end sub
Uma vez que ambos os botões executam a mesma tarefa — transferir itens de uma lista para outra —, podemos reduzir isto a um único procedimento de transferência com dois parâmetros:
- l1 do tipo [ListBox], que é a lista de origem
- l2 do tipo [ListBox], que é a lista de destino
Primeiro, verificamos se existe pelo menos um item selecionado na lista l1; caso contrário, não há nada a fazer. Para tal, examinamos a propriedade [l1.selectedindex], que representa o índice do primeiro item selecionado na lista. Se não houver nenhum, o seu valor é -1. Se houver pelo menos um item selecionado em l1, transferimo-lo para l2. Para tal, percorremos toda a lista de itens em l1 e verificamos, para cada um, se o seu atributo [selected] é verdadeiro. Se for o caso, o seu atributo [selected] é definido como [false], depois é copiado para a lista l2 e, finalmente, é removido da lista l1. Esta remoção faz com que os elementos da lista l1 sejam renumerados. É por isso que a lista de elementos em l1 é percorrida na ordem inversa. Se a percorrermos para a frente e removermos o elemento n.º 10, o elemento n.º 11 passa a ser o n.º 10 e o n.º 12 passa a ser o n.º 11. Após processar o elemento n.º 10, o nosso ciclo para a frente processará o elemento n.º 11, que, como acabámos de explicar, é o antigo n.º 12. O elemento que era o n.º 11 e agora é o n.º 10 é ignorado. Ao percorrer os elementos da lista l1 na direção oposta, evitamos este problema.
7.9. Os componentes CheckBox e RadioButton
As tags <asp:RadioButton> e <asp:CheckBox> permitem-lhe inserir um botão de opção e uma caixa de seleção, respetivamente, no código de apresentação de uma página. Criamos uma página [form8.aspx] para obter o seguinte layout:

N.º | nome | tipo | propriedades | função |
1 | Botão de opção | RadioButton1.Checked = true Botão de opção 1.Texto = 1 Botão de opção 2.Selecionado = falso Botão de opção 2.Texto = 2 Botão de opção 3.Selecionado = falso RadioButton3.Text = 3 Para os 3 botões: GroupName = radio | botões de opção | |
2 | Caixa de seleção | Checked=false para todos CheckBoxA.Text = A CheckBoxB.Text = B CheckBoxC.Text = C | caixas de seleção | |
3 | Botão | Botão [submit] | ||
4 | Caixa de lista | lista de informações |
Para garantir que o navegador trate os três botões de opção como mutuamente exclusivos, estes devem ser agrupados num grupo de botões de opção. Isto é feito utilizando o atributo [GroupName] da classe [RadioButton]. O estado da página não precisa de ser mantido nesta aplicação. Por conseguinte, definimos o atributo [EnableViewState="false"] na página. O código de apresentação é o seguinte:
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Cases à cocher
</h3>
<p>
<asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
</p>
<p>
<asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
<asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
<asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
<asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
</p>
</form>
</body>
</html>
Precisamos de escrever o procedimento [btnEnvoyer_Click] para tratar do evento [Click] deste botão. O estado de um botão de opção ou de uma caixa de seleção é determinado pelo seu atributo [Checked], que é verdadeiro se a caixa estiver marcada e falso caso contrário. Assim, basta escrever o valor do atributo [Checked] dos seis botões de opção e caixas de seleção na lista [lstInfos]. Como não há nenhuma dificuldade particular em fazer isto, vamos tentar algo um pouco diferente:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
</script>
A página pode ser visualizada como uma estrutura em árvore de controlos. No nosso exemplo, a nossa página contém texto e controlos de servidor. O texto é tratado como um controlo especial chamado [LiteralControl]. Qualquer texto cria este controlo, mesmo uma sequência de espaços entre dois controlos. Cada controlo tem um atributo ID que o identifica. É o atributo ID que aparece nas tags:
Se ignorarmos os controlos [LiteralControl], a página em questão tem os seguintes controlos:
- [HtmlForm], que é o formulário [ID=frmControls]. Este, por sua vez, é um contentor para controlos. Contém os seguintes controlos:
-- [ID=RadioButton1] do tipo [RadioButton]
-- [ID=RadioButton2] do tipo [RadioButton]
-- [ID=RadioButton3] do tipo [RadioButton]
-- [ID=CheckBoxA] do tipo [CheckBox]
-- [ID=CheckBoxA] do tipo [CheckBox]
-- [ID=CheckBoxA] do tipo [CheckBox]
-- [ID=btnEnvoyer] do tipo [Button]
Um controlo tem as seguintes propriedades:
retorna a coleção de controlos filhos do [Control], se existirem | |
retorna o controlo identificado pelo ID localizado na raiz da árvore de controlos filhos de [Control]. No exemplo acima: Page.FindControl("frmControls") refere-se ao contentor [HtmlForm]. Para aceder ao botão de opção [RadioButton1], deve escrever Page.FindControl("frmControls").FindControl("RadioButton1") | |
identificador [Control] |
Voltemos ao código do procedimento [btnEnvoyer_Click]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
Queremos apresentar o estado dos botões de opção e das caixas de seleção no formulário. Percorremos todos os controlos do formulário. Se o controlo atual for de um tipo derivado de [CheckBox], apresentamos a sua propriedade [Checked]. Uma vez que a classe [RadioButton] é derivada da classe [CheckBox], o teste aplica-se a ambos os tipos de controlos. A captura de ecrã apresentada acima mostra um exemplo do resultado.
7.10. Os componentes CheckBoxList e RadioButtonList
Por vezes, queremos permitir que o utilizador escolha entre valores que são desconhecidos no momento em que a página é concebida. Estas opções provêm de um ficheiro de configuração, de uma base de dados, etc., e só são conhecidas em tempo de execução. Existem soluções para este problema, e já as encontrámos. A lista de seleção única é adequada quando o utilizador pode fazer apenas uma escolha, e a lista de seleção múltipla quando pode fazer várias. Do ponto de vista estético, e se o número de opções não for grande, poderá preferir utilizar botões de opção em vez da lista de seleção única ou caixas de seleção em vez da lista de seleção múltipla. Isto é possível com as classes [CheckBoxList] e [RadioButtonList].
As classes [CheckBoxList] e [RadioButtonList] derivam da mesma classe [ListControl] que as classes [DropDownList] e [ListBox] discutidas anteriormente. Por conseguinte, encontraremos algumas das propriedades e métodos observados nessas classes — especificamente, aqueles que pertencem efetivamente à [ListControl].
Uma coleção do tipo [ListItemCollection] que contém os itens da lista suspensa. Os membros desta coleção são do tipo [ListItem]. | |
O número de itens na coleção [Items] | |
Elemento número i na lista - do tipo [ListItem] | |
para adicionar um novo elemento [ListItem] à coleção [Items] | |
para remover todos os itens da coleção [Items] | |
para remover o item número i da coleção [Items] | |
O primeiro [ListItem] na coleção [Items] cuja propriedade [Selected] é verdadeira | |
Índice do elemento anterior na coleção [Items] |
Algumas propriedades são específicas das classes [CheckBoxList] e [RadioButtonList]:
[horizontal] ou [vertical] para listas horizontais ou verticais. |
Os elementos da coleção [Items] são do tipo [ListItem]. Cada elemento [ListItem] irá gerar uma tag diferente, dependendo se se trata de um objeto [CheckBoxList] ou [RadioButtonList]:
Ou
Descrevemos algumas propriedades e métodos da classe [ListItem]:
- cria um elemento [ListItem] com as propriedades text e value. Um elemento ListItem(T,V) irá gerar a tag HTML <input type="checkbox" value="V">T ou <input type="radio" value="V">T, conforme apropriado. | |
Boolean. Se for verdadeiro, a opção correspondente na lista HTML terá o atributo [selected="selected"]. Este atributo indica ao navegador que o elemento correspondente deve aparecer selecionado na lista HTML | |
o texto T da opção HTML <input type=".." value="V" [selected="selected"]>T | |
o valor do atributo Value da opção HTML <input type=".." value="V" [selected="selected"]>T |
Propomos criar a seguinte página [form8b.aspx]:

N.º | nome | tipo | propriedades | função |
1 | Lista de botões de opção | EnableViewState=true RepeatDirection=horizontal | lista de botões de opção | |
2 | Lista de caixas de seleção | EnableViewState=true RepeatDirection=horizontal | lista de caixas de seleção | |
3 | Botão | Botão [submit] que exibe em [4] a lista de itens selecionados de ambas as listas | ||
4 | Caixa de lista | EnableViewState=false | lista de valores |
O código de layout da página é o seguinte:
<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Listes de cases à cocher
</h3>
<p>
<asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
</p>
<p>
<asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
</p>
</form>
</body>
</html>
O código do controlo é o seguinte:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' fill in the lists if it's the 1st time you're called
if not IsPostBack then
' texts for the RadioButton list
dim textesRadio() as String = {"1","2","3","4"}
' texts for the CheckBox list
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' radio list filling
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' selection item no. 1
RadioButtonList1.SelectedIndex=1
' checkbox list filling
for i=0 to textesCheckBox.length-1
CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
next
end if
end sub
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the lstinfos listbox
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' displays the values of selected elements of l1
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i= 0 to l1.items.count-1
' selected?
if l1.items(i).selected then
lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
end if
next
end sub
</script>
No procedimento [Page_Load], que é executado sempre que a página é carregada, as duas listas são inicializadas. Para evitar que sejam inicializadas todas as vezes, usamos a propriedade [IsPostBack] para que isso aconteça apenas na primeira vez. Nos carregamentos subsequentes, as listas serão regeneradas automaticamente pelo mecanismo [VIEWSTATE]. Assim que a página é exibida, o utilizador marca determinadas caixas de seleção e clica no botão [Submit]. Os valores do formulário são então enviados para o próprio formulário. Após a execução de [Page_Load], o procedimento [btnEnvoyer_Click] é executado. Este procedimento chama o procedimento [affiche] para preencher a lista [lstInfos]. Este procedimento recebe um objeto [ListControl] como parâmetro, o que lhe permite aceitar um objeto [RadioButtonList] ou um objeto [CheckBoxList] — classes derivadas de [ListControl]. A lista [lstInfos] pode ter o seu atributo [EnableViewState] definido como [false], uma vez que o seu estado não precisa de ser mantido entre pedidos.
7.11. Os componentes Panel e LinkButton
A tag <asp:panel> permite inserir um contentor de controlos numa página. A vantagem do contentor é que algumas das suas propriedades aplicam-se a todos os controlos que contém. É o caso da sua propriedade [Visible]. Esta propriedade existe para todos os controlos do lado do servidor. Se um contentor tiver a propriedade [Visible=false], cada um dos seus controlos será controlado pela sua própria propriedade [Visible]. Se tiver a propriedade [Visible=false], então o contentor e tudo o que ele contém não será exibido. Isto pode ser mais simples do que gerir a propriedade [Visible] de cada um dos controlos do contentor.
A tag <asp:LinkButton> permite inserir um link no código de apresentação de uma página. Tem uma finalidade semelhante à do controlo [Button]. Aciona um POST do lado do cliente utilizando uma função JavaScript associada. Criamos uma página [form9.aspx] para produzir o seguinte layout:

N.º | nome | tipo | propriedades | função |
1 | Painel | EnableViewState=true | recipiente de controlo | |
2 | ListBox | EnableViewState=true | uma lista de três valores | |
3 | LinkButton | EnableViewState=false | ligação para ocultar o contentor |
Quando o contentor está oculto, aparece um novo link:

N.º | nome | tipo | propriedades | função |
4 | LinkButton | EnableViewState=false | link para exibir o contentor |
O código do layout da página é o seguinte:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
<p>
Conteneur
</p>
<p>
<asp:ListBox id="ListBox1" runat="server">
<asp:ListItem Value="1">un</asp:ListItem>
<asp:ListItem Value="2">deux</asp:ListItem>
<asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
</asp:ListBox>
</p>
</asp:Panel>
</p>
<p>
<asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
</p>
<p>
<asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
</p>
</form>
</body>
</html>
Note que este código inicializa a lista [ListBox1] com três valores. Os manipuladores de eventos [Click] para os dois links são os seguintes:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
...
end sub
Sub lnkVoir_Click(sender As Object, e As EventArgs)
' displays container 1
panel1.Visible=true
' changing links
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' hides container 1
panel1.Visible=false
' changing links
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
Vamos utilizar o procedimento [Page_Load] para inicializar o formulário. Faremos isso na primeira solicitação (IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' the 1st time
if not IsPostBack then
' the container is shown
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. Continuar...
Os parágrafos anteriores apresentaram vários componentes do lado do servidor. Apenas algumas das suas propriedades foram abordadas em cada secção. Para explorar estes componentes com maior profundidade, o leitor pode proceder de várias formas:
- explorar as propriedades de um componente utilizando um IDE como o WebMatrix. Esta ferramenta apresenta as principais propriedades dos componentes utilizados num formulário
- consultar a documentação do .NET para explorar todas as classes correspondentes a cada componente do servidor. Este é o método preferido para obter um domínio completo do componente. Aí, encontrará a hierarquia de classes que conduz aos componentes, bem como as propriedades, métodos, construtores e eventos de cada um. Além disso, a documentação fornece, por vezes, exemplos.
Neste capítulo, utilizámos a abordagem «tudo-em-um» do [WebMatrix], o que significa que colocámos o código de apresentação e o código de controlo de uma página no mesmo ficheiro. De um modo geral, não recomendamos este método, mas sim a abordagem [codebehind] utilizada anteriormente, que coloca estes dois tipos de código em dois ficheiros separados. Vale a pena notar que a vantagem desta separação reside no facto de o código de controlo poder ser compilado sem ter de executar a aplicação web. Além disso, tal como explicámos no início deste capítulo, os nossos exemplos tinham uma estrutura muito específica: consistiam numa única página que servia de formulário trocado entre o cliente e o servidor em ciclos sucessivos de pedido-resposta, sendo o primeiro pedido do cliente um GET e os subsequentes pedidos POST.
7.13. Componentes do servidor e controlador da aplicação
Nos capítulos anteriores, criámos várias aplicações web. Todas elas foram construídas utilizando a arquitetura MVC (Model-View-Controller), que divide a aplicação em blocos distintos e facilita a manutenção. Anteriormente, criámos as nossas interfaces de utilizador utilizando tags HTML padrão. Tendo em conta o que acabámos de abordar, é natural que agora queiramos utilizar componentes de servidor. Vamos revisitar um problema que já estudámos em pormenor: o cálculo de impostos. A sua arquitetura MVC era a seguinte:

A aplicação tem duas vistas: [form.aspx] e [errors.aspx]. A vista [form.aspx] é apresentada quando o URL [main.aspx] é solicitado pela primeira vez:

O utilizador preenche o formulário:

e clica no botão [Calcular] para obter a seguinte resposta:

Numa aplicação MVC, todos os pedidos têm de passar pelo controlador, neste caso [main.aspx]. Isto significa que, assim que o formulário [form.aspx] for preenchido pelo utilizador, tem de ser enviado para [main.aspx] e não para [form.aspx]. Isto simplesmente não é possível se construirmos a interface de utilizador [form.aspx] utilizando componentes de servidor ASP. Para ver isto, vamos criar um formulário [formtest.aspx] com um componente <asp:button>:
<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
<title>test</title>
</head>
<body>
<form action="main.aspx" runat="server">
<p>
<asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
</p>
</form>
</body>
</html>
Repare no atributo [action="main.aspx"] da tag <form>. Vamos executar esta aplicação. A página de visualização mostra apenas um botão:

Vamos ver o código HTML enviado pelo servidor:
<html>
<head>
<title>test</title>
</head>
<body>
<form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />
<p>
<input type="submit" name="btnTest" value="Test" id="btnTest" />
</p>
</form>
</body>
</html>
Podemos ver que o pedido POST do formulário tem como destino o próprio formulário [action="formtest.aspx"], enquanto escrevemos a tag HTML do lado do servidor em [formtest.aspx]:
O atributo [runat="server"] da tag <form> é obrigatório quando se utilizam componentes do lado do servidor. Ocorre um erro de compilação se não incluirmos este atributo. Quando o incluímos, o atributo [action] da tag <form> é ignorado. O servidor gera sempre um atributo [action] que aponta para o próprio formulário. Podemos concluir que, numa aplicação MVC, não podemos utilizar formulários criados com a tag <form ... runat="server">. No entanto, esta tag é essencial para todos os componentes de servidor ASP que recuperam entradas do utilizador. Por outras palavras, não podemos utilizar formulários de servidor ASP numa aplicação MVC. Esta é uma revelação importante. De facto, um dos principais argumentos de venda do ASP.NET é que se pode construir uma aplicação web como uma aplicação Windows. Isto é verdade se a sua aplicação não seguir a arquitetura MVC, mas menos verdadeiro caso contrário. No entanto, a arquitetura MVC parece ser um conceito fundamental no desenvolvimento web moderno que é difícil de ignorar.
É possível utilizar a arquitetura MVC em conjunto com formulários de componentes ASP para aplicações com poucas vistas diferentes, utilizando a seguinte solução alternativa:
- a aplicação consiste numa única página que funciona como um controlador
- as vistas são representadas nesta página por diferentes contentores, um contentor por vista. Para apresentar uma vista, tornamos o seu contentor visível e ocultamos os outros
Esta é uma solução elegante que iremos agora implementar em alguns exemplos
7.14. Exemplos de aplicações MVC com componentes de servidor ASP
7.14.1. Exemplo 1
Neste primeiro exemplo, implementamos os componentes de servidor que apresentámos. A página [form10.aspx] terá o seguinte aspeto:
![]() | ![]() |
A captura de ecrã à esquerda, acima, mostra o formulário tal como aparece ao utilizador. O utilizador preenche-o e submete-o clicando em [Submit]. O servidor devolve uma vista que mostra uma lista dos valores introduzidos (captura de ecrã à direita). Uma ligação permite ao utilizador regressar ao formulário. Este é apresentado exatamente tal como foi submetido. O código de apresentação para [form10.aspx] é o seguinte:
<html>
<head>
<title>Exemple</title> <script language="javascript">
function effacer(){
alert("Vous avez cliqué sur [Effacer]")
}
</script>
</head>
<body>
<p>
Gestion d'un formulaire
</p>
<p>
<hr />
</p>
<form runat="server">
<p>
<asp:Panel id="panelinfo" runat="server" EnableViewState="False">
<p>
Liste des valeurs obtenues
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
</p>
<p>
<asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
</p>
<p>
<hr />
</p>
</asp:Panel>
</p>
<p>
<asp:Panel id="panelform" runat="server" >
<table>
<tbody>
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<asp:RadioButton id="rdOui" runat="server" GroupName="rdmarie"></asp:RadioButton>
Oui<asp:RadioButton id="rdNon" runat="server" GroupName="rdmarie" Checked="True"></asp:RadioButton>
Non</td>
</tr>
<tr>
<td>
Cases à cocher</td>
<td>
<asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
3</td>
</tr>
<tr>
<td>
Champ de saisie</td>
<td>
<asp:TextBox id="txtSaisie" runat="server" MaxLength="20" Columns="20"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Mot de passe</td>
<td>
<asp:TextBox id="txtmdp" runat="server" MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Boîte de saisie</td>
<td>
<asp:TextBox id="txtArea" runat="server" Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Liste déroulante</td>
<td>
<asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
</td>
</tr>
<tr>
<td>
Liste à choix unique</td>
<td>
<asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
<asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Liste à choix multiple</td>
<td>
<asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
<asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Champ caché</td>
<td>
<asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
</tr>
<tr>
<td>
Bouton simple</td>
<td>
<input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
</tr>
<tr>
<td>
Bouton [reset]</td>
<td>
<input id="btnReset" type="reset" value="Rétablir" /></td>
</tr>
<tr>
<td>
Bouton [submit]</td>
<td>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
</td>
</tr>
</tbody>
</table>
</asp:Panel>
</p>
</form>
</body>
</html>
A página tem dois contentores, um para cada vista: [panelform] para a vista do formulário, [panelinfo] para a vista de informações. A lista de componentes no contentor [panelForm] é a seguinte:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | visualização do formulário | |
Botão de opção | EnableViewState=true Nome do grupo=rdmarie | botões de opção | |
Caixa de seleção | EnableViewState=true | caixas de seleção | |
Caixa de texto | EnableViewState=true | campo de entrada | |
Caixa de Texto | EnableViewState=true | campo de entrada protegido | |
Caixa de Texto | EnableViewState=true | caixa de texto multilinha | |
Lista suspensa | EnableViewState=true | lista suspensa | |
Caixa de lista | EnableViewState=true SelectionMode=Single | lista de seleção única | |
Botão | EnableViewState=false | desmarca todos os itens em lstSimple | |
Caixa de lista | EnableViewState=true SelectionMode=Multiple | lista de seleção múltipla | |
Botão | EnableViewState=false | Desmarcar todos os itens em lstMultiple | |
Rótulo | EnableViewState=true Visível=false | campo oculto | |
HTML padrão | exibe um alerta | ||
Botão | EnableViewState=false | Botão [submit] no formulário | |
HTML padrão | Botão [reset] do formulário |
O papel de [VIEWSTATE] para os componentes é importante aqui. Todos os componentes, exceto os botões, devem ter a propriedade [EnableViewState=true]. Para compreender porquê, precisamos de recordar como a aplicação funciona. Suponha que o campo [txtSaisie] tem a propriedade [EnableViewState=false]:
- O cliente solicita a página [form10.aspx] pela primeira vez. Recebe a visualização do formulário
- , preenche-a e submete-a utilizando o botão [Submit]. Os campos de entrada são então enviados, e o servidor atribui o valor enviado ou o seu [VIEWSTATE] aos componentes do lado do servidor, caso estes o possuam. Assim, ao campo [txtSaisie] é atribuído o valor introduzido pelo utilizador. Portanto, para este passo, o seu [VIEWSTATE] não tem qualquer utilidade. Como resultado da operação, a vista [informations] é enviada — que, na verdade, continua a ser a página [form10.aspx], mas com um contentor de visualização diferente.
- O utilizador visualiza esta nova vista e utiliza o link [Voltar ao formulário] para regressar ao mesmo. É então enviado um POST para [form10.aspx]. No máximo, existe apenas um valor enviado: o valor selecionado pelo utilizador na lista de informações, que não é utilizado posteriormente. Em qualquer caso, não é enviado nenhum campo [txtSaisie].
- O servidor recebe o POST e atribui o valor enviado aos componentes do servidor ou ao seu [VIEWSTATE], caso tenham um. Aqui, [txtNom] não tem nenhum valor enviado. Se o seu atributo [EnableViewState] estiver definido como [false], será-lhe atribuída a cadeia de caracteres vazia. Uma vez que queremos que tenha o valor introduzido pelo utilizador, deve ter a propriedade [EnableViewState=true].
O contentor [panelinfo] tem os seguintes controlos:
name | tipo | propriedades | função |
Painel | EnableViewState=false | visualização de informações | |
Caixa de lista | EnableViewState=false | Lista de informações que resume os valores introduzidos pelo utilizador | |
LinkButton | EnableViewState=false | ligação de retorno ao formulário |
Durante os testes, se analisarmos o código HTML gerado pelo código de apresentação acima, poderemos ficar surpreendidos com o código gerado para o campo oculto [lblSecret]:
O componente [lblSecret] não é renderizado como HTML porque possui a propriedade [Visible=false]. No entanto, como possui a propriedade [EnableViewState=true], o seu valor continuará a ser armazenado no campo oculto [__VIEWSATE]. Portanto, poderemos recuperá-lo, como os testes irão demonstrar.
Ainda precisamos de escrever os manipuladores de eventos. Em [Page_Load], iremos inicializar o formulário:
Sub page_Load(sender As Object, e As EventArgs)
' the 1st time, we initialize the elements
' subsequent times, they are reset to their values by VIEWSTATE
if IsPostBack then return
' init form
' panelinfo not displayed
panelinfo.visible=false
' paneform displayed
panelform.visible=true
' radio buttons
rdNon.Checked=true
' checkboxes
chk2.Checked=true
' input field
txtSaisie.Text="qqs mots"
' password field
txtMdp.Text="ceciestsecret"
' input box
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' combo
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' simple selection list
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' multiple selection list
for i=1 to 10
lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
next
lstMultiple.Items(0).Selected=true
lstMultiple.Items(2).Selected=true
' hidden field
lblSecret.Text="secret"
End Sub
Ao clicar nos botões [lstRazSimple] e [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' raz single list
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' raz multiple list
lstMultiple.SelectedIndex=-1
End Sub
Ao clicar no botão [Enviar]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' the info panel is made visible and the form panel is hidden
panelinfo.Visible=true
panelform.visible=false
' we retrieve the posted values and put them in lstInfos
' radio buttons
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' checkboxes
info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
affiche(info)
' input field
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' password
affiche("mot de passe : " + txtMdp.Text.Trim)
' input box
dim lignes() as String
lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
dim i as integer
for i=0 to lignes.length-1
lignes(i)="["+lignes(i).Trim+"]"
next
affiche("Boîte de saisie : " + String.Join(",",lignes))
' combo
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' simple list
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' multiple list
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' hidden field
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' displays msg in lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' browse list elements
' to find those selected
dim i as integer
dim info as string=""
for i=0 to liste.Items.Count-1
if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
next
return info
end function
Por fim, ao clicar no link [Voltar ao formulário]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' display the form and hide the info panel
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. Exemplo 2
Aqui, estamos a revisitar uma aplicação anteriormente abordada que utiliza formulários HTML padrão. A aplicação permite aos utilizadores realizar simulações de cálculo de impostos. Baseia-se numa classe [impot], que não iremos revisitar aqui. Esta classe requer dados que recupera de uma fonte de dados OLEDB. Para este exemplo, iremos utilizar uma fonte de dados ACCESS.
7.14.2.1. A estrutura MVC da aplicação
A estrutura MVC da aplicação é a seguinte:

As três vistas serão incorporadas no código de apresentação do controlador [main.aspx] como contentores. Portanto, esta aplicação tem uma única página [main.aspx].
7.14.2.2. As vistas da aplicação web
A vista [form] é o formulário para introduzir informações utilizadas para calcular o imposto de um utilizador:

O utilizador preenche o formulário:

Utiliza o botão [Enviar] para solicitar um cálculo de impostos. É apresentada a seguinte vista de [simulações]:

Volta ao formulário através do link acima. Encontra-o no estado em que o preencheu. Pode cometer erros na introdução de dados:

Estes são assinalados na vista [erros]:

Volta ao formulário através do link acima. Encontra-o no estado em que o preencheu. Pode realizar novas simulações:

Em seguida, vê a vista [simulações] com uma simulação adicional:

Por fim, se a fonte de dados não estiver disponível, o utilizador é notificado na vista [erros]:

7.14.2.3. O código de apresentação da aplicação
Lembre-se de que a página [main.aspx] reúne todas as vistas. É um único formulário com três contentores:
- [panelform] para a vista [form]
- [panelerrors] para a vista [errors]
- [panelsimulations] para a vista [simulations]
Voltamos a separar o código de apresentação e o código de controlo em dois ficheiros distintos. O primeiro estará em [main.aspx] e o segundo em [main.aspx.vb]. O código para [main.aspx] é o seguinte:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
<HEAD>
<title>Calcul d'impôt </title>
</HEAD>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<FORM id="Form1" runat="server">
<asp:panel id="panelform" Runat="server">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
<TR>
<TD height="19">Etes-vous marié(e)</TD>
<TD height="19">
<asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
<asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD>
<asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD>
<asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
</TR>
</TABLE>
<P>
<asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
<asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
</asp:panel>
<asp:panel id="panelerreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<P>
<asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
<P></P>
<asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
</asp:panel>
<asp:panel id="panelsimulations" runat="server">
<P>
<TABLE>
<TR>
<TH>
Marié</TH>
<TH>
Enfants</TH>
<TH>
Salaire annuel</TH>
<TH>
Impôt à payer (euro)</TH></TR>
<asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
<asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
</asp:panel>
</FORM>
</body>
</HTML>
Definimos os três contentores. Note que todos eles se encontram dentro da tag <form runat="server">. Isto é obrigatório, pois para tirar partido dos componentes de servidor, estes devem ser colocados dentro dessa tag. O ponto-chave a compreender é que temos aqui um único formulário que será trocado entre o cliente e o servidor web. Estamos, portanto, a utilizar a configuração descrita ao longo deste capítulo sobre componentes de servidor. Vamos analisar os componentes de cada contentor:
Contentor [panelform]:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | visualização do formulário | |
Botão de opção | EnableViewState=true Nome do grupo=rdmarie | botões de opção | |
Caixa de Texto | EnableViewState=true | número de filhos | |
Caixa de Texto | EnableViewState=true | salário anual | |
Botão | Botão [submit] no formulário - inicia o cálculo do imposto | ||
Botão | Botão [submit] do formulário - limpa o formulário |
[errorPanel] contentor:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | visualização de erros | |
Botão de ligação | EnableViewState=true | ligação para o formulário | |
Literal | Código HTML para a lista de erros |
[panelsimulations] contentor:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | visualização da simulação | |
Botão de ligação | EnableViewState=true | ligação para o formulário | |
Literal | Código HTML para a lista de simulações numa tabela HTML |
7.14.2.4. O código de controlo da aplicação
O código de controlo da aplicação está distribuído pelos ficheiros [global.asax.vb] e [main.aspx.vb]. O ficheiro [global.asax] é definido da seguinte forma:
O ficheiro [global.asax.vb] é o seguinte:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' start of session - create a list of empty simulations
Session.Item("simulations") = New ArrayList
End Sub
End Class
Quando a aplicação é iniciada (primeira solicitação feita à aplicação), o procedimento [Application_Start] é executado. Ele tenta criar um objeto do tipo [impot] recuperando os seus dados de uma fonte OLEDB. Recomenda-se ao leitor que consulte o Capítulo 5, onde esta classe foi definida, caso a tenha esquecido. A criação do objeto [impot] pode falhar se a fonte de dados não estiver disponível. Nesse caso, o erro é armazenado na aplicação para que todas as solicitações subsequentes saibam que não foi possível inicializá-lo corretamente. Se a criação for bem-sucedida, o objeto [impot] criado também é armazenado na aplicação. Ele será utilizado por todas as solicitações de cálculo de impostos. Quando um cliente faz a sua primeira solicitação, é criada uma sessão para ele pelo procedimento [Application_Start]. Esta sessão destina-se a armazenar as várias simulações de cálculo de impostos que irá realizar. Estas serão armazenadas num objeto [ArrayList] associado à chave de sessão "simulations". Quando a sessão começa, esta chave é associada a um objeto [ArrayList] vazio. As informações necessárias à aplicação são colocadas no seu ficheiro de configuração [wenConfig]:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
</appSettings>
</configuration>
A chave [connectionString] especifica a cadeia de ligação à fonte OLEDB. O resto do código de controlo encontra-se em [main.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
Protected WithEvents panelform As System.Web.UI.WebControls.Panel
Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal
' local variables
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
End Sub
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
End Sub
Private Function checkData() As ArrayList
...
End Function
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
End Sub
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
End Sub
Private Sub razForm()
...
End Sub
End Class
Le premier événement traité par le code est [Page_Load] :
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' first, we look at the state of the application
If CType(Application("erreur"), Boolean) Then
' the application failed to initialize
' the error view is displayed
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' no errors - on the 1st request, the form is presented
If Not IsPostBack Then afficheFormulaire()
End Sub
Lembre-se de que, quando o procedimento [Page_Load] é executado num POST do cliente, todos os componentes do formulário têm um valor: ou o valor enviado pelo cliente, se houver, ou o valor anterior do componente através de [VIEWSTATE]. Neste formulário, todos os componentes têm a propriedade [EnableViewState=true]. Antes de processar a solicitação, garantimos que a aplicação tenha sido inicializada corretamente. Caso contrário, exibimos a visualização [errors] usando o procedimento [displayErrors]. Se esta for a primeira solicitação (IsPostBack=false), exibimos a visualização [form] usando [displayForm].
O procedimento que exibe a vista [errors] é o seguinte:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' displays the error container
panelerreurs.Visible = True
Dim i As Integer
erreursHTML.Text = ""
For i = 0 To erreurs.Count - 1
erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
lnkForm1.Text = lien
' the other containers are hidden
panelform.Visible = False
panelsimulations.Visible = False
End Sub
O procedimento tem dois parâmetros:
- uma lista de mensagens de erro em [errors]
- um texto de ligação em [link]
O código HTML a ser gerado para a lista de erros é colocado no literal [errorsHTML]. O texto do link é colocado na propriedade [Text] do objeto [LinkButton] na vista.
O procedimento que exibe a vista [form] é o seguinte:
Private Sub afficheFormulaire()
' displays the form
panelform.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
Este procedimento simplesmente torna o contentor [panelform] visível. Os componentes são apresentados com o seu valor enviado ou anterior (VIEWSTATE).
Quando o utilizador clica no botão [Calculate] na vista [form], é enviada uma solicitação POST para [main.aspx]. O procedimento [Page_Load] é executado, seguido pelo procedimento [btnCalculate_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' check the validity of the data entered
Dim erreurs As ArrayList = checkData()
' if there are errors, we report them
If erreurs.Count <> 0 Then
' the error page is displayed
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' no errors - tax is calculated
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' the result is added to existing simulations
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' the result is added to existing simulations
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' put simulations in session and context
Session.Item("simulations") = simulations
' the result page is displayed
afficheSimulations(simulations, "Retour au formulaire")
End Sub
O procedimento começa por validar os campos do formulário utilizando o procedimento [checkData], que devolve uma [ArrayList] de mensagens de erro. Se a lista não estiver vazia, é apresentada a vista [errors] e o procedimento termina. Se os dados introduzidos forem válidos, o montante do imposto é calculado utilizando o objeto [tax] que foi armazenado na aplicação no momento do arranque. Esta nova simulação é adicionada à lista de simulações já realizadas e armazenadas na sessão.
A função [CheckData] verifica a validade dos dados. Ela retorna uma [ArrayList] de mensagens de erro, que fica vazia se os dados forem válidos:
Private Function checkData() As ArrayList
' initially no errors
Dim erreurs As New ArrayList
' no. of children
Try
Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' salary
Try
Dim salaire As Long = CType(txtSalaire.Text, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' return the list of errors
Return erreurs
End Function
Por fim, a vista [simulations] é apresentada pelo seguinte procedimento [displaySimulations]:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' displays the simulations view
panelsimulations.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelform.Visible = False
' contents of simulations view
' each simulation is an array of 4 string elements
Dim simulation() As String
Dim i, j As Integer
simulationsHTML.Text = ""
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML.Text += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML.Text += "</tr>" + ControlChars.CrLf
Next
' link
lnkForm2.Text = lien
End Sub
O procedimento tem dois parâmetros:
- uma lista de simulações em [simulações]
- um texto de link em [link]
O código HTML a ser gerado para a lista de simulações está armazenado no literal [simulationsHTML]. O texto do link está armazenado na propriedade [Text] do objeto [LinkButton] na vista.
Quando o utilizador clica no botão [Clear] na vista [form], o procedimento [btnClear_click] é executado (sempre após [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' displays the empty form
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' empty the form
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
O código acima é suficientemente simples para não precisar de comentários. Ainda precisamos de tratar os cliques nos links de visualização [errors] e [simulations]:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' displays the form
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' displays the form
afficheFormulaire()
End Sub
Ambos os procedimentos limitam-se a apresentar a vista [formulário]. Sabemos que os campos do formulário receberão um valor que é ou o valor enviado para eles ou o seu valor anterior. Uma vez que, neste caso, o pedido POST do cliente não envia quaisquer valores para os campos do formulário, estes reverterão para os seus valores anteriores. O formulário é, portanto, apresentado com os valores introduzidos pelo utilizador. Recordamos que, na versão sem componentes de servidor, realizávamos nós próprios esta restauração.
7.14.2.5. Testes
Todos os ficheiros necessários à aplicação estão colocados numa pasta denominada <application-path>: ![]() | A pasta [bin] contém a DLL com as classes [impot], [impotsData] e [impotsOLEDB] necessárias para a aplicação: |
O leitor pode, se desejar, voltar ao Capítulo 5, que explica como criar o ficheiro [impot.dll] mencionado acima. Feito isto, o servidor Cassini é iniciado com os parâmetros (<application-path>,/impots5). Acedemos à URL [http://impots5/main.aspx] através de um navegador:

Se renomearmos o ficheiro ACCESS [impots.mdb] para [impots1.mdb], veremos a seguinte página:

7.14.3. Exemplo 3
Mostrámos nestes dois exemplos que é possível construir aplicações web seguindo a arquitetura MVC utilizando componentes do lado do servidor. O último exemplo demonstra que a solução com componentes do lado do servidor é mais simples do que a solução que utiliza tags HTML padrão. Os nossos dois exemplos tinham apenas uma página com várias vistas dentro da mesma página. É possível ter uma arquitetura MVC com vários formulários ASP do lado do servidor, desde que o facto de esses formulários enviarem os seus próprios valores para si próprios não constitua um problema. Este é frequentemente o caso de aplicações que possuem menus. Vejamos o seguinte exemplo:

Reunimos links para as aplicações que escrevemos até agora numa única página. Este tipo de aplicação adapta-se bem a uma arquitetura MVC. A única diferença é que já não existe um único controlador, mas sim vários.

O controlador [main.aspx] atua como o controlador principal. É ele que é chamado pelos links na página inicial da aplicação. Ele realizará operações comuns a todas as ações possíveis e, em seguida, executará a ação específica associada ao link utilizado. Em seguida, ele passará o controlo para um dos controladores secundários, o responsável pela execução da ação. A partir deste ponto, a comunicação ocorre entre o cliente e este controlador específico. Já não passamos pelo controlador principal [main.aspx]. Por conseguinte, já não estamos na estrutura MVC com um único controlador que filtra todos os pedidos. Cada um dos controladores acima pode apresentar múltiplas vistas utilizando o mecanismo de contentor dentro de uma única página, tal como descrevemos.
O facto de já não termos um único controlador a escolher quais as vistas a enviar para o cliente tem as suas desvantagens. Tomemos o tratamento de erros, por exemplo. Cada ação exposta pela aplicação pode precisar de apresentar uma vista de erro. Cada controlador [applix.aspx] terá a sua própria vista [errors], porque esta é simplesmente um contentor específico dentro da página do controlador. Não há forma de ter uma única vista [errors] que seja utilizada por todas as aplicações individuais. Na verdade, tal vista inclui normalmente um link de retorno para o formulário com erros, e esse formulário deve ser restaurado para o estado em que foi validado para permitir que o utilizador corrija os seus erros. Esta restauração é realizada através do mecanismo [VIEWSTATE], que não funciona entre controladores diferentes. Se as aplicações forem desenvolvidas por pessoas diferentes, existe o risco de ter páginas de erro com aparência diferente dependendo da ação escolhida pelo utilizador, o que compromete a consistência da aplicação como um todo. Veremos um pouco mais adiante que o ASP.NET oferece uma solução para este problema específico de partilha de vistas. Isto pode ser implementado como um novo componente de servidor que nós próprios criamos. A simples utilização deste componente nas várias aplicações garante a consistência da aplicação global. Mais difícil de gerir é a questão da ordem das ações. Quando todos os pedidos passam por um único controlador, este pode verificar se a ação solicitada é compatível com a anterior. Este código de validação está localizado num único local. Aqui, terá de ser distribuído pelos vários controladores, complicando a manutenção da aplicação global.
Voltemos à nossa aplicação acima. A sua página de entrada é uma página HTML padrão [home.htm]:
<html>
<head>
<TITLE>Composants ASP Serveur</TITLE>
<meta name="pragma" content="no-cache">
</head>
<frameset rows="130,*" frameborder="0">
<frame name="banner" src="bandeau.htm" scrolling="no">
<frameset cols="200,*">
<frame name="contents" src="options.htm">
<frame name="main" src="main.htm">
</frameset>
<noframes>
<p id="p1">
Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de
frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version
ultérieure.
</p>
</noframes>
</frameset>
</html>
Esta página inicial é composta por três frames denominados banner, contents e main:
![]() |
A página [bandeau.htm] colocada no quadro [banner] é a seguinte:

O seu código HTML é o seguinte:
<html>
<head>
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
<title>bandeau</title>
</head>
<body>
<P>
<TABLE>
<TR>
<TD><IMG alt="logo université d'angers" src="univ01.gif"></TD>
<TD>Composants serveurs ASP</TD>
</TR>
</TABLE>
</P>
<HR>
</body>
</html>
A página [options.htm] está localizada no banner [contents]. Trata-se de um conjunto de links:
![]() | |
Os vários links apontam todos para o controlador principal [main.aspx] com um parâmetro [action] que indica a ação a ser executada. Especificamos que o destino do link deve ser apresentado no quadro [main] (target="main").
A primeira página apresentada no quadro [main] é [main.htm]:
|
O controlador principal [main.aspx, main.aspx.vb] é o seguinte:
[main.aspx]
[main.aspx.vb]
Classe pública main
Inherits System.Web.UI.Page
Private Sub Page\_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recuperar a ação a ser executada
Dim ação As String
Se Request.QueryString("action") for nulo, então
action = "label"
Else
action = Request.QueryString("action").ToString.ToLower
Fim Se
' executar a ação
Selecionar Caso ação
Case "label"
Server.Transfer("form2.aspx")
Case "botão"
Server.Transfer("form3.aspx")
Caso "caixa de texto1"
Server.Transfer("form4.aspx")
Campo "textbox2"
Server.Transfer("form5.aspx")
Caso "lista suspensa"
Server.Transfer("form6.aspx")
Caso "listbox"
Server.Transfer("form7.aspx")
Caso "checkbox"
Server.Transfer("form8.aspx")
Caso "checkboxlist"
Server.Transfer("form8b.aspx")
Caso "painel"
Server.Transfer("form9.aspx")
Caso Outro
Server.Transfer("form2.aspx")
Fim do Case
Fim Sub
Fim da Classe
O nosso controlador é simples. Dependendo do valor do parâmetro [action], transfere o processamento do pedido para a página apropriada. Não oferece qualquer valor acrescentado em comparação com uma página HTML com links. No entanto, a simples adição de uma página de autenticação demonstraria a sua utilidade. Se o utilizador tivesse de se autenticar (nome de utilizador, palavra-passe) para aceder às aplicações, o controlador [main.aspx] seria um bom local para verificar se essa autenticação foi concluída.




