Skip to content

2. Uma breve introdução ao ASP.NET

Propomos aqui apresentar, com a ajuda de alguns exemplos, os conceitos de ASP.NET que nos serão úteis no restante do documento. Esta introdução não permite compreender as subtilezas das interações cliente/servidor de uma aplicação web. Para tal, pode-se consultar:

Esta introdução destina-se a quem pretenda avançar rapidamente, aceitando, numa primeira fase, deixar de lado alguns pontos que podem ser importantes. A continuação do documento permite aprofundar esses pontos. Quem já conhece o ASP.NET pode passar diretamente para o parágrafo 3.

2.1. Um projeto de exemplo

2.1.1. Criação do projeto

  • No [1], cria-se um novo projeto com o Visual Web Developer
  • em [2], escolhe-se um projeto web em Visual C#
  • em [3], indica-se que se pretende criar uma aplicação web ASP.NET
  • em [4], atribui-se um nome à aplicação. Será criada uma pasta para o projeto com esse nome.
  • em [5], indica-se a pasta pai da pasta [4] do projeto
  • em [6], o projeto criado
  • [Default.aspx] é uma página web criada por predefinição. Contém as etiquetas HTML e as etiquetas ASP.NET
  • [Default.aspx.cs] contém o código de gestão dos eventos provocados pelo utilizador na página [Defaul.aspx] apresentada no seu navegador
  • [Default.aspx.designer.cs] contém a lista de componentes ASP.NET da página [Default.aspx]. Cada componente ASP.NET inserido na página [Default.aspx] dá origem à declaração desse componente em [Default.aspx.designer.cs].
  • [Web.config] é o ficheiro de configuração do projeto ASP.NET.
  • [References] é a lista dos DLL utilizados pelo projeto web. Estes DLL são bibliotecas de classes que o projeto irá utilizar. Em [7] encontra-se a lista dos DLL definidos por predefinição nas referências do projeto. A maioria é desnecessária. Se o projeto precisar de utilizar uma DLL que não conste na lista de [7], esta pode ser adicionada através de [8].

2.1.2. A página [Default.aspx]

Se o projeto for executado através de [Ctrl-F5], a página [Default.aspx] é apresentada num navegador:

  • em [1], a página URL do projeto web. O Visual Web Developer possui um servidor web integrado que é iniciado quando se solicita a execução de um projeto. Este servidor escuta numa porta aleatória, neste caso a 1490. A porta de escuta é normalmente a porta 80. Em [1], não é solicitada nenhuma página. Neste caso, é apresentada a página [Default.aspx], daí o seu nome de página por predefinição.
  • Em [2], a página [Default.aspx] está vazia.
  • No Visual Web Developer, a página [Default.aspx] [3] pode ser criada visualmente (separador [Design]) ou utilizando tags (separador [Source])
  • em [4], a página [Defaul.aspx] no modo [Design]. Esta é criada através da inserção de componentes que se encontram na caixa de ferramentas [5].

O modo [Source] [6] permite aceder ao código-fonte da página:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>
  • a linha 1 é uma diretiva ASP.NET que enumera algumas propriedades da página
    • a diretiva Page aplica-se a uma página web. Existem outras diretivas, tais como Application, WebService, ... que se aplicam a outros objetos ASP.NET
    • o atributo CodeBehind indica o ficheiro que gere os eventos da página
    • o atributo Language indica a linguagem .NET utilizada pelo ficheiro CodeBehind
    • o atributo Inherits indica o nome da classe definida no interior do ficheiro CodeBehind
    • O atributo AutoEventWireUp="true" indica que a ligação entre um evento em [Default.aspx] e o seu gestor em [Defaul.aspx.cs] é feita através do nome do evento. Assim, oevento Load na página [Default.aspx] será tratado pelo método Page_Load da classe Intro._Default, definida pelo atributo Inherits.
  • As linhas 4-14 descrevem a página [Defaul.aspx] utilizando balizas:
    • HTML clássicas, tais como a baliza <body> ou <div>
    • ASP.NET. Estas são as balizas que possuem o atributo runat="server". As balizas ASP.NET são processadas pelo servidor web antes do envio da página ao cliente. São transformadas em tags HTML. O navegador do cliente recebe, assim, uma página HTML padrão, na qual já não existem tags ASP.NET.

A página [Default.aspx] pode ser alterada diretamente a partir do seu código-fonte. Por vezes, isto é mais simples do que recorrer ao modo [Design]. Alteramos o código-fonte da seguinte forma:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>

Na linha 6, atribuímos um título à página através da baliza HTML <title>. Na linha 9, inserimos um texto no corpo (<body>) da página. Se executarmos o projeto (Ctrl-F5), obtemos o seguinte resultado no navegador:

 

2.1.3. Os ficheiros [Default.aspx.designer.cs] e [Default.aspx.cs]

O ficheiro [Default.aspx.designer.cs] declara os componentes da página [Defaul.aspx]:


//------------------------------------------------------------------------------
// <gerado automaticamente>
//      Este código foi gerado por uma ferramenta.
//      Versão do runtime: 2.0.50727.3603
//
//      As alterações feitas neste ficheiro podem causar um comportamento incorreto e serão perdidas se
//      o código for regenerado.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Intro {
    
    
    public partial class _Default {
        
        /// <summary>
        /// Controlo form1.
        /// </summary>
        /// <remarks>
        /// Campo gerado automaticamente.
        /// Para alterar, mova a declaração do campo do ficheiro de design para o ficheiro de código-por-trás.
        /// </remarks>
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
    }
}

Neste ficheiro encontra-se a lista dos componentes ASP.NET da página [Default.aspx] que possuem um identificador. Estes correspondem às balizas de [Default.aspx] que possuem o atributo runat="server" e o atributo id. Assim, o componente da linha 23 acima corresponde à baliza


  <form id="form1" runat="server">

de [Default.aspx].

O programador interage pouco com o ficheiro [Default.aspx.designer.cs]. No entanto, este ficheiro é útil para conhecer a classe de um componente específico. Assim, vemos abaixo que o componente form1 é do tipo HtmlForm. O programador pode então explorar essa classe para conhecer as suas propriedades e métodos. Os componentes da página [Default.aspx] são utilizados pela classe do ficheiro [Default.aspx.cs]:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {

    }
  }
}

Note-se que a classe definida nos ficheiros [Default.aspx.cs] e [Default.aspx.designer.cs] é a mesma (linha 10): Intro._Default. É a palavra-chave partial que permite estender a declaração de uma classe por vários ficheiros, neste caso dois.

