Persistindo o estado de uma página Web

 

A história completa sobre como persistir o estado de uma página Web ASP.NET, incluindo o ViewState, o Form e os dados QueryString, via emulação de PostBack.

Resumo

Uma necessidade muito comum para qualquer aplicação, consiste em exibir uma página, permitir ao usuário abrir uma sub-página, e voltar para página original ("pai") quando a sub-página for fechada. Um exemplo típico seria a escolha de um item de uma lista, exibi-lo ou editá-lo, e voltar à mesma posição na lista onde começamos. Isto não é nada demais em uma aplicação client-server tradicional, mas torna-se bastante difícil de fazer em uma aplicação Web .NET usando o ASP.NET Framework.

 

O que queremos é que a página se comporte normalmente em uma situação de postback normal, mas se a página for exibida sem um postback, queremos enganá-la para que aceite nossa informação de postback armazenada, como um postback real. Este artigo explica como atingirmos isto, do modo menos evasivo possível.

Objetivo

Por ser esta uma exigência freqüente em qualquer aplicação, ficamos surpresos pelo fato de não ser suportado pelo ASP.NET Framework, mais surpresos ainda de não termos achado nenhuma referência sobre um método satisfatório de atingirmos isto. Achamos muitas sugestões vagas sobre como sobrepor os métodos Page.LoadPageStateFromPersistenceMedium() e o Page.SavePageStateToPersistenceMedium(viewState), e embora isto seja parte da solução, há muito mais trabalho a ser feito para atingirmos os resultados desejados.

 

Depois de muito procurar, achamos um artigo do Paul Wilson chamado "ViewState: Restore after Redirect". O artigo provê a base para boa parte da solução apresentada aqui, mas tem uma falha importante que será discutida adiante. Depois de desenvolver nossas próprias melhorias para a solução dele, decidimos escrever este artigo para prover uma referência única, consistente e fácil de achar sobre o assunto.

Vá até a solução, caso apenas queira usar a funcionalidade, caso contrário leia este artigo para obter uma explicação de como funciona...

Solução Tentativa

A suposição imediata para a maioria das pessoas, inclusive nós, quando confrontados com este problema é que o estado de uma página pode ser completamente reconstruído usando o ViewState. Porém, este não é o caso.

O problema com ViewState

Ainda existe muita confusão sobre o que o ViewState contém, e como armazenar e acessar a informação. Isto provavelmente é devido à pobre documentação provida sobre o assunto e as muitas estruturas de dados obscuras utilizadas para representá-lo. A propriedade Page.ViewState representa isto usando uma classe chamada System.Web.UI.StateBag, o método Page.LoadPageStateFromPersistenceMedium() nos deixa adivinhando ao digitarmos isto como um objeto genérico, embora seja de fato uma árvore com uma raiz do tipo System.Web.UI.Triplet, e o método Control.SaveViewState(), usa uma representação diferente novamente, a qual até onde pudéssemos determinar, é de fato um objeto genérico que só tem que ser compatível com o correspondente método Control.LoadViewState(object).

O string VIEWSTATE, representa o objeto Page.LoadPageStateFromPersistenceMedium() que foi serializado utilizando a classe LosFormatter. Estávamos quase desistindo, depois de peneirarmos esta má referencia cruzada da documentação, pensando que o ViewState é obviamente algo que a Microsoft não quer que a gente mexa por qualquer razão.

 

Há outros bons artigos que apresentam estruturas de dados ViewState, mas as boas notícias são que realmente não temos que entendê-los. Uma coisa que é importante saber, porém, é que o ViewState não contém toda a informação exigida para reconstruir o estado de uma página, ao contrário do que poderíamos pensar. Contém toda a informação com exceção dos dados que estão disponíveis na coleção Request.Form, à qual iremos nos referir neste artigo e no código como PostData.

 

Tampouco contém quaisquer dados providos no string de consulta da requisição, a qual é a lista de parâmetros fornecida ao término da URL de requisição e acessada usando a propriedade Request.QueryString. O PostData é usado para repovoar os valores atuais dos campos do form e de outros controles, e é essencial para a reconstrução do estado da página. Não podemos assumir que uma página não utiliza parâmetros QueryString, para reconstruir seu estado durante um postback.

 

