Post Cache Substitution

Quando colocamos uma determinada página em OutputCache nas versões 1.x do ASP.NET, tínhamos/temos problemas quando existem nesta página, regiões dinamicas que, devem ser sempre atualizadas quando um Refresh ou PostBack acontecer.

Com este cenário, ou teríamos que optar por não deixar a página em OutputCache, ou criar UserControls (arquivos ASCX) para habilitar o Fragment Caching e assim, atualizar essas informações dinamicas. No ASP.NET 2.0 foi introduzido um novo conceito chamado de Post Cache Substitution. Trata-se de um controle chamado Substitution, que temos à disposição na ToolBox do Visual Studio .NET 2005. Devemos arrastá-lo para o local da página onde o conteúdo será dinamico e, através da propriedade MethodName do mesmo, apontamos para um método estático que, obrigatoriamente deverá estar de acordo com o delegate HttpResponseSubstitutionCallback. Este será o método qual o ASP.NET executará sempre.

Abaixo é exibido um exemplo simples que ilustra o funcionamento desta nova feature:

<%@ OutputCache Duration=”60″ VaryByParam=”none” %>
….
<asp:Substitution
     ID=”Substitution1″ 
     runat=”server” 
     MethodName=”GetCurrentDateTime” />

<asp:Label
     ID=”Label1″ 
     runat=”server” 
     Text=”Label”>
</asp:Label>

E no CodeBehind:

    protected void Page_Load(object sender, EventArgs e)
    {
        this.Label1.Text = DateTime.Now.ToString();
    }

    public static string GetCurrentDateTime(HttpContext ctx)
    {
        return DateTime.Now.ToString();
    }

Esse código fará com que a página fique em OutputCache durante 60 segundos, porém a região da página que é determinada pelo controle Substitution, sempre será atualizada.

Encriptando seções do Web.Config

No ASP.NET 2.0, temos uma nova versão do utilitário de linha de comando aspnet_regiis.exe. Consequentemente temos novas funcionalidades adicionadas a ele e, uma delas, é a possibilidade de encriptarmos seções do arquivo Web.Config de uma determinada aplicação ASP.NET. A sua sintaxe para uso é simples:

C:>aspnet_regiis -prov DataProtectionConfigurationProvider -pef appSettings C:WebSite1

A seguir a explicação dos parametros:

-prov: indica o provider que quer utilizar para encriptar a seção.
-pef + seção + diretório físico: indica a seção do arquivo Web.Config que será encriptada, fornecendo em seguida, o caminho físico do diretório da aplicação.

Client Side Callbacks

Um dos principais desafios dos desenvolvedores de aplicações Web é construir uma aplicação intuitiva e ao mesmo tempo dinâmica, tornando a aplicação o mais próximo possível do “mundo Windows”. Como essa aproximação é sempre comparada pelos clientes, o principal ponto negativo que as pessoas que estão migrando suas aplicações Windows para aplicações Web encontram é o fato de quando submetemos algo para ser processado no servidor, a página causa um Refresh, ou seja, ela é totalmente reconstruída.

Com esse comportamento as aplicações Web foram bastante crucificadas quando comparadas as aplicações Windows tradicionais. Com a necessidade de ter algo ainda mais dinâmico, algumas tecnologias foram criadas em paralelo as de desenvolvimento dinâmico de sites, tornando as aplicações Web ainda mais interativas. Uma das primeiras tecnologias que apareceram para preencher essa lacuna foi o Remote Scripting, que fornece uma infraestrutura para invocar método server-side, ou seja, que rodam no servidor sem a necessidade de submeter a página. Essa tecnologia utiliza Java Applets para fazer a comunicação com o servidor, comunicando através de protocolo HTTP. Há também opções onde não são necessários Applets para isso, utilizando apenas JavaScript e DHTML, como podemos ver neste componente.