Na linha 10, acima, vemos que a classe [_Default] estende a classe [Page] e herda os seus eventos. Um deles é o evento Load, que ocorre quando a página é carregada pelo servidor web. Na linha 12, o método Page_Load que gere o evento Load da página. É geralmente aqui que se inicializa a página antes da sua exibição no navegador do cliente. Neste caso, o método Page_Load não faz nada.

A classe associada a uma página web, neste caso a classe Intro._Default, é criada no início do pedido do cliente e destruída quando a resposta ao cliente é enviada. Por conseguinte, não pode ser utilizada para memorizar informações entre dois pedidos. Para tal, é necessário recorrer ao conceito de sessão do utilizador.

2.2. Os eventos de uma página web ASP.NET

Criamos a seguinte página [Default.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" Text="Valider" />
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server"></asp:ListBox>
  </p>
  </form>
</body>
</html>

O modo [Design] da página é o seguinte:

 

O ficheiro [Default.aspx.designer.cs] é o seguinte:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
    }
}

Nele encontram-se todos os componentes ASP.NET da página [Default.aspx] que possuem um identificador.

Alteramos o ficheiro [Default.aspx.cs] da seguinte forma:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
    }
  }
}

A classe [_Default] (linha 5) trata de três eventos:

  • o evento Init (linha 7), que ocorre quando a página é inicializada
  • o evento Load (linha 13), que ocorre quando a página é carregada pelo servidor web. O evento Init ocorre antes do evento Load.
  • o evento Click no botão ButtonValider (linha 19), que ocorre quando o utilizador clica no botão [Valider]

O tratamento de cada um destes três eventos consiste em adicionar uma mensagem ao componente Listbox, denominada ListBoxEvts. Esta mensagem apresenta a hora e o nome do evento. Cada mensagem é colocada no início da lista. Assim, as mensagens que se encontram no topo da lista são as mais recentes.

Ao executar o projeto, obtém-se a seguinte página:

Em [1], verifica-se que os eventos Page_Init e Page_Load ocorreram por esta ordem. Recorde-se que o evento mais recente encontra-se no topo da lista. Quando o navegador solicita a página [Default.aspx] diretamente através da sua URL [2], fá-lo através de um comando HTTP (Protocolo de Transferência HyperText) denominado GET. Assim que a página for carregada no navegador, o utilizador irá provocar eventos na página. Por exemplo, irá clicar no botão [Valider] [3]. Os eventos provocados pelo utilizador, assim que a página estiver carregada no navegador, desencadeiam um pedido à página [Default.aspx], mas desta vez com um comando HTTP denominado POST. Resumindo:

  • o carregamento inicial de uma página P num navegador é efetuado por uma operação HTTP GET
  • os eventos que ocorrem posteriormente na página geram, em cada ocasião, um novo pedido para a mesma página P, mas desta vez com um comando HTTP POST. É possível que uma página P saiba se foi solicitada com um comando GET ou com um comando POST, o que lhe permite comportar-se de forma diferente, se necessário, o que na maioria das vezes é o caso.

Pedido inicial de uma página ASPX: GET

  • em [1], o navegador solicita a página ASPX através de um comando HTTP GET sem parâmetros.
  • em [2], o servidor web envia-lhe, em resposta, o fluxo HTML, que é a tradução da página ASPX solicitada.

Tratamento de um evento ocorrido na página exibida pelo navegador: POST

  • em [1], durante um evento na página HTML, o navegador solicita a página ASPX já obtida através de uma operação GET, desta vez com um comando HTTP POST acompanhado de parâmetros. Estes parâmetros correspondem aos valores dos componentes que se encontram dentro da baliza <form> da página HTML apresentada pelo navegador. Estes valores são designados por «valores enviados pelo cliente». Serão utilizados pela página ASPX para processar o pedido do cliente.
  • Na página [2], o servidor web envia-lhe, em resposta, o fluxo HTML, que é a tradução da página ASPX solicitada inicialmente pela POST ou de outra página, caso tenha ocorrido uma transferência ou redirecionamento de página.

Voltemos à nossa página de exemplo:

  • em [2], a página foi obtida através de um GET.
  • em [1], vemos os dois eventos que ocorreram durante este GET

Se, acima, o utilizador clicar no botão [Valider] [3], a página [Default.aspx] será solicitada com um POST. Este POST será acompanhado de parâmetros que corresponderão aos valores de todos os componentes incluídos na tag <form> da página [Default.aspx]: os dois TextBox e [TextBoxNom, TextBoxAge], o botão [ButtonValider], a lista [ListBoxEvts]. Os valores enviados para os componentes são os seguintes:

  • TextBox: o valor introduzido
  • Button: o texto do botão, neste caso o texto «Validar»
  • Listbox: o texto da mensagem selecionada no ListBox

Em resposta ao POST, obtém-se a página [4]. Trata-se, mais uma vez, da página [Default.aspx]. Este é o comportamento normal, a menos que haja transferência ou redirecionamento da página pelos gestores de eventos da página. É possível verificar que ocorreram dois novos eventos:

  • o evento Page_Load, que ocorreu durante o carregamento da página
  • o evento ButtonValider_Click, que ocorreu devido ao clique no botão [Valider]

É possível observar que:

  • o evento Page_Init não ocorreu na operação HTTP POST, enquantose tinha ocorrido no evento HTTP GET
  • o evento Page_Load ocorre sempre, quer seja num GET ou num POST. É neste método que, geralmente, precisamos de saber se estamos perante um GET ou um POST.
  • Após a execução do POST, a página [Default.aspx] foi reenviada ao cliente com as alterações introduzidas pelos gestores de eventos. É sempre assim. Assim que os eventos de uma página P são processados, essa mesma página P é reenviada ao cliente. Existem duas formas de contornar esta regra. O último gestor de eventos executado pode
    • transferir o fluxo de execução para outra página P2.
    • redirecionar o navegador do cliente para outra página P2.

Em ambos os casos, é a página P2 que é devolvida ao navegador. Os dois métodos apresentam diferenças às quais voltaremos mais tarde.

  • O evento ButtonValider_Click ocorreu após o evento Page_Load. Por conseguinte, é este gestor que pode tomar a decisão de transferir ou redirecionar para uma página P2.
  • A lista de eventos [4] manteve os dois eventos exibidos durante o carregamento inicial GET da página [Default.aspx]. Isto é surpreendente, tendo em conta que a página [Default.aspx] foi recriada durante o POST. Deveríamos encontrar a página [Default.aspx] com os seus valores de conceção, tendo, portanto, um ListBox vazio. A execução dos gestores Page_Load e ButtonValider_Click deveria, em seguida, inserir duas mensagens. No entanto, encontram-se quatro. É o mecanismo do VIEWSTATE que explica isto. Durante o GET inicial, o servidor web envia a página [Default.aspx] com uma tag HTML <input type="hidden" ...> denominada campo oculto (linha 10 abaixo).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>
        Introduction ASP.NET