Isto significa que o ViewState, o PostData e o QueryString devem todos ser armazenados, para podermos reconstruir o estado completamente. Alguns leitores expertos podem querer saber por que o _ViewState precisa ser armazenado separadamente quando o PostData já contém o _ViewState no campo _VIEWSTATE oculto. A razão é que o campo _VIEWSTATE contém o _ViewState da página quando foi exibida por último, e não reflete qualquer mudança que pode ter sido feita durante o processamento da requisição atual.

 

Embora o fato de que o PostData não é salvo no _ViewState ser mencionado em outros artigos sobre este assunto, nenhum, na verdade, tenta persistir isto junto com o ViewState, deixe os dados QueryString “pra lá”. O artigo do Paul Wilson mencionado antes, tentou resolver este problema acrescentando um manipulador de evento Change a todo controle da página, o qual força o PostData pertinente a ser incluído no ViewState. Isto de longe não é o ideal, pois exige uma intervenção constante para garantir que todos os controles são atualizados pelo mecanismo de estado persistente.

O "PageState"

Aqui, apresentaremos o termo PageState que é uma combinação do ViewState, PostData e da URL de requisição da página. Esta combinação contém toda a informação exigida para reconstruir o estado da página, pois a QueryString faz parte da URL. Podemos representar isto usando a seguinte classe:

 

public class PageState {

    public object__ ViewStateObject;

    public NameValueCollection PostData;

    public Uri Url;

    public PageState(object viewStateObject,

        NameValueCollection postData, Uri url) {

       __ ViewStateObject=viewStateObject;

        PostData=postData;

        Url=url;

    }

}

 

Note que os métodos Page.LoadPageStateFromPersistenceMedium() e Page.SavePageStateToPersistenceMedium(viewState) também recorrem a alguma coisa  chamada de "PageState" nos seus nomes . Esta é apenas uma inconsistência infeliz, pois eles realmente só lidam com o ViewState.

 

Agora que sabemos o que precisamos persistir, tudo o que temos a fazer é sobrepor o método Page.SavePageStateToPersistenceMedium(viewState) para criar um objeto PageState e armazená-lo em algum lugar dentro da Session, e sobrepor o método Page.LoadPageStateFromPersistenceMedium() para carregá-lo novamente quando a página for a seguir exibida. Correto?

Uma primeira tentativa

Tentemos o seguinte código:

 

/ / pageState eh nao nulo se um postback esta sendo emulado da

/ / PageState persistida:

private PageState pageState = null;

protected bool flagToIndicateThatPageIsBeingRedirected=false;

protected override object LoadPageStateFromPersistenceMedium() {

    pageState=LoadPageState(Request.Url);

    if (IsPostBack || pageState==null) {

        // este eh um postback normal, portanto usar a

        // page state persistida

        pageState = null;

        // limpar a page state na midia de persistencia

        // para nao ser re-utilizada:

        RemoveSavedPageState(Request.Url);

        return base.LoadPageStateFromPersistenceMedium();

    }

    if (pageState.Url.AbsoluteUri!=Request.Url.AbsoluteUri) {

/ / A url, e consequentemente o string em questao, nao casam com o

/ / estado da pagina, portanto recarregar esta pagina

/ / imediatamente com os URL persistidos:

        Response.Redirect(pageState.Url.AbsoluteUri,true);

    }

        // limpar a page state na midia de persistencia

        // para nao ser re-utilizada:

    RemoveSavedPageState(Request.Url);

    Request.Form = pageState.PostData;

    return pageState.ViewStateObject;

}

 

protected override void SavePageStateToPersistenceMedium(object viewState)

{

    if (flagToIndicateThatPageIsBeingRedirected) {

/ / persistir o estado atual

      SavePageState(Request.Url,new PageState(viewState, Request.Form,Request.Url));

    } else {

/ / retornar ao comportamento normal

      base.SavePageStateToPersistenceMedium(viewState);

    }

}

 

protected static PageState LoadPageState(Uri pageURL) {

  return (PageState)HttpContext.Current.Session[GetPageStateKey(pageURL)];

}

 

protected static void SavePageState(Uri pageURL, PageState pageState) {

    HttpContext.Current.Session[GetPageStateKey(pageURL)]=pageState;

}

 