Atualmente, se fala bastante em uma tecnologia chamada AJAX (Asynchronous JavaScript and XML), a qual tem a mesma finalidade da anterior e utiliza o objeto XMLHTTPRequest para a comunicação entre o browser cliente e o servidor. Na versão ASP.NET 2.0, a Microsoft implementou o que chamamos de Client-Side Callbacks. Essa é uma opção paleativa que a Microsoft introduziu no ASP.NET 2.0; paleativa porque a própria Microsoft trabalha atualmente em um projeto chamado Atlas, qual brevemente estará disponível. Esse projeto trará aos desenvolvedores ASP.NET uma facilidade enorme no desenvolvimento, encapsulando boa parte do código Javascript que é requerido nestes cenários. Inclusive serão fornecidos diversos controles Drag & Drop, permitindo assim a codificação declarativamente.

Semelhante ao Remote Scripting, os Client-Side Callbacks permitem chamar um código qualquer VB.NET ou C#, que corre no servidor, através do cliente, sem a necessidade de atualizar a página. A única diferença é que ele utiliza XmlHTTP para se comunicar com o servidor ao invés de Java Applet.

O processo é bastante simples: um determinado controle, do lado do cliente (browser), efetua um pedido, de forma assíncrona, a uma função do servidor através de uma função Javascript que é automaticamente embutida (esta é chamada de WebForm_DoCallback) na página pelo runtime do ASP.NET, e esta por sua vez é responsável por criar e configurar a estrutura necessária para enviar o pedido para o servidor. Depois que a função (embutida pelo ASP.NET) é executada, resta saber como invocar o método Javascript quando a resposta do servidor for retornada (callback). Eis o momento que entra em cena a Interface ICallbackEventHandler.

Esta Interface contém somente dois métodos: RaiseCallbackEvent(string) e GetCallbackResult(). Durante o processo de callback o ciclo de vida da página é um pouco diferente da normalidade, onde todos os eventos serão executados normalmente até o evento PreRender; como esse evento é responsável pela geração do código HTML da página, ele deve ser suprimido. Ao invés de retornar o HTML gerado pelo evento PreRender, é devolvido apenas o retorno do processamento do método GetCallbackResult(). O método RaiseCallbackEvent é invocado anteriormente, depois da conclusão do evento LoadComplete, onde podemos fazer inicializações de objetos, entre outras coisas necessárias para atender à este pedido. O porque de termos dois métodos nesta Interface é justamente para permitir o processamento assíncrono do pedido. Para termos uma idéia do fluxo de processamento de uma página com callback e sem callback, basta analisar a imagem abaixo:

Figura 1 – Fluxo de páginas com e sem callbacks.

Para vermos o funcionamento em um ambiente “quase real”, devemos recuperar o nome da pessoa dado um número de C.P.F., ele irá fazer uma pesquisa e, se encontrar alguma pessoa com este código, o nome da mesma será retornado. Claro que isso não reflete exatamente o mundo real, já que nestes casos o ideal seria fazer uma consulta em alguma base de dados. O código abaixo mostra a implementação da Interface ICallbackEventHandler no CodeBehind de uma página ASPX.

public partial class Cadastro : System.Web.UI.Page, ICallbackEventHandler
{
    private string _cpf;
    
    public void RaiseCallbackEvent(string eventArgs)
    {
        this._cpf = eventArgs;
    }

    public string GetCallbackResult()
    {
        if(this._cpf.Length != 11)
            throw new ArgumentException("Comprimento do CPF incorreto.");

        if(this._cpf == "00011122233")
            return "Israel Aece";
        else if(this._cpf == "00011122244")
            return "Juliano Aece";
        else if(this._cpf == "00011122255")
            return "Claudia Fernanda";

        throw new ArgumentException("Registro inexistente.");
    }
}

Como podemos ver no código acima, o CodeBehind da nossa página já está preparado para trabalhar com o processo de callback. Mas somente isso não é necessário: devemos lembrar que o cliente (browser) é responsável por disparar o processo e, quando a função de callback retornar, também deverá receber o resultado, manipulá-lo e, conseqüentemente, apresentar ao usuário. A função que é responsável por disparar o ínicio do processo é chamada de WebForm_DoCallback, pois, como já vimos anteriormente, ela é embutida automaticamente pelo ASP.NET e a única coisa que temos que nos preocupar é quem (controle) e quando (evento) vai disparar esse processo.