</title></head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form name="form1" method="post" action="default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTMzMTEyNDMxMg9kFgICAw9kFgICBw8QZBAVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQUKwMCZ2dkZGRW1AnTL8f/q7h2MXBLxctKD1UKfg==" />
</div>
..............................

No campo de ID «__VIEWSTATE», o servidor web codifica o valor de todos os componentes da página. Faz isso tanto no GET inicial como nos POST que se seguem. Quando surge um POST numa página P:

  • o navegador solicita a página P, enviando na sua solicitação os valores de todos os componentes que se encontram dentro da baliza <form>. Acima, pode ver-se que o componente «__VIEWSTATE» se encontra dentro da baliza <form>. O seu valor é, portanto, enviado ao servidor durante um POST.
  • A página P é instanciada e inicializada com os seus valores de construção
  • o componente «__VIEWSTATE» é utilizado para restabelecer nos componentes os valores que estes tinham quando a página P foi enviada anteriormente. É assim, por exemplo, que a lista de eventos [4] recupera as duas primeiras mensagens que tinha quando foi enviada em resposta ao GET inicial do navegador.
  • Os componentes da página P assumem então como valores os valores enviados pelo navegador. Nesse momento, o formulário da página P encontra-se no estado em que o utilizador o enviou.
  • O evento Page_Load é processado. Aqui, adiciona uma mensagem à lista de eventos [4].
  • O evento que provocou o POST é processado. Aqui, o ButtonValider_Click adiciona uma mensagem à lista de eventos [4].
  • A página P é reenviada. Os componentes têm como valor:
    • ou o valor enviado, c.a.d; ou o valor que o componente tinha no formulário quando este foi enviado para o servidor
    • ou um valor fornecido por um dos gestores de eventos.

No nosso exemplo,

  • os dois componentes TextBox recuperarão o seu valor enviado, uma vez que os gestores de eventos não os alteram
  • a lista de eventos [4] recupera o seu valor enviado, c.a.d. todos os eventos já registados na lista, mais dois novos eventos criados pelos métodos Page_Load e ButtonValider_Click.

O mecanismo do VIEWSTATE pode ser ativado ou desativado ao nível de cada componente. Vamos desativá-lo para o componente [ListBoxEvts]:

  • no [1], o VIEWSTATE do componente [ListBoxEvts] está desativado. O dos TextBox e [2] está ativado por predefinição.
  • No [3], os dois eventos devolvidos após o GET inicial
  • em [4], preencheu-se o formulário e clicou-se no botão [Valider]. Será efetuada uma transição POST para a página [Default.aspx].
  • em [6], o resultado devolvido após clicar no botão [Valider]
  • o mecanismo do VIEWSTATE ativado explica que os TextBox e [7] tenham mantido o valor registado no [4]
  • o mecanismo do VIEWSTATE desativado explica que o componente [ListBoxEvts] [8] não tenha mantido o seu conteúdo [5].

2.3. Gestão dos valores enviados

Vamos analisar aqui os valores enviados pelos dois TextBox quando o utilizador clica no botão [Valider]. A página [Default.aspx] no modo [Design] evolui da seguinte forma:

O código-fonte do elemento adicionado em [1] é o seguinte:


  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
</p>

Utilizaremos o componente [LabelPost] para apresentar os valores introduzidos nos dois TextBox e [2]. O código do gestor de eventos [Default.aspx.cs] passa a ser o seguinte:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // exibe o nome e a idade
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
    }
  }
}

Na linha 24, atualiza-se o componente LabelPost:

  • O LabelPost é do tipo [System.Web.UI.WebControls.Label] (ver Default.aspx.designer.cs). A sua propriedade Text representa o texto apresentado pelo componente.
  • TextBoxNom e TextBoxAge são do tipo [System.Web.UI.WebControls.TextBox]. A propriedade Text de um componente TextBox é o texto apresentado na área de introdução de dados.
  • O método Trim() elimina os espaços que possam preceder ou seguir uma cadeia de caracteres

Tal como explicado anteriormente, quando o método ButtonValider_Click é executado, os componentes da página têm o valor que tinham quando a página foi submetida pelo utilizador. As propriedades Text de ambos os TextBox têm, portanto, como valor os textos introduzidos pelo utilizador no navegador.

Eis um exemplo:

  • em [1], os valores enviados
  • em [2], a resposta do servidor.
  • em [3], os TextBox recuperaram o valor enviado através do mecanismo do VIEWSTATE ativado
  • em [4], as mensagens do componente ListBoxEvts provêm dos métodos Page_Init, Page_Load, ButtonValider_Click e de um VIEWSTATE inibido
  • No [5], o componente LabelPost obteve o seu valor através do método ButtonValider_Click. Conseguimos recuperar os dois valores introduzidos pelo utilizador nos dois TextBox e [1].

Como se pode ver acima, o valor enviado para a idade é a cadeia «yy», um valor inválido. Vamos adicionar à página componentes denominados validadores. Estes servem para verificar a validade dos dados enviados. Esta validade pode ser verificada em dois locais:

  • no lado do cliente. Uma opção de configuração do validador permite definir se os testes devem ou não ser realizados no navegador. Nesse caso, são efetuados por código incorporado na página HTML. Quando o utilizador submete os valores introduzidos no formulário, estes são primeiro verificados pelo código JavaScript. Se algum dos testes falhar, o envio não é efetuado. Desta forma, evita-se uma ida e volta com o servidor, tornando a página mais responsiva.
  • no servidor. Embora as verificações do lado do cliente possam ser opcionais, do lado do servidor são obrigatórias, independentemente de ter havido ou não verificação do lado do cliente. Com efeito, quando uma página recebe valores enviados, não tem como saber se estes foram verificados pelo cliente antes do envio. Do lado do servidor, o programador deve, portanto, verificar sempre a validade dos dados enviados.

A página [Default.aspx] evolui da seguinte forma:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
        </td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
        </td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click" 
    Text="Valider" CausesValidation="False"/>
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server" EnableViewState="False">
    </asp:ListBox>
  </p>
  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
  </p>
  <p>
    Eléments validés par le serveur :
    <asp:Label ID="LabelValidation" runat="server"></asp:Label>
  </p>
  <asp:Label ID="LabelErreursSaisie" runat="server" ForeColor="Red"></asp:Label>
  </form>
</body>
</html>

Foram adicionados validadores às linhas 20, 32 e 35. Na linha 58, é utilizado um componente Label para apresentar os valores enviados válidos. Na linha 60, é utilizado um componente Label para apresentar uma mensagem de erro caso existam erros de introdução de dados.