protected static void RemoveSavedPageState(Uri pageURL) {

    SavePageState(pageURL,null);

}

 

private static string GetPageStateKey(Uri pageURL) {

/ / Retorna uma chave que identificara o PageState desta pagina univocamente

/ / em um namespace global baseado no caminho de URL.

  return "_PAGE_STATE_"+pageURL.AbsolutePath;

}

 

O PageState persistido, se existir, será armazenado para possível uso posterior em uma variável de classe chamada pageState.

O flagToIndicateThatPageIsBeingRedirected, é um flag booleano que precisa ser fixado sempre que um método Response.Redirect() for chamado, de forma que o método SavePageStateToPersistenceMedium(viewState) saiba se os states devem ser persistidos.

Os métodos LoadPageState(Uri), SavePageState(Uri,PageState), RemoveSavedPageState(Uri) e GetPageStateKey(Uri) no final, encapsulam o processo de carregar, salvar e remover o estado da página de uma midia de persistência arbitrária (neste caso o objeto Session) usando uma chave baseada na URL da página.

 

O código em LoadPageStateFromPersistenceMedium() redireciona a página, caso a URL da requisição atual não case com a URL persistida, reconhecendo que não podemos fixar a URL da requisição (e conseqüentemente a QueryString), simplesmente mudando suas propriedades. Redirecionar para a URL persistida parece o modo mais limpo para garantir que as propriedades Request.Url e Request.QueryString sejam iguais àquelas do momento em que o estado foi salvo.

Se tentarmos compilar este código, veremos que a compilação falha, pois a propriedade Request.Form é somente leitura. Para piorar as coisas, o objeto interno System.Web.HttpValueCollection que armazena os dados nesta propriedade, também é só leitura, portanto nem ao menos podemos povoá-la com nossos próprios dados. Por enquanto, ignoremos este problema comentando esta linha.

 

Uma vez compilado o código, um teste simples revelará que alguns outros problemas estão impedindo que as coisas funcionem como desejado. Para encurtar a história, temos também os seguintes problemas:

1.     O método LoadPageStateFromPersistenceMedium() nunca é chamado pelo ASP.NET Framework se IsPostBack for falso;

2.     Do momento que IsPostBack ainda devolverá falso quando estivermos emulando um PostBack, qualquer outro código na página que o utiliza não funcionará corretamente;

3.     O método SavePageStateToPersistenceMedium() nunca é chamado pelo ASP.NET Framework se o Response.Redirect() tiver sido previamente chamado.

 

Vemos que os primeiros dois problemas, podem ser resolvidos facilmente sobrepondo o método da página DeterminePostBackMode(), que é usado pelo framework para determinar o valor de IsPostBack e se o método LoadPageStateFromPersistenceMedium() for executado. Um post back é designado por um valor de retorno não nulo de DeterminePostBackMode().

 

A documentação deste método não é muito explícita a respeito de qual exatamente é o valor de retorno usado, mas nossa experiência sugere que seja usado para alimentar o evento que processa os métodos, mas (estranhamente) não para povoar valores de controle. Podemos, então, sobrepor o método, para executar o comportamento padrão se um PostBack real estiver acontecendo, ou retornar um objeto não nulo caso desejemos emular um PostBack com nossos dados persistidos.

Note que ao retornar nosso objeto PageState.PostData salvo, será disparado o mesmo evento que causou o redirecionamento original, que não é o que queremos. O objeto Request.Form vazio faz o truque e é do tipo correto.

 

