Introdução
Em um ambiente sem estado como um aplicativo Web, compreender o conceito de estado de sessão não tem significado real. No entanto, o gerenciamento eficaz do estado é um recurso obrigatório para a maioria das aplicações Web. O Microsoft ASP.NET, assim como muitos outros ambientes de programação do lado do servidor, fornecem uma camada de abstração que permite que os aplicativos armazenem dados persistentes por usuário e por aplicativo.
É importante observar que o estado da sessão de um aplicativo Web são os dados que o aplicativo armazena em cache e recupera em diferentes solicitações. Uma sessão representa todas as solicitações enviadas pelo usuário enquanto conectado ao site, e o estado da sessão é a coleção de dados persistentes gerados e consumidos pelo usuário durante a sessão. O estado de cada sessão é independente um do outro e deixa de existir quando a sessão do usuário termina.
O estado da sessão não tem correspondência com nenhuma das entidades lógicas que compõem o protocolo e a especificação HTTP. As sessões são uma camada de abstração construída por ambientes de desenvolvimento do lado do servidor, como ASP tradicional e ASP.NET. A forma como o ASP.NET exibe o estado da sessão e como o estado da sessão é implementado internamente depende da infraestrutura da plataforma. Portanto, o ASP tradicional e o ASP.NET implementam o estado da sessão de maneiras completamente diferentes, e mais melhorias e aprimoramentos são esperados na próxima versão do ASP.NET.
Este artigo discute como implementar o estado da sessão no ASP.NET 1.1 e como otimizar o gerenciamento do estado da sessão em aplicativos Web gerenciados.
Visão geral do estado da sessão ASP.NET
O estado da sessão não faz parte da infraestrutura HTTP. Ou seja, deve haver um componente estrutural que vincule o estado da sessão a cada solicitação recebida. O ambiente de execução (ASP tradicional ou ASP.NET) pode aceitar palavras-chave como Session e utilizá-las para indicar o bloco de dados armazenado no servidor. Para resolver chamadas com êxito para um objeto Session, o ambiente de tempo de execução deve adicionar o estado da sessão ao contexto de chamada da solicitação que está sendo processada. A forma como isso é feito varia entre plataformas, mas é fundamental para aplicações web com estado.
No ASP tradicional, o estado da sessão é implementado como objetos COM de thread livre contidos na biblioteca asp.dll. (Você está curioso? O CLSID deste objeto é na verdade D97A6DA0-A865-11cf-83AF-00A0C90C2BD8.) Este objeto armazena dados organizados como uma coleção de pares nome/valor. O espaço reservado “nome” representa a chave usada para recuperar as informações, enquanto o espaço reservado “valor” representa o que está armazenado no estado da sessão. Os pares nome/valor são agrupados por ID de sessão para que cada usuário veja apenas os pares nome/valor que ele criou.
No ASP.NET, a interface de programação para o estado da sessão é quase a mesma do ASP tradicional. Mas as suas implementações básicas são completamente diferentes. A primeira é mais flexível, escalável e tem capacidades de programação mais fortes do que a segunda. Antes de nos aprofundarmos no estado da sessão do ASP.NET, vamos revisar brevemente alguns dos recursos estruturais da infraestrutura de sessão do ASP.NET.
No ASP.NET, qualquer solicitação HTTP recebida é canalizada através do módulo HTTP. Cada módulo pode filtrar e modificar a grande quantidade de informações transportadas pela solicitação. A informação associada a cada solicitação é chamada de “contexto de chamada”, que é representado pelo objeto HttpContext na programação. Não devemos pensar no contexto da solicitação como outro contêiner de informações de estado, embora a coleção de itens que ele fornece seja apenas um contêiner de dados. O objeto HttpContext difere de todos os outros objetos de estado (por exemplo, Sessão, Aplicativo e Cache) porque tem um tempo de vida limitado além do tempo necessário para tratar a solicitação. Quando uma solicitação passa por uma série de módulos HTTP registrados, seu objeto HttpContext conterá uma referência ao objeto de estado. Quando a solicitação pode finalmente ser processada, o contexto de chamada associado é vinculado à sessão específica (Sessão) e aos objetos de estado global (Aplicativo e Cache).
O módulo HTTP responsável por definir o estado da sessão de cada usuário é SessionStateModule. A estrutura deste módulo é projetada com base na interface IHttpModule, que fornece um grande número de serviços relacionados ao estado de sessão para aplicativos ASP.NET. Inclui a geração de IDs de sessão, gerenciamento de sessão sem cookies, recuperação de dados de sessão de provedores de estado externos e vinculação de dados ao contexto de chamada da solicitação.
O módulo HTTP não armazena dados de sessão internamente. O estado da sessão é sempre salvo em um componente externo denominado "provedor de estado". O provedor de estado encapsula completamente os dados do estado da sessão e se comunica com outras partes por meio dos métodos da interface IStateClientManager. O módulo HTTP do estado da sessão chama métodos nesta interface para ler e salvar o estado da sessão. O ASP.NET 1.1 oferece suporte a três provedores de estado diferentes, conforme mostrado na Tabela 1.
Tabela 1: Status Cliente Provedor
Descrição do Provedor
Os valores da sessão InProc permanecem objetos ativos na memória do processo de trabalho ASP.NET (aspnet_wp.exe ou w3wp.exe no Microsoft® Windows Server® 2003). Esta é a opção padrão.
Os valores da sessão StateServer são serializados e armazenados na memória em um processo separado (aspnet_state.exe). O processo também pode ser executado em outros computadores.
Os valores da sessão SQLServer são serializados e armazenados em tabelas do Microsoft® SQL Server®. As instâncias do SQL Server podem ser executadas local ou remotamente.
O módulo HTTP do estado da sessão lê o provedor de estado atualmente selecionado na seção
O código acima realmente acessa o valor da sessão criado pelo módulo HTTP na memória local. lê dados de um provedor estatal específico (veja a Figura 1). O que acontece se outras páginas também tentarem acessar o estado da sessão de forma síncrona? Nesse caso, a solicitação atual pode parar de processar dados inconsistentes ou obsoletos. Para evitar isso, o módulo de estado da sessão implementará um mecanismo de bloqueio de leitor/gravador e enfileirará o acesso aos valores de estado. As páginas com permissões de gravação no estado da sessão manterão o bloqueio de gravação dessa sessão até que a solicitação termine. Uma página pode solicitar permissão de gravação para o estado da sessão definindo a propriedade EnableSessionState da diretiva @Page como verdadeira. (Esta é a configuração padrão). No entanto, uma página também pode ter acesso somente leitura ao estado da sessão, por exemplo, quando a propriedade EnableSessionState está definida como ReadOnly. Neste caso, o módulo manterá o bloqueio do leitor para aquela sessão até que a solicitação daquela página termine. Como resultado, ocorrerão leituras simultâneas. Se uma solicitação de página definir um bloqueio de leitor, outras solicitações simultâneas na mesma sessão não poderão atualizar o estado da sessão, mas pelo menos poderão ler. Ou seja, se uma solicitação somente leitura estiver sendo processada para uma sessão, a solicitação somente leitura pendente terá prioridade mais alta do que uma solicitação que exija acesso total. Se uma solicitação de página definir um bloqueio de gravação para o estado da sessão, todas as outras páginas serão bloqueadas, independentemente de desejarem ler ou gravar conteúdo. Por exemplo, se dois quadros tentarem escrever na Sessão ao mesmo tempo, um quadro deverá esperar até que o outro termine antes de poder escrever. Comparando provedores de estado Por padrão, os aplicativos ASP.NET armazenam o estado da sessão na memória de um processo de trabalho, especificamente em um slot dedicado do objeto Cache. Quando o modo InProc é selecionado, o estado da sessão é armazenado em slots no objeto Cache. Este slot está marcado como privado e não pode ser acessado programaticamente. Em outras palavras, se você enumerar todos os itens no cache de dados do ASP.NET, nenhum objeto semelhante ao estado da sessão fornecido será retornado. Os objetos de cache fornecem dois tipos de slots: slots privados e slots públicos. Os programadores podem adicionar e manipular slots públicos, mas os slots privados só podem ser usados pelo sistema (especificamente, classes definidas na system.web part). O estado de cada sessão ativa ocupa um slot dedicado no cache. O slot é nomeado com base no ID da sessão e seu valor é uma instância de uma classe interna não declarada chamada SessionStateItem. O provedor de estado InProc obtém o ID da sessão e recupera o elemento correspondente no cache. O conteúdo do objeto SessionStateItem é então inserido no objeto de dicionário HttpSessionState e acessado pelo aplicativo por meio da propriedade Session. Observe que há um bug no ASP.NET 1.0 que torna os slots privados do objeto Cache enumeráveis programaticamente. Se você executar o código a seguir no ASP.NET 1.0, poderá enumerar os itens correspondentes aos objetos contidos em cada estado de sessão atualmente ativo. foreach (elemento de entrada de dicionário no cache) Este bug foi resolvido no ASP.NET 1.1 e quando você enumera o conteúdo em cache, nenhum slot do sistema será listado mais. InProc é provavelmente a opção de acesso mais rápida, de longe. Mas tenha em mente que quanto mais dados armazenados em uma sessão, mais memória o servidor web consome, aumentando potencialmente o risco de degradação do desempenho. Se você planeja usar qualquer solução fora do processo, considere cuidadosamente os possíveis efeitos da serialização e desserialização. A solução fora do processo usa um serviço do Windows NT (aspnet_state.exe) ou uma tabela do SQL Server para armazenar valores de sessão. Portanto, o estado da sessão permanece fora do processo de trabalho do ASP.NET e são necessárias camadas adicionais de código para serializar e desserializar entre o estado da sessão e o meio de armazenamento real. Isso acontece sempre que uma solicitação é processada e deve ser otimizada ao mais alto nível. Como os dados da sessão precisam ser copiados do repositório externo para o dicionário da sessão local, a solicitação resulta em degradação de desempenho que varia de 15% (fora do processo) a 25% (SQL Server). Note-se que embora esta seja apenas uma estimativa aproximada, deverá estar próxima do impacto mínimo e o impacto máximo será muito superior a este. Na verdade, esta estimativa não leva totalmente em conta a complexidade dos tipos realmente salvos no estado da sessão. No cenário de armazenamento fora do processo, o estado da sessão sobrevive por mais tempo, tornando o aplicativo mais poderoso porque protege contra falhas do Microsoft® Internet Information Services (IIS) e do ASP.NET. Ao separar o estado da sessão dos aplicativos, você também pode estender mais facilmente os aplicativos existentes para arquiteturas Web Farm e Web Garden. Além disso, o estado da sessão é armazenado em um processo externo, eliminando essencialmente o risco de perda periódica de dados devido a loops de processo. Veja como usar os serviços do Windows NT. Conforme mencionado acima, o serviço NT é um processo denominado aspnet_state.exe, geralmente localizado na pasta C:WINNTMicrosoft.NETFrameworkv1.1.4322. O diretório real depende da versão do Microsoft® .NET Framework que você está executando. Antes de usar o servidor de estado, você deve garantir que o serviço esteja pronto e em execução no computador local ou remoto usado como dispositivo de armazenamento de sessão. O serviço de estado faz parte e é instalado com o ASP.NET, portanto, você não precisa executar um instalador adicional. Por padrão, o serviço de status não está em execução e precisa ser iniciado manualmente. O aplicativo ASP.NET tentará estabelecer uma conexão com o servidor de estado imediatamente após ser carregado. Portanto, o serviço deve estar pronto e em execução, caso contrário uma exceção HTTP será lançada. A imagem a seguir mostra a caixa de diálogo de propriedades do serviço. Os aplicativos ASP.NET precisam especificar o endereço TCP/IP do computador onde o serviço de estado da sessão está localizado. As configurações a seguir devem ser inseridas no arquivo web.config do aplicativo. <configuração>; O atributo stateConnectionString contém o endereço IP do computador e a porta usada para troca de dados. O endereço padrão do computador é 127.0.0.1 (localhost) e a porta padrão é 42424. Você também pode indicar o computador pelo nome. Usar um computador local ou remoto é totalmente transparente para o código. Observe que caracteres não ASCII não podem ser usados no nome e o número da porta é obrigatório. Se você usar o armazenamento de sessão fora do processo, o estado da sessão ainda existirá e estará disponível para uso futuro, independentemente do que acontecer com o processo de trabalho do ASP.NET. Se o serviço for interrompido, os dados serão retidos e recuperados automaticamente quando o serviço for restaurado. Entretanto, se o serviço do provedor de status parar ou falhar, os dados serão perdidos. Se você deseja que seu aplicativo seja poderoso, use o modo SQLServer em vez do modo StateServer. <configuração>; Você pode especificar a string de conexão através do atributo sqlConnectionString. Observe que a sequência de atributos deve conter o ID do usuário, a senha e o nome do servidor. Ele não pode conter tags como Banco de Dados e Catálogo Inicial porque o padrão dessas informações é um nome fixo. IDs de usuário e senhas podem ser substituídas por configurações de segurança integradas. Como criar um banco de dados? O ASP.NET fornece dois pares de scripts para configurar o ambiente do banco de dados. O primeiro par de scripts é denominado InstallSqlState.sql e UninstallSqlState.sql e está localizado na mesma pasta que o serviço Session State NT. Eles criam um banco de dados chamado ASPState e vários procedimentos armazenados. No entanto, os dados são armazenados no banco de dados TempDB da área de armazenamento temporário do SQL Server. Isso significa que se o computador SQL Server for reiniciado, os dados da sessão serão perdidos. Para contornar essa limitação, use um segundo par de scripts. O segundo par de scripts é denominado InstallPersistSqlState.sql e UninstallPersistSqlState.sql. Neste caso, o banco de dados ASPState é criado, mas as tabelas são criadas no mesmo banco de dados e também são persistentes. Ao instalar o suporte do SQL Server para sessões, também é criado um trabalho para excluir sessões expiradas no banco de dados de estado da sessão. A tarefa é denominada ASPState_Job_DeleteExpiredSessions e está sempre em execução. Observe que para que este trabalho funcione corretamente, o serviço SQLServerAgent precisa estar em execução. Não importa o modo escolhido, a forma como as operações do estado da sessão são codificadas não muda. Você sempre pode trabalhar na propriedade Session e ler e escrever valores normalmente. Todas as diferenças de comportamento são tratadas em um nível inferior de abstração. A serialização de estado é talvez a diferença mais importante entre os modos de sessão. Serialização e desserialização de estado Ao usar o modo em processo, os objetos são armazenados no estado de sessão como instâncias ativas de suas respectivas classes. Se nenhuma serialização e desserialização reais ocorrerem, significa que você pode realmente armazenar qualquer objeto criado na Sessão (incluindo objetos que não podem ser serializados e objetos COM), e acessá-los não será muito caro. Se você escolher um provedor estatal fora do processo, a história é outra. Em uma arquitetura fora de processo, os valores da sessão são copiados da mídia de armazenamento local (banco de dados AppDomain externo) para a memória do AppDomain que trata a solicitação. Uma camada de serialização/desserialização é necessária para realizar esta tarefa e representa um dos principais custos dos provedores de estado fora de processo. O principal impacto que esta situação tem no seu código é que apenas objetos serializáveis podem ser armazenados no dicionário de sessão. O ASP.NET usa dois métodos para serializar e desserializar dados, dependendo do tipo de dados envolvidos. Para tipos básicos, o ASP.NET usa um serializador interno otimizado; para outros tipos, incluindo objetos e classes definidas pelo usuário, o ASP.NET usa o formatador binário .NET. Os tipos básicos incluem strings, datas e horas, valores booleanos, bytes, caracteres e todos os tipos numéricos. Para esses tipos, usar um serializador personalizado é mais rápido do que usar o formatador binário .NET comum padrão. O serializador otimizado não é divulgado ou documentado publicamente. É apenas um leitor/gravador binário e usa uma arquitetura de armazenamento simples, mas eficaz. O serializador usa a classe BinaryWriter para escrever uma representação de bytes do tipo e, em seguida, grava uma representação de bytes do valor correspondente a esse tipo. Ao ler bytes serializados, a classe primeiro extrai um byte, detecta o tipo de dados a ser lido e, em seguida, chama o método ReadXxx específico do tipo na classe BinaryReader. Observe que o tamanho dos tipos booleanos e numéricos é bem conhecido, mas não para strings. No fluxo de dados subjacente, a string é sempre prefixada com um comprimento fixo (um código inteiro de 7 bits escrito por vez), e o leitor usa esse fato para determinar o tamanho correto da string. O valor da data é salvo escrevendo apenas o número total de tokens que compõem a data. Portanto, para serializar a sessão, a data deverá ser do tipo Int64. Você pode usar a classe BinaryFormatter para executar operações de serialização em objetos mais complexos (bem como em objetos personalizados), desde que a classe que os contém esteja marcada como serializável. Todos os tipos não básicos são identificados pelo mesmo ID de tipo e armazenados no mesmo fluxo de dados que os tipos básicos. No geral, as operações de serialização podem resultar em uma degradação de desempenho de 15% a 25%. Observe, entretanto, que esta é uma estimativa aproximada baseada na suposição de que tipos básicos são usados. Quanto mais complexos forem os tipos usados, maior será a sobrecarga. O armazenamento eficiente de dados de sessão é difícil de implementar sem o uso extensivo de tipos primitivos. Portanto, pelo menos em teoria, usar três slots de sessão para salvar três propriedades de string diferentes de um objeto é melhor do que serializar o objeto inteiro. Mas e se o objeto que você deseja serializar contiver 100 propriedades? Você quer usar 100 slots ou apenas um slot? Em muitos casos, uma abordagem melhor é converter um tipo complexo em vários tipos mais simples. Esta abordagem é baseada em conversores de tipo. Um "conversor de tipo" é um serializador leve que retorna as principais propriedades de um tipo como uma coleção de strings. Conversores de tipo são classes externas vinculadas a uma classe base usando atributos. Cabe ao gravador decidir quais propriedades serão salvas e como. Os conversores de tipo também são úteis para armazenamento ViewState e representam um método mais eficiente de armazenamento de sessão do que formatadores binários. Ciclo de vida da sessão Um ponto importante sobre o gerenciamento de sessões do ASP.NET é que o ciclo de vida do objeto de estado da sessão começa somente quando o primeiro item é adicionado ao dicionário na memória. Uma sessão ASP.NET é considerada iniciada somente após a execução do trecho de código a seguir. Session["MySlot"] = "Alguns dados"; O dicionário Session geralmente contém o tipo Object. Para ler os dados de trás para frente, o valor retornado precisa ser convertido para um tipo mais específico. string data = (string) Session["MySlot"]; Quando a página salva dados na Sessão, o valor será carregado na classe de dicionário especialmente criada contida na classe HttpSessionState. O conteúdo do dicionário é carregado no provedor de estado quando a solicitação atualmente processada é concluída. Se o estado da sessão estiver vazio porque os dados não foram colocados no dicionário programaticamente, os dados não serão serializados no meio de armazenamento e, mais importante, não serão servidos no ASP.NET Cache, SQL Server ou NT State Services Create um slot para rastrear a sessão atual. Isto ocorre por motivos de desempenho, mas tem um impacto importante em como os IDs de sessão são tratados: um novo ID de sessão será gerado para cada solicitação até que alguns dados sejam armazenados no dicionário de sessão. Quando é necessário conectar o estado da sessão com uma solicitação que está sendo processada, o módulo HTTP recupera o ID da sessão (se não for a solicitação inicial) e procura-o no provedor de estado configurado. Se nenhum dado for retornado, o módulo HTTP gera um novo ID de sessão para a solicitação. Isso pode ser facilmente testado com a seguinte página: <%@ Page Language="C#" Trace="true" %>;
{
Response.Write(elem.Key + ": " + elem.Value.ToString());
}
Figura 2: Caixa de diálogo Propriedades do servidor de estado do ASP.NET
stateConnectionString="tcpip=expoware:42424" />;
</system.web>;
;
sqlConnectionString="servidor=127.0.0.1;uid=<id do usuário>;;pwd=<senha>;;"
</system.web>;
;
</html>;