A página [Default.aspx] no modo [Design] é a seguinte:

  • Os componentes [1] e [2] são do tipo RequiredFieldValidator. Este validador verifica se um campo de introdução de dados não está vazio.
  • O componente [3] é do tipo RangeValidator. Este validador verifica se um campo de introdução de dados contém um valor compreendido entre dois limites.
  • No [4], as propriedades do validador [1].

Vamos apresentar os dois tipos de validadores através das suas tags no código da página [Default.aspx]:


          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
  • ID: o identificador do componente
  • ControlToValidate: o nome do componente cujo valor é verificado. Neste caso, pretende-se que o componente TextBoxNom não tenha um valor vazio (cadeia vazia ou sequência de espaços)
  • ErrorMessage: mensagem de erro a apresentar no validador em caso de dados inválidos.
  • EnableClientScript: valor booleano que indica se o validador deve ser executado também no lado do cliente. Este atributo tem, por predefinição, o valor True quando não é explicitamente definido como acima.
  • Display: modo de exibição do validador. Existem dois modos:
    • static (padrão): o validador ocupa espaço na página, mesmo que não exiba qualquer mensagem de erro
    • dynamic: o validador não ocupa espaço na página se não exibir nenhuma mensagem de erro.

          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
  • Tipo: o tipo dos dados verificados. Neste caso, a idade é um número inteiro.
  • MinimumValue, MaximumValue: os limites dentro dos quais o valor verificado deve situar-se

A configuração do componente que gera o POST influencia o modo de validação. Neste caso, esse componente é o botão [Valider]:


  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click"  Text="Valider" CausesValidation="True" />
  • CausesValidation: define o modo automático ou o nome das validações do lado do servidor. Este atributo tem o valor predefinido «True» se não for explicitamente mencionado. Neste caso,
    • do lado do cliente, são executados os validadores com os valores de EnableClientScript a True. O POST só ocorre se todos os validadores do lado do cliente forem bem-sucedidos.
    • Do lado do servidor, todos os validadores presentes na página são executados automaticamente antes do processamento do evento que provocou o POST. Neste caso, seriam executados antes da execução do método ButtonValider_Click. Neste método, é possível saber se todas as validações foram bem-sucedidas ou não. Page.IsValid é «True» se todas tiverem sido bem-sucedidas, «False» caso contrário. Neste último caso, é possível interromper o processamento do evento que provocou o POST. A página enviada é devolvida tal como foi introduzida. Os validadores que falharam apresentam então a sua mensagem de erro (atributo ErrorMessage).

Se CausesValidation tiver o valor False, então

  • do lado do cliente, nenhum validador é executado
  • No lado do servidor, cabe ao programador solicitar ele próprio a execução dos validadores da página. Faz-o através do método Page.Validate(). Dependendo do resultado das validações, este método define a propriedade Page.IsValid como «True» ou «False».

No [Default.aspx.cs], o código de processamento do ButtonValider_Click evolui da seguinte forma:


protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // exibe-se o nome e a idade
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // A página é válida?
      Page.Validate();
      if (!Page.IsValid)
      {
        // mensagem de erro global
        LabelErreursSaisie.Text = "Veuillez corriger les erreurs de saisie...";
        LabelErreursSaisie.Visible = true;
        return;
      }
      // oculta-se a mensagem de erro
      LabelErreursSaisie.Visible = false;
      // exibe-se o nome e a idade validados
      LabelValidation.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}

No caso de o botão [Valider] ter o seu atributo CausesValidation definido como True e os validadores terem o seu atributo EnableClientScript definido como True, o método ButtonValider_Click só é executado quando os valores enviados são válidos. Pode-se então questionar o significado do código que se encontra a partir da linha 8. É importante lembrar que é sempre possível escrever um cliente programado que envie valores não verificados para a página [Default.aspx]. Por isso, esta deve sempre repetir os testes de validade.

  • linha 8: inicia a execução de todos os validadores da página. No caso de o botão [Valider] ter o seu atributo definido entre CausesValidation e True, isto é feito automaticamente e não há necessidade de o repetir. Existe aqui uma redundância.
  • linhas 9-15: caso em que um dos validadores tenha falhado
  • linhas 16-19: caso em que todos os validadores tenham sido bem-sucedidos

Eis dois exemplos de execução:

  • em [1], um exemplo de execução no caso em que:
    • o botão [Valider] tenha a sua propriedade alterada de CausesValidation para True
    • os validadores tenham a sua propriedade alterada de EnableClientScript para True

As mensagens de erro [2] foram apresentadas pelos validadores executados do lado do cliente pelo código JavaScript da página. Não houve qualquer POST para o servidor, tal como mostra a etiqueta dos elementos enviados [3].

  • Em [4], um exemplo de execução no caso em que:
    • o botão [Valider] tenha a sua propriedade CausesValidation definida como False
    • os validadores têm a sua propriedade EnableClientScript definida como False

As mensagens de erro [5] foram apresentadas pelos validadores executados no lado do servidor. Tal como mostra [6], houve efetivamente um POST para o servidor. Em [7], a mensagem de erro apresentada pelo método [ButtonValider_Click] em caso de erros de introdução de dados.

  • no [8], um exemplo obtido com dados válidos. O [9,10] mostra que os elementos enviados foram validados. Ao realizar testes repetidos, é necessário definir a propriedade EnableViewState da etiqueta [LabelValidation] como False, para que a mensagem de validação não permaneça visível ao longo das execuções.

2.4. Gestão dos dados de âmbito da aplicação

Voltemos à arquitetura de execução de uma página ASPX:

A classe da página ASPX é instanciada no início do pedido do cliente e destruída no final do mesmo. Por isso, não pode ser utilizada para armazenar dados entre dois pedidos. Pode ser necessário armazenar dois tipos de dados:

  • dados partilhados por todos os utilizadores da aplicação web. Trata-se, geralmente, de dados de leitura única. São utilizados três ficheiros para implementar esta partilha de dados:
    • [Web.Config]: o ficheiro de configuração da aplicação
    • [Global.asax, Global.asax.cs]: permite definir uma classe, denominada classe global da aplicação, cuja duração corresponde à da aplicação, bem como gestores para determinados eventos dessa mesma aplicação.

A classe global da aplicação permite definir dados que estarão disponíveis para todos os pedidos de todos os utilizadores.

  • dados partilhados pelas solicitações de um mesmo cliente. Estes dados são armazenados num objeto denominado «Sessão». Fala-se então de «sessão do cliente» para designar a memória do cliente. Todas as solicitações de um cliente têm acesso a esta sessão. Podem armazenar e ler informações nessa sessão

