4. Os fundamentos do desenvolvimento ASP.NET
4.1. O conceito de aplicativo web ASP.NET
4.1.1. Introdução
Uma aplicação web é uma aplicação que reúne diversos documentos (HTML, código .NET, imagens, sons, etc.). Esses documentos devem estar na mesma raiz, chamada de raiz da aplicação web. A essa raiz está associado um caminho virtual do servidor web. Já abordamos o conceito de pasta virtual para o servidor web Cassini. Esse conceito também existe para o servidor web IIS. Uma diferença importante entre os dois servidores é que, em um determinado momento, o IIS pode ter qualquer número de pastas virtuais, enquanto o servidor web Cassini tem apenas uma, aquela que foi especificada no momento de sua inicialização. Isso significa que o servidor IIS pode hospedar várias aplicações web simultaneamente, enquanto o servidor Cassini hospeda apenas uma por vez. Nos exemplos anteriores, o servidor Cassini era sempre iniciado com os parâmetros (<webroot>,/aspnet), que associavam a pasta virtual /aspnet à pasta física <webroot>. Portanto, o servidor web sempre atendia à mesma aplicação web. Isso não nos impediu de escrever e testar páginas diferentes e independentes dentro dessa única aplicação web. Cada aplicação web possui recursos próprios, localizados em sua raiz física <webroot>:
- uma pasta [bin] na qual é possível colocar classes pré-compiladas
- um arquivo [global.asax] que permite inicializar a aplicação web como um todo, bem como o ambiente de execução de cada um de seus usuários
- um arquivo [web.config] que permite configurar o funcionamento da aplicação
- um arquivo [default.aspx] que funciona como porta de entrada da aplicação
- ...
Assim que uma aplicação utiliza um desses três recursos, ela precisa de um caminho físico e virtual próprios. De fato, não há motivo para que duas aplicações web diferentes sejam configuradas da mesma maneira. Todos os nossos exemplos anteriores puderam ser colocados na mesma aplicação (<webroot>,/aspnet) porque não utilizavam nenhum dos recursos mencionados acima.
Voltemos à arquitetura MVC recomendada no início deste capítulo para o desenvolvimento de um aplicativo web:

A aplicação web é composta por arquivos de classe (controlador, classes de negócio, classes de acesso a dados) e arquivos de apresentação (documentos HTML, imagens, sons, folhas de estilo, etc.). Todos esses arquivos serão colocados em uma mesma raiz, que às vezes chamaremos de <application-path>. Essa raiz será associada a um caminho virtual <application-vpath>. A associação entre esse caminho virtual e o caminho físico é feita por meio da configuração do servidor web. Vimos que, para o servidor Cassini, essa associação ocorre ao iniciar o servidor. Por exemplo, em uma janela do DOS, iniciaria-se o Cassini com:
Na pasta <application-path>, encontraremos, de acordo com nossas necessidades:
- a pasta [bin] para armazenar classes pré-compiladas (DLL)
- o arquivo [global.asax], quando precisarmos realizar inicializações, seja durante a inicialização do aplicativo, seja durante a inicialização de uma sessão do usuário
- o arquivo [web.config], quando precisarmos configurar o aplicativo
- o arquivo [default.aspx] quando precisarmos de uma página padrão no aplicativo
Para respeitar esse conceito de aplicativo web, os exemplos a seguir serão todos colocados em uma pasta <application-path> específica do aplicativo, à qual será associada uma pasta virtual <application-vpath>, sendo que o servidor Cassini será iniciado de forma a vincular esses dois parâmetros.
4.1.2. Configurar um aplicativo web
Se <application-path> for a raiz de uma aplicação ASP.NET, é possível usar o arquivo <application-path>\web.config para configurá-la. Esse arquivo está no formato XML. Veja um exemplo:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
É importante observar que as tags XML diferenciam maiúsculas de minúsculas. Todas as informações de configuração devem estar entre as tags <configuration> e </configuration>. Existem várias seções de configuração que podem ser utilizadas. Apresentamos aqui apenas uma delas, a seção <appSettings>, que permite inicializar dados com a tag <add>. A sintaxe dessa tag é a seguinte:
Quando o servidor web inicia um aplicativo, ele verifica se em <application-path> existe um arquivo chamado web.config. Se houver, ele o lê e armazena suas informações em um objeto do tipo [ConfigurationSettings], que estará disponível para todas as páginas do aplicativo enquanto este estiver ativo. A classe [ConfigurationSettings] possui um método estático [AppSettings]:

