7. Componentes do servidor ASP - 1
7.1. Introduction
Neste capítulo, descrevemos a tecnologia recomendada no ASP.NET para construir a interface do utilizador. Sabemos que existem duas fases bem distintas no processamento de uma página .aspx pelo servidor web:
- primeiro, ocorre a execução do controlador da página. Este é constituído por código localizado quer na própria página .aspx (solução WebMatrix), quer num ficheiro separado (solução Visual Studio.NET).
- Em seguida, o código de apresentação da página .aspx é executado para ser transformado 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:
- as tags clássicas HTML. É o que temos utilizado até agora.
- as tags HTML do servidor
- as tags webforms
Independentemente da biblioteca de tags utilizada, a função do controlador de página não se altera. Este deve calcular o valor dos parâmetros dinâmicos que aparecem no código de apresentação. Até agora, esses parâmetros dinâmicos eram simples: eram objetos do tipo [String]. Assim, se no código de apresentação tivermos uma tag <%=nome%>:
- o controlador de página declara uma variável [nom] 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 <%=nom%> é substituída pelo valor calculado pelo código de controlo
Sabemos que a separação entre controlador e apresentação é arbitrária e que é possível misturar código de controlador e código de apresentação na mesma página. Já explicámos por que razão este método não é recomendado e continuaremos a respeitar a separação entre controlador e apresentação.
As tags [HTML serveur] e [WebForms] permitem introduzir no código de apresentação objetos mais complexos do que o simples objeto [String]. Por vezes, isto reveste-se de real interesse. Tomemos o exemplo de um formulário com uma lista. Esta deve ser apresentada ao cliente com um código HTML semelhante ao seguinte:
<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 selecionar são elementos dinâmicos e, por isso, têm de ser gerados pelo controlador da página. Já nos deparámos com este problema e resolvemo-lo inserindo no código de apresentação a baliza
Esta baliza será substituída pelo valor [String] da variável [uneListeHTML]. Este valor, calculado pelo controlador, deverá corresponder ao código HTML da lista, c.a.d. "<select name=..>...</select>". Não é particularmente difícil de fazer e parece ser uma solução elegante que evita colocar código de geração diretamente na parte de apresentação da página. Aqui, haveria um ciclo com verificações a inserir nessa parte, que ficaria assim fortemente «poluída». No entanto, este método apresenta uma desvantagem. A separação entre controlo e apresentação de uma página serve também para delimitar dois domínios de competência:
- o do programador .NET, que se ocupa do controlador da página
- o do designer gráfico, que se ocupa da parte de apresentação da mesma
Aqui, vemos que a geração do código HTML da lista foi transferida para o controlador. O designer gráfico pode querer intervir neste código HTML para alterar o aspeto «visual» da lista. Será obrigado a trabalhar na parte [contrôleur] e, por conseguinte, a sair da sua área de competência, com os riscos de erros introduzidos inadvertidamente no código que isso acarreta.
As bibliotecas de tags de servidor resolvem este problema. Oferecem um objeto que representa uma lista HTML. Assim, a biblioteca [WebForms] disponibiliza a seguinte tag:
Esta baliza representa um objeto do tipo [ListBox] que pode ser manipulado pelo controlador de página. Este objeto possui propriedades para representar as diferentes opções da lista HTML e indicar a opção selecionada. O controlador de página irá, portanto, atribuir os valores adequados a essas propriedades. Quando a parte de apresentação for executada, a baliza
será substituída pelo código HTML, que representa os objetos [uneListe] e c.a.d. o código «<select ..>...</select>». Por enquanto, não há nenhuma diferença fundamental em relação ao método anterior, a não ser uma forma de codificação orientada para objetos, o que é interessante. Voltemos ao nosso designer gráfico, que precisa de alterar o «aspecto» da lista. As tags de servidor têm atributos de estilo (BackColor, Bordercolor, BorderWidth, ...) que permitem definir o aspeto visual do objeto HTML correspondente. Assim, poderemos escrever:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
A vantagem é que o designer gráfico permanece no código de apresentação para efetuar essas alterações. Trata-se de uma vantagem clara em relação ao método anterior. As bibliotecas de tags de servidor facilitam, portanto, a construção da parte de apresentação das páginas, aquilo a que nos capítulos anteriores chamámos de interface do utilizador. O objetivo deste capítulo é apresentá-las. Veremos que, por vezes, estas bibliotecas oferecem objetos complexos, tais como calendários ou tabelas ligadas a fontes de dados. São extensíveis, c.a.d, permitindo que o utilizador crie a sua própria biblioteca de tags. Assim, pode criar uma tag que gere um banner numa página. Todas as páginas que utilizem essa tag terão, então, o mesmo banner.
A geração HTML efetuada para uma baliza adapta-se ao tipo de navegador do cliente. Quando este envia um pedido ao servidor web, inclui entre os seus cabeçalhos HTTP um cabeçalho [User-Agent: xx], em que [xx] identifica o cliente. Eis um exemplo:
Com esta informação, o servidor web pode conhecer as capacidades do cliente, nomeadamente o tipo de código HTML que este consegue gerir. De facto, ao longo do tempo, surgiram várias versões da linguagem HTML. Os navegadores mais recentes suportam as versões mais recentes da linguagem, o que os navegadores mais antigos não conseguem fazer. Dependendo do cabeçalho HTTP [User-Agent:] que o cliente lhe enviou, o servidor enviar-lhe-á uma versão HTML que ele saberá compreender. Trata-se de uma ideia interessante e útil, pois o programador não precisa, assim, de se preocupar com o tipo de navegador do cliente da sua aplicação.
Por fim, versões avançadas do IDE, como o Visual Studio.NET, o WebMatrix, etc., permitem uma conceção «ao estilo Windows» da interface Web. Estas ferramentas, embora não sejam indispensáveis, constituem, no entanto, uma ajuda decisiva para o programador. Este desenha a interface Web com a ajuda de componentes gráficos que coloca nessa interface. Tem acesso direto às propriedades de cada um dos componentes da interface, que pode assim configurar à sua vontade. Estas propriedades serão traduzidas no código HTML de apresentação da interface em atributos da baliza <asp:> do componente. A vantagem para o programador é que não precisa de se lembrar nem da lista nem da sintaxe dos atributos de cada baliza. Esta é uma vantagem considerável quando não se conhece perfeitamente as bibliotecas de tags de servidor disponibilizadas pelo ASP.NET. Quando esta sintaxe for dominada, alguns programadores poderão preferir codificar diretamente as tags no código de apresentação da página, sem passar pela fase de conceção gráfica. Nesse caso, um IDE deixa de ser útil. Basta um simples editor de texto. Dependendo da forma de trabalhar, a ênfase recai então nos componentes (utilização de um IDE) ou nas tags (utilização de um editor de texto). Existe equivalência entre estes dois termos. O componente é o objeto que será manipulado pelo código de controlo da página. O IDE dá-nos acesso às suas propriedades na fase de conceção. Os valores atribuídos a essas propriedades são traduzidos imediatamente para os atributos da baliza do componente no código de apresentação. Na 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á, por sua vez, gerar o código HTML do componente, utilizando, por um lado, os atributos definidos na fase de conceção para a baliza de servidor correspondente e, por outro, os valores das propriedades do componente calculados pelo código de controlo.
7.2. O contexto de execução dos exemplos
Vamos ilustrar a conceção de interfaces web baseadas em componentes de servidor com programas cujo contexto de execução será, na maioria das vezes, o seguinte:
- a aplicação web será composta por uma única página P contendo um formulário F,
- o cliente fará a sua primeira solicitação diretamente a esta página P. Isto consistirá em solicitar o URL da página P através de um navegador. Trata-se, portanto, de uma solicitação GET que será efetuada para este URL P. O servidor fornecerá a página P e, consequentemente, o formulário F que esta contém,
- o utilizador preencherá o formulário e enviá-lo-á, c.a.d, ou seja, realizará uma ação que obrigará o navegador a enviar o formulário F para o servidor. A operação POST do navegador terá sempre como destino a página P. O servidor voltará a apresentar a página P com o formulário F, cujo conteúdo poderá ter sido alterado pela ação do utilizador.
- Em seguida, as etapas 2 e 3 serão retomadas.
Trata-se de um processo de execução muito específico, fora do qual certos conceitos expostos a seguir 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 «controlador de aplicação». Neste tipo de arquitetura, o POST dos formulários tem como destino o controlador e não os próprios formulários. No entanto, veremos que construir um formulário com componentes de servidor implica que esse formulário seja enviado para si próprio.
7.3. O componente Label
7.3.1. Utilização
A baliza <asp:label> permite inserir um texto dinâmico no código de apresentação de uma página. Portanto, não faz mais do que a baliza <%=variável%> utilizada até agora. A análise desta primeira baliza vai permitir-nos descobrir o mecanismo das balizas de servidor. Criamos uma página com uma parte de controlo [form1.aspx.vb] e uma parte de apresentação [form1.aspx]. Trata-se de apresentar a hora:

Este problema já foi abordado no capítulo 2 e o leitor é convidado a consultá-lo caso deseje saber como foi resolvido. 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>
Introduzimos a baliza <asp:label>. Nas bibliotecas de balizas, o atributo [runat="server"] é obrigatório. O atributo ID identifica o componente. O controlador deverá referenciá-lo com 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
' guarda a consulta atual em request.txt na pasta da página
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
' insere a hora em 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 apresentados pelas balizas <asp:> pertencem ao espaço de nomes [System.Web.UI.WebControls]. Assim, será possível importar sistematicamente este espaço de nomes:
Imports System.Web.UI.WebControls
O objeto [Label] possui várias propriedades, entre as quais a propriedade [Text], que representa o texto que será exibido pela tag <asp:label> correspondente. Aqui, atribuímos a esta propriedade a hora atual. Fazemo-lo no procedimento [Form_Load] do controlador, que é executado sistematicamente. No procedimento [Form_Init], que também é sempre executado, mas antes do procedimento [Form_Load], guardamos o pedido do cliente num ficheiro [request.txt] na pasta da aplicação. Teremos oportunidade de analisar este ficheiro para compreender alguns aspetos do funcionamento das páginas que utilizam tags de servidor.
O objeto [Label] possui inúmeras propriedades, métodos e eventos. O leitor é convidado a consultar a documentação sobre a classe [Label] para os descobrir. Daqui em diante, será sempre assim. Para cada tag, apresentamos apenas as poucas propriedades de que precisamos.
7.3.2. Os 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, acedemos à 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>
Vê-se que a baliza do servidor
<asp:Label Runat="server" ID="lblHeure" />
foi transformada no seguinte código HTML:
Foi a propriedade [Text] do objeto [lblHeure] que foi colocada entre as tags <span> e </span>. O pedido efetuado pelo cliente e armazenado 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
Tudo muito normal.
7.3.3. Criar a aplicação com WebMatrix
Criámos manualmente o código de apresentação da página [form1.aspx]. Este método pode ser utilizado se conhecermos as balizas. Basta, então, um simples editor de texto para criar a interface do utilizador. Para começar, é frequentemente necessária uma ferramenta de design gráfico combinada com a geração automática de código, porque não conhecemos a sintaxe das balizas de que precisamos. Vamos agora criar a mesma aplicação com a ferramenta WebMatrix. Depois de iniciar o WebMatrix, selecionamos a opção [File/New File]:

Criamos uma página ASP.NET denominada [form2.aspx]. Depois de validar o assistente anterior, obtemos a janela de conceção da página [form2.aspx]:


Recorde-se que o WebMatrix coloca o código de controlo da página e o código de apresentação num único ficheiro, neste caso o [form2.aspx]. O separador [All] apresenta o conteúdo deste ficheiro de texto. É possível verificar desde já que não está vazio:

A desvantagem deste tipo de ferramentas é que, muitas vezes, geram código desnecessário. É o que acontece aqui, onde o WebMatrix gerou uma baliza <form> HTML, quando não vamos criar nenhum formulário... Além disso, verifica-se que o documento não possui a tag <title>. Corrigimos estes dois problemas imediatamente para obter a seguinte nova versão:

O que designamos por «código controlador» será inserido entre as tags <script> e </script>, o que garante uma separação, pelo menos visual, entre os dois tipos de código: controlo e apresentação. Voltamos ao separador [Design] para desenhar a nossa interface. Está disponível uma lista de componentes numa janela de ferramentas à esquerda da janela de conceção:

A janela de ferramentas dá acesso a dois tipos de componentes:
- os componentes [WebControls], que se traduzem em tags <asp:>
- os componentes [HTML Elements], que se traduzem em tags HTML clássicas. No entanto, é possível adicionar aos atributos de uma tag HTML o atributo [runat="server"]. Neste caso, a baliza HTML e os seus atributos ficam acessíveis ao controlador através de um objeto cujas propriedades correspondem às da baliza HTML que representa. Anteriormente, estas balizas foram designadas como balizas HTML do servidor.
Clicamos duas vezes no componente [Label] da lista de controlos [WebControls]. No separador [Design], obtemos 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>
Em primeiro lugar, é possível verificar que a baliza <script> desapareceu. Foi gerada uma baliza <asp:label>. Esta tem o nome [Label1] e o valor [Label]. Voltemos ao separador [Design] para alterar estes dois valores. Clicamos uma vez no componente [Label] para abrir a janela de propriedades desse componente, no canto inferior direito:

Sugere-se ao leitor que consulte as propriedades do objeto [Label]. Duas delas são relevantes neste contexto:
- Texto: é o texto que o rótulo deve exibir — colocamos a cadeia vazia (c.a.d. nada)
- ID: é o seu identificador — colocamos lblHeure
O separador [Design] fica assim:

e o código de [All] passa a ser:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
A parte de apresentação da página está concluída. Resta-nos escrever o código de controlo responsável por inserir a hora na propriedade [Text] de [lblHeure]. Adicionamos no separador [All] o seguinte código:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' insere a hora em 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-se que, no código do controlador, o objeto [lblHeure] não é declarado como tinha sido feito anteriormente:
Protected lblHeure As New System.Web.UI.WebControls.Label
Com efeito, todos os componentes de servidor <asp:> da parte de apresentação são objeto de uma declaração implícita na parte de código de controlo. Por isso, declará-los provoca um erro de compilação, indicando que o objeto já está declarado. Estamos prontos para a execução. Selecionamos a opção [View/Start] ou o atalho [F5]. O Cassini é iniciado automaticamente com os seguintes parâmetros:

Aceitamos estes valores. O navegador predefinido do sistema é iniciado automaticamente para aceder ao URL [http://localhost/form2.aspx]. Obtemos o seguinte resultado:

Posteriormente, utilizaremos principalmente a ferramenta WebMatrix para facilitar a construção e os testes dos pequenos programas que iremos escrever.
7.4. O componente Literal
7.4.1. Utilização
A baliza <asp:literal> permite inserir texto dinâmico no código de apresentação de uma página, tal como a baliza <asp:label>. O seu principal atributo é [Text], que representa o texto que será inserido tal como está no fluxo HTML da página. Esta baliza é suficiente se não se pretender formatar o texto que se pretende inserir no fluxo HTML. Com efeito, embora a classe [Label] permita aplicar formatação através de atributos como o [BorderColor, BorderWidth, Font, ...], a classe [Literal] não possui nenhum desses atributos. O leitor poderá reproduzir na íntegra o exemplo anterior, substituindo o componente [Label] por um componente [Literal].
7.5. O componente Button
7.5.1. Utilização
A baliza <asp:Button> permite inserir um botão do tipo [Submit] num formulário, o que implica uma gestão de eventos semelhante à que se encontra nas aplicações Windows. É este aspeto que pretendemos aprofundar 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 | text=Botão1 | botão de envio | |
2 | Botão | text=Botão 2 | botão de envio | |
3 | Rótulo | text= | mensagem informativa |
O código gerado pelo WebMatrix para esta parte é 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>
Encontramos os componentes [Button] e [Label], utilizados na conceção gráfica da página, nas tags <asp:>. De notar a tag <form runat="server">, que foi gerada automaticamente. Trata-se de uma baliza de servidor HTML, c.a.d. Uma baliza clássica HTML, representada, no entanto, por um objeto que pode ser manipulado pelo controlador. O código da tag <form> será gerado a partir do valor que o controlador atribuir a este objeto.
Adicionemos, na parte do código relativa ao controlador, o procedimento [Page_Init] que gere o evento [Init] da página. Colocamos aí o código que guarda o pedido do cliente no ficheiro [request.txt]. Vamos precisar disso para compreender o mecanismo dos botões.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' guarda a solicitação atual em request.txt na pasta da página
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
Note-se que, no texto acima, não incluímos a cláusula [Handles MyBase.Init] a seguir à declaração do procedimento [Page_Init]. Com efeito, o evento [Init] do objeto [Page] tem um gestor predefinido chamado [Page_Init]. Se utilizarmos este nome de gestor, a cláusula [Handles Page.Init] torna-se desnecessária. No entanto, a sua inclusão não provoca qualquer erro.
7.5.2. Testes
Executamos a aplicação em WebMatrix através de [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>
É importante destacar os seguintes pontos:
- a baliza <form runat="server"> transformou-se na baliza HTML
Foram definidos dois atributos: [method="post"] e [action="form3.aspx"]. Será possível atribuir valores diferentes a estes atributos? Tentaremos esclarecer esta questão mais adiante. Basta reter aqui que o formulário será enviado para o URL [form3.aspx]. Foram também definidos outros dois atributos: [name, id]. Na maioria das vezes, estes são ignorados. No entanto, se a página contiver código JavaScript executado no navegador, o atributo [name] da tag <form> é útil.
- As tags <asp:button> passaram a ser tags HTML de botões [submit]. Um clique em qualquer um destes botões provocará, portanto, um «post» do formulário [_ctl10] para a URL [form3.aspx].
- A tag <asp:label> passou a ser 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. Como o [__VIEWSTATE] faz parte do formulário, o seu valor será enviado juntamente com o resto para o servidor. Isto permitirá que este saiba qual o componente do formulário que alterou o seu valor e, eventualmente, tome decisões. Estas assumirão a forma de eventos do tipo «o TextBox tal e tal alterou o seu valor».
7.5.3. Os pedidos do cliente
Depois de obter a página [form3.aspx] no navegador, solicitemo-la novamente e analisemos o pedido que foi enviado pelo navegador para a obter. Recorde-se que a nossa aplicação o armazena no ficheiro [request.txt] na 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
Temos aqui um GET clássico. Vamos agora clicar no botão [Bouton1] da página no navegador. Aparentemente, nada acontece. No entanto, sabemos que o formulário foi enviado. É o código HTML da página que o indica. Isto é 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, de facto, que o cliente efetuou um POST para o URL [/form3.aspx]. A última linha mostra os valores enviados:
- o valor do campo oculto __VIEWSTATE
- o valor do botão em que se clicou
Se clicarmos em [Bouton2], os valores enviados pelo navegador são os seguintes:
O valor do campo oculto é sempre o mesmo, mas foi o valor de [Button2] que foi enviado. O servidor pode, assim, saber qual o botão que foi utilizado. Vai utilizá-lo para desencadear um evento que poderá ser processado pela página, assim que esta tiver sido carregada.
7.5.4. Gerir o evento Click de um objeto Button
Recorde-se o funcionamento de uma página .aspx. Esta é um objeto derivado da classe [Page]. Chamemos à classe derivada [unePage]. Quando o servidor recebe um pedido para uma página deste tipo, é instanciado um objeto do tipo [unePage] através da operação new unePage(...). Em seguida, o servidor gera dois eventos denominados [Init] e [Load], por esta ordem. O objeto [unePage] pode tratá-los fornecendo os gestores de eventos [Page_Init] e [Page_Load]. Posteriormente, serão gerados outros eventos. Teremos oportunidade de voltar a este assunto. Se o pedido do cliente for um POST, o servidor irá gerar o evento [Click] do botão que provocou esse POST. Se a classe [unePage] tiver definido um manipulador para este evento, este será chamado. Vejamos este mecanismo com o WebMatrix. No separador [Design] de [form3.aspx], cliquemos duas vezes no botão [Bouton1]. Somos então direcionados automaticamente para o separador [Code], no corpo de um procedimento denominado [Button1_Click]. Para compreender melhor, vamos ao separador [All] e analisemos o código na íntegra. 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"] à baliza <asp:Button> de [Button1]. Este atributo indica a rotina responsável por tratar o evento [Click] no objeto [Button1], neste caso a rotina [Button1_Click]. Resta-nos apenas escrever essa rotina:
Sub Button1_Click(sender As Object, e As EventArgs)
' clique no botão 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
O procedimento insere uma mensagem informativa no rótulo [lblInfo]. Procedemos da mesma forma para o botão [Bouton2], 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)
' guarda a solicitação atual em request.txt na pasta da página
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)
' clique no botão 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' clique no botão 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>
Iniciamos a execução através do [F5] para obter a seguinte página:

Se analisarmos o código HTML recebido, verificaremos que não sofreu alterações em relação ao da 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 em [Bouton1], 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 constatar que o campo oculto [__VIEWSTATE] alterou o seu valor. Isto reflete a alteração do valor do componente [lblInfo].
7.5.5. Os eventos ao longo do ciclo de vida de uma aplicação ASP.NET
A documentação ASP.NET apresenta a lista de eventos gerados pelo servidor ao longo do ciclo de vida de uma aplicação:
- aquando da primeira solicitação recebida pela aplicação, será gerado o evento [Start] no objeto [HttpApplication] da aplicação. Este evento pode ser tratado pelo procedimento [Application_Start] do 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 tiver enviado um token de sessão, é iniciada uma nova sessão e é gerado um evento [Start] no objeto [Session] associado à solicitação. Este evento pode ser tratado pelo procedimento [Session_Start] do ficheiro [global.asax] da aplicação.
- O servidor gera o evento [BeginRequest] no objeto [HttpApplication]. Este pode ser tratado pelo procedimento [Application_BeginRequest] do ficheiro [global.asax] da aplicação.
- O servidor carrega a página solicitada pela requisição. Irá instanciar um objeto [Page] e, em seguida, gerar dois eventos nesse objeto: [Init] e, depois, [Load]. Estes dois eventos podem ser geridos pelos procedimentos [Page_Init] e [Page_Load] da página.
- A partir dos valores postados recebidos, o servidor irá gerar outros eventos: [TextChanged] para um componente [TextBox] cujo valor foi alterado, [CheckedChanged] para um botão de opção cujo valor foi alterado, [SelectedIndexChanged] para uma lista cujo elemento selecionado foi alterado, ... Teremos oportunidade de mencionar os principais eventos para cada um dos componentes de servidor que iremos apresentar. Cada evento E num objeto denominado O pode ser tratado por um procedimento com o nome O_E.
- A ordem de tratamento dos eventos anteriores não é garantida. Por isso, os gestores não devem fazer quaisquer suposições sobre essa ordem. No entanto, é garantido que o evento [Click] do botão que provocou o POST é tratado em último lugar.
- Assim que a página estiver pronta, o servidor irá enviá-la ao cliente. Antes disso, 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. Nessa altura, 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 do âmbito de um pedido do cliente:
- o evento [End] num objeto [Session] da aplicação ocorre quando uma sessão termina. Isto pode acontecer por pedido explícito do código de uma página ou porque o tempo de vida atribuído à sessão foi excedido. O procedimento [Session_End] do ficheiro [global.asax] gere este evento. Nele, são geralmente libertados recursos obtidos no [Session_Start].
- O evento [End] no objeto [HttpApplication] da aplicação ocorre quando a aplicação termina. Isto acontece, nomeadamente, quando se encerra o servidor Web. O procedimento [Application_End] do ficheiro [global.asax] gere este evento. Nele, são geralmente libertados recursos obtidos no [Application_Start].
É importante destacar os seguintes pontos:
- o modelo de eventos anterior baseia-se em trocas clássicas cliente-servidor HTTP. Isto fica perfeitamente evidente quando se analisam os cabeçalhos HTTP trocados.
- o processamento dos eventos anteriores é sempre feito do lado do servidor. O clique num botão pode, naturalmente, ser processado por um script JavaScript do lado do servidor. Mas, nesse caso, não se trata de um evento do servidor e estamos perante uma tecnologia independente de ASP.NET.
Em que momento são processados os eventos, quer sejam processados do lado do servidor (eventos relacionados com os componentes do servidor) ou do lado do navegador por scripts JavaScript?
Tomemos o exemplo de uma lista suspensa. Quando o utilizador altera o elemento selecionado nessa lista, o evento (alteração do elemento selecionado) pode ou não ser processado e, caso seja, pode ocorrer em diferentes momentos.
- Se se pretender processá-lo imediatamente, existem duas soluções:
- pode ser processado pelo navegador através de um script JavaScript. Nesse caso, o servidor não intervém. Para que isso seja possível, é necessário que a página possa ser reconstruída com os valores presentes na própria página.
- pode ser processado pelo servidor. Para tal, 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, se utiliza um componente de servidor denominado [DropDownList] e define-se o seu atributo [AutoPostBack] como [true]. Isto significa que, em caso de alteração do elemento selecionado na lista suspensa, o formulário deve ser imediatamente enviado para o servidor. Neste caso, o servidor gera, para o objeto [DropDownList], um código HTML associado a uma função JavaScript encarregada de executar um [submit] assim que ocorrer o evento «alteração do elemento selecionado». Este [submit] enviará o formulário para o servidor e, nesse formulário, serão inseridos campos ocultos para indicar que o [post] resulta de uma alteração na seleção da lista suspensa. O servidor irá então gerar o evento [SelectedIndexChanged], que a página poderá processar.
- Se se pretender processá-lo, mas não imediatamente, define-se o atributo [AutoPostBack] do componente de servidor [DropDownList] como [false]. Neste caso, o servidor gera, para o objeto [DropDownList], o código HTML clássico de uma lista <select> sem nenhuma função JavaScript associada. Assim, nada acontece quando o utilizador altera a seleção na lista suspensa. No entanto, quando o utilizador validar o formulário através de um botão [submit], por exemplo, o servidor poderá reconhecer que houve uma alteração na seleção. De facto, verificámos que o formulário enviado ao servidor continha um campo oculto [__VIEWSTATE] que representava, de forma codificada, o estado de todos os elementos do formulário enviado. Quando o servidor recebe o novo formulário enviado pelo cliente, poderá verificar se o elemento selecionado na lista suspensa mudou ou não. Se sim, irá gerar o evento [SelectedIndexChanged], que a página poderá então gerir. Para distinguir este mecanismo do anterior, alguns autores afirmam que o evento «alteração da seleção» foi «armazenado em cache» quando ocorreu no navegador. Só será processado pelo servidor quando o navegador lhe enviar o formulário, frequentemente na sequência de um clique num botão [submit].
- Por fim, se não se pretender processar o evento, define-se o atributo [AutoPostBack] do componente de servidor [DropDownList] para [false] e não se escreve o manipulador do seu evento [SelectedIndexChanged].
Depois de compreender o mecanismo de tratamento de eventos, o programador não conceberá uma aplicação web como uma aplicação Windows. Com efeito, se uma alteração na seleção de uma caixa combinada de uma aplicação Windows pode ser utilizada para alterar imediatamente o aspeto do formulário em que esta se encontra, hesitar-se-á mais em processar esse evento imediatamente numa aplicação web, caso isso implique um «post» do formulário para o servidor e, consequentemente, uma ida e volta na rede entre o cliente e o servidor. É por isso que a propriedade [AutoPostBack] dos componentes do servidor é definida por predefinição como [false]. Além disso, o mecanismo [AutoPostBack], que se baseia em 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 autorizou a execução de scripts JavaScript no seu navegador. Os formulários são, portanto, frequentemente construídos da seguinte forma:
- os componentes do servidor do formulário têm a sua propriedade definida de [AutoPostBack] a [false]
- o formulário tem um ou mais botões responsáveis por efetuar o [POST] do formulário
- no código do controlador da página, escrevem-se os manipuladores apenas dos eventos que se pretende gerir, na maioria das vezes o evento [Click] num dos botões.
7.6. O componente TextBox
7.6.1. Utilização
A baliza <asp:TextBox> permite inserir um campo de introdução de dados no código de apresentação de uma página. Criamos uma página [form4.aspx] para obter a seguinte apresentação:

4321Esta página, criada com o WebMatrix, contém os seguintes componentes:
n.º | nome | tipo | propriedades | função |
1 | TextBox | AutoPostback=true Text= | campo de introdução | |
2 | TextBox | AutoPostback=false Text= | campo de introdução | |
3 | Rótulo | text= | mensagem informativa sobre o conteúdo de [TextBox1] | |
3 | Etiqueta | text= | mensagem informativa sobre o conteúdo de [TextBox2] |
O código gerado pelo WebMatrix para esta parte é 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]. É então gerado o esqueleto do gestor do evento [TextChanged] deste objeto (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 à baliza <asp:TextBox id="TextBox1"> para designar o gestor do evento [TextChanged] em [TextBox1]. É o procedimento [TextBox1_Changed] que estamos agora a escrever.
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' alteração do texto
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
No procedimento, escrevemos no rótulo [lblInfo1] uma mensagem a sinalizar o evento e a indicar o conteúdo de [TextBox1]. Fazemos o mesmo para [TextBox2]. Indicamos também a hora para acompanhar melhor o processamento dos eventos. O código final de [form4.aspx] é o seguinte:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' guarda a solicitação atual em request.txt na pasta da página
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)
' alteração de texto
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)
' alteração de texto
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 memorizar o pedido do cliente, tal como no exemplo anterior.
7.6.2. Testes
Executamos a aplicação com o procedimento WebMatrix através do procedimento [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>
Há muitos elementos neste código que foram gerados automaticamente pelo servidor. Destacamos os seguintes pontos:
- existem três campos ocultos: [__VIEWSTATE], que já vimos anteriormente, [__EventTarget] e [__EventArgument]. Estes dois últimos campos são utilizados para gerir o evento «change» do navegador no campo de introdução de dados [TextBox1]
- as tags de servidor <asp:textbox> deram origem às tags HTML <input type="text" ...>, que correspondem aos campos de entrada
- a baliza de servidor <asp:textbox id="TextBox1" AutoPostBack="true" ...> deu origem a uma baliza <input type="text" ...> com um atributo [onchange="__doPostBack('TextBox1','')"]. Este atributo indica que, em caso de alteração do conteúdo de [TextBox1], a função JavaScript [_doPostBack(...)] deve ser executada. Esta é a 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, envia o formulário. Este é, portanto, enviado para o servidor. Este é o efeito de [AutoPostBack]. O evento «change» do navegador provoca a execução do código «__doPostBack('TextBox1','')». Conclui-se que, no formulário enviado, o campo oculto [__EventTarget] terá o valor «TextBox1» e o campo oculto [__EventArgument] terá o valor «». Isto permitirá ao servidor identificar o componente que originou o POST.
- A baliza de servidor <asp:textbox id="TextBox2"...> deu origem a uma baliza <input type="text" ...> clássica, 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 fazer o nosso primeiro teste. Digitemos um texto no primeiro campo de entrada:

e, em seguida, coloquemos o cursor no segundo campo de introdução de dados. Imediatamente, obtemos uma nova página:

O que aconteceu? Quando o cursor saiu do primeiro campo de introdução, o navegador verificou se o conteúdo deste tinha mudado. E assim foi. Por isso, gerou, do lado do navegador, 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, portanto, recarregada pelo servidor. Os valores enviados pelo formulário permitiram que o servidor, por sua vez, detectasse que o conteúdo da tag de servidor [TextBox1] tinha mudado. O procedimento [TextBox1_Changed] foi, portanto, executado no lado do servidor. Este colocou uma mensagem no rótulo [lblInfo1]. Assim que este procedimento terminou, o [form4.aspx] foi enviado para o navegador. É por isso que agora temos um texto no [lblInfo1]. Dito isto, pode ser surpreendente que haja algo no campo de introdução de dados [TextBox1]. De facto, nenhum procedimento executado no lado do servidor atribui um valor a este campo. Trata-se de 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 convertidos nas tags <span>. Recorde-se que o formulário possui um campo oculto 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 os componentes <asp:label>, caso existam. Como o campo oculto [__VIEWSTATE] é enviado pelo navegador do cliente, o servidor consegue restabelecer o estado anterior de todos os componentes do formulário. Resta-lhe apenas alterar aqueles cujo valor foi alterado pelo POST.
Vejamos agora, no [request.txt], o pedido que foi feito 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=
É possível ver perfeitamente o POST, bem como os parâmetros enviados. Voltemos ao nosso navegador e introduzamos um texto no segundo campo de introdução:

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

Saímos do campo de introdução 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? Em primeiro lugar, o navegador enviou o formulário. Este facto está refletido na solicitação do cliente armazenada 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 valores anteriores dos componentes através do campo oculto [__VIEWSTATE] que o cliente lhe enviou. Graças aos campos enviados [TextBox1] e [TextBox2], o servidor irá atribuir aos componentes [TextBox1] e [TextBox2] os valores que lhe foram enviados. É através deste mecanismo que o cliente irá recuperar o formulário tal como foi enviado. Em seguida, ainda graças aos campos enviados [__VIEWSTATE], [TextBox1] e [TextBox2], o servidor irá detetar que os valores dos campos de introdução de dados [TextBox1] e [TextBox2] foram alterados. Assim, irá gerar 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 um novo valor. Em seguida, a página [form4.aspx], assim modificada, é reenviada ao cliente.
Agora, alteramos novamente o campo de introdução n.º 1:

Quando retiramos o cursor de introdução do campo 1, ocorre o evento [Change] no navegador. Em seguida, decorre a sequência de eventos já explicada (envio do navegador para o servidor, ..., envio da resposta do servidor). Obtém-se a seguinte resposta:

Devido à hora exibida em cada uma das mensagens, verifica-se que apenas o procedimento [TextBox1_Changed] foi executado no servidor. O procedimento [TextBox2_TextChanged] não foi executado precisamente porque o valor de [TextBox2] não se alterou. Por fim, vamos inserir um novo texto no campo n.º 2:

Em seguida, coloquemos o cursor no campo n.º 1 e, depois, voltemos a colocá-lo no campo n.º 2. A página não muda. Porquê? Porque não alteramos o valor do campo n.º 1, o evento do navegador [Change] 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 existe nenhum POST. Se houvesse um, o servidor detetaria que o conteúdo de [TextBox2] mudou e deveria refletir esse facto em [lblInfo2].
O que se retira deste exemplo é que não faz sentido atribuir a propriedade [AutoPostBack] de um [TextBox] a um [true]. Na maioria das vezes, isso provoca uma troca de dados cliente-servidor desnecessária.
7.6.3. A função do campo __VIEWSTATE
Vimos que o servidor inseria sistematicamente um campo oculto chamado __VIEWSTATE no formulário que gerava. Referimos que este campo representava o estado do formulário e que, se lhe fosse devolvido esse campo oculto, o servidor era capaz de reconstituir o valor anterior do formulário. O estado de um formulário é a soma dos estados dos seus componentes. Cada um deles possui uma propriedade [EnableViewState] com valor booleano que indica se o estado do componente deve ou não ser colocado no campo oculto [__VIEWSATE]. Por predefinição, esta propriedade tem o valor [true], o que faz com que o estado de todos os componentes de um formulário seja colocado em [__VIEWSTATE]. Por vezes, isso não é desejável.
Vamos fazer algumas experiências para compreender melhor o papel da propriedade [EnableViewState]. Defina esta propriedade como [false] para os dois campos de introdução de dados:
...
<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>
...
Vamos agora executar a aplicação e introduzir um primeiro texto no campo n.º 1 antes de passarmos para o campo n.º 2. É então enviada uma solicitação POST para o servidor e obtém-se a seguinte resposta:

Digitemos um texto no campo n.º 2, alteremos o do campo n.º 1 e, em seguida, voltemos ao campo n.º 2 (por esta ordem). É enviada uma nova solicitação POST para o servidor e obtemos a seguinte resposta:

Por enquanto, tudo continua como antes. Agora, alteremos o conteúdo do campo n.º 1 e passemos 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, uma vez que a hora do [lblInfo2] foi alterada. No entanto, não houve qualquer alteração. Isto explica-se pela propriedade [EnableViewState=false] de [TextBox2]. Esta propriedade faz com que o servidor não tenha inserido no campo [__VIEWSTATE] do formulário o estado anterior de [TextBox2]. Isto equivale a atribuir-lhe a cadeia vazia como estado anterior. Quando ocorreu o POST, devido à alteração do conteúdo de [TextBox1], o servidor comparou o valor atual de [TextBox2] — que era [deux] — com o seu valor anterior (a cadeia vazia). Concluiu que o [TextBox2] tinha alterado o seu valor e gerou o evento [TextChanged] para o [TextBox2]. É possível validar este funcionamento colocando a cadeia vazia em [TextBox2]. De acordo com o 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 isto em conta, vamos analisar a propriedade [EnableViewState] dos quatro componentes do formulário:
pretende-se manter o estado deste componente para que o servidor saiba se este sofreu ou não alterações | |
idem | |
Não se pretende manter o estado deste componente. Pretende-se que o texto seja recalculado a cada novo POST. Se não for recalculado, deve ficar vazio. Tudo isto é conseguido com [EnableViewState=false] | |
idem |
A nossa página de apresentação fica assim:
...
<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 repetir a mesma série de testes que fizemos anteriormente. No ponto em que obtivemos o ecrã
version 1

, obtemos agora:
version 2

Nesta etapa, alterávamos o conteúdo do campo 1 sem alterar o do campo 2, fazendo com que o procedimento [TextBox2_TextChanged] não fosse executado no servidor, o que implicava que o campo [lblInfo2] não recebia um novo valor. Por isso, é apresentado com o seu valor anterior. Na versão 1, [EnableViewState=true], esse valor anterior era o valor introduzido. Na versão 2, [EnableViewState=false], esse valor anterior é a cadeia vazia.
Por vezes, não é necessário manter o estado anterior dos componentes. Em vez de definir [EnableViewState=false] para cada um deles, pode-se indicar 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 é colocado no campo oculto [__VIEWSTATE]. Tudo acontece então como se o seu estado anterior fosse a cadeia vazia.
Vamos agora utilizar o cliente [curl] para destacar outros mecanismos. Primeiro, solicitamos a 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 do servidor o código HTML, proveniente de [form4.aspx]. Não é diferente do que o navegador tinha recebido. Recorde-se aqui a solicitação feita 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 fazer o mesmo POST, mas sem enviar o campo [__VIEWSTATE]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primeiro+texto --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>
..............
Deve-se ter em conta os seguintes pontos:
- o servidor detetou um evento [TextChanged] no [TextBox1], uma vez que gerou o texto [lblInfo1]. A ausência de [__VIEWSTATE] não o impediu. Na sua ausência, o servidor assume que o valor anterior de um campo de introdução de dados é a cadeia vazia.
- Conseguiu repor o texto inserido para [TextBox1] no atributo [value] da baliza [TextBox1], para que o campo [TextBox1] reaparecesse com o valor introduzido. Para tal, não precisa do [__VIEWSTATE], mas apenas do valor enviado para o [TextBox1]
Agora, vamos repetir a mesma consulta sem alterar nada. Obtemos a seguinte resposta:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primeiro+texto --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 detetar que o campo [TextBox1] não tinha alterado o seu valor. Por isso, age como se o valor anterior fosse a cadeia vazia. Por isso, gerou aqui o evento [TextChanged] sobre [TextBox1]. Vamos repetir a mesma consulta, mas desta vez com o campo [TextBox1] vazio e o [TextBox2] não vazio:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=segundo+texto --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 considerado como a cadeia vazia. Como o valor lançado de [TextBox1] também era a cadeia vazia, o evento [TextChanged] relativo a [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. Sabe-se que, neste caso, o componente mantém o seu valor anterior. No entanto, neste caso, não é isso que acontece: o [lblInfo1] perdeu o seu valor anterior. Isto porque esse valor é procurado no [__VIEWSTATE]. Como esse campo está ausente, foi atribuída uma cadeia vazia ao [lblInfo1]. No caso de [TextBox2], o servidor comparou o seu valor enviado ([second texte]) com o seu valor anterior. Na ausência de [__VIEWSTATE], esse valor anterior é igual à cadeia vazia. Como o valor publicado de [TextBox2] é diferente da cadeia vazia, foi gerado o evento [TextChanged] sobre [TextBox2]. O procedimento [TextBox2_TextChanged] foi executado e o campo [lblInfo2] recebeu um novo valor.
Podemos questionar-nos se os parâmetros [__EVENTTARGET] e [__EVENTARGUMENT] são realmente úteis. Ao não enviar estes parâmetros, o servidor não saberá por qual evento foi desencadeado o [submit]. Vamos experimentar:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=segundo+texto --data TextBox1=primeiro+texto
..............................
<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>
.....................
Verifica-se que nenhum evento [TextChanged] foi processado. Além disso, os campos lançados [TextBox1] e [TextBox2] não recuperam os valores que lhes foram atribuídos. Na verdade, tudo acontece como se tivéssemos executado um GET. Tudo volta ao normal se o campo [__EVENTTARGET] estiver presente nos campos lançados, mesmo que não tenha qualquer valor:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=segundo+texto --data TextBox1=primeiro+texto
.......
<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] permite também gerar as balizas HTML <input type="password"..> e <textarea>..</textarea>, c.a.d. Estas etiquetas correspondem, respetivamente, a um campo de introdução de dados protegido e a um campo de introdução de dados multilinha. Esta geração é controlada pela propriedade [TextMode] do componente [TextBox]. Possui três valores possíveis:
valor | etiqueta HTML gerada |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Analisamos a utilização destas propriedades com o seguinte exemplo [form5.aspx]:

n.º | nome | tipo | propriedades | função |
1 | Botão | botão [submit] - serve para adicionar o conteúdo de [TextBox1] ao de [TextBox2] | ||
2 | TextBox | TextMode=Password Text= | campo de introdução protegido | |
3 | TextBox | TextMode=Multilinhas Text= | agrupa as entradas efetuadas em [TextBox1] |
A propriedade [EnableViewState] da página é definida como [false]. Do lado do servidor, gerimos o evento de clique no botão [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' junta-se o conteúdo de [textBox1] ao de [TextBox2]
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
Para compreender este código, é necessário recordar o modo de processamento do POST de um formulário. As rotinas [Page_Init] e [Page_Load] são executadas em primeiro lugar. Seguem-se, em seguida, todas as rotinas de eventos armazenadas em cache. Por fim, é executado o procedimento que gere o evento que provocou o [POST], neste caso o procedimento [btnAjouter_Click]. Quando os gestores de eventos são executados, todos os componentes da página que têm um valor no POST assumem esse valor. Os restantes recuperaram o seu valor anterior, caso a sua propriedade [EnableViewState] estivesse definida como [true], ou o seu valor de design, caso a sua propriedade [EnableViewState] estivesse definida como [false]. Aqui, os valores dos campos [TextBox1] e [TextBox2] passarão a fazer parte do POST criado pelo cliente. Além disso, no código anterior, o [textbox1.text] terá como valor o valor lançado pelo cliente, o mesmo se aplicando ao [textbox2.text]. O procedimento [btnAjouter_Click] insere no campo [TextBox2] o valor enviado para [TextBox2], somado ao valor enviado para [TextBox1], somado ao marcador de fim de linha [ControlChars.CrLf], definido no espaço de nomes [Microsoft.VisualBasic]. Não é necessário importar este espaço, uma vez que o servidor web o importa por predefinição.
O código final de [form5.aspx] é o seguinte:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' junta-se o conteúdo de [textBox1] ao 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 mais acima, apresentámos a captura de ecrã de uma execução.
7.7. O componente DropDownList
A baliza <asp:DropDownList> permite inserir uma lista suspensa no código de apresentação de uma página. Criamos uma página [form6.aspx] para obter a seguinte apresentação:

n.º | nome | tipo | propriedades | função |
1 | DropDownList | 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>
De momento, a lista suspensa não contém nenhum elemento. Vamos preenchê-la no procedimento [Page_Load]. Para tal, precisamos de conhecer algumas das propriedades e métodos da classe [DropDownList]:
coleção do tipo [ListItemCollection] dos elementos da lista suspensa. Os membros desta coleção são do tipo [ListItem]. | |
número de elementos da coleção [Items] | |
elemento n.º i da lista - do tipo [ListItem] | |
para adicionar um novo elemento [ListItem] à coleção [Items] | |
para eliminar todos os elementos da coleção [Items] | |
para eliminar o elemento n.º i da coleção [Items] | |
1.º elemento [ListItem] da coleção [Items] cuja propriedade [Selected] tem o valor «verdadeiro» | |
N.º do elemento [SelectedItem] na coleção [Items] |
Os elementos da coleção [Items] da classe [DropDownList] são do tipo [ListItem]. Cada elemento [ListItem] dá origem a uma baliza HTML <option>:
Descrevemos algumas propriedades e métodos da classe [ListItem]:
construtor - cria um elemento [ListItem] com as propriedades [texte] e [value]. Um elemento ListItem(T,V) dará origem à baliza HTML <option value="V">T</option>. A classe [ListITem] permite, portanto, descrever os elementos de uma lista HTML | |
booleano. Se for verdadeiro, a opção correspondente da 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, no procedimento [Page_Load] da página, o código de preenchimento da lista suspensa [DropDownList1]:
Sub Page_Load(sender As Object, e As EventArgs)
' preenche-se o combo se for a primeira vez que se é chamado
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
Uma vez inicializado o componente [DropDownList1], a sua tradução HTML será a 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] é executada. Esta é chamada pela primeira vez por um GET e, em seguida, por um POST sempre que o utilizador seleciona um novo elemento na lista suspensa. É necessário executar sempre o código de preenchimento desta lista na [Page_Load]? A resposta depende do atributo [EnableViewState] do componente [DropDownList1]. Se este atributo estiver definido como verdadeiro, sabe-se que o estado do componente [DropDownList1] será mantido ao longo das consultas no campo oculto [__VIEWSTATE]. Este estado inclui duas coisas:
- a lista de todos os valores da lista suspensa
- o valor do elemento selecionado nessa lista
Parece, então, tentador definir a propriedade [EnableViewState] do componente [DropDownList1] como [true], para não ter de recalcular os valores a inserir na lista. O problema, porém, é que, como o procedimento [Page_Load] é executado sempre que a página [form6.aspx] é solicitada, esses valores serão, de qualquer forma, calculados. O objeto [Page], do qual [form6.aspx] é uma instância, possui um atributo [IsPostBack] com valor booleano. Se este atributo estiver definido como verdadeiro, isso significa que a página foi chamada por um POST. Se for falso, significa que a página foi chamada por um GET. No nosso sistema de comunicação 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. Conclui-se que a propriedade [IsPostBack] pode servir-nos para detetar a primeira chamada GET do cliente. Os valores da lista suspensa só são gerados durante esta primeira chamada. Nas solicitações seguintes, esses valores serão gerados pelo mecanismo do [VIEWSTATE]. Noutras situações, o conteúdo de uma lista pode variar de uma solicitação para outra e deve, por isso, ser recalculado em cada uma delas. Nesse caso, definirá-se o atributo [EnableViewState] da lista para [false], a fim de evitar um cálculo duplo desnecessário do conteúdo da lista, a menos que seja necessário conhecer os elementos selecionados anteriormente na lista, uma vez que essa informação é conservada no [VIEWSTATE].
O atributo [AutoPostBack] da lista [DropDownList1] foi definido como verdadeiro. Isto significa que o navegador enviará o formulário assim que detetar o evento «alteração do elemento selecionado» na lista suspensa. Por sua vez, o servidor irá detetar, através do [VIEWSTATE] e dos valores enviados, que o elemento selecionado no componente [DropDownList1] mudou. Em seguida, irá acionar o evento [SelectedIndexChanged] nesse componente. Iremos processá-lo com o seguinte procedimento:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' alteração da seleção
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 do tipo [ListItem] graças ao [VIEWSTATE]. Além disso, um deles, do tipo [ListItem], tem o seu atributo [Selected] definido como verdadeiro, sendo este o cujo valor foi enviado pelo navegador. É possível aceder a este elemento de várias formas:
é o primeiro elemento [ListItem] da lista cujo atributo [Selected] está definido como verdadeiro | |
corresponde à parte [texte] da baliza HTML do elemento <option value="...">texto</option> selecionado pelo utilizador | |
corresponde à parte [value] da baliza HTML do elemento <option value="...">texto</option> selecionado pelo utilizador | |
número na coleção [DropDownList1.Items] do primeiro elemento [ListItem] cujo atributo [Selected] tem o valor «verdadeiro» |
O código final de [form6.aspx] é o seguinte:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' preenche-se o menu suspenso se for a primeira vez que se é chamado
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)
' alteração da seleção
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 baliza <asp:ListBox> permite inserir uma lista no código de apresentação de uma página. Criamos [form7.aspx] para obter a seguinte apresentação:

n.º | nome | tipo | propriedades | função |
1 | TextBox | EnableViewState=false | campo de introdução | |
2 | Botão | botão [submit] que transfere para a Lista 1 o conteúdo de txtSaisie, caso este não esteja vazio. | ||
3 | ListBox | EnableViewState=true SelectionMode=Single | lista de valores com seleção única | |
4 | ListBox | EnableViewState=true SelectionMode=Multiple | lista de valores com seleção múltipla | |
5 | Botão | botão [submit] que transfere para [liste 2] o elemento selecionado de [liste 1] | ||
6 | Botão | botão [submit] que transfere para [liste 1] os elementos selecionados de [liste 2] |
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] analisada anteriormente. Encontram-se aqui todas as propriedades e métodos analisados para a classe [DropDownList], uma vez que, na verdade, pertenciam à classe [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 elemento poderá ser selecionado. Se SelectionMode=Multiple, poderão ser selecionados vários elementos. Para tal, o atributo [multiple="multiple"] será gerado na baliza <select> da lista HTML. |
Vamos tratar os eventos. O clique no botão [Ajouter] será tratado pela seguinte rotina [btnAjouter_Click]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' adição à lista 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' limpar txtSaisie
txtSaisie.text=""
End Sub
Se o texto introduzido em [txtSaisie] não for uma cadeia vazia ou em branco, é adicionado um novo elemento à lista [ListBox1]. Sabemos que temos de adicionar um elemento do tipo [ListItem]. Anteriormente, utilizámos o construtor [ListItem(T as String, V as String)] para realizar uma tarefa semelhante. Esse elemento dá origem à baliza HTML [<option value="V">T</option>]. Aqui, utilizamos o construtor [ListItem(T as String)], que gera as balizas HTML, [<option value="T">T</option>] e c.a.d. o texto [T] da opção é utilizado para formar o valor da opção. Assim que o conteúdo de [txtSaisie] for adicionado à lista [ListBox1], o campo [txTSaisie] é esvaziado.
Os cliques nos botões [Effacer] serão processados pelos seguintes procedimentos:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' limpar a lista 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' limpar a lista 2
ListBox2.Items.Clear
End Sub
Os cliques nos botões de transferência entre listas são, por sua vez, processados pelos seguintes procedimentos:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' transferência do elemento selecionado da lista 1 para a lista 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' transferência do elemento selecionado da lista 2 para a lista 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' transferências dos elementos selecionados da l1 para a l2
' há algo a fazer?
if l1.selectedindex=-1 then return
dim i as integer
' começamos pelo fim
for i=l1.items.count-1 to 0 step -1
' selecionado?
if l1.items(i).selected then
' já não está selecionado
l1.items(i).selected=false
' transferência para l2
l2.items.add(l1.items(i))
' eliminação em l1
l1.items.removeAt(i)
end if
next
end sub
Como os dois botões realizam a mesma função, ou seja, transferir elementos de uma lista para outra, é possível reduzir o processo 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
Em primeiro lugar, verifica-se se existe, de facto, pelo menos um elemento selecionado na lista l1; caso contrário, não há nada a fazer. Para tal, examina-se a propriedade [l1.selectedindex], que representa o número do primeiro elemento selecionado na lista. Se não houver nenhum, o seu valor é -1. Se houver pelo menos um elemento selecionado na l1, efetua-se a transferência para a l2. Para tal, percorre-se toda a lista de elementos da l1 e verifica-se, para cada um deles, se o seu atributo [selected] está definido como verdadeiro. Se for o caso, o seu atributo [selected] é alterado para [false], sendo depois copiado para a lista l2 e, por fim, eliminado da lista l1. Esta eliminação provoca uma renumeração dos elementos da lista l1. É por isso que a lista de elementos de l1 é percorrida ao contrário. Se a percorrermos na ordem normal e eliminarmos o elemento n.º 10, o elemento n.º 11 passa a ser o n.º 10 e o n.º 12 passa a ser o elemento n.º 11. Depois de processar o elemento n.º 10, o nosso ciclo no sentido normal irá processar o elemento n.º 11, que, de acordo com o que acabámos de explicar, é o antigo n.º 12. O elemento que tinha o n.º 11 e que agora tem o n.º 10 fica por processar. Ao percorrer os elementos da lista l1 no sentido inverso, evitamos este problema.
7.9. Os componentes CheckBox, RadioButton
As balizas <asp:RadioButton> e <asp:CheckBox> permitem inserir, respetivamente, um botão de opção e uma caixa de seleção no código de apresentação de uma página. Criamos uma página [form8.aspx] para obter a seguinte apresentação:

n.º | nome | tipo | propriedades | função |
1 | RadioButton | RadioButton1.Checked=true RadioButton1.Text=1 RadioButton2.Checked=false RadioButton2.Text=2 RadioButton3.Checked=false RadioButton3.Text=3 para os 3 botões: GroupName=radio | botões de opção | |
2 | CheckBox | Checked=false para todos CheckBoxA.Text=A CheckBoxB.Text=B CheckBoxC.Text=C | caixas de seleção | |
3 | Botão | botão [submit] | ||
4 | ListBox | lista de informações |
Para que o navegador trate os três botões de opção como mutuamente exclusivos, é necessário agrupá-los num conjunto de botões de opção. Isto é feito através do atributo [GroupName] da classe [RadioButton]. O estado da página não precisa de ser mantido nesta aplicação. Por isso, atribuímos o atributo [EnableViewState="false"] à 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>
Temos de escrever a rotina [btnEnvoyer_Click] para processar o evento [Click] neste botão. O estado de um botão de opção ou de uma caixa de seleção é determinado pelo seu atributo [Checked], um valor booleano que assume o valor «verdadeiro» se a caixa estiver marcada e «falso» caso contrário. Basta, portanto, escrever na lista [lstInfos] o valor do atributo [Checked] dos seis botões de opção e caixas de seleção. Como não há nenhuma dificuldade particular nisso, vamos inovar um pouco:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' colocamos informações na caixa de lista
for each c as control in FindControl("frmControls").controls
' o controlo é derivado de 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 vista como uma estrutura em árvore de controlos. No nosso exemplo, a nossa página contém textos e controlos de servidor. Os textos são considerados como um controlo específico denominado [LiteralControl]. Qualquer texto dá origem a este controlo, mesmo uma sequência de espaços entre dois controlos. Cada controlo possui um atributo ID que o identifica. É o atributo ID que aparece nas balizas:
Se ignorarmos os controlos [LiteralControl], a página em análise apresenta os seguintes controlos:
- [HtmlForm], que é o formulário [ID=frmControls]. Este, por sua vez, é um contentor de 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 possui as seguintes propriedades:
retorna a coleção de controlos filhos de [Control], caso existam | |
retorna o controlo identificado por ID, localizado na raiz da árvore de controlos filhos de [Control]. No exemplo acima: Page.FindControl("frmControls") designa o contentor [HtmlForm]. Para aceder ao botão de opção [RadioButton1], será necessário escrever Page.FindControl("frmControls").FindControl("RadioButton1") | |
identificador de [Control] |
Voltemos ao código do procedimento [btnEnvoyer_Click]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' inserir informações na caixa de lista
for each c as control in FindControl("frmControls").controls
' O controlo é derivado de CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
Pretendemos apresentar o estado dos botões de opção e das caixas de seleção que se encontram no formulário. Percorremos todos os controlos do mesmo. Se o controlo atual for de um tipo derivado de [CheckBox], apresentamos a sua propriedade [Checked]. Uma vez que a classe [RadioButton] deriva da classe [CheckBox], o teste é válido para ambos os tipos de controlos. A captura de ecrã apresentada acima mostra um exemplo de execução.
7.10. Os componentes CheckBoxList, RadioButtonList
Por vezes, pretende-se que o utilizador escolha entre valores que não se conhecem no momento da conceção da página. Estas opções provêm de um ficheiro de configuração, de uma base de dados, etc., e só são conhecidas no momento da execução. Existem soluções para este problema e já as encontrámos. A lista de seleção única é adequada quando o utilizador só pode fazer 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 elevado, pode ser preferível utilizar botões de rádio em vez da lista de seleção simples ou caixas de seleção em vez da lista de seleção múltipla. Isso é possível com os componentes [CheckBoxList] e [RadioButtonList].
As classes [CheckBoxList] e [RadioButtonList] derivam da mesma classe [ListControl] que as classes [DropDownList] e [ListBox] analisadas anteriormente. Por isso, vamos encontrar algumas das propriedades e métodos já abordados para estas classes, que, na verdade, pertenciam à classe [ListControl].
coleção do tipo [ListItemCollection] dos elementos da lista suspensa. Os membros desta coleção são do tipo [ListItem]. | |
número de elementos da coleção [Items] | |
elemento n.º i da lista - do tipo [ListItem] | |
para adicionar um novo elemento [ListItem] à coleção [Items] | |
para eliminar todos os elementos da coleção [Items] | |
para eliminar o elemento n.º i da coleção [Items] | |
1.º elemento [ListItem] da coleção [Items] cuja propriedade [Selected] tem o valor «verdadeiro» | |
número 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] dará origem a uma baliza diferente, consoante se trate de um objeto [CheckBoxList] ou [RadioButtonList]:
Ou
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) dará origem à tag HTML <input type="checkbox" value="V">T ou <input type="radio" value="V">T, consoante o caso. | |
booleano. Se for verdadeiro, a opção correspondente da 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 | RadioButtonList | EnableViewState=true RepeatDirection=horizontal | lista de botões de opção | |
2 | CheckBoxList | EnableViewState=true RepeatDirection=horizontal | lista de caixas de seleção | |
3 | Botão | botão [submit] que apresenta em [4] a lista dos elementos selecionados nas duas listas | ||
4 | ListBox | EnableViewState=false | lista de valores |
O código de apresentação 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 de controlo é o seguinte:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' preenche-se as listas se for a primeira vez que o controlo é chamado
if not IsPostBack then
' textos para a lista RadioButton
dim textesRadio() as String = {"1","2","3","4"}
' textos para a lista CheckBox
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' preenchimento da lista de opções de selecção
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' seleção do elemento n.º 1
RadioButtonList1.SelectedIndex=1
' preenchimento da lista de caixas de seleção
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)
' insere informações na caixa de lista lstinfos
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' exibe os valores dos elementos selecionados da l1
' há algo a fazer?
if l1.selectedindex=-1 then return
dim i as integer
' começamos pelo fim
for i= 0 to l1.items.count-1
' selecionado?
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 isso aconteça sempre, utiliza-se a propriedade [IsPostBack] para que a inicialização ocorra apenas na primeira vez. Nas vezes seguintes, as listas serão regeneradas automaticamente pelo mecanismo do [VIEWSTATE]. Assim que a página for apresentada, o utilizador assinala algumas caixas de seleção e utiliza o botão [Envoyer]. Os valores do formulário são então enviados para o próprio formulário. Após a execução do [Page_Load], é executado o procedimento [btnEnvoyer_Click]. Este recorre ao procedimento [affiche] para preencher a lista [lstInfos]. Este recebe como parâmetro um objeto do tipo [ListControl], o que permite enviar-lhe, indistintamente, 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 as diferentes consultas.
7.11. Os componentes Panel, LinkButton
A baliza <asp:panel> permite inserir um contentor de controlos numa página. A vantagem do contentor é que algumas das suas propriedades se aplicam a todos os controlos que contém. É o caso da sua propriedade [Visible]. Esta propriedade existe para todos os controlos de 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 contém não são apresentados. Isto pode ser mais simples do que gerir a propriedade [Visible] de cada um dos controlos do contentor.
A baliza <asp:LinkButton> permite inserir um link no código de apresentação de uma página. Tem uma função semelhante à do botão [Button]. Na verdade, provoca um POST do lado do cliente através de uma função JavaScript que lhe está associada. Criamos uma página [form9.aspx] para obter a seguinte apresentação:

n.º | nome | tipo | propriedades | função |
1 | Painel | EnableViewState=true | recipiente de controlos | |
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, surge um novo link:

n.º | nome | tipo | propriedades | função |
4 | LinkButton | EnableViewState=false | ligação para apresentar o contentor |
O código de apresentação 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-se que este código inicializa a lista [ListBox1] com três valores. Os gestores de eventos [Clic] nas duas ligações 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)
' exibe o contentor 1
panel1.Visible=true
' alterar os links
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' oculta o contentor 1
panel1.Visible=false
' altera os links
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
Utilizaremos o procedimento [Page_Load] para inicializar o formulário. Faremos isso na primeira consulta (IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' pela primeira vez
if not IsPostBack then
' mostra-se o contentor
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. Para continuar...
Os parágrafos anteriores apresentaram vários componentes do servidor. Em cada um deles, foram apresentadas apenas algumas das suas propriedades. Para aprofundar o estudo destes componentes, o leitor pode proceder de várias formas:
- descobrir as propriedades de um componente com um IDE, como o WebMatrix. Este, de facto, apresenta as principais propriedades dos componentes utilizados num formulário
- consultar a documentação de .NET para descobrir todas as classes correspondentes a cada um dos componentes de servidor. Este é o método a preferir para um domínio total do componente. Nela, descobrirá-se a árvore de classes que conduz aos componentes, as propriedades, métodos, construtores e eventos de cada uma delas. Além disso, a documentação fornece, por vezes, exemplos.
Neste capítulo, utilizámos a técnica «tudo num só» [WebMatrix], c.a.d, na qual 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 o chamado [codebehind], utilizado anteriormente, que coloca estes dois códigos em dois ficheiros separados. Recorde-se que a vantagem desta separação reside no facto de o código de controlo poder ser compilado sem ser necessário executar a aplicação web. Além disso, os nossos exemplos — como explicámos logo no início do capítulo — tinham um perfil muito específico: consistiam numa única página que era um formulário trocado entre o cliente e o servidor em ciclos sucessivos de pedido-resposta, sendo a primeira solicitação do cliente um GET e as seguintes um POST.
7.13. Componentes do servidor e controlador de aplicações
Nos capítulos anteriores, criámos várias aplicações web. Todas elas foram construídas de acordo com a arquitetura MVC (Modelo-Vista-Controlador), que divide a aplicação em blocos bem distintos e facilita a sua manutenção. Na altura, construíamos as nossas interfaces de utilizador com tags HTML padrão. Com o que acabámos de ver, é natural que agora queiramos utilizar componentes de servidor. Retomemos um problema já estudado em pormenor, que era o cálculo de um imposto. A sua arquitetura MVC era a seguinte:

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

O utilizador preenche o formulário:

e utiliza o botão [Calculer] para obter a seguinte resposta:

Numa aplicação MVC, qualquer pedido tem de passar pelo controlador, neste caso o [main.aspx]. Isto significa que, quando o formulário [formulaire.aspx] for preenchido pelo utilizador, deve ser enviado para [main.aspx] e não para [formulaire.aspx]. Isto simplesmente não é possível se construirmos a interface de utilizador [formulaire.aspx] com componentes de servidor ASP. Para percebermos isso, vamos construir 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>
Deve-se notar o atributo [action="main.aspx"] da baliza <form..>. Vamos executar esta aplicação. A página de apresentação apresenta apenas um botão:

Vejamos 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>
Vemos que o POST do formulário tem como destino o próprio formulário [action="formtest.aspx"], quando, no [formtest.aspx], tínhamos escrito a baliza HTML do servidor:
O atributo [runat="server"] da baliza <form> é-nos imposto pela utilização dos componentes do servidor. Ocorre um erro de compilação se não inserirmos este atributo. Quando o inserimos, o atributo [action] da baliza <form> é ignorado. O servidor gera sempre um atributo [action] que aponta para o próprio formulário. Conclui-se que, numa aplicação MVC, não é possível utilizar formulários criados com a baliza <form ... runat="server">. No entanto, esta baliza é indispensável para todos os componentes de servidor ASP que recolhem entradas do utilizador. É o mesmo que dizer que não é possível utilizar formulários do servidor ASP numa aplicação MVC. Trata-se de uma grande descoberta. Com efeito, um dos pontos fortes do marketing do ASP.NET é que é possível construir uma aplicação web tal como uma aplicação Windows. Isto é verdade se a nossa aplicação não seguir a arquitetura MVC, mas é ainda mais verdadeiro se a seguir. Ora, a arquitetura MVC parece ser um conceito fundamental do desenvolvimento web atual, que parece difícil de ignorar.
É possível utilizar a arquitetura MVC em conjunto com formulários com componentes ASP para aplicações com poucas vistas diferentes, graças ao seguinte artifício:
- a aplicação consiste numa única página que funciona como controlador
- as vistas traduzem-se nessa página em diferentes contentores, um contentor por vista. Para apresentar uma vista, torna-se visível o respetivo contentor e ocultam-se os restantes
Esta é uma solução elegante que vamos 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 é apresentado ao cliente. Este preenche-o e valida-o através do código [Envoyer]. O servidor devolve-lhe uma página que apresenta uma lista dos valores introduzidos (captura de ecrã à direita). Um link permite ao utilizador regressar ao formulário. Este volta a encontrá-lo tal como o validou. O código de apresentação de [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 e [panelinfo] para a vista de informações. A lista de componentes do contentor [panelForm] é a seguinte:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | vista do formulário | |
RadioButton | EnableViewState=true GroupName=rdmarie | botões de opção | |
CheckBox | EnableViewState=true | caixas de seleção | |
TextBox | EnableViewState=true | campo de introdução de dados | |
TextBox | EnableViewState=true | campo de introdução protegido | |
TextBox | EnableViewState=true | campo de introdução de texto com várias linhas | |
DropDownList | EnableViewState=true | lista suspensa | |
ListBox | EnableViewState=true SelectionMode=Single | lista de seleção única | |
Botão | EnableViewState=false | desmarca todos os elementos de lstSimple | |
ListBox | EnableViewState=true SelectionMode=Multiple | lista de seleção múltipla | |
Botão | EnableViewState=false | desmarca todos os elementos de lstMultiple | |
Rótulo | EnableViewState=true Visível=false | campo oculto | |
HTML padrão | exibe um alerta | ||
Botão | EnableViewState=false | botão [submit] do formulário | |
HTML padrão | botão [reset] do formulário |
O papel do [VIEWSTATE] para os componentes é aqui importante. Todos os componentes, exceto os botões, devem ter a propriedade [EnableViewState=true]. Para compreender o motivo, é necessário recordar o funcionamento da aplicação. Suponhamos que o campo [txtSaisie] tenha a propriedade [EnableViewState=false]:
- o cliente solicita pela primeira vez a página [form10.aspx]. Obtém a vista do formulário
- preenche-o e envia-o através do botão [Envoyer]. Os campos de introdução de dados são então enviados e o servidor atribui aos componentes do servidor o valor enviado ou o seu estado [VIEWSTATE], caso tivessem um. Assim, ao campo [txtSaisie] é atribuído o valor introduzido pelo utilizador. Além disso, nesta etapa, o seu estado [VIEWSTATE] não tem qualquer utilidade. Como resultado da operação, é enviada a vista [informations] — na verdade, continua a ser a página [form10.aspx], mas com um contentor diferente apresentado.
- O utilizador consulta esta nova vista e utiliza o link [Retour au formulaire] para regressar a esta. É então efetuada uma transição de POST para [form10.aspx]. Nesse momento, existe, no máximo, um valor enviado: o valor selecionado pelo utilizador na lista de informações, informação essa que não é posteriormente utilizada. De qualquer forma, não há nenhum campo [txtSaisie] enviado.
- O servidor recebe o POST e atribui aos componentes do servidor o valor enviado ou o seu estado [VIEWSTATE], caso tivessem algum. Neste caso, o [txtNom] não tem nenhum valor enviado. Se o seu atributo [EnableViewState] for [false], ser-lhe-á atribuída a cadeia vazia. Como se pretende que tenha o valor introduzido pelo utilizador, é necessário que tenha a propriedade [EnableViewState=true].
O contentor [panelinfo] possui os seguintes controlos:
nome | tipo | propriedades | função |
Painel | EnableViewState=false | vista de informações | |
ListBox | 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 é convertido para o código HTML, pois possui a propriedade [Visible=false]. No entanto, como possui a propriedade [EnableViewState=true], o seu valor será, ainda assim, conservado no campo oculto [__VIEWSATE]. Assim, será possível recuperá-lo, como os testes irão demonstrar.
Resta-nos escrever os gestores de eventos. Em [Page_Load], iremos inicializar o formulário:
Sub page_Load(sender As Object, e As EventArgs)
' na primeira vez, inicializam-se os elementos
' nas vezes seguintes, estes recuperam os seus valores através do VIEWSTATE
if IsPostBack then return
' inicialização do formulário
' o painel de informações não é apresentado
panelinfo.visible=false
' painel de formulário exibido
panelform.visible=true
' botões de opção
rdNon.Checked=true
' caixas de seleção
chk2.Checked=true
' campo de introdução
txtSaisie.Text="qqs mots"
' campo de palavra-passe
txtMdp.Text="ceciestsecret"
' caixa de entrada
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' menu suspenso
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' lista de seleção única
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' lista de seleção múltipla
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
' campo oculto
lblSecret.Text="secret"
End Sub
Ao clicar nos botões [lstRazSimple] e [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' limpar lista simples
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' limpar lista de seleção múltipla
lstMultiple.SelectedIndex=-1
End Sub
Ao clicar no botão [Envoyer]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' o painel de informações é exibido e o painel do formulário é ocultado
panelinfo.Visible=true
panelform.visible=false
' recuperam-se os valores enviados e colocam-se em lstInfos
' botões de opção
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' caixas de seleção
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)
' campo de introdução
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' palavra-passe
affiche("mot de passe : " + txtMdp.Text.Trim)
' caixa de texto
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))
' menu suspenso
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' lista simples
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' lista múltipla
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' campo oculto
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' exibe mensagem em lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' percorre os elementos da lista
' para encontrar os que estão selecionados
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 na ligação [Retour vers le formulaire ]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' exibe-se o formulário e oculta-se o painel de informações
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. Exemplo 2
Retomamos aqui uma aplicação já abordada com formulários HTML padrão. A aplicação permite realizar simulações de cálculos de impostos. Baseia-se numa classe [impot] que não iremos abordar aqui. Esta classe necessita de dados que encontra numa fonte de dados OLEDB. Para o exemplo, será utilizada uma fonte 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] sob a forma de contentores. Assim, esta aplicação tem uma única página [main.aspx].
7.14.2.2. As vistas da aplicação web
A vista [formulaire] é o formulário de introdução de informações que permite o cálculo do imposto de um utilizador:

O utilizador preenche o formulário:

Utiliza o botão [Envoyer] para solicitar o cálculo do seu imposto. Obtém a seguinte vista [simulations]:

Volta ao formulário através do link acima. Encontra-o tal como o tinha preenchido. Pode cometer erros de introdução de dados:

Estes erros são-lhe assinalados pela vista [erreurs]:

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

O utilizador obtém então a vista [simulations] com mais uma simulação:

Por fim, se a fonte de dados não estiver disponível, isso é indicado ao utilizador na vista [erreurs]:

7.14.2.3. O código de apresentação da aplicação
Recorde-se que a página [main.aspx] reúne todas as vistas. Trata-se de um único formulário com três contentores:
- [panelform] para a vista [formulaire]
- [panelerreurs] para a vista [erreurs]
- [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 de [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>
Delimitámos os três contentores. Note-se que todos eles se encontram dentro da baliza <form runat="server">. Isto é obrigatório, pois, para podermos tirar partido das vantagens dos componentes de servidor, estes têm de ser colocados dentro dessa baliza. O ponto importante a reter é que temos aqui um único formulário que será trocado entre o cliente e o servidor web. Estamos, portanto, na configuração utilizada ao longo de todo este capítulo sobre os componentes de servidor. Vamos detalhar os componentes de cada contentor:
Contentor [panelform]:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | vista do formulário | |
RadioButton | EnableViewState=true GroupName=rdmarie | botões de opção | |
TextBox | EnableViewState=true | número de filhos | |
TextBox | EnableViewState=true | salário anual | |
Botão | Botão [submit] do formulário — inicia o cálculo do imposto | ||
Botão | Botão [submit] do formulário - limpa o formulário |
Contentor [panelerreurs]:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | visualização de erros | |
LinkButton | EnableViewState=true | ligação para o formulário | |
Literal | código HTML da lista de erros |
Contentor [panelsimulations]:
nome | tipo | propriedades | função |
Painel | EnableViewState=true | vista de simulações | |
LinkButton | EnableViewState=true | ligação para o formulário | |
Literal | código HTML da 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 encontra-se distribuído pelos ficheiros [global.asax.vb] e [main.aspx.vb]. O ficheiro [global.asax] está 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)
' cria-se um objeto de importação
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' insere-se o objeto na aplicação
Application("objImpot") = objImpot
' sem erros
Application("erreur") = False
Catch ex As Exception
'ocorreu um erro, que é registado na aplicação
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' início da sessão — cria-se uma lista de simulações vazia
Session.Item("simulations") = New ArrayList
End Sub
End Class
Quando a aplicação é iniciada (1.ª solicitação feita à aplicação), é executado o procedimento [Application_Start]. Este procura criar um objeto do tipo [impot], obtendo os seus dados de uma fonte OLEDB. Recomenda-se ao leitor que consulte o capítulo 5, onde esta classe foi definida, caso se tenha esquecido dela. 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 consultas posteriores saibam que esta não conseguiu ser inicializada corretamente. Se a criação decorrer sem problemas, o objeto [impot] criado também é armazenado na aplicação. Será utilizado por todas as consultas de cálculo de impostos. Quando um cliente efetua a sua primeira consulta, é criada uma sessão para ele pelo procedimento [Application_Start]. Esta sessão destina-se a armazenar as diferentes simulações de cálculo de impostos que ele irá realizar. Estas serão armazenadas num objeto [ArrayList] associado à chave de sessão «simulações». Quando a sessão é iniciada, 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 [chaineConnexion] identifica a cadeia de ligação à fonte OLEDB. A outra parte 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
' variáveis locais
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
' em primeiro lugar, verifica-se o estado da aplicação
If CType(Application("erreur"), Boolean) Then
' a aplicação não conseguiu inicializar-se
' exibe-se a vista de erros
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' sem erros — na primeira solicitação, apresentamos o formulário
If Not IsPostBack Then afficheFormulaire()
End Sub
Recorde-se que, quando o procedimento [Page_Load] é executado num cliente POST, todos os componentes do formulário têm um valor: ou o valor enviado pelo cliente, caso exista, ou o valor anterior do componente, graças ao [VIEWSTATE]. Neste formulário, todos os componentes têm a propriedade [EnableViewState=true]. Antes de começarmos a processar o pedido, certificamo-nos de que a aplicação conseguiu inicializar-se corretamente. Se não for esse o caso, exibimos a vista [erreurs] com o procedimento [afficheErreurs]. Se for a primeira solicitação (IsPostBack=false), exibimos a vista [formulaire] com [afficheFormulaire].
O procedimento que apresenta a vista [erreurs] é o seguinte:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' exibe o contentor de erros
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
' os restantes contentores estão ocultos
panelform.Visible = False
panelsimulations.Visible = False
End Sub
O procedimento tem dois parâmetros:
- uma lista de mensagens de erro em [erreurs]
- um texto de ligação em [lien]
O código HTML a gerar para a lista de erros é colocado no literal [erreursHTML]. O texto do link é, por sua vez, colocado na propriedade [Text] do objeto [LinkButton] da vista.
O procedimento que apresenta a vista [formulaire] é o seguinte:
Private Sub afficheFormulaire()
' exibe o formulário
panelform.Visible = True
' os restantes contentores estão ocultos
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
Este procedimento limita-se a tornar visível o contentor [panelform]. Os componentes são apresentados com o seu valor lançado ou anterior (VIEWSTATE).
Quando o utilizador clica no botão [Calculer] da vista [formulaire], é efetuada uma transição de POST para [main.aspx]. O procedimento [Page_Load] é executado, seguido do procedimento [btnCalculer_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' verifica-se a validade dos dados introduzidos
Dim erreurs As ArrayList = checkData()
' se houver erros, avisem-nos
If erreurs.Count <> 0 Then
' é apresentada a página de erros
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' sem erros — calcula-se o imposto
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' o resultado é adicionado às simulações existentes
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' adiciona-se o resultado às simulações existentes
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' as simulações são colocadas na sessão e no contexto
Session.Item("simulations") = simulations
' exibe-se a página de resultados
afficheSimulations(simulations, "Retour au formulaire")
End Sub
O procedimento começa por verificar a validade dos campos do formulário através do procedimento [checkData], que devolve uma lista [ArrayList] de mensagens de erro. Se a lista não estiver vazia, é apresentada a vista [erreurs] e o procedimento é concluído. Se os dados introduzidos forem válidos, o montante do imposto é calculado através do objeto do tipo [impot], que tinha sido armazenado na aplicação no momento do seu arranque. Esta nova simulação é adicionada à lista de simulações já realizadas e armazenada na sessão.
A função [CheckData] verifica a validade dos dados. Devolve uma lista [ArrayList] de mensagens de erro, vazia se os dados forem válidos:
Private Function checkData() As ArrayList
' Inicialmente, sem erros
Dim erreurs As New ArrayList
' número de filhos
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
' salário
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
' apresenta a lista de erros
Return erreurs
End Function
Por fim, a vista [simulations] é apresentada pelo seguinte procedimento [afficheSimulations]:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' exibe a vista de simulações
panelsimulations.Visible = True
' os restantes contentores estão ocultos
panelerreurs.Visible = False
panelform.Visible = False
' conteúdo da vista «simulações»
' cada simulação é um array de 4 elementos do tipo string
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
' ligação
lnkForm2.Text = lien
End Sub
O procedimento tem dois parâmetros:
- uma lista de simulações em [simulations]
- um texto de ligação em [lien]
O código HTML a gerar para a lista de simulações é colocado no literal [simulationsHTML]. O texto do link é, por sua vez, colocado na propriedade [Text] do objeto [LinkButton] da vista.
Quando o utilizador clica no botão [Effacer] da vista [formulaire], é executado o procedimento [btnEffacer_click] (sempre após o [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' exibe o formulário vazio
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' esvazia o formulário
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. Resta-nos tratar do clique nos links das vistas [erreurs] e [simulations]:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' exibe o formulário
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' exibe o formulário
afficheFormulaire()
End Sub
Ambos os procedimentos limitam-se a apresentar a vista [formulaire]. Sabemos que os campos desta vista irão receber um valor que é, ou o valor que lhes foi enviado, ou o seu valor anterior. Como, neste caso, o POST do cliente não envia nenhum valor para os campos do formulário, estes irão recuperar o seu valor anterior. O formulário é, portanto, apresentado com os valores introduzidos pelo utilizador. Recordamos que, na versão sem componentes de servidor, tínhamos realizado nós próprios este trabalho de restauração.
7.14.2.5. Testes
Todos os ficheiros necessários para a aplicação estão colocados numa pasta <application-path>: ![]() | A pasta [bin] contém a DLL com as classes [impot], [impotsData] e [impotsOLEDB] necessárias para a aplicação: |
O leitor poderá, se assim o desejar, rever o capítulo 5, onde é explicada a forma de criar o ficheiro [impot.dll] acima referido. Feito isto, o servidor Cassini é iniciado com os parâmetros (<application-path>,/impots5). Solicita-se o URL [http://impots5/main.aspx] através de um navegador:

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

7.14.3. Exemplo 3
Mostrámos nestes dois exemplos que é possível construir aplicações web que respeitem a arquitetura MVC com componentes de servidor. O último exemplo demonstra que a solução com componentes de servidor é mais simples do que a solução que utiliza as balizas 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 servidor, desde que o facto de estes enviarem os valores do formulário para si próprios não constitua um problema. Este é frequentemente o caso das aplicações com menu. Vejamos o seguinte exemplo:

Reunimos numa mesma página links para as aplicações que criámos até agora. Este tipo de aplicação adapta-se bem a uma arquitetura MVC. Simplesmente, já não há um, mas sim vários controladores.

O controlador [main.aspx] desempenha o papel de controlador principal. É ele que é chamado pelos links da página inicial da aplicação. Este controlador poderá realizar operações comuns a todas as ações possíveis e, em seguida, executará a ação específica associada ao link utilizado. Passará então o controlo para um dos controladores secundários, aquele encarregado de executar a ação. A partir desse momento, as interações ocorrem entre o cliente e esse controlador específico. Já não se passa pelo controlador principal [main.aspx]. Portanto, já não estamos no contexto MVC com um controlador único que filtra todas as solicitações. Cada um dos controladores acima referidos pode apresentar várias vistas através do mecanismo de contentores dentro de uma única página, tal como já apresentámos.
O facto de já não termos um controlador único que seleciona as vistas a enviar ao cliente apresenta algumas desvantagens. Tomemos o exemplo da gestão de erros. Cada uma das ações expostas pela aplicação pode ter de apresentar uma vista de erros. Cada controlador [applix.aspx] terá a sua própria vista [erreurs], porque esta é simplesmente um contentor específico da página do controlador. Não há forma de ter uma vista [erreurs] única que fosse utilizada por todas as aplicações individuais. Com efeito, essa vista apresenta geralmente um link de retorno para o formulário com erros, e este deve ser restaurado no estado em que foi validado, para permitir que o utilizador corrija os seus erros. Esta restauração é feita através do mecanismo do [VIEWSTATE], que não funciona entre controladores diferentes. Se as aplicações forem desenvolvidas por pessoas diferentes, corre-se o risco de ter páginas de erro com um aspeto diferente consoante a ação escolhida pelo utilizador, o que prejudica a homogeneidade da aplicação global. Veremos um pouco mais adiante que o ASP.NET oferece uma solução para este problema específico de visualização partilhada. Esta solução pode ser implementada num novo componente de servidor que nós próprios iremos construir. Basta utilizar este componente nas diferentes aplicações para garantir a homogeneidade da aplicação global. Mais difícil de gerir é o problema da ordem das ações. Quando todas as solicitações passam por um único controlador, este pode verificar se a ação solicitada é compatível com a anterior. Este código de controlo encontra-se num único local. Aqui, será necessário distribuí-lo pelos diferentes controladores, o que complica a manutenção da aplicação global.
Voltemos à nossa aplicação acima referida. A página inicial desta é uma página clássica HTML:
<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], inserida 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á inserida no banner [contents]. Trata-se de um conjunto de links:
![]() | |
Todos os diferentes links apontam para o controlador principal [main.aspx], com um parâmetro [action] que indica a ação a realizar. Solicita-se que o destino dos links seja apresentado no quadro [main] (target="main").
A primeira página apresentada no quadro [main] é a [main.htm]:
|
O controlador principal [main.aspx, main.aspx.vb] é o seguinte:
[main.aspx]
[main.aspx.vb]
Public Class main
Herda de System.Web.UI.Page
Sub Privada Page_Load(ByVal remetente As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera-se a ação a realizar
Dim ação As String
If Request.QueryString("action") Is Nothing Then
action = "label"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' executa-se a ação
Select Case ação
Case "label"
Server.Transfer("form2.aspx")
Case "button"
Server.Transfer("form3.aspx")
Case "textbox1"
Server.Transfer("form4.aspx")
Case "textbox2"
Server.Transfer("form5.aspx")
Caso "dropdownlist"
Server.Transfer("form6.aspx")
Caixa «listbox»
Server.Transfer("form7.aspx")
Caixa de seleção "casesacocher"
Server.Transfer("form8.aspx")
Caso "lista de caixas de seleção"
Server.Transfer("form8b.aspx")
Caixa «painel»
Server.Transfer("form9.aspx")
Caso Else
Server.Transfer("form2.aspx")
End Select
End Sub
End Class
O nosso controlador é simples. Dependendo do valor do parâmetro [action], ele redireciona o processamento do pedido para a página adequada. Não acrescenta qualquer valor acrescentado em relação a uma página HTML com links. No entanto, bastaria adicionar uma página de autenticação para perceber 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 efetuada.