Acima, mostramos os tipos de memória aos quais uma página tem acesso:

  • a memória da aplicação, que contém, na maioria das vezes, dados de leitura única e que está acessível a todos os utilizadores.
  • a memória de um utilizador específico, ou sessão, que contém dados de leitura/gravação e que está acessível às solicitações sucessivas do mesmo utilizador.
  • Embora não esteja representada acima, existe uma memória de pedido, ou contexto de pedido. O pedido de um utilizador pode ser processado por várias páginas ASPX sucessivas. O contexto do pedido permite que uma página 1 transmita informação a uma página 2.

Estamos aqui interessados nos dados de âmbito Application, aqueles que são partilhados por todos os utilizadores. A classe global da aplicação pode ser criada da seguinte forma:

  • em [1], adiciona-se um novo elemento ao projeto
  • em [2], adiciona-se a classe global da aplicação
  • em [3], mantém-se o nome predefinido [Global.asax] para o novo elemento
  • em [4], foram adicionados dois novos ficheiros ao projeto
  • em [5], é apresentada a marcação de [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
  • A baliza Application substitui a baliza Page que tínhamos para [Default.aspx]. Identifica a classe de aplicação global
  • Codebehind: define o ficheiro no qual está definida a classe de aplicação global
  • Inherits: define o nome desta classe

A classe Intro.Global gerada é a seguinte:


using System;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {

    protected void Application_Start(object sender, EventArgs e)
    {

    }

    protected void Session_Start(object sender, EventArgs e)
    {

    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {

    }

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {

    }

    protected void Application_Error(object sender, EventArgs e)
    {

    }

    protected void Session_End(object sender, EventArgs e)
    {

    }

    protected void Application_End(object sender, EventArgs e)
    {

    }
  }
}
  • linha 5: a classe global da aplicação deriva da classe HttpApplication

A classe é gerada com esqueletos de gestores de eventos da aplicação:

  • linhas 8, 38: gerem os eventos Application_Start (início da aplicação) e Application_End (fim da aplicação quando o servidor web é desligado ou quando o administrador encerra a aplicação)
  • linhas 13, 33: gerem os eventos Session_Start (início de uma nova sessão de cliente com a chegada de um novo cliente ou ao expirar uma sessão existente) e Session_End (fim de uma sessão de cliente, seja explicitamente por programação, seja implicitamente por exceder o tempo permitido para uma sessão).
  • linha 28: gere o evento Application_Error (ocorrência de uma exceção não tratada pelo código da aplicação e encaminhada para o servidor)
  • linha 18: gere o evento Application_BeginRequest (chegada de um novo pedido).
  • linha 23: gere o evento Application_AuhenticateRequest (ocorre quando um utilizador se autentica).

O método [Application_Start] é frequentemente utilizado para inicializar a aplicação a partir das informações contidas em [Web.Config]. O método gerado aquando da criação inicial de um projeto tem o seguinte aspeto:


<?xml version="1.0" encoding="utf-8"?>

<configuration>
    <configSections>
...
    </configSections>  
  
    <appSettings/>
    <connectionStrings/>
  
    <system.web>
...
    </system.web>

    <system.codedom>
....
    </system.codedom>
    
    <!-- 
        La section system.webServer est requise pour exécuter ASP.NET AJAX sur Internet
        Information Services 7.0.  Elle n'est pas nécessaire pour les versions précédentes d'IIS.
    -->
    <system.webServer>
...
    </system.webServer>

    <runtime>
....
    </runtime>

</configuration>

Para a nossa aplicação atual, este ficheiro é desnecessário. Se o eliminarmos ou renomearmos, a aplicação continua a funcionar normalmente. Vamos analisar as tags das linhas 8 e 9:

  • <appsettings> permite definir um dicionário de informações
  • <connectionStrings> permite definir cadeias de ligação a bases de dados

Consideremos o seguinte ficheiro [Web.config]:


<?xml version="1.0" encoding="utf-8"?>

<configuration>
    <configSections>
...
    </configSections>  
  
  <appSettings>
    <add key="cle1" value="valeur1"/>
    <add key="cle2" value="valeur2"/>
  </appSettings>
  <connectionStrings>
    <add connectionString="connectionString1" name="conn1"/>
  </connectionStrings>
  
    <system.web>
...

Este ficheiro pode ser utilizado pela seguinte classe global da aplicação:


using System;
using System.Configuration;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
    public static string Param2 { get; set; }
    public static string ConnString1 { get; set; }
    public static string Erreur { get; set; }

    protected void Application_Start(object sender, EventArgs e)
    {
      try
      {
        Param1 = ConfigurationManager.AppSettings["cle1"];
        Param2 = ConfigurationManager.AppSettings["cle2"];
        ConnString1 = ConfigurationManager.ConnectionStrings["conn1"].ConnectionString;
      }
      catch (Exception ex)
      {
        Erreur = string.Format("Erreur de configuration : {0}", ex.Message);
      }
    }

    protected void Session_Start(object sender, EventArgs e)
    {

    }

  }
}
  • linhas 8-11: quatro propriedades estáticas P. Como o tempo de vida da classe Global é o mesmo que o da aplicação, qualquer consulta feita à aplicação terá acesso a estas propriedades P através da sintaxe Global.P.
  • linhas 17-19: o ficheiro [Web.config] está acessível através da classe [System.Configuration.ConfigurationManager]
  • linhas 17-18: recupera os elementos da baliza <appSettings> do ficheiro [Web.config] através do atributo key.
  • linha 19: recupera os elementos da baliza <connectionStrings> do ficheiro [Web.config] através do atributo name.

Os atributos estáticos das linhas 8-11 estão acessíveis a partir de qualquer gestor de eventos das páginas ASPX carregadas. Utilizamo-los no gestor [Page_Load] da página [Default.aspx]:


    protected void Page_Load(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
      // recuperam-se as informações da classe global da aplicação
      LabelGlobal.Text = string.Format("Param1={0},Param2={1},ConnString1={2},Erreur={3}", Global.Param1, Global.Param2, Global.ConnString1, Global.Erreur);
}
  • linha 6: os quatro atributos estáticos da classe global da aplicação são utilizados para preencher um novo rótulo da página [Default.aspx]

Ao executar, obtemos o seguinte resultado:

Acima, vemos que os parâmetros de [web.config] foram recuperados corretamente. A classe global da aplicação é o local adequado para armazenar informações partilhadas por todos os utilizadores.

2.5. Gestão de dados no âmbito da sessão

Aqui, vamos analisar como memorizar informações ao longo das solicitações de um determinado utilizador:

Cada utilizador tem a sua própria memória, a que chamamos de sessão.

Vimos que a classe de aplicação global dispõe de dois gestores para gerir os eventos:

  • Session_Start: início de uma sessão
  • Session_end: fim de uma sessão