Para obter o valor de uma chave C do arquivo de configuração, escreve-se ConfigurationSettings.AppSettings("C"). Obtém-se uma sequência de caracteres. Para utilizar o arquivo de configuração anterior, vamos criar uma página [default.aspx]. O código VB do arquivo [default.aspx.vb] será o seguinte:
Imports System.Configuration
Public Class _default
Inherits System.Web.UI.Page
Protected nom As String
Protected age As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'recuperam-se as informações de configuração
nom = ConfigurationSettings.AppSettings("nom")
age = ConfigurationSettings.AppSettings("age")
End Sub
End Class
Percebe-se que, ao carregar a página, os valores dos parâmetros de configuração [nom] e [age] são recuperados. Eles serão exibidos pelo código de apresentação de [default.aspx]:
<%@ Page src="default.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="_default" %>
<html>
<head>
<title>Configuration</title>
</head>
<body>
Nom :
<% =nom %><br/>
Age :
<% =age %><br/>
</body>
</html>
Para o teste, colocamos os arquivos [web.config], [default.aspx] e [default.aspx.vb] na mesma pasta:
D:\data\devel\aspnet\poly\chap2\config1>dir
30/03/2004 15:06 418 default.aspx.vb
30/03/2004 14:57 236 default.aspx
30/03/2004 14:53 186 web.config
Seja <application-path> a pasta onde estão os três arquivos do aplicativo. O servidor Cassini é iniciado com os parâmetros (<application-path>,/aspnet/config1). Solicitamos os arquivos URL e [http://localhost/aspnet/config1]. Como [config1] é uma pasta, o servidor web irá procurar um arquivo [default.aspx] dentro dela e exibi-lo caso o encontre. Neste caso, ele o encontrará:

4.1.3. Aplicativo, Sessão, Contexto
4.1.3.1. O arquivo global.asax
O código do arquivo [global.asax] é sempre executado antes que a página solicitada pela requisição atual seja carregada. Ele deve estar localizado na raiz <application-path> do aplicativo. Se existir, o arquivo [global.asax] é utilizado em diversos momentos pelo servidor web:
- quando a aplicação web é iniciada ou encerrada
- quando uma sessão de usuário é iniciada ou encerrada
- quando uma solicitação do usuário é iniciada
Assim como nas páginas .aspx, o arquivo [global.asax] pode ser escrito de diferentes maneiras e, em particular, separando o código VB em uma classe controladora e o código de apresentação. Essa é a escolha padrão da ferramenta Visual Studio e faremos o mesmo aqui. Normalmente, não há apresentação a ser feita, pois essa função cabe às páginas .aspx. O conteúdo do arquivo [global.asax] fica, então, reduzido a uma diretiva que faz referência ao arquivo contendo o código do controlador:
<%@ Application src="Global.asax.vb" Inherits="Global" %>
Observe-se que a diretiva não é mais [Page], mas sim [Application]. O código do controlador [global.asax.vb] associado e gerado pela ferramenta Visual Studio é o seguinte:
Imports System
Imports System.Web
Imports System.Web.SessionState
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando o aplicativo é iniciado
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando a sessão é iniciada
End Sub
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' É acionado no início de cada solicitação
End Sub
Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
' É acionado durante uma tentativa de autenticação do usuário
End Sub
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando ocorre um erro
End Sub
Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando a sessão é encerrada
End Sub
Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando o aplicativo é encerrado
End Sub
End Class
Observe-se que a classe do controlador deriva da classe [HttpApplication]. Ao longo do ciclo de vida de um aplicativo, ocorrem vários eventos importantes. Esses eventos são gerenciados por procedimentos cuja estrutura básica é apresentada acima.
- [Application_Start]: vale lembrar que uma aplicação web está “confinada” a um caminho virtual. A aplicação é iniciada assim que uma página localizada nesse caminho virtual é solicitada por um cliente. O procedimento [Application_Start] é então executado. Essa será a única vez. Nesse procedimento, realizaremos toda a inicialização necessária para a aplicação, como, por exemplo, criar objetos cuja duração de vida seja igual à da aplicação.
- [Application-End]: é executado quando a aplicação é encerrada. A cada aplicação está associado um tempo de inatividade, configurável em [web.config], após o qual a aplicação é considerada encerrada. Portanto, é o servidor web que toma essa decisão com base nas configurações da aplicação. O tempo de inatividade de um aplicativo é definido como o período durante o qual nenhum cliente fez uma solicitação por um recurso do aplicativo.
- [Session-Start]/[Session_End]: A cada cliente é atribuída uma sessão, a menos que a aplicação esteja configurada para não utilizar sessões. Um cliente não é necessariamente um usuário diante de sua tela. Se ele tiver aberto dois navegadores para acessar a aplicação, ele representa dois clientes. Um cliente é identificado por um token de sessão que deve ser anexado a cada uma de suas solicitações. Esse token de sessão é uma sequência de caracteres gerada aleatoriamente pelo servidor web e é única. Dois clientes não podem ter o mesmo token de sessão. Esse token acompanhará o cliente da seguinte maneira:
- o cliente que faz sua primeira solicitação não envia um token de sessão. O servidor web reconhece isso e atribui um a ele. Esse é o início da sessão, e o procedimento [Session_Start] é executado. Essa será a única vez.
- o cliente faz suas solicitações subsequentes enviando o token que o identifica. Isso permitirá que o servidor web recupere informações relacionadas a esse token. Isso possibilitará o acompanhamento entre as diferentes solicitações do cliente.
- A aplicação pode disponibilizar ao cliente um formulário para encerrar a sessão. Nesse caso, é o próprio cliente que solicita o encerramento da sua sessão. O procedimento [Session_End] será executado. Isso ocorrerá apenas uma vez.
- O cliente pode nunca solicitar ele mesmo o encerramento de sua sessão. Nesse caso, após um determinado período de inatividade da sessão — também configurável por meio do procedimento [web.config] —, a sessão será encerrada pelo servidor web. O procedimento [Session_End] será então executado.
- [Application_BeginRequest]: esse procedimento é executado assim que uma nova solicitação chega. Portanto, ele é executado a cada solicitação de qualquer cliente. É um bom momento para examinar a solicitação antes de encaminhá-la para a página solicitada. É possível até mesmo decidir redirecioná-la para outra página.
- [Application_Error]: é executado sempre que ocorre um erro não tratado explicitamente pelo código do controlador [global.asax.vb]. Aqui, é possível redirecionar a solicitação do cliente para uma página que explique a causa do erro.
Se nenhum desses eventos precisar ser tratado, o arquivo [global.asax] pode ser ignorado. Foi isso que foi feito nos primeiros exemplos deste capítulo.
4.1.3.2. Exemplo 1
Vamos desenvolver um aplicativo para compreender melhor os três momentos: o início do aplicativo, da sessão e de uma solicitação do cliente. O arquivo [global.asax] será o seguinte:
O arquivo [global.asax.vb] associado será o seguinte:
Imports System
Imports System.Web
Imports System.Web.SessionState
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando o aplicativo é iniciado
' registra a hora
Dim startApplication As String = Date.Now.ToString("T")
' é armazenada no contexto do aplicativo
Application.Item("startApplication") = startApplication
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando a sessão é iniciada
' registra-se a hora
Dim startSession As String = Date.Now.ToString("T")
' é inserida na sessão
Session.Item("startSession") = startSession
End Sub
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' registra-se a hora
Dim startRequest As String = Date.Now.ToString("T")
' a hora é inserida na sessão
Context.Items("startRequest") = startRequest
End Sub
End Class
Os pontos importantes do código são os seguintes:
- o servidor web disponibiliza à classe [HttpApplication], a partir de [global.asax.vb], um certo número de objetos:
- Aplicação do tipo [HttpApplicationState] — representa a aplicação web — fornece acesso a um dicionário de objetos [Application.Item] acessível a todos os clientes da aplicação — permite o compartilhamento de informações entre diferentes clientes — o acesso simultâneo de vários clientes a um mesmo dado em leitura/gravação requer a sincronização dos clientes.
- Sessão do tipo [HttpSessionState] — representa um cliente específico — dá acesso a um dicionário de objetos [Session.Item] acessível a todas as solicitações desse cliente — permitirá armazenar informações sobre um cliente que poderão ser recuperadas ao longo de suas solicitações.
- Solicitação do tipo [HttpRequest] — representa a solicitação HTTP atual do cliente
- Resposta do tipo [HttpResponse] — representa a resposta HTTP que está sendo gerada pelo servidor para o cliente
- Servidor do tipo [HttpServerUtility] — oferece métodos utilitários, notadamente para redirecionar a solicitação para uma página diferente daquela prevista inicialmente.
- Contexto do tipo [HttpContext] — esse objeto é recriado a cada nova solicitação, mas é compartilhado por todas as páginas envolvidas no processamento da solicitação — permite transmitir informações de uma página para outra durante o processamento de uma solicitação por meio de seu dicionário Items.
- O procedimento [Application_Start] registra o início da aplicação em uma variável armazenada em um dicionário acessível no nível da aplicação
- o procedimento [Session_Start] registra o início da sessão em uma variável armazenada em um dicionário acessível no nível da sessão
- o procedimento [Application_BeginRequest] registra o início da consulta em uma variável armazenada em um dicionário acessível no nível da consulta (c.a.d, disponível durante todo o tempo de seu processamento, mas perdida ao final do mesmo)
A página de destino será a seguinte página [main.aspx]:
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
<head>
<title>global.asax</title>
</head>
<body>
jeton de session :
<% =jeton %><br/>
début Application :
<% =startApplication %><br/>
début Session :
<% =startSession %><br/>
début Requête :
<% =startRequest %><br/>
</body>
</html>
Esta página de apresentação exibe valores calculados por seu controlador [main.aspx.vb]:
Public Class main
Inherits System.Web.UI.Page
Protected startApplication As String
Protected startSession As String
Protected startRequest As String
Protected jeton as String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recuperam-se as informações do aplicativo e da sessão
jeton=Session.SessionId
startApplication = Application.Item("startApplication").ToString
startSession = Session.Item("startSession").ToString
startRequest = Context.Items("startRequest").ToString
End Sub
End Class
O controlador se limita a recuperar as três informações colocadas, respectivamente, no aplicativo, na sessão e no contexto por [global.asax.vb].
Testamos a aplicação da seguinte maneira:
- os arquivos são reunidos em uma mesma pasta <application-path>

- o servidor Cassini é iniciado com os parâmetros (<application-path>,/aspnet/globalasax1)
- um primeiro cliente solicita a URL [http://localhost/aspnet/globalasax1/main.aspx] e obtém o seguinte resultado:

- o mesmo cliente faz uma nova solicitação (opção “Atualizar” do navegador):

Pode-se observar que apenas a hora da solicitação mudou. Isso demonstra duas coisas:
- os procedimentos [Application_Start] e [Session_Start] de [global.asax] não foram executados na segunda solicitação.
- os objetos [Application] e [Session], nos quais estavam armazenadas as horas de início do aplicativo e da sessão, ainda estão disponíveis para a segunda solicitação.
- Abrimos um segundo navegador para criar um segundo cliente e solicitamos novamente a mesma URL:

Desta vez, observamos que a hora da sessão mudou. O segundo navegador, embora esteja na mesma máquina, foi considerado um segundo cliente e uma nova sessão foi criada para ele. É possível constatar que os dois clientes não possuem o mesmo token de sessão. A hora de início do aplicativo não mudou, o que significa que:
- o procedimento [Application_Start] de [global.asax.vb] não foi executado
- o objeto [Application], no qual foi armazenada a hora de início da aplicação, está acessível ao segundo cliente. Portanto, é nesse objeto que devem ser armazenadas as informações que os diferentes clientes do aplicativo precisam compartilhar, enquanto o objeto [Session] serve para armazenar informações que as consultas de um mesmo cliente precisam compartilhar.
4.1.3.3. Uma visão geral
Com o que aprendemos até agora, podemos traçar um primeiro esquema explicativo do funcionamento de um servidor web e das aplicações web que ele atende:

O esquema anterior mostra um servidor atendendo a duas aplicações, denominadas A e B, cada uma com dois clientes. Um servidor web é capaz de atender a várias aplicações web simultaneamente. Essas aplicações são totalmente independentes umas das outras. Vamos nos concentrar na aplicação A. O processamento de uma solicitação do cliente-1A à aplicação A ocorrerá da seguinte maneira:
- o cliente 1A solicita ao servidor web um recurso que pertence ao domínio da aplicação A. Isso significa que ele solicita um URL na forma [http://machine:port/VA/ressource], em que VA é o caminho virtual da aplicação A.
- Se o servidor web detectar que essa é a primeira solicitação de um recurso do aplicativo A, ele aciona o evento [Application_Start] do arquivo [global.asax] do aplicativo A. Um objeto [ApplicationA] do tipo [HttpApplicationState] será criado. Os diversos códigos do aplicativo armazenarão nesse objeto dados com escopo [Application], c.a.d, relativos a todos os usuários. O objeto [ApplicationA] permanecerá ativo até que o servidor web encerre o aplicativo A.
- Se, além disso, o servidor web detectar que está lidando com um novo cliente do aplicativo A, ele acionará o evento [Session_Start] do arquivo [global.asax] do aplicativo A. Um objeto [Session-1A] do tipo [HttpSessionState] será criado. Esse objeto permitirá que o aplicativo A armazene objetos de escopo [Session] e c.a.d, pertencentes a um cliente específico. O objeto [Session-1A] existirá enquanto o cliente 1A fizer solicitações. Ele permitirá o acompanhamento desse cliente. O servidor web detecta que está lidando com um novo cliente em dois casos:
- o cliente não enviou um token de sessão nos cabeçalhos HTTP de sua solicitação
- o cliente enviou um token de sessão que não existe (falha no cliente ou tentativa de invasão) ou que já não existe mais. Um token de sessão expira, de fato, após um determinado período de inatividade do cliente (20 minutos por padrão com IIS). Esse período é configurável.
- Em todos os casos, o servidor web acionará o evento [Application_BeginRequest] do arquivo [global.asax]. Esse evento inicia o processamento de uma solicitação do cliente. É comum não processar esse evento e passar o controle para a página solicitada pelo cliente, que, por sua vez, processará a solicitação. Também é possível usar esse evento para analisar a solicitação, processá-la e decidir qual página deve ser enviada como resposta. Usaremos essa técnica para implementar um aplicativo que siga a arquitetura MVC da qual falamos.
- Depois de passar pelo filtro [global.asax], a solicitação do cliente é encaminhada para uma página .aspx que irá processá-la. Veremos mais adiante que é possível fazer a solicitação passar por um filtro composto por várias páginas. A última delas será responsável por enviar a resposta ao cliente. As páginas podem adicionar à solicitação inicial do cliente informações que elas mesmas calcularam. Elas podem armazenar essas informações na coleção Context.Items. De fato, todas as páginas envolvidas no processamento da solicitação de um cliente têm acesso a esse repositório de dados.
- O código das diferentes páginas tem acesso aos reservatórios de dados que são os objetos [ApplicationA], [Session-1A], ... É preciso lembrar que o servidor web processa simultaneamente vários clientes para o aplicativo A. Todos esses clientes têm acesso ao objeto [Application A]. Caso precisem modificar dados nesse objeto, é necessário realizar um trabalho de sincronização entre os clientes. Além disso, cada cliente XA tem acesso ao repositório de dados [Session-XA]. Como este é reservado a ele, não há necessidade de sincronização nesse caso.
- O servidor web atende a várias aplicações web simultaneamente. Não há nenhuma interferência entre os clientes dessas diferentes aplicações.
A partir dessas explicações, destacam-se os seguintes pontos:
- em um determinado momento, um servidor web atende a vários clientes simultaneamente. Isso significa que ele não aguarda o término de uma solicitação para processar outra. Em um instante T, há, portanto, várias solicitações em andamento pertencentes a clientes diferentes para aplicativos diferentes. Os códigos de processamento que ocorrem ao mesmo tempo dentro do servidor web são, por vezes, chamados de threads de execução.
- os threads de execução dos clientes de diferentes aplicações web não interferem entre si. Há isolamento.
- Os threads de execução dos clientes de uma mesma aplicação podem precisar compartilhar dados:
- os threads de execução das solicitações de dois clientes diferentes (que não tenham o mesmo token de sessão) podem compartilhar dados por meio do objeto [Application].
- As threads de execução de solicitações sucessivas de um mesmo cliente podem compartilhar dados por meio do objeto [Session].
- os threads de execução de páginas sucessivas que processam uma mesma solicitação de um determinado cliente podem compartilhar dados por meio do objeto [Context].
4.1.3.4. Exemplo 2
Vamos desenvolver um novo exemplo que ilustre o que acabamos de ver. Reunimos na mesma pasta os seguintes arquivos:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando o aplicativo é iniciado
' inicializa o contador de clientes
Application.Item("nbRequêtes") = 0
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando a sessão é iniciada
' inicializa o contador de solicitações
Session.Item("nbRequêtes") = 0
End Sub
End Class
O princípio do aplicativo será contar o número total de solicitações feitas ao aplicativo e o número de solicitações por cliente. Quando a aplicação é iniciada ([Application_Start]), o contador de solicitações feitas à aplicação é zerado. Esse contador é definido no escopo [Application], pois deve ser incrementado por todos os clientes. Quando um cliente acessa pela primeira vez o [Session_Start], o contador de solicitações feitas por esse cliente é zerado. Esse contador está localizado no escopo [Session], pois se refere apenas a um determinado cliente.
Após a execução de [global.asax], o próximo arquivo [main.aspx] será executado:
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
<head>
<title>application-session</title>
</head>
<body>
jeton de session :
<% =jeton %>
<br />
requêtes Application :
<% =nbRequêtesApplication %>
<br />
requêtes Client :
<% =nbRequêtesClient %>
<br />
</body>
</html>
Ele exibe três informações calculadas por seu controlador:
- a identidade do cliente por meio de seu token de sessão: [jeton]
- o número total de solicitações feitas ao aplicativo: [nbRequêtesApplication]
- o número total de solicitações feitas pelo cliente identificado em 1: [nbRequêtesClient]
As três informações são calculadas em [main.aspx.vb]:
Public Class main
Inherits System.Web.UI.Page
Protected nbRequêtesApplication As String
Protected nbRequêtesClient As String
Protected jeton As String
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' mais uma solicitação para o aplicativo
Application.Item("nbRequêtes") = CType(Application.Item("nbRequêtes"), Integer) + 1
' mais uma solicitação na sessão
Session.Item("nbRequêtes") = CType(Session.Item("nbRequêtes"), Integer) + 1
' inicializa as variáveis de apresentação
nbRequêtesApplication = Application.Item("nbRequêtes").ToString
jeton = Session.SessionID
nbRequêtesClient = Session.Item("nbRequêtes").ToString
End Sub
End Class
Quando o [main.aspx.vb] é executado, estamos processando uma solicitação de um determinado cliente. Utilizamos o objeto [Application] para incrementar o número de solicitações do aplicativo e o objeto [Session] para incrementar o número de solicitações do cliente cuja solicitação está sendo processada. Vale lembrar que, embora todos os clientes de uma mesma aplicação compartilhem o mesmo objeto [Application], cada um deles possui um objeto [Session] próprio.
Testamos a aplicação colocando os quatro arquivos anteriores em uma pasta que chamamos de <application-path> e iniciamos o servidor Cassini com os parâmetros (<application-path>,/aspnet/webapplia). Abrimos um primeiro navegador e acessamos a URL [http://localhost/aspnet/webapplia/main.aspx]:

Fazemos uma segunda solicitação com o botão [Reload]:

Abrimos um segundo navegador para acessar a mesma URL. Para o servidor web, trata-se de um novo cliente:

É possível observar que o token de sessão mudou e que, portanto, temos um novo cliente. Isso se reflete no número de solicitações do cliente. Voltemos agora ao primeiro navegador e solicitemos novamente a mesma URL:

O número de solicitações feitas ao aplicativo é contabilizado corretamente.
4.1.3.5. Sobre a necessidade de sincronizar os clientes de um aplicativo
Na aplicação anterior, o contador de solicitações feitas à aplicação é incrementado no procedimento [Form_Load] da página [main.aspx] da seguinte maneira:
' mais uma solicitação para o aplicativo
Application.Item("nbRequêtes") = CType(Application.Item("nbRequêtes"), Integer) + 1
Essa instrução, embora simples, requer várias instruções do processador para ser executada. Suponhamos que sejam necessárias três:
- leitura do contador
- incremento do contador
- gravação do contador
O servidor web é executado em uma máquina multitarefa, o que significa que cada tarefa recebe o processador por alguns milissegundos antes de perdê-lo e recuperá-lo depois que todas as outras tarefas também tiverem recebido seu tempo de execução. Suponhamos que dois clientes, A e B, façam uma solicitação ao servidor web ao mesmo tempo. Suponhamos que o cliente A seja atendido primeiro, que ele chegue à rotina [Form_Load] a partir de [main.aspx.vb], leia o contador (=100) e seja interrompido porque seu tempo de execução se esgotou. Suponhamos agora que seja a vez do cliente B e que este tenha o mesmo destino: ele consegue ler o valor do contador (=100), mas não tem tempo de incrementá-lo. Os clientes A e B estão ambos com um contador igual a 100. Suponhamos que seja novamente a vez do cliente A: ele incrementa seu contador, altera-o para 101 e, em seguida, encerra a operação. É a vez do cliente B, que possui o valor antigo do contador e não o novo. Portanto, ele também altera o valor do contador para 101 e encerra a operação. O valor do contador de solicitações do aplicativo agora está incorreto.
Para ilustrar esse problema, retomamos o aplicativo anterior e o modificamos da seguinte maneira:
- os arquivos [global.asax], [global.asax.vb] e [main.aspx] não sofrem alterações
- o arquivo [main.aspx.vb] passa a ter o seguinte conteúdo:
Imports System.Threading
Public Class main
Inherits System.Web.UI.Page
Protected nbRequêtesApplication As Integer
Protected nbRequêtesClient As Integer
Protected jeton As String
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' mais uma solicitação para o aplicativo e a sessão
' leitura de contadores
nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
' espera de 5 s
Thread.Sleep(5000)
' incremento dos contadores
nbRequêtesApplication += 1
nbRequêtesClient += 1
' gravação dos contadores
Application.Item("nbRequêtes") = nbRequêtesApplication
Session.Item("nbRequêtes") = nbRequêtesClient
' inicialização das variáveis de apresentação
jeton = Session.SessionID
End Sub
End Class
A atualização dos medidores foi dividida em quatro fases:
- leitura do contador
- suspensão da thread de execução
- incrementação do contador
- regravação do contador
Vamos considerar novamente nossos dois clientes, A e B. Entre a fase de leitura e a de incremento dos contadores de solicitações, forçamos o thread de execução a parar por 5 segundos. Isso terá como consequência imediata a perda do processador, que será então alocado a outra tarefa. Suponhamos que o cliente A seja o primeiro a passar. Ele lerá o valor N do contador e será interrompido por 5 segundos. Se, durante esse período, o cliente B tiver o processador disponível, ele deverá ler o mesmo valor N do contador. No final, os dois clientes deveriam exibir o mesmo valor do contador, o que seria anormal.
Testamos a aplicação colocando os quatro arquivos anteriores em uma pasta que chamamos de <application-path> e iniciamos o servidor Cassini com os parâmetros (<application-path>,/aspnet/webapplib). Preparamos dois navegadores diferentes com a URL [http://localhost/aspnet/webapplib/main.aspx]. Abrimos o primeiro para que ele solicite o URL e, sem esperar pela resposta, que chegará 5 segundos depois, abrimos o segundo navegador. Após pouco mais de 5 segundos, obtemos o seguinte resultado:

Percebemos:
- que temos dois clientes diferentes (não têm o mesmo token de sessão)
- que cada cliente fez uma solicitação
- que o contador de solicitações feitas ao aplicativo deveria, portanto, estar em 2 em um dos dois navegadores. Mas não é o caso.
Agora, vamos fazer outro experimento. Com o mesmo navegador, enviamos cinco solicitações para a URL [http://localhost/aspnet/webapplib/main.aspx]. Mais uma vez, as enviamos uma após a outra, sem aguardar os resultados. Quando todas as solicitações foram executadas, obtemos o seguinte resultado para a última:

É possível observar que:
- que as 5 solicitações foram consideradas como provenientes do mesmo cliente, pois o contador de solicitações do cliente está em 5. Embora não tenha sido mostrado acima, verifica-se que o token de sessão é, de fato, o mesmo para as 5 solicitações.
- que o contador de solicitações feitas ao aplicativo está correto.
O que concluir disso? Nada definitivo. Talvez o servidor web não comece a executar uma solicitação de um cliente se este já tiver uma em execução? Portanto, nunca haveria simultaneidade na execução das solicitações de um mesmo cliente. Elas seriam executadas uma após a outra. Esse ponto deve ser verificado. De fato, pode depender do tipo de cliente utilizado.
4.1.3.6. Sincronização dos clientes
O problema evidenciado na aplicação anterior é um problema clássico (mas não simples de resolver) de acesso exclusivo a um recurso. Em nosso problema específico, é preciso garantir que dois clientes, A e B, não possam estar ao mesmo tempo na sequência de código:
- leitura do contador
- incremento do contador
- gravação do contador
Essa sequência de código é chamada de sequência crítica. Ela requer a sincronização das threads que a executam simultaneamente. A plataforma .NET oferece diversas ferramentas para garantir essa sincronização. Aqui, utilizaremos a classe [Mutex].

Aqui, utilizaremos apenas os construtores e métodos a seguir:
cria um objeto de sincronização M | |
A thread T1, que executa a operação M.WaitOne(), solicita a propriedade do objeto de sincronização M. Se o mutex M não estiver sendo mantido por nenhuma thread (o que ocorre inicialmente), ele é “concedido” à thread T1 que o solicitou. Se, pouco depois, uma thread T2 realizar a mesma operação, ela ficará bloqueada. De fato, um mutex só pode pertencer a uma thread. Ele será desbloqueado quando a thread T1 liberar o mutex M que está detendo. Assim, várias threads podem ficar bloqueadas aguardando o mutex M. | |
A thread T1, que executa a operação M.ReleaseMutex(), libera a posse do mutex M. Quando a thread T1 perder o processador, o sistema poderá atribuí-lo a uma das threads que aguardam o mutex M. Apenas um deles o obterá por sua vez; os demais que aguardam o Mutex M permanecerão bloqueados |
Um mutex M gerencia o acesso a um recurso compartilhado R. Uma thread solicita o recurso R por meio de M.WaitOne() e o libera por meio de M.ReleaseMutex(). Uma seção crítica de código que deve ser executada por apenas uma thread por vez é um recurso compartilhado. A sincronização da execução da seção crítica pode ser feita da seguinte forma:
onde M é um objeto Mutex. É claro que nunca se deve esquecer de liberar um Mutex que se tornou desnecessário, para que outra thread possa entrar na seção crítica por sua vez; caso contrário, as threads que aguardam um mutex que nunca foi liberado nunca terão acesso ao processador. Além disso, é preciso evitar a situação de interbloqueio (deadlock), na qual duas threads aguardam uma à outra. Consideremos as seguintes ações que ocorrem sequencialmente no tempo:
- um thread T1 obtém a posse de um mutex M1 para ter acesso a um recurso compartilhado R1
- um thread T2 obtém a posse de um mutex M2 para acessar um recurso compartilhado R2
- o thread T1 solicita o mutex M2. Ele fica bloqueado.
- A thread T2 solicita o mutex M1. Ela fica bloqueada.
Aqui, os threads T1 e T2 estão aguardando um ao outro. Esse caso ocorre quando os threads precisam de dois recursos compartilhados: o recurso R1, controlado pelo mutex M1, e o recurso R2, controlado pelo mutex M2. Uma solução possível é solicitar os dois recursos ao mesmo tempo por meio de um único mutex M. Mas isso nem sempre é possível, especialmente se isso implicar em uma ocupação prolongada de um recurso caro. Outra solução é que um thread que possua M1 e não consiga obter M2 libere M1 para evitar o interbloqueio.
Se colocarmos em prática o que acabamos de aprender, nossa aplicação fica da seguinte forma:
- os arquivos [global.asax] e [main.aspx] não sofrem alterações
- o arquivo [global.asax.vb] passa a ser o seguinte:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports System.Threading
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando o aplicativo é iniciado
' inicialização do contador de clientes
Application.Item("nbRequêtes") = 0
' criação de um bloqueio de sincronização
Application.Item("verrou") = New Mutex
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' É acionado quando a sessão é iniciada
' inicialização do contador de solicitações
Session.Item("nbRequêtes") = 0
End Sub
End Class
A única novidade é a criação de um [Mutex], que será utilizado pelos clientes para sincronização. Como ele deve estar acessível a todos os clientes, ele é colocado no objeto [Application].
- O arquivo [main.aspx.vb] passa a ter a seguinte forma:
Imports System.Threading
Public Class main
Inherits System.Web.UI.Page
Protected nbRequêtesApplication As Integer
Protected nbRequêtesClient As Integer
Protected jeton As String
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' mais uma solicitação para o aplicativo e a sessão
' entramos em uma seção crítica — recuperamos o bloqueio de sincronização
Dim verrou As Mutex = CType(Application.Item("verrou"), Mutex)
' solicita-se acesso exclusivo à seção crítica a seguir
verrou.WaitOne()
' leitura dos contadores
nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
' espera de 5 s
Thread.Sleep(5000)
' incremento dos contadores
nbRequêtesApplication += 1
nbRequêtesClient += 1
' gravação dos contadores
Application.Item("nbRequêtes") = nbRequêtesApplication
Session.Item("nbRequêtes") = nbRequêtesClient
' permitimos o acesso à seção crítica
verrou.ReleaseMutex()
' inicialização das variáveis de apresentação
jeton = Session.SessionID
End Sub
End Class
Percebe-se que o cliente:
- solicita entrar sozinho na seção crítica. Para isso, ele solicita a propriedade exclusiva do mutex [verrou]
- libera o mutex [verrou] ao final da seção crítica para que outro cliente possa, por sua vez, entrar na seção crítica.
Testamos a aplicação colocando os quatro arquivos anteriores em uma pasta que chamamos de <application-path> e iniciamos o servidor Cassini com os parâmetros (<application-path>,/aspnet/webapplic). Preparamos dois navegadores diferentes com a URL [http://localhost/aspnet/webapplic/main.aspx]. Abrimos o primeiro para que ele solicite o URL e, sem esperar pela resposta, que chegará 5 segundos depois, abrimos o segundo navegador. Após pouco mais de 5 segundos, obtemos o seguinte resultado:

Desta vez, o contador de solicitações do aplicativo está correto.
O que fica claro nesta longa demonstração é a necessidade absoluta de sincronizar os clientes de uma mesma aplicação web, caso precisem atualizar elementos compartilhados por todos os clientes.
4.1.3.7. Gerenciamento do token de sessão
Já falamos várias vezes sobre o token de sessão que o cliente e o servidor web trocam entre si. Vamos relembrar como isso funciona:
- o cliente faz uma primeira solicitação ao servidor. Ele não envia um token de sessão.
- Devido à ausência do token de sessão na solicitação, o servidor reconhece um novo cliente e atribui-lhe um token. A esse token está associado também um objeto [Session], que será usado para armazenar informações específicas desse cliente. O token acompanhará todas as solicitações desse cliente. Ele será incluído nos cabeçalhos HTTP da resposta à primeira solicitação do cliente.
- O cliente agora conhece seu token de sessão. Ele o reenviará nos cabeçalhos HTTP de cada uma das solicitações seguintes que fizer ao servidor web. Graças ao token, o servidor poderá localizar o objeto [Session] associado ao cliente.
Para ilustrar esse mecanismo, retomamos o aplicativo anterior, modificando apenas o arquivo [main.aspx.vb]:
Imports System.Threading
Public Class main
Inherits System.Web.UI.Page
Protected nbRequêtesApplication As Integer
Protected nbRequêtesClient As Integer
Protected jeton As String
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' mais uma solicitação para o aplicativo e a sessão
' entrando em uma seção crítica — recuperando o bloqueio de sincronização
Dim verrou As Mutex = CType(Application.Item("verrou"), Mutex)
' solicita-se acesso exclusivo à seção seguinte
verrou.WaitOne()
' leitura de contadores
nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
' espera de 5 s
Thread.Sleep(5000)
' incremento dos contadores
nbRequêtesApplication += 1
nbRequêtesClient += 1
' gravação dos contadores
Application.Item("nbRequêtes") = nbRequêtesApplication
Session.Item("nbRequêtes") = nbRequêtesClient
' permitir o acesso à seção crítica
verrou.ReleaseMutex()
' inicialização das variáveis de apresentação
jeton = Session.SessionID
End Sub
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' armazenamos a solicitação do cliente em request.txt na pasta do aplicativo
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
End Class
Quando ocorre o evento [Page_Init], salvamos a solicitação do cliente na pasta do aplicativo. Vale lembrar alguns pontos:
- [TemplateSourceDirectory] representa o caminho virtual da página em execução,
- MapPath (TemplateSourceDirectory) representa o caminho físico correspondente. Isso nos permite construir o caminho físico do arquivo a ser gerado,
- [Request] é um objeto que representa a solicitação em andamento. Esse objeto foi construído a partir da solicitação bruta enviada pelo cliente, c.a.d, uma sequência de linhas de texto no formato:

- Request.Save([FileName]) salva a solicitação completa do cliente (os cabeçalhos HTTP e, eventualmente, o documento que se segue) em um arquivo cujo caminho é passado como parâmetro.
Assim, poderemos saber exatamente qual foi a solicitação do cliente. Testamos a aplicação colocando os quatro arquivos anteriores em uma pasta que chamamos de <application-path> e iniciamos o servidor Cassini com os parâmetros (<application-path>,/aspnet/session1). Em seguida, usando um navegador, solicitamos o URL
[http://localhost/aspnet/session1/main.aspx]. Obtemos o seguinte resultado:

Usamos o arquivo [request.txt], salvo por [main.aspx.vb], para acessar a solicitação do navegador:
GET /aspnet/session1/main.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0,5
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.0; en-US; rv:1.7b) Gecko/20040316
Constatamos que o navegador fez a solicitação de URL e [/aspnet/session1/main.aspx], enviando outras informações das quais já falamos no capítulo anterior. Não se observa nenhum token de sessão. A página recebida como resposta, por sua vez, mostra que o servidor criou um token de sessão. Ainda não sabemos se o navegador o recebeu. Vamos agora fazer uma segunda solicitação com o mesmo navegador (Atualizar). Obtemos a seguinte nova resposta:

De fato, há um acompanhamento da sessão, já que o número de solicitações da sessão foi incrementado corretamente. Vejamos agora o conteúdo do arquivo [request.txt]:
GET /aspnet/session1/main.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0,5
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
Cookie: ASP.NET_SessionId=y153tk45sise0lrhdzrf22m3
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7b) Gecko/20040316
Observa-se que, para essa segunda solicitação, o navegador enviou ao servidor um novo cabeçalho HTTP [Cookie:], definindo uma informação chamada [ASP.NET_SessionId] e cujo valor é o token de sessão que apareceu na resposta à primeira solicitação. Graças a esse token, o servidor web associará essa nova solicitação ao objeto [Session], identificado pelo token [y153tk45sise0lrhdzrf22m3], e recuperará o contador de solicitações associado.
Ainda não sabemos por meio de qual mecanismo o servidor enviou o token ao cliente, pois não temos acesso à resposta HTTP do servidor. Vale lembrar que essa resposta tem a mesma estrutura que a solicitação do cliente, ou seja, um conjunto de linhas de texto no formato:

Tivemos a oportunidade de usar um cliente web que nos dava acesso à resposta HTTP do servidor web, o cliente curl. Estamos usando-o novamente, em uma janela do DOS, para consultar a mesma URL que o navegador anterior:
E:\curl>curl --include http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:31:42 GMT
X-AspNet-Version: 1.1.4322
Set-Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close
<HTML>
<HEAD>
<title>application-session</title>
</HEAD>
<body>
jeton de session :
qxnxmqmvhde3al55kzsmx445
<br>
requêtes Application :
3
<br>
requêtes Client :
1
<br>
</body>
</HTML>
Temos a resposta para nossa pergunta. O servidor web envia o token de sessão na forma de um cabeçalho HTTP [Set-Cookie:]:
Vamos fazer a mesma solicitação sem enviar o token de sessão. Obtemos a seguinte resposta:
E:\curl>curl --include http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:36:06 GMT
X-AspNet-Version: 1.1.4322
Set-Cookie: ASP.NET_SessionId=cs2p12mehdiz5v55ihev1kaz; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close
<HTML>
<HEAD>
<title>application-session</title>
</HEAD>
<body>
jeton de session :
cs2p12mehdiz5v55ihev1kaz
<br>
requêtes Application :
4
<br>
requêtes Client :
1
<br>
</body>
</HTML>
Como não reenviamos o token de sessão, o servidor não conseguiu nos identificar e nos forneceu um novo token. Para dar continuidade a uma sessão já iniciada, o cliente deve reenviar ao servidor o token de sessão que recebeu. Faremos isso aqui usando a opção [--cookie clé=valeur] do curl, que irá gerar o cabeçalho HTTP [Cookie: clé=valeur]. Vimos que o navegador enviou esse cabeçalho HTTP durante sua segunda solicitação.
E:\curl>curl --include --cookie ASP.NET_SessionId=cs2p12mehdiz5v55ihev1kaz http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:40:20 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close
<HTML>
<HEAD>
<title>application-session</title>
</HEAD>
<body>
jeton de session :
cs2p12mehdiz5v55ihev1kaz
<br>
requêtes Application :
5
<br>
requêtes Client :
2
<br>
</body>
</HTML>
É possível observar várias coisas:
- o contador de solicitações do cliente foi incrementado, demonstrando que o servidor reconheceu nosso token.
- o token de sessão exibido pela página é, de fato, aquele que enviamos
- o token de sessão não consta mais nos cabeçalhos HTTP enviados pelo servidor web. Na verdade, ele só o envia uma vez: durante a geração do token ao iniciar uma nova sessão. Depois que o cliente obtém seu token, cabe a ele usá-lo quando quiser para ser reconhecido.
Nada impede que um cliente utilize vários tokens de sessão, como mostra o exemplo a seguir com [curl], no qual usamos o token obtido em nossa primeira solicitação (solicitação nº 1):
E:\curl>curl --include --cookie ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445 http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:48:47 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close
<HTML>
<HEAD>
<title>application-session</title>
</HEAD>
<body>
jeton de session :
qxnxmqmvhde3al55kzsmx445
<br>
requêtes Application :
6
<br>
requêtes Client :
2
<br>
</body>
</HTML>
O que significa esse exemplo? Enviamos um token obtido um pouco antes. Quando o servidor web cria um token, ele o mantém enquanto o cliente associado a esse token continuar enviando solicitações a ele. Após um certo tempo de inatividade (20 minutos por padrão com IIS), o token é excluído. O exemplo anterior mostra que utilizamos um token ainda ativo.
Pode-se ter curiosidade em saber quais foram as solicitações HTTP do cliente [curl] durante todas essas operações. Sabemos que elas foram registradas no arquivo [request.txt]. Aqui está a última:
GET /aspnet/session1/main.aspx HTTP/1.1
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4
De fato, encontramos ali o cabeçalho HTTP, que envia o token de sessão.
As informações transmitidas pelo servidor por meio dos cabeçalhos HTTP e [Set-Cookie:] são chamadas de cookies. O servidor pode utilizar esse mecanismo para transmitir outras informações além do token de sessão. Quando o servidor S transmite um cookie a um cliente, ele também indica o tempo de vida D do mesmo e o valor associado URL U. Isso significa que, para o cliente, quando ele solicita ao servidor S uma URL no formato /U/caminho, ele pode reenviar o cookie caso não o tenha recebido há um tempo superior a D. Nada impede que um cliente não cumpra esse código de conduta. Os navegadores, por sua vez, respeitam-no. Alguns navegadores permitem acessar o conteúdo dos cookies que recebem. Esse é o caso do navegador Mozilla. Veja, por exemplo, as informações relacionadas ao cookie enviado pelo servidor em um exemplo anterior:

Nele encontramos:
- o nome do cookie [ASP.NET_SessionId]
- seu valor: [y153...m3]
- o dispositivo ao qual ele está associado: [localhost]
- a URL à qual ele está associado: [/]
- sua validade: [at end of session]
O navegador enviará, portanto, o token de sessão sempre que solicitar um URL no formato [http://localhost/...], c.a.d. sempre que solicitar uma URL ao servidor web da máquina [localhost]. A validade do cookie é igual à da sessão. Para o navegador, isso significa que o cookie nunca expira. Ele o enviará sempre que solicitar uma URL da máquina [localhost]. Assim, se o navegador receber o token de sessão no dia D, for fechado e reutilizado no dia seguinte, ele reenviará o token de sessão (que foi armazenado em um arquivo). O servidor receberá esse token, que ele já não possui, pois um token de sessão tem uma validade limitada no servidor (20 minutos no IIS). Assim, ele iniciará uma nova sessão.
É possível desativar o uso de cookies em um navegador. Nesse caso, o cliente recebe o token de sessão, mas não o reenvia, o que impede o rastreamento da sessão. Para demonstrar isso, desativamos o uso de cookies em nosso navegador (Mozilla, neste caso):

Além disso, excluímos todos os cookies existentes:

Feito isso, reiniciamos o servidor Cassini para começar do zero e, com o navegador, solicitamos novamente a URL [http://localhost/aspnet/session1/main.aspx]:

Vamos verificar se nosso navegador armazenou um cookie:

Constatamos que o navegador não armazenou o cookie do token de sessão que o servidor lhe enviou. Portanto, é de se esperar que não haja rastreamento de sessão. Solicitamos novamente a mesma URL (Atualizar):

O resultado é exatamente o esperado. O navegador não reenviou o token de sessão, que havia recebido, mas não armazenado. O servidor, portanto, iniciou uma nova sessão com um novo token. Concluímos deste exemplo que nossa política de rastreamento de sessão fica comprometida se o usuário tiver desativado o uso de cookies em seu navegador. No entanto, existe outra maneira, além dos cookies, de trocar o token de sessão entre o servidor e o cliente. É possível, de fato, informar ao servidor web que o aplicativo funciona sem cookies. Isso é feito por meio do arquivo de configuração [web.config]:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<system.web>
<sessionState cookieless="true" timeout="10" />
</system.web>
</configuration>
O arquivo de configuração acima indica que o aplicativo funcionará sem cookies (cookieless="true") e que o tempo máximo de inatividade de um token de sessão é de 10 minutos (timeout="10"). Após esse período, a sessão associada ao token é encerrada. O processo de troca do token de sessão entre o servidor e o cliente será o seguinte:
- o cliente solicita a URL [http://machine:port/V/chemin], em que V é uma pasta virtual do servidor web
- o servidor gera um token J e responde ao cliente para que ele seja redirecionado para a URL [http://machine:port/V/(J)/chemin]. Assim, ele inseriu o token na URL a ser consultada, imediatamente após a pasta virtual V
- o cliente obedece a esse redirecionamento e solicita a nova URL URL [http://machine:port/V/(J)/chemin].
- O servidor responde a essa solicitação e envia uma página de resposta.
Vamos ilustrar esses diferentes pontos. Colocamos toda a aplicação anterior em uma nova pasta <application-path>. Colocamos nessa mesma pasta o arquivo [web.config] anterior. Além disso, modificamos o código de apresentação [main.aspx] para incluir um link:
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
<HEAD>
<title>application-session</title>
</HEAD>
<body>
jeton de session :
<% =jeton %>
<br>
requêtes Application :
<% =nbRequêtesApplication %>
<br>
requêtes Client :
<% =nbRequêtesClient %>
<br>
<a href="main.aspx">Recharger l'application</a>
</body>
</HTML>
Este link aponta para a página [main.aspx] e, portanto, é equivalente ao botão (Atualizar) do navegador. O servidor Cassini é iniciado com os parâmetros (<application-path>,/session2). Estamos fazendo uma exceção à nossa prática habitual, que consistia em anotar a pasta virtual [/aspnet/XX]. De fato, devido à inserção do token de sessão na URL, a pasta virtual deve conter apenas um elemento: /XX. Primeiramente, utilizamos o cliente [curl] para solicitar a URL [http://localhost/session2/main.aspx]:
E:\curl>curl --include http://localhost/session2/main.aspx
HTTP/1.1 302 Found
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 13:52:36 GMT
X-AspNet-Version: 1.1.4322
Location: /session2/(hinadjag3bt0u155g5hqe245)/main.aspx
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 163
Connection: Close
<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href='/session2/(hinadjag3bt0u155g5hqe245)/main.aspx'>here
</body></html>
Vemos que o servidor responde com o cabeçalho HTTP [HTTP/1.1 302 Found] em vez de [HTTP/1.1 200 OK]. Trata-se de um cabeçalho que solicita ao cliente que seja redirecionado para a URL indicada pelo cabeçalho HTTP Location [Location: /session2/(hinadjag3bt0u155g5hqe245)/main.aspx]. É possível observar o token de sessão que foi inserido na URL de redirecionamento. Um navegador que recebe essa resposta solicita a nova URL de forma transparente para o usuário, que não percebe a nova solicitação. Caso o navegador não gerencie sozinho o redirecionamento, um documento HTML é enviado após o código HTTP acima. Nele há um link para a URL de redirecionamento, no qual o usuário poderá clicar.
Agora, vamos fazer o mesmo com um navegador em que os cookies foram desativados. Solicitamos, mais uma vez, a URL [http://localhost/session2/main.aspx]. Recebemos a seguinte resposta do servidor:

Em primeiro lugar, observemos que a URL exibida pelo navegador não é a que solicitamos. Isso indica que ocorreu um redirecionamento. De fato, o navegador sempre exibe a URL URL do último documento recebido. Portanto, se ele não exibe a URL [http://localhost/session2/main.aspx], é porque foi solicitado que ele redirecionasse para outra URL. Pode haver vários redirecionamentos. A URL exibida pelo navegador é a URL do último redirecionamento. Podemos constatar que o token de sessão está presente na URL exibida pelo navegador. Isso fica evidente porque esse token também é exibido pelo nosso programa na página.
Vamos relembrar o código do link que foi inserido na página:
<a href="main.aspx">Recharger l'application</a>
Trata-se de um link relativo, pois não começa com o sinal /, o que o tornaria um link absoluto. Relativo a quê? Para entender esse ponto, é preciso voltar à URL do documento atualmente exibido: [http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx]. Os links relativos encontrados neste documento serão relativos ao caminho [http://localhost/session2/(gu5ee455pkpffn554e3b1a32)]. Assim, nosso link acima é equivalente ao link:
<a href=" http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx">Recharger l'application</a>
É isso que o navegador nos mostra quando passamos o mouse sobre o link:

Se clicarmos no link [Recharger l'application], será chamada a URL
[http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx] que é chamada. O servidor receberá, portanto, o token de sessão e poderá recuperar as informações a ele associadas. É isso que a resposta do navegador nos mostra:

Vale lembrar que, se precisarmos rastrear uma sessão em um aplicativo web e não tivermos certeza de que os navegadores dos usuários desse aplicativo permitirão o uso de cookies, então
- devemos configurar a aplicação para que funcione sem cookies
- as páginas da aplicação devem conter links relativos e não absolutos
4.2. Recuperar as informações de uma solicitação do cliente
4.2.1. O ciclo de solicitação-resposta do cliente-servidor web
Vale lembrar aqui o contexto cliente-servidor de um aplicativo web:

A solicitação de um cliente para um aplicativo web é processada da seguinte maneira:
- o cliente abre uma conexão TCP/IP para uma porta P do serviço web da máquina M que hospeda a aplicação web
- ele envia, por essa conexão, uma sequência de linhas de texto de acordo com o protocolo HTTP. Esse conjunto de linhas forma o que chamamos de solicitação do cliente. Ela tem o seguinte formato:

Depois que a solicitação é enviada, o cliente aguarda a resposta.
- A primeira linha dos cabeçalhos HTTP especifica a ação solicitada ao servidor web. Ela pode assumir várias formas:
- GET url HTTP/<versão>, sendo que <versão> é atualmente igual a 1.0 ou 1.1. Nesse caso, a solicitação não inclui a parte [Document]
- POST url HTTP/<versão>. Nesse caso, a solicitação inclui uma parte [Document], geralmente uma lista de informações destinadas ao aplicativo web
- PUT url HTTP/<versão>. O cliente envia um documento na parte [Document] e deseja armazená-lo no servidor no endereço url
Quando o cliente deseja transmitir informações para o aplicativo web ao qual se conectou, ele dispõe basicamente de dois meios:
- (continuação)
- sua solicitação é [GET url_enrichie HTTP/<version>], onde url_enrichie tem o formato [url?param1=val1¶m2=val2&...]. Além da URL, o cliente transmite uma série de informações no formato [clé=valeur].
- sua solicitação é [POST url HTTP/<version>]. Na parte [Document], ele transmite informações no mesmo formato anterior: [param1=val1¶m2=val2&...].
- No servidor, toda a cadeia de processamento da solicitação do cliente tem acesso a ela por meio de um objeto global chamado Request. O servidor web colocou nesse objeto a solicitação completa do cliente em um formato que vamos descobrir. O aplicativo solicitado processará esse objeto e construirá uma resposta para o cliente. Essa resposta está disponível em um objeto global chamado Response. O papel do aplicativo web é construir um objeto [Response] a partir do objeto [Request] recebido. A cadeia de processamento também dispõe dos objetos globais [Application] e [Session], dos quais já falamos e que permitirão o compartilhamento de dados entre diferentes clientes (Aplicativo) ou entre solicitações sucessivas de um mesmo cliente (Sessão).
- A aplicação enviará sua resposta ao servidor por meio do objeto [Response]. Essa resposta, uma vez na rede, terá o seguinte formato: HTTP:

Assim que essa resposta for enviada, o servidor encerrará a conexão de rede de recepção (a menos que o cliente tenha solicitado que isso não seja feito).
- O cliente receberá a resposta e, por sua vez, encerrará a conexão (na transmissão). O que será feito com essa resposta depende do tipo de cliente. Se for um navegador e o documento recebido for um documento HTML, ele será exibido. Se o cliente for um programa, a resposta será analisada e processada.
- O fato de, após o ciclo de solicitação-resposta, a conexão que ligava o cliente ao servidor ser encerrada torna o protocolo HTTP um protocolo sem estado. Na próxima solicitação, o cliente estabelecerá uma nova conexão de rede com o mesmo servidor. Como não se trata mais da mesma conexão de rede, o servidor não tem como (no nível TCP/IP e HTTP) associar essa nova conexão a uma anterior. É o sistema de token de sessão que permitirá essa associação.
4.2.2. Recuperar as informações transmitidas pelo cliente
Analisaremos agora algumas propriedades e métodos do objeto [Request], que permite que o código da aplicação tenha acesso à solicitação do cliente e, portanto, às informações que ele transmitiu. O objeto [Request] é do tipo [HttpRequest]:

Essa classe possui diversas propriedades e métodos. Nosso foco são as propriedades HttpMethod, QueryString, Form e Params, que nos permitirão acessar os elementos da cadeia de informações [param1=val1¶m2=val2&...].
método de consulta do cliente: GET, POST, HEAD, ... | |
coleção dos elementos da string de consulta param1=val1¶m2=val2&.. da primeira linha HTTP [méthode]?param1=val1¶m2=val2&... onde [méthode] pode ser GET, POST, HEAD. | |
coleta dos elementos da string de consulta param1=val1¶m2=val2&... presentes na parte [Document] da consulta (método POST). | |
reúne várias coleções: QueryString, Form, ServerVariables e Cookies em uma única coleção. |
4.2.3. Exemplo 1
Vamos implementar esses elementos em um primeiro exemplo. O aplicativo terá apenas um elemento [main.aspx]. O código de apresentação [main.aspx] será o seguinte:
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
<head>
<title>Requête client</title>
</head>
<body>
Requête :
<% = méthode %>
<br />
nom :
<% = nom %>
<br />
âge :
<% = age %>
<br />
</body>
</html>
A página exibe três informações [méthode, nom, age] calculadas por seu controlador [main.aspx.vb]:
Public Class main
Inherits System.Web.UI.Page
Protected nom As String = "xx"
Protected age As String = "yy"
Protected méthode As String
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' a solicitação do cliente é armazenada em request.txt na pasta do aplicativo
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recuperam-se os parâmetros da solicitação
méthode = Request.HttpMethod.ToLower
If Not Request.QueryString("nom") Is Nothing Then nom = Request.QueryString("nom").ToString
If Not Request.QueryString("age") Is Nothing Then age = Request.QueryString("age").ToString
If Not Request.Form("nom") Is Nothing Then nom = Request.Form("nom").ToString
If Not Request.Form("age") Is Nothing Then age = Request.Form("age").ToString
End Sub
End Class
Quando a página é carregada (Form_Load), as informações [nom, age] são recuperadas da solicitação do cliente. Elas são pesquisadas nas duas coleções [QueryString] e [Form]. . Além disso, em [Page_Init], armazenamos a solicitação do cliente para podermos verificar o que ele enviou. Colocamos esses dois arquivos em uma pasta <application-path> e iniciamos o servidor Cassini com os parâmetros (<application-path>,/request1); em seguida, usando um navegador, acessamos a URL
[http://localhost/request1/main.aspx?nom=tintin&age=27]. Obtemos a seguinte resposta:

As informações transmitidas pelo cliente foram recuperadas corretamente. A solicitação do navegador armazenada no arquivo [request.txt] é a seguinte:
GET /request1/main.aspx?nom=tintin&age=27 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
Vemos que o navegador fez uma solicitação GET. Para fazer uma solicitação POST, vamos usar o cliente [curl]. Em uma janela do DOS, digitamos o seguinte comando:
para exibir os cabeçalhos HTTP da resposta | |
para enviar a informação param=valor por meio de um POST |
A resposta do servidor é a seguinte:
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Fri, 02 Apr 2004 09:27:25 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 178
Connection: Close
<html>
<head>
<title>Requête client</title>
</head>
<body>
Requête :
post
<br />
nom :
tintin
<br />
âge :
27
<br />
</body>
</html>
O servidor, mais uma vez, recuperou corretamente os parâmetros enviados, desta vez por um POST. Para confirmar esse último ponto, é possível verificar o conteúdo do arquivo [request.txt]:
POST /request1/main.aspx HTTP/1.1
Pragma: no-cache
Content-Length: 17
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4
nom=tintin&age=27
O cliente [curl] já executou um POST. Agora, vamos combinar os dois métodos de transmissão de informações. Colocamos [age] na URL solicitada e [nom] no documento enviado:
A solicitação enviada por [curl] é a seguinte (request.txt):
POST /request1/main.aspx?age=27 HTTP/1.1
Pragma: no-cache
Content-Length: 10
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4
nom=tintin
Percebe-se que a idade foi enviada na URL solicitada. Ela será obtida na coleção [QueryString]. O nome, por sua vez, foi enviado no documento enviado para essa URL. Ele será obtido na coleção [Form]. A resposta recebida pelo cliente [curl]:
<html>
<head>
<title>Requête client</title>
</head>
<body>
Requête :
post
<br />
nom :
tintin
<br />
âge :
27
<br />
</body>
</html>
Por fim, não vamos enviar nenhuma informação ao servidor:
E:\curl>curl --include http://localhost/request1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Fri, 02 Apr 2004 12:43:14 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 173
Connection: Close
<html>
<head>
<title>Requête client</title>
</head>
<body>
Requête :
get
<br />
nom :
xx
<br />
âge :
yy
<br />
</body>
</html>
Recomenda-se ao leitor que releia o código do controlador [main.aspx.vb] para compreender esta resposta.
4.2.4. Exemplo 2
É possível que o cliente envie vários valores para uma mesma chave. O que acontece, então, se, no exemplo anterior, solicitarmos a URL [http://localhost/request1/main.aspx?nom=tintin&age=27&nom=milou], na qual a chave [nom] aparece duas vezes? Vamos tentar com um navegador:

Nosso aplicativo recuperou corretamente os dois valores associados à chave [nom]. A exibição é um pouco enganosa. Ela foi obtida pela instrução
If Not Request.QueryString("nom") Is Nothing Then nom = Request.QueryString("nom").ToString
O método [ToString] gerou a sequência [tintin,milou], que foi exibida. Ela oculta o fato de que, na realidade, o objeto [Request.QueryString("nom")] é uma matriz de sequências de caracteres {"tintin","milou"}. O exemplo a seguir ilustra esse ponto. A página de apresentação [main.aspx] será a seguinte:
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
<HEAD>
<title>Requête client</title>
</HEAD>
<body>
<P>Informations passées par le client :</P>
<form runat="server">
<P>QueryString :</P>
<P><asp:listbox id="lstQueryString" runat="server" EnableViewState="False" Rows="6"></asp:listbox></P>
<P>Form :</P>
<P><asp:listbox id="lstForm" runat="server" EnableViewState="False" Rows="2"></asp:listbox></P>
</form>
</body>
</HTML>
Há novidades nesta página que utiliza o que chamamos de controles de servidor. Eles são identificados pelo atributo [runat="server"]. Ainda é cedo para introduzir o conceito de controle de servidor. Basta saber que, neste caso:
- a página possui duas listas (tags <asp:listbox>)
- que essas listas são objetos (lstQueryString, lstForm) do tipo [ListBox], que serão criados pelo controlador da página
- que esses objetos só existem no servidor web. No momento da resposta, eles serão transformados em tags HTML convencionais que o cliente poderá entender. Um objeto [listbox] será, assim, transformado (também chamado de renderizado) em tags HTML <select> e <option>.
- O principal objetivo desses objetos é livrar o código de apresentação de todo o código VB, que permanece restrito ao controlador.
O controlador [main.aspx.vb] responsável por construir os dois objetos [lstQueryString] e [lstForm] é o seguinte:
Imports System.Collections
Imports System
Imports System.Collections.Specialized
Public Class main
Inherits System.Web.UI.Page
Protected infosQueryString As ArrayList
Protected WithEvents lstQueryString As System.Web.UI.WebControls.ListBox
Protected WithEvents lstForm As System.Web.UI.WebControls.ListBox
Protected infosForm As ArrayList
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' a solicitação do cliente é armazenada em request.txt na pasta do aplicativo
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera-se todo o conjunto de informações do QueryString
infosQueryString = getValeurs(Request.QueryString)
lstQueryString.DataSource = infosQueryString
lstQueryString.DataBind()
infosForm = getValeurs(Request.Form)
lstForm.DataSource = infosForm
lstForm.DataBind()
End Sub
Private Function getValeurs(ByRef data As NameValueCollection) As ArrayList
' inicialmente, uma lista de informações vazia
Dim infos As New ArrayList
' recuperam-se as chaves da coleção
Dim clés() As String = data.AllKeys
' percorre-se a tabela de chaves
Dim valeurs() As String
For Each clé As String In clés
' valores associados à chave
valeurs = data.GetValues(clé)
' apenas um valor?
If valeurs.Length = 1 Then
infos.Add(clé + "=" + valeurs(0))
Else
' vários valores
For ivalue As Integer = 0 To valeurs.Length - 1
infos.Add(clé + "(" + ivalue.ToString + ")=" + valeurs(ivalue))
Next
End If
Next
' retorna o resultado
Return infos
End Function
End Class
Os pontos importantes desse código são os seguintes:
- no [Form_Load], a página recupera as duas coleções [QueryString] e [Form]. Ela utiliza uma função [getValeurs] para inserir o conteúdo dessas duas coleções em dois objetos do tipo [ArrayList], que conterão cadeias de caracteres do tipo [clé=valeur] se a chave da coleção estiver associada a um único valor, ou [clé(i)=valeur], caso a chave esteja associada a vários valores.
- Cada um dos objetos [ArrayList] é, em seguida, vinculado a um dos objetos [ListBox] da página de apresentação por meio de duas instruções:
- [ListBox.DataSource=ArrayList] e [ListBox.DataBind]. Esta última instrução transfere os elementos de [DataSource] para a coleção [Items] do objeto [ListBox]
note-se que nenhum dos dois objetos [ListBox] é criado explicitamente por uma operação [New]. Deduz-se, portanto, que, na presença da tag <asp:listbox id="xx">...<asp:listbox/>, o servidor web cria ele mesmo o objeto [ListBox] referenciado pelo atributo [id] da tag.
- A função [getValeurs] utiliza o objeto do tipo [NameValueCollection] que lhe é passado como parâmetro para produzir um resultado do tipo [ArrayList].
Colocamos os dois arquivos anteriores em uma pasta <application-path> e iniciamos o servidor Cassini com os parâmetros (<application-path>,/request2); em seguida, solicitamos a URL
[http://localhost/request2/main.aspx?nom=tintin&age=27]. Obtemos a seguinte resposta:

Agora acessamos uma URL em que a chave [nom] aparece duas vezes:

Constatamos que o objeto [Request.QueryString("nome")) era, de fato, um array. Aqui, as solicitações foram feitas por meio do método GET. Usamos o cliente [curl] para fazer uma consulta POST:
E:\curl>curl --data nom=milou --data nom=tintin --data age=14 --data age=27 http://localhost/request2/main.aspx
<HTML>
<HEAD>
<title>Requête client</title>
</HEAD>
<body>
<P>Informations passées par le client :</P>
<form name="_ctl0" method="post" action="main.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtMTI3MjA1MzUzMTs7PtCDC7NG4riDYIB4YjyGFpVAAviD" />
<P>QueryString :</P>
<P><select name="lstQueryString" size="6" id="lstQueryString">
</select></P>
<P>Form :</P>
<P><select name="lstForm" size="2" id="lstForm">
<option value="nom(0)=milou">nom(0)=milou</option>
<option value="nom(1)=tintin">nom(1)=tintin</option>
<option value="age(0)=14">age(0)=14</option>
<option value="age(1)=27">age(1)=27</option>
</select></P>
</form>
</body>
</HTML>
É possível observar que o cliente recebe, de fato, o código HTML padrão para as duas listas da página. Aparecem informações que não inserimos nós mesmos, como o campo oculto [_VIEWSTATE]. Essas informações foram geradas pelas tags <asp:xx runat="server">. Precisaremos aprender a dominá-las.
4.3. Implementação de uma arquitetura MVC
4.3.1. O conceito
Vamos encerrar este longo capítulo com a implementação de um aplicativo construído de acordo com o modelo MVC (Model-View-Controller). Um aplicativo web arquitetado segundo esse modelo se parece com isto:

- o cliente envia suas solicitações a uma entidade específica da aplicação chamada controlador
- o controlador analisa a solicitação do cliente e a executa. Para isso, ele conta com o auxílio de classes que agrupam a lógica de negócios da aplicação e de classes de acesso aos dados.
- dependendo do resultado da execução da solicitação, o controlador opta por enviar uma determinada página como resposta ao cliente
Em nosso modelo, todas as solicitações passam por um único controlador, que é o maestro de toda a aplicação web. A vantagem desse modelo é que podemos agrupar no controlador tudo o que precisa ser feito antes de cada solicitação. Suponhamos, por exemplo, que a aplicação exija uma autenticação. Essa autenticação é feita uma única vez. Uma vez bem-sucedida, a aplicação armazenará na sessão as informações relacionadas ao usuário que acabou de se autenticar. Como um cliente pode acessar diretamente uma página da aplicação sem se autenticar, cada página deverá, portanto, verificar na sessão se a autenticação foi efetivamente realizada. Se todas as solicitações passam por um único controlador, é ele quem pode realizar essa tarefa. As páginas para as quais a solicitação for eventualmente encaminhada não precisarão fazer isso.
4.3.2. Controlando uma aplicação MVC sem sessão
Pelo que vimos até agora, pode-se supor que o arquivo [global.asax] poderia desempenhar o papel de controlador. De fato, sabemos que todas as solicitações passam por ele. Ele está, portanto, bem posicionado para controlar tudo. A aplicação a seguir o utiliza para esse fim. Seu caminho virtual será [http://localhost/mvc1/main.aspx]. Para indicar o que deseja, o cliente incluirá na URL um parâmetro action=valor. Dependendo do valor do parâmetro [action], o controlador [global.asax] direcionará a solicitação para uma página específica:
- [main.aspx] se o parâmetro action não estiver definido ou se action=main
- [action1.aspx] se action=action1
- [inconnu.aspx] se action não se enquadrar nos casos 1 e 2
As páginas [main.aspx, action1.aspx, inconnu.aspx] limitam-se a exibir o valor de [action] que provocou sua exibição. Listamos abaixo os oito arquivos desta aplicação e os comentamos quando necessário:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' recuperamos a ação a ser realizada
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "main"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' colocamos a ação no contexto da solicitação
Context.Items("action") = action
' executa-se a ação
Select Case action
Case "main"
Server.Transfer("main.aspx", True)
Case "action1"
Server.Transfer("action1.aspx", True)
Case Else
Server.Transfer("inconnu.aspx", True)
End Select
End Sub
End Class
Pontos a serem observados:
- interceptamos todas as solicitações do cliente no procedimento [Application_BeginRequest], que é executado automaticamente ao início de cada nova solicitação feita ao aplicativo.
- Nesse procedimento, temos acesso ao objeto [Request], que é a representação da solicitação HTTP do cliente. Como esperamos uma URL no formato [http://localhost/mvc1/main.aspx?action=xx], procuramos uma chave [action] na coleção [Request.QueryString]. Se ela não estiver lá, definimos por padrão a ação como “main”.
- O valor do parâmetro [action] é inserido no objeto [Context]. Assim como os objetos [Application, Session, Request, Response, Server], esse objeto é global e acessível em qualquer código. Esse objeto é passado de página em página se a solicitação for processada por várias páginas, como será o caso aqui. Ele é excluído assim que a resposta for enviada ao cliente. Sua duração é, portanto, igual à do processamento da solicitação.
- De acordo com o valor do parâmetro [action], a solicitação é encaminhada para a página apropriada. Para isso, utiliza-se o objeto global [Server], que, graças ao seu método, permite transferir a solicitação atual para outra página. Seu primeiro parâmetro é o nome da página de destino; o segundo, um valor booleano que indica se as coleções [QueryString] e [Form] devem ou não ser transferidas para a página de destino. Neste caso, a resposta é sim.
Os arquivos [main.aspx] e [main.aspx.vb]:
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
<head>
<title>main</title></head>
<body>
<h3>Page [main]</h3>
Action : <% =action %>
</body>
</HTML>
Public Class main
Inherits System.Web.UI.Page
Protected action As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera-se a ação em andamento
action = Me.Context.Items("action").ToString
End Sub
End Class
O controlador [main.aspx.vb] limita-se a recuperar o valor da chave [action] no contexto, sendo esse valor exibido pelo código de apresentação. O objetivo aqui é mostrar a passagem do objeto [Context] entre diferentes páginas que processam a mesma solicitação do cliente. As páginas [action1.aspx] e [inconnu.aspx] funcionam de maneira semelhante:
[action1.aspx]
<%@ Page src="action1.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="action1" %>
<HTML>
<head>
<title>action1</title></head>
<body>
<h3>Page [action1]</h3>
Action : <% =action %>
</body>
</HTML>
[action1.aspx.vb]
Public Class action1
Inherits System.Web.UI.Page
Protected action As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera-se a ação em andamento
action = Me.Context.Items("action").ToString
End Sub
End Class
[inconnu.aspx]
<%@ Page src="inconnu.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="inconnu" %>
<HTML>
<head>
<title>inconnu</title></head>
<body>
<h3>Page [inconnu]</h3>
Action : <% =action %>
</body>
</HTML>
[inconnu.aspx.vb]
Public Class inconnu
Inherits System.Web.UI.Page
Protected action As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera-se a ação em andamento
action = Me.Context.Items("action").ToString
End Sub
End Class
Para testar, os documentos anteriores são colocados em uma pasta <application-path> e o Cassini é iniciado com os parâmetros (<application-path>,/mvc1). Acessamos a URL [http://localhost/mvc1/main.aspx]:

A solicitação não enviou nenhum parâmetro [action]. O código do controlador da aplicação [global.asax.vb] gerou a página [main.aspx]. Agora estamos solicitando a URL [http://localhost/mvc1/main.aspx?action=action1]:

O código do controlador da aplicação [global.asax.vb] gerou a página [action1.aspx]. Agora solicitamos a URL [http://localhost/mvc1/main.aspx?action=xx]:

A ação não foi reconhecida e o controlador [global.asax.vb] gerou a página [inconnu.aspx].
4.3.3. Controlar um aplicativo MVC com sessão
Na maioria das vezes, as diferentes solicitações de um cliente para um aplicativo precisam compartilhar informações. Vimos uma solução possível para esse problema: armazenar as informações a serem compartilhadas no objeto [Session] da solicitação. Esse objeto é, de fato, compartilhado por todas as solicitações e é capaz de armazenar informações no formato (chave, valor), em que a chave é do tipo [String] e o valor é de qualquer tipo derivado de [Object].
No exemplo anterior, as diferentes páginas associadas às diversas ações eram chamadas no procedimento [Application_BeginRequest] do arquivo [global.asax.vb]:
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' recupera-se a ação a ser executada
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "main"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' coloca-se a ação no contexto da solicitação
Context.Items("action") = action
' executa-se a ação
Select Case action
Case "main"
Server.Transfer("main.aspx", True)
Case "action1"
Server.Transfer("action1.aspx", True)
Case Else
Server.Transfer("inconnu.aspx", True)
End Select
End Sub
Verifica-se que, no procedimento [Application_BeginRequest], o objeto [Session] não está acessível. O mesmo ocorre na página para a qual a execução é transferida. Portanto, esse modelo não pode ser utilizado em um aplicativo com sessão. Podemos atribuir a função de controlador a qualquer página, por exemplo, [default.aspx]. Os arquivos [global.asax, global.asax.vb] são então removidos e substituídos pelos arquivos [default.aspx, default.aspx.vb]:
[default.aspx]
[default.aspx.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Public Class controleur
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera-se a ação a ser realizada
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "main"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' coloca-se a ação no contexto da solicitação
Context.Items("action") = action
' recupera-se a ação anterior, se houver
Context.Items("actionPrec") = Session.Item("actionPrec")
If Context.Items("actionPrec") Is Nothing Then Context.Items("actionPrec") = ""
' a ação atual é armazenada na sessão
Session.Item("actionPrec") = action
' executa-se a ação
Select Case action
Case "main"
Server.Transfer("main.aspx", True)
Case "action1"
Server.Transfer("action1.aspx", True)
Case Else
Server.Transfer("inconnu.aspx", True)
End Select
End Sub
End Class
Para destacar o mecanismo de sessão, as diferentes páginas exibirão, além da ação atual, a ação que a precedeu. Para uma sequência de ações A1, A2, ..., An, quando a ação Ai ocorre, o controlador acima:
- insere a ação atual Ai no contexto
- recupera na sessão a ação Ai-1 que a precedeu. Caso não haja nenhuma (como no caso da ação A1), a cadeia fica vazia para a ação anterior.
- insere a ação atual Ai na sessão, substituindo Ai-1
- transfere a execução para a página adequada
As três páginas do aplicativo são as seguintes:
[main.aspx]
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
<HEAD>
<title>main</title>
</HEAD>
<body>
<h3>Page [main]</h3>
Action courante :
<% =action %>
<br>
Action précédente :
<% =actionPrec %>
</body>
</HTML>
[action1.aspx]
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
<head>
<title>action1</title></head>
<body>
<h3>Page [action1]</h3>
Action courante :
<% =action %>
<br>
Action précédente :
<% =actionPrec %>
</body>
</HTML>
[inconnu.aspx]
<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
<head>
<title>inconnu</title>
</head>
<body>
<h3>Page [inconnu]</h3>
Action courante :
<% =action %>
<br>
Action précédente :
<% =actionPrec %>
</body>
</HTML>
Como as três páginas exibem as mesmas informações [action, actionPrec], todas elas podem ter o mesmo controlador de página. Portanto, todas foram derivadas da classe [main] do arquivo [main.aspx.vb]:
Public Class main
Inherits System.Web.UI.Page
Protected action As String
Protected actionPrec As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera-se a ação em andamento
action = Me.Context.Items("action").ToString
' e a ação anterior
actionPrec = Me.Context.Items("actionPrec").ToString
End Sub
End Class
O código acima se limita a recuperar as informações inseridas no contexto pelo controlador da aplicação [default.aspx.vb].
Todos esses arquivos estão localizados em <application-path> e o Cassini é iniciado com os parâmetros (<application-path>,/mvc2). Primeiro, solicitamos a URL [http://localhost/mvc2]:

A URL [http://localhost/mvc2] indica uma pasta. Sabemos que, nesse caso, é o documento [default.aspx] dessa pasta que é retornado pelo servidor, caso exista. Aqui, nenhuma ação foi especificada. Portanto, foi executada a ação [main]. Passemos para a ação [action1]:

A ação atual e a ação anterior foram identificadas corretamente. Passemos para a ação [xx]:

4.4. Conclusion
Agora temos os elementos básicos a partir dos quais toda aplicação ASP.ET é construída. No entanto, ainda precisamos introduzir um conceito importante: o de formulário. Esse é o tema do capítulo a seguir.