protected override NameValueCollection DeterminePostBackMode() {

    pageState=LoadPageState(Request.Url);

    NameValueCollection normalReturnObject=

                       base.DeterminePostBackMode();

/ / retornar ao comportamento normal se nao ha

/ / nenhum pagestate persistido:

    if (pageState==null) return normalReturnObject;

    if (normalReturnObject!=null) {

/ / este eh um postback normal, portanto

/ / nao persistir o estado da pagina

        pageState=null;

/ / limpar o estado da pagina da midia de persistencia

/ / para que nao seja usado novamente:

        RemoveSavedPageState(Request.Url);

        return normalReturnObject;

    }

/ / Se chegamos neste ponto, desejaremos

/ / restabelecer o estado da pagina persistida.

/ / Verificar se a requisição atual

/ / casa com a URL persistida:

    if (pageState.Url.AbsoluteUri!=Request.Url.AbsoluteUri) {

/ / A url, e consequentemente o string questao,

/ / nao casa com o

/ / estado da pagina, portanto recarregar esta pagina

/ / imediatamente com a URL persistida:

        Response.Redirect(pageState.Url.AbsoluteUri,true);

    }

/ / limpar o estado da pagina da midia de persistencia

/ / para que nao seja usado novamente:

    RemoveSavedPageState(Request.Url);

/ / retornar um valor nao nulo para indicar

/ / um PostBack ao framework:

    return Request.Form;

}

 

protected override object LoadPageStateFromPersistenceMedium() {

/ / retornar ao comportamento normal se nao desejamos

/ / restabelecer o estado da pagina persistido:

    if (pageState==null)

        return base.LoadPageStateFromPersistenceMedium();

/ / caso contrario, devolver o ViewStateObject

/ / contendo pageState persistido: (A seguinte linha foi comentada

/ /  pois lidaremos com o problema

/ / da propriedade Request.Form somente leitura depois)

/ / Request.Form=pageState.PostData;

    return pageState.ViewStateObject;

}

 

Note que o código que verifica a requisição da URL, foi movido do método LoadPageStateFromPersistenceMedium() para o método DeterminePostBackMode() , o qual acontece mais cedo no ciclo de vida da página.

Resolver o terceiro problema também é relativamente fácil. Em lugar de chamar o método Response.Redirect(), outras partes do código deveriam fixar uma variável de classe que contém a URL para redirecioná-la simplesmente. O método SavePageStateToPersistenceMedium() pode então chamar Response.Redirect(), depois de salvar o estado caso a variável seja fixada.

 

/ / redirectSavingPageStateURL contem a URL a ser redirecionada:

private string redirectSavingPageStateURL = null;

public void RedirectSavingPageState(string url) {

/ / Chamar este metodo em lugar de

/ / Response.Redirect(url) irah

/ / restabelecer o estado atual da pagina

/ / quando for exibida a seguir

    redirectSavingPageStateURL=url;

}

 

protected override void SavePageStateToPersistenceMedium(object viewState)

{

    if (redirectSavingPageStateURL==null) {

/ / retornar ao comportamento normal

        base.SavePageStateToPersistenceMedium(viewState);

    } else {

/ / persistir o estado atual e redirecionar para a pagina nova:

        SavePageState(Request.Url, new PageState(viewState,Request.Form,Request.Url));

        Response.Redirect(redirectSavingPageStateURL);

    }

}

O problema com Request.Form

Agora, voltemos ao problema com a propriedade somente leitura Request.Form.

Este é um problema para o qual a solução alternativa ainda é razoavelmente deselegante. Esperamos que a Microsoft faça o ajuste para tornar a propriedade Request.Form read/write em uma próxima versão, mas por enquanto, temos que nos satisfazer substituindo nossa própria propriedade PostData onde acessamos diretamente Request.Form em todos os lugares da página.

A propriedade PostData retornará nosso PostData persistido se foi carregado, caso contrário retornar o objeto Request.Form:

 

public NameValueCollection PostData {

    get {

        return (pageState!=null) ?

            pageState.PostData: Request.Form;

    }

}

 

Porém, ainda há um problema. O ASP.NET Framework  povoa valores de controle automaticamente com os dados do Request.Form, e não provê qualquer modo de especificar uma fonte alternativa para estes dados (ver a documentação do MSDN em processing postback data, para mais detalhes). Precisamos, então, levar a cabo esta operação manualmente, quando o estado persistido está carregado. Isto é bastante simples, e é representado pelo seguinte código:

 

/ / Povoar os controles com PostData, enquanto salvamos

/ / uma lista dos que foram modificados:

ArrayList modifiedControls=new ArrayList();

LoadPostData(this,modifiedControls);

/ / Levantar o evento PostDataChanged para todos os controles modificados:

foreach (IPostBackDataHandler control in modifiedControls)

    control.RaisePostDataChangedEvent();

 

que usa o seguinte método private:

 