Antes de apresentar o código responsável pela geração da função que será colocada no cliente para disparar o callback, é importante conhecer duas novas propriedades introduzidas no ASP.NET 2.0, para manipularmos os callbakcs. A primeira delas, a propriedade SupportsCallback, que está contida dentro de Request.Browser, que retorna um valor verdadeiro ou falso indicando se o browser do cliente suporta ou não os callbacks. Já a segunda é a propriedade IsCallback, que foi adicionada na classe Page que, assim como a propriedade IsPostBack, retorna um valor verdadeiro ou falso indicando se é ou não uma requisição resultante de um processo de callback.

Agora precisamos saber como atribuir a função WebForm_DoCallback em algum evento de um determinado controle no CodeBehind. A classe Page, através de uma nova propriedade chamada ClientScript, fornece um objeto do tipo ClientScriptManager, que este por sua vez, disponibiliza ao desenvolvedor um método chamado GetCallbackEventReference. Este método, dados seus parâmetros (os quais veremos a seguir), retorna uma String que refere-se à uma função client-side que será disparada através de algum evento (também no cliente), que dará início ao processo de callback. Para ilustrar o cenário que descrevemos acima, teremos o seguinte formulário:

Figura 2 – Formulário para os testes de callback.

Como prometido, vamos analisar detalhadamente a função GetCallbackEventReference. A assinatura do mesmo é a seguinte:

public string GetCallbackEventReference (
	string target,
	string argument,
	string clientCallback,
	string context,
	string clientErrorCallback,
	bool useAsync
)

O primeiro parâmetro, target, é o nome do controle de servidor que tratará o callback e o mesmo deve implementar a Interface ICallbackEventHandler. O segundo parâmetro, argument, é um argumento passado do lado cliente para o método do servidor RaiseCallbackEvent, proveniente da Interface ICallbackEventHandler. O terceiro parâmetro, clientCallback, é o nome da função cliente (Javascript) que será disparada quando o callback for processado e retornado para o cliente. Este método tem a responsabilidade de tratar e exibir o resultado. O parâmetro context é um valor que definimos e não é enviado ao servidor, mas é um valor que é passado a todos os métodos cliente. Como algum erro no processamento pode acontecer e uma Exception pode ser lançada, definimos através do parâmetro clientErrorCallback uma função que está também no cliente, que será responsável por tratar algum eventual erro. Finalmente o último dos parâmetros: useAsync; definimos como True se quisermos executar o processo de forma assíncrona e False se quisermos executá-lo de forma síncrona. Agora que já sabemos quais são as funções de cada um dos parâmetros, vejamos como fica o evento Load do WebForm:

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsCallback && Request.Browser.SupportsCallback)
    {
        string funcaoJS = this.ClientScript.GetCallbackEventReference(
            this,
            "document.getElementById('" + this.txtCPF.ClientID + "').value",
            "ExibeNome",
            "'Aqui é o contexto.'",
            "TrataErro",
            true);

        this.btnBuscar.OnClientClick = string.Concat(funcaoJS, "; return false;");
    }
}

Com isso, só falta agora no cliente definirmos a função que tratará o retorno do callback e a função que será disparada caso algum Exception ocorrer. O código cliente fica semelhante ao que vemos abaixo:

    function ExibeNome(arg, context)
    {
        document.getElementById('txtNome').value = arg;
    }
    
    function TrataErro(arg, context)
    {
        alert(arg);
        alert('Contexto: ' + context);
    }

Como podemos ver na figura 3, logo abaixo, o método WebForm_DoCallback foi atribuído no evento onClick do Button, já configurado com os devidos parâmetros que definimos no codebehind através do método GetCallbackEventReference. Para quem não percebeu como o método apareceu no evento onClick do Button, o retorno da função GetCallbackEventReference é atribuído à propriedade OnClientClick do Button que será responsável por iniciar o callback, ou melhor, será responsável por buscar o nome dado um C.P.F..

