ASP .NET - Resolvendo o problema de estado da sessão


Neste artigo vamos abordar duas maneiras de resolver o problema de estado da sessão.

O que quer dizer problema de estado da sessão ?

Quando o usuário de um web site (geralmente comércio eletrônico) clica no botão do navegador para retornar para página ASP .NET anterior de um formulário e então posta o formulário , o estado da sessão da aplicação pode não corresponder ao do formulário.

O problema é mais fácil de entender em um cenário de carrinho de compras.

Para isso eu vou usar como exemplo o carrinho de compras do meu artigo: ASP .NET - Criando um carrinho de compras II (VB .NET)

Neste cenário o carrinho de compras de um usuário é armazenado na sessão (geralmente) exibindo os itens selecionados:

O cliente adicionou dois itens ao seu carrinho que é armazenado na sessão,

e contém os itens Ipad 16 e NoteBook LG conforme mostra a figura ao lado.

 

Quando o cliente, após realizar selecionar alguns itens, deleta um dos itens do carrinho, temos uma alteração dos dados na sessão.

O usuário removeu o item NoteBook Lg do carrinho de compras.

Temos agora armazenado na sessão o carrinho com um item.

Se o cliente mudar de idéia e clicar no botão retornar do navegador, todos os itens serão exibidos novamente, mesmo que o estado da sessão contenha somente um item.

O cliente clicou no botão retornar do navegador e a página voltou a exibir os dois itens

existentes no carrinho, mas na sessão o carrinho atual possui somente um item.

Se neste momento o cliente encerrar as compras (checkout) o pedido irá mostrar apenas um item quando no formulário temos exibido um número diferente o que pode confundir o cliente. (Isso se quando ele clicar no botão do navegador para retornar não ocorrer um erro.)

Para tentar resolver este problema podemos adotar um dos seguintes procedimentos :

Vamos analisar cada uma das opções citadas.

1 - Desabilitar o cache de páginas do Navegador

O problema :

Quando o usuário clica no botão do navegador para retornar para a página anterior o navegador retorna a página anterior localizada no cache local do navegador sem notificar o servidor e como resultado as informações armazenadas no estado da sessão podem estar fora de sincronia com os dados exibidos pela página atual.

A solução :

Desabilitar o cache de páginas do navegador

Como fazer :

Para desabilitar o cache do navegador em aplicações ASP .NET podemos usar os seguintes métodos:

Método Descrição
Response.Cache.SetCacheability Indica como a página deverá ser armazenada no cache. Para suprimir o cache devemos usar: HttpCacheability.NoCache
Response.Cache.SetExpires Especifica quando o cache da página expira. Usar Now().AddSeconds(-1) para marcar a página como já expirada.
Response.Cache.SetNoStore Define que o navegador não utilize o cache.
Response.AppendHeader Inclui um cabeçalho no objeto response HTTP. Especificando "Pragma" para a chave e "no-cache" para o valor desabilita o cache.

Exemplo:

Response.Cache.SetCacheability(HttpCacheability.NoCache)
Response.Cache.SetExpires(Now().AddSeconds(-1)
)
Response.Cache.SetNoStore()
Response.AppendHeader(
"Pragma" , "no-cache")

Todos esses métodos funcionam incluindo informação no cabeçalho HTTP que são enviados ao navegador com a página.

Infelizmente alguns navegadores ignoram esses cabeçalhos , de forma que esta técnica não garante que as páginas não serão armazenadas no cache para todos os navegadores.

Mesmo assim não é uma má idéia adotar incluir o código do exemplo no evento Load das páginas ASP .NET com informações importantes que não deverão estar no cache.
2 - Usar timestamps ou números aleatórios para monitorar as páginas

O problema :

Quando o usuário clica no botão do navegador para retornar para a página anterior o navegador retorna a página anterior localizada no cache local do navegador sem notificar o servidor e como resultado as informações armazenadas no estado da sessão podem estar fora de sincronia com os dados exibidos pela página atual.

A solução :

Usar timestamps ou números aleatórios para monitorar as páginas de forma a identificar quando a página não é a atual - O objetivo é monitorar as páginas dos formulários críticos da aplicação via código de forma a detectar quando o cliente tenta postar uma página que não é a página atual. Para fazer isso uma das maneiras é usar timestamps ou números aleatórios para rastrear a utilização das páginas;

Como fazer :

A técnica básica para implementar esta solução é armazenar um timestamp em dois locais distintos quando a página é postada: no View State e na Session State.

  1. O timestamp do View State é enviado de volta ao navegador e armazenado no cache com o resto da informação na página;
  2. O timestamp da Session é armazenado no servidor;

- Assim quando o usuário postar a página pela segunda vez o evento Page_Load chama uma função privada chamada EstaExpirada;
- Esta função retorna o
timestamp do View State e o timestamp da Session comparando-os;
- Se eles forem idênticos a página é a página atual e a função EstaExpirada retorna False;
- Se os valores forem diferentes isto indica que o cliente postou uma página que estava no cache usando o botão retornar do navegador;
- Neste caso a função EstaExpirada retorna True e o evento Page_Load redireciona o cliente para uma página chamada Expirada.aspx;
- A página Expirada.aspx
exibe uma mensagem indicando que a página esta desatualizada e não pode ser postada;

Obs: Antes de realizar a comparação dos timestamps no View State e na Session a função EstaExpirada verifica se ambos os itens existem. Se eles não existirem a função retorna False de forma que os timestamps atuais possam ser salvos tanto no View State e na Session.

Outro detalhe importante é que como a página precisa ser postada de volta ao servidor você não pode usar a propriedade PostBackUrl do controle Button para exibir outra página se você desejar primeiro verificar você desejar verificar que a página atual não expirou.

Exemplo de código: (Este código deverá estar na página que verifica o timestamp)

Partial Class Carrinho
    Inherits System.Web.UI.Page

    Private carrinho As ItemLista
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  
        if EstaExpirada() Then
            Response.Redirect("Expirada.aspx")
        Else
            Me.SalvarTimeStamps()
        End If

        carrinho = ItemLista.GetCarrinho

        If Not IsPostBack Then
            Me.ExibeCarrinho()
        End If
    End Sub

    Private Function EstaExpirada() As Boolean
       If Session("Carrinho_TimeStamp") Is Nothing Then
             Return False
       ElseIf ViewState("TimeStamp") Is Nothing Then
             Return False
       ElseIf ViewState("TimeStamp").ToString = Session("Carrinho_TimeStamp").ToString Then
             Return False
       Else
             Return True
       End If
   End Function  

   Private Sub SalvarTimeStamps()
     Dim dtm As DateTime = DateTime.Now
     ViewState.Add("TimeStamp" , dtm)
     Session.Add("Carrinho_TimeStamp", dtm)
   End Sub

End Class

Existem outras técnicas para contornar o problema mas as duas abordadas são bem simples e de fácil implementação.

"Passará o céu e a terra, mas as minhas palavras jamais passarão." (Mateus 24:35)

Referências:


José Carlos Macoratti