/ / / < summary >

/ / / Este metodo executa recursao de primeiro nivel em

/ / / todos os controles que contem o controle especificado,

/ / / chamando o LoadPostData do framework em cada um e

/ / / adicionando os modificados aa lista modifiedControls.

/ / / < / summary >

private void LoadPostData(Control control, ArrayList modifiedControls) {

/ / Executar recursao dos controles filho:

    foreach (Control childControl in control.Controls)

        LoadPostData(childControl, modifiedControls);

/ / Carregar os dados postados para este controle:

    if (control is IPostBackDataHandler) {

/ / Obter o valor do atributo do nome do controle,

/ / que e o GroupName de radio buttons,

/ / ou igual ao atributo de ID para todos os outros controles:

        string nameAttribute=(control is RadioButton) ?

                    ((RadioButton)control).GroupName : control.ID;

/ / Nao processar este controle se sua chave nao estiver dentro do

/ / PostData, pois a implementacao LoadPostData de alguns

/ / controles levantam uma excecao neste caso.

        if (PostData[nameAttribute]==null) return;

/ / Chamar o LoadPostData do framework para este controle

/ / usando o atributo de nome como a chave de post data:

        if (((IPostBackDataHandler)control).LoadPostData(nameAttribute, PostData))

            modifiedControls.Add(control);

    }

}

 

Tudo o que precisamos fazer é chamar este código depois que o framework carregar o ViewState, e teremos nossa solução final! Um lugar apropriado para inserir este código, é no método OnLoad do form.

Um último aperfeiçoamento

Outro requerimento muito comum de uma sub-página, consiste na capacidade de passar dados de volta para a página pai. A solução discutida aqui, não suporta isto facilmente, pois a idéia toda era reconstruir o estado da página exatamente antes da sub-página ser chamada. O modo mais óbvio para passar dados de volta, seria a sub-página incluí-los no string da consulta ao redirecioná-los para a página pai, mas a solução atual considera que o string de consulta faz parte do estado da página que deve ser restabelecido ao seu estado original.

 

Para fazer com que a consulta passe os dados de volta para a sub-página disponível para a página pai, precisaremos salvá-los antes de serem sobrepostos com o string de consulta original. Podemos armazená-lo salvando os dados junto com os dados de estado originais do objeto PageState, e acessá-los por uma propriedade nova chamado PassBackData.

O método DeterminePostBackMode() contém o código que salva o string de consulta no objeto PageState, e o resto do código para suportá-lo é bastante auto-explicativo.

A solução final

Nosso código agora tem o seguinte aspecto:

 

/ / / < summary>

/ / / A PageState persistida. Nao serah nula se um postback

/ / / estah sendo emulado do PageState persistido.

/ / / < / summary >

 

private PageState pageState=null;

 

/ / / < summary >

/ / / Contem a URL a ser redirecionada

/ / / < / resumo >

 

private string redirectSavingPageStateURL=null;

 

/ / / < resumo >

/ / / Indica se a pagina atual estah sendo ou foi

/ / / restabelecida de um estado persistido

/ / / < / summary >

 

public bool IsRestoredPageState {

    get {

        return pageState!=null;

    }

}

/ / / <  summary >

/ / / Retorna os dados postados do

/ / / PageState persistido caso existir,

/ / / caso contrario os dados postados atuais de Request.Form

/ / / < / summary >

 

public NameValueCollection PostData {

    get {

        return (IsRestoredPageState) ?

               pageState.PostData : Request.Form;

    }

}

 

/ / / <  summary >

/ / / Retorna os dados passados da sub-pagina que

/ / / podem ser usados para fazer mudancas

/ / / no estado da pagina salvo.

/ / / Estes dados sao passados pelo string de consulta.

/ / / < / summary >

 

public NameValueCollection PassBackData {

  get {

    return IsRestoredPageState ? pageState.PassBackData : null;

  }

}

/ / / <  summary >

/ / / Chamar este metodo em vez de Response.Redirect(url)

/ / / restabelece o estado atual desta pagina quando

/ / / for exibida a seguir.

/ / / < / summary >

 

public void RedirectSavingPageState(string url) {

    redirectSavingPageStateURL=url;

}

/ / / <  summary >

