Páginas

domingo, 13 de maio de 2012

"Sacaneando" a OOP - chamando métodos não públicos em .NET

Olá, pessoal.

Hoje vou demonstrar aqui como burlar as regras da Orientação a Objetos em relação ao escopo, ou seja, conseguir chamar um método privado fora da sua classe, por exemplo.

A ideia desse post veio de uma necessidade que tive em acessar o conteúdo do viewstate de uma página ASP.NET a partir de um user control. ViewState é um artifício criado para o ASP.NET Webforms para se permitir simular a manutenção de estado entre as várias requisições à páginas web (visto que tradicionalmente a abordagem de trabalho com web é stateless). Vamos dar um exemplo. Suponha que eu tenha um grid em uma página ASP.NET, e então eu queira definir uma ordenação para uma coluna qualquer desse grid. Logicamente, na próxima vez que eu quiser fazer uma requisição para essa página, vou querer que essa ordenação que eu fiz continue. É nesse caso que entra o viewstate, pois será nele que essa informação de ordenação será armazenada.

Na prática, o viewstate é um campo do tipo hidden, no qual o ASP.NET armazena uma série de informações. Veja na imagem abaixo um exemplo de como o viewstate fica presente no HTML.


Como citamos no exemplo, o grid armazenou a sua ordenação dentro do viewstate. Apesar do viewstate ser único para uma página, dentro do ASP.NET cada controle tem um próprio objeto no qual ele armazena o seu estado (cada controle tem seu objeto ViewState). É no final do processo de renderização da página que o ASP.NET pega todos esses objetos de viewstate e consolida em um só, jogando o resultado no controle hidden __VIEWSTATE, mas enquanto isso não ocorre, cada viewstate de cada objeto é isolado um do outro. A propriedade ViewState (do tipo StateBag), presente no objeto System.Web.UI.Control, do qual classes como Page e UserControl herdam, tem seu escopo como protected.

É por essa característica que tive que recorrer a uma "alternativa técnica" (para não falar gambiarra) de forma a acessar o viewstate de uma página (objeto System.Web.UI.Page) a partir de um UserControl (objeto System.Web.UI.UserControl). Vejamos o código abaixo.

    public partial class WebUserControl1 : System.Web.UI.UserControl
    {
        protected void Button1_Click(object sender, EventArgs e)
        {
            //this.Page.ViewState["TESTE_PAGINA"] = TextBox1.Text;    // dá erro
            var viewstate = ObterVIewStatePagina();
            viewstate["TESTE_PAGINA"] = TextBox1.Text;
        }

        private StateBag ObterViewStatePagina()
        {
            PropertyInfo property = typeof(Page).GetProperty("ViewState",
                BindingFlags.NonPublic | BindingFlags.Instance);
            return (StateBag)property.GetValue(this.Page, new object[] { });
        }
    }

Como comentado no código de exemplo, se tentarmos acessar a propriedade ViewState de this.Page a partir do UserControl, teremos problemas. O código não compila, obviamente. Mas então entra em ação o método ObterViewStatePagina. Nele, em primeiro lugar obtemos via reflection a referência à propriedade de nome ViewState da página. Perceba que passamos para o método GetProperty, além do nome da propriedade, qual é o binding flags que queremos que esse método utilize como filtro para recuperar a referência via reflection. Passamos BindingFlags.NonPublic exatamente para que sejam retornar métodos de escopo private e protected. BindingFlags.Instance também é necessário pois a propriedade ViewState é da instância da classe (não é static).

Como resultado, temos essa referência, que podemos obter o valor ao chamar o método GetValue, passando como parâmetro exatamente o objeto Page do qual queremos obter o StateBag. A partir disso, podemos fazer qualquer operação em cima do viewstate da página.

No exemplo pegamos uma propriedade, mas é possível chamar métodos, alterar valores de atributos, etc.

Três observações:

  1. Essa técnica pode ser usada como alternativa para testar métodos não-públicos em testes de unidade sem o uso da classe PrivateObject.
  2. Isso tem que ser usado com bastante cuidado. Se um método ou propriedade não está público, é porque quem programou a classe não quis expor essa informação. Tenha em mente que em próximas versões dessa classe, o programador pode mudar a forma como ele escreve o código, a assinatura do método não-público pode mudar, o próprio método pode sumir ou passar a fazer algo que você não quer.
  3. E por último, voltando a falar de viewstate, tome cuidado com seu uso, pois quanto mais informação você armazena nele, mais pesada a página fica.

2 comentários:

  1. Ola Fabio...Voce poderia me ajudar com um problema? Como faço para acessar um user control pai, eu tenho um user control, e dentro deste user control eu joguei outro, e este user control por ultimo preciso acessar um metodo do primeiro user control. Entendeu + ou -?! Fico no aguardo de uma resposta.Obrigado
    Bruno Freitas

    ResponderExcluir
    Respostas
    1. Bruno, boa tarde. Acho que entendi o que você quer fazer.

      Neste caso, o que eu sugiro é usar eventos. O seu user control filho disponibiliza um evento que é assinado pelo user control pai. Assim, toda vez que você precisar passar alguma informação do filho para o pai, no seu user control filho você aciona o evento, que é capturado pelo user control pai. Então, no código do handler do evento no pai, você faz a chamada ao método que precisa.

      Busquei na Internet e achei um artigo que explica isso, e que você pode tomar como base.

      http://csharpbrasil.com.br/csharp/criando-eventos-personalizados-em-usercontrol-no-asp-net/

      []'s

      Excluir