O mecanismo da sessão funciona da seguinte forma:

  • na primeira solicitação de um utilizador, o servidor web cria um token de sessão que atribui ao utilizador. Este token é uma sequência de caracteres única para cada utilizador. É enviado pelo servidor na resposta à primeira solicitação do utilizador.
  • Nas solicitações seguintes, o utilizador (o navegador da Web) inclui na sua solicitação o token de sessão que lhe foi atribuído. Assim, o servidor Web consegue reconhecê-lo.
  • Uma sessão tem um período de validade. Quando o servidor web recebe uma solicitação de um utilizador, calcula o tempo decorrido desde a solicitação anterior. Se esse tempo exceder o período de validade da sessão, é criada uma nova sessão para o utilizador. Os dados da sessão anterior são perdidos. Com o servidor web IIS (Internet Information Server) da Microsoft, as sessões têm, por predefinição, uma duração de 20 minutos. Este valor pode ser alterado pelo administrador do servidor web.
  • O servidor web sabe que se trata da primeira solicitação de um utilizador porque essa solicitação não inclui um token de sessão. É a única.

Qualquer página ASP.NET tem acesso à sessão do utilizador através da propriedade Session da página, do tipo [System.Web.SessionState.HttpSessionState]. Iremos utilizar as seguintes propriedades P e métodos M da classe HttpSessionState:

Nome
Tipo
Função
Item[String clé]
P
A sessão pode ser estruturada como um dicionário. Item[clé] é o elemento da sessão identificado por clé. Em vez de escrever [HttpSessionState].Item[clé], também se pode escrever [HttpSessionState].[clé].
Limpar
M
esvazia o dicionário da sessão
Desistir
M
encerra a sessão. A sessão deixa então de ser válida. Uma nova sessão será iniciada com o próximo pedido do utilizador.

Como exemplo de memória do utilizador, vamos contar o número de vezes que um utilizador clica no botão [Valider]. Para obter este resultado, é necessário manter um contador na sessão do utilizador.

A página [Default.aspx] evolui da seguinte forma:

A classe global da aplicação [Global.asax.cs] evolui da seguinte forma:


using System;
using System.Configuration;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
...

    protected void Application_Start(object sender, EventArgs e)
    {
...
    }

    protected void Session_Start(object sender, EventArgs e)
    {
      // contador de pedidos
      Session["nbRequêtes"] = 0;
    }

  }
}

Na linha 19, utiliza-se a sessão do utilizador para armazenar um contador de pedidos identificado pela chave «nbRequêtes». Este contador é atualizado pelo gestor [ButtonValider_Click] da página [Default.aspx]:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
....

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // regista-se o evento
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // exibe-se o nome e a idade publicados
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // número de pedidos
      Session["nbRequêtes"] = (int)Session["nbRequêtes"] + 1;
      LabelNbRequetes.Text = Session["nbRequêtes"].ToString();
      // a página é válida?
      Page.Validate();
      if (!Page.IsValid)
      {
...
      }
...
    }
  }
}
  • linha 16: o contador de pedidos é incrementado
  • linha 17: o contador é apresentado na página

Eis um exemplo de execução:

2.6. Gestão do GET / POST no carregamento de uma página

Já referimos que existem dois tipos de pedidos para uma página ASPX:

  • a solicitação inicial do navegador, efetuada com um comando HTTP GET. O servidor responde enviando a página solicitada. Vamos supor que essa página é um formulário, c.a.d, e que na página ASPX enviada existe uma tag <form runat="server"...>.
  • As solicitações seguintes são feitas pelo navegador em resposta a determinadas ações do utilizador no formulário. O navegador efetua então uma solicitação HTTP POST.

Quer se trate de uma solicitação GET ou de uma solicitação POST, o método [Page_Load] é executado. No GET, este método é normalmente utilizado para inicializar a página enviada ao navegador do cliente. Posteriormente, através do mecanismo do VIEWSTATE, a página permanece inicializada e só é alterada pelos gestores de eventos que provocam os POST. Não é necessário reinicializar a página no Page_Load. Daí a necessidade de este método saber se o pedido do cliente é um GET ou um POST.

Analisemos o exemplo seguinte. Adicionamos uma lista suspensa à página [Default.aspx]. O conteúdo desta lista será definido no gestor Page_Load da solicitação GET:

A lista suspensa é declarada em [Default.aspx.designer.cs] da seguinte forma:


        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;

Iremos utilizar os seguintes métodos M e propriedades P da classe [DropDownList]:

Nome
Tipo
Função
Itens
P
a coleção do tipo ListItemCollection dos elementos do tipo ListItem da lista suspensa
SelectedIndex
P
o índice, a partir de 0, do elemento selecionado na lista suspensa quando o formulário é enviado
SelectedItem
P
o elemento do tipo ListItem selecionado na lista suspensa quando o formulário é enviado
SelectedValue
P
o valor do tipo string do elemento do tipo ListItem selecionado na lista suspensa quando o formulário é enviado. Iremos definir em breve este conceito de valor.

A classe ListItem dos elementos de uma lista suspensa serve para gerar as balizas <option> da baliza HTML <select>:

1
2
3
4
5
<select ....>
    <option value="val1">texte1</option>
    <option value="val2">texte2</option>
....
</select>

Na baliza <option>

  • textei é o texto apresentado na lista suspensa
  • vali é o valor enviado pelo navegador se textei for o texto selecionado na lista suspensa

Cada opção pode ser gerada por um objeto LisItem criado com o construtor ListItem(string texto, string valor).

Em [Default.aspx.cs], o código do gestor [Page_Load] evolui da seguinte forma:


    protected void Page_Load(object sender, EventArgs e)
    {
      // regista-se o evento
      ...
      // recuperam-se as informações da classe global da aplicação
      ...
      // inicialização da lista suspensa de nomes apenas durante o GET inicial
      if (!IsPostBack)
      {
        for (int i = 0; i < 3; i++)
        {
          DropDownListNoms.Items.Add(new ListItem("nom"+i,i.ToString()));
        }
      }
}
  • linha 8: a classe Page tem um atributo IsPostBack do tipo booleano. Na verdade, isso significa que a solicitação do utilizador é um POST. As linhas 10 a 13 são, portanto, executadas apenas no GET inicial do cliente.
  • linha 12: adiciona-se à lista [DropDownListNoms] um elemento do tipo ListItem (string texto, string valor). O texto apresentado para o (i+1)-ésimo elemento será nomi e o valor enviado para esse elemento, caso seja selecionado, será i.

O gestor [ButtonValider_Click] é alterado para apresentar o valor enviado pela lista suspensa:


    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // regista-se o evento
...
      // exibem-se os valores enviados
      LabelPost.Text = string.Format("nom={0}, age={1}, combo={2}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNoms.SelectedValue);
      // número de pedidos
...
}