/ / / Chamar este metodo para redirecionar para a URL relativa especificada,

/ / / especificando se restabelece o estado salvo da pagina

/ / / (assumindo que o estado foi salvo quando foi exibida por ultimo).

/ / / A URL especificada eh relativa aa requisicao atual.

/ / / Este metodo normalmente eh chamado de uma "sub-pagina",

/ / / que nao estende esta classe.

/ / / < / summary >

 

public static void RedirectToSavedPage(string url,

                                bool restorePageState) {

    if (!restorePageState) RemoveSavedPageState(url);

    HttpContext.Current.Response.Redirect(url);

}

/ / / <  summary >

/ / / Chamar este metodo para limpar os PageState salvos da pagina com a

/ / / URL especifica relativa. Isto garante que o proximo redirecionamento

/ / / especificado para a pagina nao revertera ao estado salvo.

/ / / A URL especificada eh relativa aa da requisicao atual.

/ / / < / summary >

 

public static void RemoveSavedPageState(string url) {

  RemoveSavedPageState(new Uri(HttpContext.Current.Request.Url,

                                                          url));

}

/ / / <  summary >

/ / / Este metodo eh chamado pelo framework depois do evento Init para

/ / / determinar se um postback estah

/ / / sendo executado. A propriedade Page.IsPostBack

/ / / retorna verdadeiro se o metodo retornar nao nulo.

/ / / < / summary >

 

protected override NameValueCollection DeterminePostBackMode() {

    pageState=LoadPageState(Request.Url);

    NameValueCollection normalReturnObject=

                   base.DeterminePostBackMode();

/ / retornar ao comportamento normal se

/ / nao ha nenhum pagestate persistido:

    if (!IsRestoredPageState) return normalReturnObject;

    if (normalReturnObject!=null) {

/ / este eh um postback normal, portanto nao usar o estado da pagina persistido

        pageState=null;

/ / limpar o estado da pagina da midia de persistencia para

/ / que nao seja novamente usado:

        RemoveSavedPageState(Request.Url);

        return normalReturnObject;

    }

/ / Se chegamos a este ponto, desejaremos restabelecer o estado da pagina persistido.

/ / Exceto o PassBackData se ainda nao o fizemos:

    if (pageState.PassBackData==null) {

        pageState.PassBackData=Request.QueryString;

/ / chamar SavePageState novamente caso a mudanca que fizemos

/ / nao seja puramente persistida em memoria:

        SavePageState(pageState.Url,pageState);

    }

/ / Verificar se a URL da requisição atual casa a URL persistida:

    if (pageState.Url.AbsoluteUri!=Request.Url.AbsoluteUri) {

/ / A url, e consequentemente o string questao,

/ / nao casa com o estado da pagina, portanto recarregar esta pagina

/ / imediatamente com o URL persistido:

        Response.Redirect(pageState.Url.AbsoluteUri,true);

    }

/ / limpar o estado da pagina da midia de persistencia para

/ / que nao seja novamente usado:

    RemoveSavedPageState(Request.Url);

/ / retornar um valor nao nulo para indicar um PostBack ao framework:

    return Request.Form;

}

/ / / < summary >

/ / / Este metodo eh chamado pelo framework

/ / / depois de DeterminePostBackMode(),mas antes da manipulacao personalizada do evento.

/ / / Voltar ao estado de visao que o

/ / / framework usa para restabelecer o estado dos controles.

/ / / < / summary >

 

protected override object LoadPageStateFromPersistenceMedium() {

/ / retornar ao comportamento normal se nao queremos

/ / restabelecer o estado da pagina persistido:

    if (!IsRestoredPageState)

        return base.LoadPageStateFromPersistenceMedium();

/ / caso contrario, retornar o ViewStateObject contido no pageState persistido:

    return pageState.ViewStateObject;

}

/ / / < summary >

/ / / Este metodo eh chamado pelo framework depois

/ / / que LoadPageStateFromPersistenceMedium()levantar o evento Load.

/ / / Sao povoados os controles com dados PostData aqui porque tem que

/ / / acontecer depois que o framework carregar

/ / / o estado de visao depois do qual acontece

/ / / a execuçao do metodo LoadPageStateFromPersistenceMedium()

/ / / < / summary >

 