Figura 3 – Código definido no cliente.

Depois de todas essas configurações realizadas, podemos agora executar algum código no servidor sem a necessidade de atualizar a página. A responsável pela atualização dos dados retornados do callback é uma função Javascript que estará no cliente. É importante dizer que o controle GridView também fornece as funcionalidades de ordenação e paginação dos registros, sem a necessidade de atualizar a página toda. É muito simples colocar em funcionamento essa técnica, bastando apenas marcar a propriedade EnableSortingAndPagingCallbacks como True e a paginação e ordenação do controle GridView passa a ser feita sem a atualização da página como um todo.

CONCLUSÃO: Como pudemos ver no decorrer deste artigo, a Microsoft facilitou o desenvolvimento para trabalhar com o processamento no servidor sem a necessidade de atualizarmos/reconstruirmos a página toda, evitando que se recorra a componentes de terceiros para essa necessidade. Mas vale lembrar que a escrita de código Javascript ainda é necessária, já que depois que o callback é retornado, há necessidade de atualizar partes da página para exibir o resultado ao cliente. Para finalizar, é importante ratificar que a Microsoft está trabalhando em um projeto, chamado Atlas, que permitirá e deixará a codificação mais flexível para esse tipo de utilização.

Visual Studio 2005 Web Application Project Preview

Já está online a versão preview do Visual Studio 2005 Web Application Project, qual o ScottG postou em seu blog recentemente.

Para quem não leu, trata-se de uma forma de compilação e desenvolvimento de aplicações Web – ASP.NET, onde o principal objetivo é ter um impacto bem menor do que temos atualmente com a nova forma de compilação do ASP.NET 2.0, onde será trazido de volta  a forma com que fazemos no VS.NET 2002/2003.

Para quem se interessou, aqui está o link do site do projeto que foi criado pelo ScottG: http://webproject.scottgu.com, e tem ali todas as informações necessários para a instalação e criação dos projetos.

SqlDataSource – Uma propriedade interessante

Estava eu dando manutenção em uma página de uma aplicação construída em ASP.NET 2.0, onde em um relatório que, dado uma consulta utilizando o controle SqlDataSource e seus devidos parametros, é executado uma Stored Procedure dentro de uma base de dados SQL Server e, o result-set é atribuído à um controle DataList no WebForm.