Na linha 6, o valor publicado para a lista [DropDownListNoms] é obtido através da propriedade SelectedValue da lista. Eis um exemplo de execução:

  • em [1], o conteúdo da lista suspensa após o GET inicial e imediatamente antes do primeiro POST
  • em [2], a página após o primeiro POST.
  • em [3], o valor enviado para a lista suspensa. Corresponde ao atributo value do ListItem selecionado na lista.
  • em [4], a lista suspensa. Contém os mesmos elementos que após o GET inicial. É o mecanismo do VIEWSTATE que explica isto.

Para compreender a interação entre o VIEWSTATE da lista DropDownListNoms e o teste if (! IsPostBack) do gestor Page_Load de [Default.aspx], convida-se o leitor a repetir o teste anterior com as seguintes configurações:

Caso
DropDownListNoms.EnableViewState
teste if(! IsPostBack) em Page_Load de [Default.aspx]
1
true
présent
2
false
présent
3
true
absent
4
false
absent

Os diferentes testes apresentam os seguintes resultados:

  1. este é o caso apresentado acima
  2. a lista é preenchida durante o GET inicial, mas não durante os POST que se seguem. Como o EnableViewState está incorreto, a lista fica vazia após cada POST
  3. a lista é preenchida tanto após o GET inicial como nos POST seguintes. Como o EnableViewState corresponde ao vrai, temos 3 nomes após o GET inicial, 6 nomes após o primeiro POST, 9 nomes após o segundo POST, ...
  4. A lista é preenchida tanto após o GET inicial como durante os POST que se seguem. Como o EnableViewState corresponde ao faux, a lista é preenchida com apenas 3 nomes em cada pedido, quer se trate do GET inicial ou dos POST que se seguem. Repete-se o comportamento do caso 1. Existem, portanto, duas formas de obter o mesmo resultado.

2.7. Gestão do VIEWSTATE dos elementos de uma página ASPX

Por predefinição, todos os elementos de uma página ASPX têm a sua propriedade EnableViewState definida como True. Sempre que a página ASPX é enviada para o navegador do cliente, esta contém o campo oculto __VIEWSTATE, cujo valor é uma cadeia de caracteres que codifica o conjunto de valores dos componentes cujas propriedades variam de EnableViewState a True. Para minimizar o tamanho desta cadeia, pode-se procurar reduzir o número de componentes cujas propriedades vão de EnableViewState a True.

Recorde-se como os componentes de uma página ASPX obtêm os seus valores após a execução de um POST:

  1. a página ASPX é instanciada. Os componentes são inicializados com os seus valores de conceção.
  2. o valor __VIEWSTATE enviado pelo navegador é utilizado para atribuir aos componentes o valor que tinham quando a página ASPX foi enviada ao navegador na vez anterior.
  3. Os valores enviados pelo navegador são atribuídos aos componentes
  4. os gestores de eventos são executados. Estes podem alterar o valor de determinados componentes.

A partir desta sequência, deduz-se que os componentes que:

  • têm o seu valor enviado
  • têm o seu valor alterado por um gestor de eventos

podem ter a sua propriedade alterada de EnableViewState para Faux, uma vez que o seu valor de VIEWSTATE (etapa 2) será alterado por uma das etapas 3 ou 4.

A lista dos componentes da nossa página está disponível em [Default.aspx.designer.cs]:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorAge;
        protected global::System.Web.UI.WebControls.RangeValidator RangeValidatorAge;
        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
        protected global::System.Web.UI.WebControls.Label LabelPost;
        protected global::System.Web.UI.WebControls.Label LabelValidation;
        protected global::System.Web.UI.WebControls.Label LabelErreursSaisie;
        protected global::System.Web.UI.WebControls.Label LabelGlobal;
        protected global::System.Web.UI.WebControls.Label LabelNbRequetes;
    }
}

O valor da propriedade EnableViewState destes componentes poderá ser o seguinte:

Composant
Valor lançado
EnableViewState
Pourquoi
TextBoxNom
valor introduzido no TextBox
False
o valor do componente é lançado
TextBoxAge
idem
  
RequiredFieldValidatorNom
nenhum
False
ausência de noção de valor do componente
RequiredFieldValidatorAge
idem
  
RangeValidatorAge
idem
  
LabelPost
nenhuma
False
obtém o seu valor através de um gestor de eventos
LabelValidation
idem
  
LabelErreursSaisie
idem
  
LabelGlobal
idem
  
LabelNbRequetes
idem
  
DropDownListNoms
«valor» do elemento selecionado
True
pretende-se manter o conteúdo da lista ao longo das consultas sem ter de a regenerar
ListBoxEvts
"value" do elemento selecionado
False
o conteúdo da lista é gerado por um gestor de eventos
ButtonValider
texto do botão
False
o componente mantém o seu valor de conceção

2.8. Redirecionamento de uma página para outra

Até agora, as operações GET e POST devolviam sempre a mesma página [Default.aspx]. Vamos considerar o caso em que um pedido é processado por duas páginas ASPX sucessivas, [Default.aspx] e [Page1.aspx], e em que é esta última que é devolvida ao cliente. Além disso, veremos como a página [Default.aspx] pode transmitir informações à página [Page1.aspx] através de uma memória a que chamaremos «memória da solicitação».

Criamos a página [Page1.aspx]:

  • em [1], adicionamos um novo elemento ao projeto
  • em [2], adicionamos um elemento [Web Form] denominado [Page1.aspx] [3]
  • no [4], a página adicionada
  • em [5], a página depois de construída

O código-fonte de [Page1.aspx] é o seguinte:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page1.aspx.cs" Inherits="Intro.Page1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page1</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 1</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>
  • linha 13: um rótulo que servirá para apresentar uma informação transmitida pela página [Default.aspx]
  • linha 15: um link HTML para a página [Default.aspx]. Quando o utilizador clica neste link, o navegador solicita a página [Default.aspx] com uma operação GET. A página [Default.aspx] é então carregada como se o utilizador tivesse digitado diretamente o seu URL no navegador.

A página [Default.aspx] é enriquecida com um novo componente do tipo LinkButton:

O código-fonte deste novo componente é o seguinte:


  <asp:LinkButton ID="LinkButtonToPage1" runat="server" CausesValidation="False" 
    EnableViewState="False" onclick="LinkButtonToPage1_Click">Forward vers Page1</asp:LinkButton>
  • CausesValidation="False": ao clicar na ligação, será desencadeado um POST para [Defaul.aspx]. O componente [LinkButton] comporta-se como o componente [Button]. Neste caso, não se pretende que o clique no link acione a execução dos validadores.
  • EnableViewState="False": não é necessário manter o estado do link ao longo das solicitações. Este mantém os seus valores de conceção.
  • onclick="LinkButtonToPage1_Click": nome do método que, em [Defaul.aspx.cs], gere o evento Click no componente LinkButtonToPage1.