override protected void OnLoad(EventArgs e) {

/ / O seguinte codigo emula o que ASP.NET faz "automagicamente"

/ / quando povoa os controles com dados postados antes de processar

/ / os eventos. A diferença eh que este aqui povoa com nossos

/ / dados postados persistido em lugar dos

/ / dados postados atuais do Request.Form.

    if (IsRestoredPageState) {

/ / Povoar controles com PostData, salvando uma lista dos que foram modificados:

        ArrayList modifiedControls=new ArrayList();

        LoadPostData(this,modifiedControls);

/ / Levantar o evento PostDataChanged para todos os controles modificados:

        foreach (IPostBackDataHandler control in modifiedControls)

            control.RaisePostDataChangedEvent();

    }

    base.OnLoad(e);

}

/ / / < summary >

/ / / Este metodo executa recursao de primeiro nivel para todos os

/ / / controles contidos no controle especificado,

/ / / chamando o LoadPostData do framework em cada um e

/ / / adicionando os modificados aa lista modifiedControls.

/ / / < / summary >

 

private void LoadPostData(Control control, ArrayList modifiedControls) {

/ / Executar recursao de controles filho:

    foreach (Control childControl in control.Controls)

        LoadPostData(childControl,modifiedControls);

/ / Carregar os dados postados para este controle:

    if (control is IPostBackDataHandler) {

/ / Obter o valor do atributo de nome do controle,

/ / que eh o GroupName de radio button, ou igual ao atributo de ID para todos os outros controles:

      string nameAttribute=(control is RadioButton) ?

                  ((RadioButton)control).GroupName : control.ID;

/ / Nao processar este controle se a chave nao estiver dentro do

/ / PostData, pois a implementaçao de LoadPostData de alguns

/ / controles levantam uma excecao neste caso.

      if (PostData[nameAttribute]==null) return;

/ / Chamar o LoadPostData do framework neste controle

/ / usando o atributo de nome como a chave de dados postados:

      if (((IPostBackDataHandler)control).LoadPostData(nameAttribute,PostData))

          modifiedControls.Add(control);

    }

}

/ / / < summary >

/ / / Este metodo eh chamado pelo framework entre os eventos PreRender e Render.

/ / / So eh chamado se esta pagina serah re-exibida, nao se Response.Redirect

/ / / foi chamado. Para garantir que seja chamada

/ / / antes de redirecionarmos, tivemos que adiar

/ / / a chamada ao Response.Redirect ate agora.

/ / / < / summary >

protected override void SavePageStateToPersistenceMedium(object viewState)

{

    if (redirectSavingPageStateURL==null) {

/ / retornar ao comportamento normal

        base.SavePageStateToPersistenceMedium(viewState);

    } else {

/ / persistir o estado atual e redirecionar para a pagina nova

        SavePageState(Request.Url, new PageState(viewState,Request.Form,Request.Url));

        Response.Redirect(redirectSavingPageStateURL);

    }

}

/ / / < summary >

/ / / Sobrepor este metodo para carregar o estado de uma midia de persistencia

/ / / diferente do objeto Session.

/ / / < / summary >

 

protected static PageState LoadPageState(Uri pageURL) {

  return

   (PageState)HttpContext.Current.Session[GetPageStateKey(pageURL)];

}

/ / / < summary >

/ / / Sobrepor este metodo para salvar o estado para uma midia de persistência

/ / / diferente do objeto Session.

/ / / < / summary >

 

protected static void SavePageState(Uri pageURL, PageState pageState)

{

    HttpContext.Current.Session[GetPageStateKey(pageURL)]=pageState;

}

/ / / < summary >

/ / / Sobrepor este metodo para remover o estado de uma midia de persistencia

/ / / diferente do objeto Session.

/ / / < / summary >

 

protected static void RemoveSavedPageState(Uri pageURL) {

    SavePageState(pageURL,null);

}

/ / / < summary >

/ / / Retornar uma chave que identificarah uma pagina univocamente em um

/ / / namespace global baseado na URL.

/ / / < / summary >

 

private static string GetPageStateKey(Uri pageURL) {

    return "_PAGE_STATE_"+pageURL.AbsolutePath;

}

 