Até então tudo sem problemas e, o pior (para muitos, “o melhor”), sem nenhuma linha de código de servidor (VB.NET ou C#) escrita no CodeBehind. O problema começou quando defini para True a propriedade ConvertEmptyStringToNull de alguns parametros que, capturam os valores de controles do WebForm (como TextBox) para realizar a consulta. Estando essa propriedade definida como True, os parametros que não tiverem seus valores informados, serão convertidos em NULL e, consequentemente, enviados para a query ou Stored Procedure ser executada.

A partir daí as coisas deixaram de funcionar. Fiquei procurando, achando que tinha algo errado no próprio DataList e, mesmo através do Profiler do SQL Server, via que a Stored Procedure não era executada. Depois de algum tempo analisando as propriedades do controle SqlDataSource, vi que existe uma propriedade chamada CancelSelectOnNullParameter que, por padrão é definida como True e, como o próprio nome diz, se existir algum parametro nulo, a query/Stored Procedure não é executada.

Página de Login – ASP.NET 2.0

Hoje enquanto fazia um deployment de uma aplicação ASP.NET 2.0 para o servidor, me deparei com um problema um pouco diferente dos quais eu já havia passado:

Como podem notar, eu criei uma página chamada Login.aspx, qual contém um formulário com username e password para que o usuário possa se identificar e, se o mesmo for válido, terá acesso à uma área restrita da aplicação. Pois bem, o problema começou porque o nome da página que dei (Login), que é o mesmo nome do controle Login que faz parte dos WebControls. A forma de deployment que optei, updateable, que permite a alteração do código ASPX depois de distribuído, ou melhor, instalado no webserver, faz com que a herança seja efetuada em runtime, logo, eu acredito que ele entende que a classe Login, especificado no atributo Inherits da diretiva Page, faz com que o runtime do ASP.NET tentasse herdar e converter a minha página de login do controle (UI) Login (como mostrado na figura acima), que não era o caso.

Para resolver o problema, voce pode nomear a sua página de login para algo mais específico, como por exemplo MembershipLogin ou LoginPage, ou se ainda desejar manter o nome Login, então acredito que, se criar o mesmo dentro de um namespace próprio e especificar isso no atributo Inherits da diretiva Page, é provável que resulte.

Percorrendo os Itens do DataGrid

Para compor este cenário, teremos que ter uma coluna do tipo TemplateColumn no DataGrid e, dentro dela, devemos colocar um controle CheckBox que irá especificar se o registro está ou não marcado. Depois do DataGrid devidamente carregado, para cada item (linha), com excessão do Header e Footer, será criado automaticamente um controle CheckBox para que o usuário possa marcar os registros que desejar.

Agora, no evento Click de um botão qualquer, iremos percorrer os itens do DataGrid e, dentro deste laço For Each, será necessário fazer algumas verificações antes de acessar diretamente o CheckBox que está contido dentro do item. A primeira verificação deve ser feita analisando se o item é do tipo Item ou AlternatingItem, quais são as seções (linhas) que podem conter os CheckBoxes. Em seguida, através do método FindControl, recuperamos a instância do controle CheckBox e verificamos se ele está ou não marcado, através da propriedade Checked. Se estiver, recuperamos o valor da terceira célula (índice 2) através da propriedade Text da mesma e, conseqüentemente, adicionamos em um controle Label.

O exemplo abaixo mostra como recuperar no evento Click de um botão, os itens marcados de um DataGrid:

private void Button1_Click(object sender, System.EventArgs e)
{
    foreach(DataGridItem item in this.DataGrid1.Items)
    {
        if(item.ItemType == ListItemType.Item ||
            item.ItemType == ListItemType.AlternatingItem)
        {
		
            CheckBox chk = (CheckBox)item.FindControl("CheckBox1");
            if(chk.Checked)
                this.Label1.Text += item.Cells[2].Text + "<br>";
        }
    }
}

DataBinder

Dando uma vasculhada pelo Help do Visual Studio .NET 2005, me deparei com um novo recurso do ASP.NET 2.0, qual apresenta uma nova opção (mais simples) para criarmos expressões de DataBinding que, no ASP.NET 1.x fazemos através do método Eval da classe DataBinder, onde em um dos seus overloads, é passado como parametro a referencia à um objeto (geralmente “Container.DataItem”) que é de onde que o campo de dados será recuperado e exibido ao cliente.

Além deste parametro, é também passado uma string contendo o nome do campo ou propriedade a ser recuperada. Isso é em runtime avaliado e, consequentemente, renderizado para o cliente. Um exemplo do uso desta sintaxe é mostrada abaixo:

<%# DataBinder.Eval(Container.DataItem, “expressao”) %>

expressao é o nome da propriedade a ser recuperada. Existe ainda um outro parametro para o método Eval, que recebe também uma string contendo uma possível formatação para este campo, que depois de avaliado e retornado, o ASP.NET se encarrega também de aplicar a formatação.

Tudo isso agora se resumiu à chamada de um método, ou seja, temos agora uma forma compacta de fazer isso:

<%# Eval(“expressao”) %>

Isso é possível graças à um método protegido que temos na classe Page chamado Eval (que é herdado da classe TemplateControl). Se decompilarmos esse método, veremos que ele faz o trabalho semelhante ao que fazíamos no HTML da versão 1.x, ou seja, invoca o método DataBinder.Eval:

Protected Friend Function Eval(ByVal expression As String) As Object
      Me.CheckPageExists
      Return DataBinder.Eval(Me.Page.GetDataItem, expression)
End Function

Está aqui uma forma facilitada, mas ainda propícia à erros, já que apenas informamos uma string, para que o mesmo possa avaliar e retornar um valor, portanto, se não existir um campo correspondente, o erro somente será detectado em runtime, quando uma Exception for atirada.