O código do gestor LinkButtonToPage1_Click é o seguinte:


  // para a Página 1
  protected void LinkButtonToPage1_Click(object sender, EventArgs e)
  {
    // inserir informações no contexto
    Context.Items["msg1"] = "Message de Default.aspx pour Page1";
    // encaminha-se a solicitação para a Página 1
    Server.Transfer("Page1.aspx",true);
}

Na linha 7, o pedido é encaminhado para a página [Page1.aspx] através do método [Server.Transfer]. O segundo parâmetro do método, que é true, indica que é necessário passar para [Page1.aspx] toda a informação que foi enviada para [Default.aspx] durante o POST. Isto permite, por exemplo, que o [Page1.aspx] tenha acesso aos valores enviados através de uma coleção denominada Request.Form. A linha 5 utiliza o que se denomina «contexto da solicitação». O acesso a este contexto é feito através da propriedade Context da classe Page. Este contexto pode servir de memória entre as diferentes páginas que processam a mesma solicitação, neste caso [Default.aspx] e [Page1.aspx]. Para tal, utiliza-se o dicionário Items.

Quando a [Page1.aspx] é carregada pela operação Server.Transfer("Page1.aspx",true), tudo acontece como se o [Page1.aspx] tivesse sido chamado por um GET de um navegador. O gestor Page_Load do [Page1.aspx] é executado normalmente. Vamos utilizá-lo para apresentar a mensagem inserida pelo [Default.aspx] no contexto do pedido:


using System;

namespace Intro
{
  public partial class Page1 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      Label1.Text = Context.Items["msg1"] as string;
    }
  }
}

Na linha 9, a mensagem inserida pelo [Default.aspx] no contexto da consulta é apresentada no Label1.

Eis um exemplo de execução:

  • na página [Default.aspx] [1], clica-se na ligação [2], que nos leva à página Page1
  • em [3], é apresentada a página Page1
  • em [4], a mensagem criada em [Default.aspx] e apresentada por [Page1.aspx]
  • em [5], a página URL apresentada no navegador é a da página [Default.aspx]

2.9. Redirecionamento de uma página para outra

Apresentamos aqui outra técnica funcionalmente semelhante à anterior: quando o utilizador solicita a página [Default.aspx] através de uma POST, recebe como resposta outra página, a [Page2.aspx]. No método anterior, o pedido do utilizador era processado sucessivamente por duas páginas: [Default.aspx] e [Page1.aspx]. No método de redirecionamento de página que apresentamos agora, há dois pedidos distintos do navegador:

  • em [1], o navegador efetua uma solicitação POST à página [Default.aspx]. Esta processa a solicitação e envia uma resposta denominada de redirecionamento ao navegador. Esta resposta é um simples fluxo HTTP (linhas de texto) que solicita ao navegador que se redirecione para outro URL, [Page2.aspx]. A [Default.aspx] não envia o fluxo HTML nesta primeira resposta.
  • Em [2], o navegador faz uma solicitação GET à página [Page2.aspx]. Esta é então enviada como resposta ao navegador.
  • Se a página [Default.aspx] pretender transmitir informações à página [Page2.aspx], pode fazê-lo através da sessão do utilizador. Ao contrário do método anterior, o contexto da solicitação não é utilizável aqui, uma vez que existem duas solicitações distintas e, portanto, dois contextos distintos. É, portanto, necessário utilizar a sessão do utilizador para permitir a comunicação entre as páginas.

Tal como foi feito para a [Page1.aspx], adicionamos ao projeto a página [Page2.aspx]:

  • em [1], a página [Page2.aspx] foi adicionada ao projeto
  • em [2], o aspeto visual de [Page2.aspx]
  • em [3], adicionamos à página [Default.aspx] um componente LinkButton [4] que irá redirecionar o utilizador para [Page2.aspx].

O código-fonte de [Page2.aspx] é semelhante ao de [Page1.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page2.aspx.cs" Inherits="Intro.Page2" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page2</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 2</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>

No [Default.aspx], a adição do componente LinkButton gerou o seguinte código-fonte:


<asp:LinkButton ID="LinkButtonToPage2" runat="server" 
    onclick="LinkButtonToPage2_Click">Redirection vers Page2</asp:LinkButton>

É o gestor [LinkButtonToPage2_Click] que assegura o redirecionamento para [Page2.aspx]. O seu código em [Defaul.aspx.cs] é o seguinte:


    protected void LinkButtonToPage2_Click(object sender, EventArgs e)
    {
      // insere-se uma mensagem na sessão
      Session["msg2"] = "Message de [Default.aspx] pour [Page2.aspx]";
      // redireciona o cliente para [Page2.aspx]
      Response.Redirect("Page2.aspx");
}
  • linha 4: insere-se uma mensagem na sessão do utilizador
  • linha 5: o objeto Response é uma propriedade de qualquer página ASPX. Representa a resposta enviada ao cliente. Possui um método Redirect que faz com que a resposta enviada ao cliente seja uma ordem de redirecionamento HTTP.

Quando o navegador receber a ordem de redirecionamento para [Page2.aspx], irá executar um GET nessa página. Nesta página, o método [Page_Load] será executado. Iremos utilizá-lo para recuperar a mensagem inserida por [Default.aspx] na sessão e exibi-la. O código [Page2.aspx.cs] é o seguinte:


using System;

namespace Intro
{
  public partial class Page2 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      // exibe-se a mensagem inserida na sessão por [Default.aspx]
      Label1.Text = Session["msg2"] as string;
    }
  }
}

Após a execução, obtêm-se os seguintes resultados:

  • em [1], clica-se na ligação de redirecionamento de [Default.aspx]. É efetuado um POST para a página [Default.aspx]
  • em [2], o navegador foi redirecionado para [Page2.aspx]. Isto é visível no URL apresentado pelo navegador. No método anterior, esta URL era a de [Default.aspx], uma vez que a única solicitação efetuada pelo navegador foi para essa URL. Aqui, há um primeiro redirecionamento de POST para [Default.aspx] e, em seguida, sem o conhecimento do utilizador, um segundo redirecionamento de GET para [Page2.aspx].
  • No [3], verifica-se que o [Page2.aspx] recuperou corretamente a mensagem colocada pelo [Default.aspx] na sessão.

2.10. Conclusion

Apresentámos, através de alguns exemplos, os conceitos de ASP.NET que nos serão úteis no resto do documento. Esta introdução não permite compreender as subtilezas das trocas cliente/servidor de uma aplicação web. Para tal, pode-se consultar:

  • Programação ASP.NET [Développement WEB avec ASP.NET 1.1 ]