A propriedade pública IsRestoredPageState permite ao desenvolvedor da página determinar se a mesma foi restabelecida de um estado persistido. Na maioria dos casos, não será necessário distinguir entre um postback normal e um postback emulado de um estado persistido, mas dar ao desenvolvedor acesso a esta informação, certamente poderá ser útil em algumas circunstâncias. Também é internamente usado para melhor a legibilidade em lugar de verificar um valor não nulo de pageState.

 

Os métodos de url RedirectToSavedPage(string estático, restorePageState de bool) e RemoveSavedPageState(string), foram planejados para que outras páginas possam controlar se o estado desta página foi restabelecido quando for exibido a seguir.

O fonte para download no início deste artigo, contém uma classe System.Web.UI.Page estendida chamada X.Web.UI.PersistentStatePage, que contém toda esta funcionalidade. Podemos estender a classe PersistentStatePage em lugar da classe Page padrão, quando precisarmos de uma página que requeira um estado persistente, ou podemos copiar o código simplesmente nas nossas próprias páginas.

 

A classe PageState também foi modificada para torná-la serializável, o que será necessário quando for armazenada em um banco de dados ou em outro médio persistente. Isto envolve a criação de uma classe serializável para “envelopar” o objeto view state, pois a classe System.Web.UI.Triplet usada pelo framework para representar isto, não aplica o atributo [Serializable] e, portanto, não pode ser serializado automaticamente.

A classe PageState modificada e a classe de envelopamento SerializableViewState é definida como segue:

 

/ / / < summary >

/ / / Este eh o objeto armazenado na midia de persistência,

/ / / contendo o estado de visao, dados postados, e URL.

/ / / < / summary >

[Serializable]

public class PageState {

    private SerializableViewState serializableViewState;

    public NameValueCollection PostData;

    public Uri Url;

    public NameValueCollection PassBackData;

    public PageState(object viewStateObject, NameValueCollection postData, Uri url) {

        serializableViewState= new SerializableViewState(viewStateObject);

        PostData = postData;

        Url=url;

    }

    public object__ ViewStateObject {

        get {

            return serializableViewState.ViewStateObject;

        }

    }

}

/ / / < summary >

/ / / Este eh um envolopamento simples do objeto visao state

/ / / para torna-lo serializavel.

/ / / < / summary >

[Serializable]

public class SerializableViewState : ISerializable {

    public object__ ViewStateObject;

    private const string__ ViewStateStringKey="ViewStateString";

   

    public SerializableViewState(object viewStateObject) {

       __ ViewStateObject=viewStateObject;

    }

    public SerializableViewState(SerializationInfo info, StreamingContext context) {

       __ ViewStateObject=new LosFormatter().Deserialize(info.GetString(ViewStateStringKey));

    }

   

    public void GetObjectData(SerializationInfo info, StreamingContext context) {

        StringWriter stringWriter=new StringWriter();

        new LosFormatter().Serialize(stringWriter, ViewStateObject);

        info.AddValue(ViewStateStringKey, stringWriter.ToString());

    }

}

Usando a solução

Para usar a solução como provida no download do fonte nas nossas página, as seguintes mudanças de código são requeridas:

1.     A página deverá estender X.Web.UI.PersistentStatePage em lugar de System.Web.UI.Page, ou alternativamente copiar o código para a própria página;

2.     Usar RedirectSavingPageState(url) em lugar de Response.Redirect(url) sempre que desejarmos que a página atual restabeleça seu estado atual quando for exibida a seguir;

3.     Substituir toda ocorrência de Request.Form (caso exista) com PostData nas páginas de salvamento de estado.

 

Para redirecionar de volta para uma página que salvou seu estado a partir de uma sub-página, ou qualquer outra página:

 

·         Chamar o método PersistentStatePage.RedirectToSavedPage(url, restorePageState), especificando se seu estado deverá ser restabelecido,

·         ou chamar o método Response.Redirect(url) normalmente, o que é equivalente a chamar PersistentStatePage.RedirectToSavedPage(url, true).

 

De qualquer modo, podemos passar dados para a página pai incluindo-os no string de consulta da url especificado, o qual pode ser acessado então pela propriedade PassBackData na página pai. Boa Sorte com os projetos ASP.NET.