Resolvendo o problema do “d” na serialização JavaScript (JSON) do Asp.Net    

Olá pessoal!

Hoje vou falar de um problema que muitos desenvolvedores devem encarar quando tentam utilizar a Serialização para JavaScript a partir da versão 3.5 do framework.

Muitas vezes você deve ter migrado uma aplicação sua da versão 2.0 para a versão 3.5 ou superior, e suas requisições Ajax, ou plugins JQuery, pararam de funcionar e você perdeu horas tentando resolver. Ou quando descobriu o motivo, entrou em pânico porque viu que deveria alterar o retorno de todas as suas chamadas para começar a tratar uma propriedade “d” nos seus retornos JSON.

Se vicê já se deparou com isso, vamos aos fatos!

Exemplificando

Vou fazer um código simples, um PageMethod que retorno o nome e a idade de uma pessoa, vamos ver o retorno na versão 2.0 do .Net e na versão 3.5 ou superior, e ver o que pode impactar nossa aplicação. Esse exemplo é igual ao do post anterior:

Meu código C# (PageMethod)

   1: [WebMethod]
   2: public static object ObterPessoa()
   3: {
   4:    return new { Nome = "Frederico", Idade = 25 };
   5: }

Meu código JavaScript:

   1: $().ready(function () {
   2:        $.ajax({
   3:            type: "post",
   4:            contentType: "application/json",
   5:            url: "Default.aspx/ObterPessoa",
   6:            success: retorno
   7:        });
   8:    });
   9:  
  10:    function retorno(data) {
  11:        alert(data.Nome + " - " + data.Idade);
  12: }

O resultado como esperado:

image

A comunicação no Fiddler, conforme o esperado (Clique para ampliar):

image

O que quero mostrar é que o resultado do JSON foi exatamente como o esperado:

{"Nome":"Frederico","Idade":25}

E se eu mudar para o site na versão 3.5? Sem alteração nenhuma de código, meu alerta ficará assim:

image

E o Fiddler mostraria o problema (clique para ampliar):

image

Novamente, repare no JSON retornado:

{"d":{"Nome":"Frederico","Idade":25}}

Repare que agora, o .Net adicionou uma propriedade “d” no objeto, e todo o meu retorno está dentro dessa propriedade “d”.

Isso pode gerar muitos problemas. Imagine que você utilize um plugin Autocomplete, que espera uma lista de itens, e ao invés disso ele recebe um objeto apenas com uma propriedade “d” que por sua vez tem uma lista de itens.

Isso pode gerar grandes impactos em códigos. Veja, para meu simples código funcionar eu já teria que mudar o meu método que trata o retorno, para ficar da seguinte forma:

   1: function retorno(data) {
   2:    alert(data.d.Nome + " - " + data.d.Idade);
   3: }

E finalmente o alerta paracer como o esperado:

image

Veja que tive que adicionar o “d”. Imagine agora no impacto se você estiver utilizando JQGrid, tendo que adicionar o mapa para o D em todos os seus JSONReaders.

Agora que entendemos o problemas, vamos aos motivos

A Microsoft resolveu implementar essa alteração na forma de serialização para resolver um problema de segurança, conhecido como JSON Hijacking, que consiste em um hacker interceptar uma requisição JSON e alterar o comportamento de sites mediante requisições desse tipo, lendo e escrevendo informações de forma anônima, como se fosse outra pessoa. Como o objetivo do post não é falar dos motivos, você pode ver mais informações sobre isso nos links abaixo (Uma rápida busca no Google por JSON Hijacking pode te dar outros resultados).

http://haacked.com/archive/2009/06/25/json-hijacking.aspx

http://www.thespanner.co.uk/2011/05/30/json-hijacking/

http://www.openajax.org/whitepapers/Ajax%20and%20Mashup%20Security.php

Resolvendo o problema com JQuery

Como é muito provavel que qualquer controle Ajax da própria Microsoft ou do AjaxToolkit já esteja preparado para essa alteração do “d”. O foco da solução é para o mundo JQuery. A maior parte dos problemas ocorre com plugins, então como podemos solucionar esse problema de forma geral, uma vez por todas, para todos os plugins?

Simples, usando uma técnica que já vimos no último post: Definindo informações padrões do método ajax do JQuery.

Utilizaremos o método ajaxSetup para definir um filtro a todo retorno que recebermos de requisições assíncronas iniciadas pelo JQuery ou qualquer pluigin baseado nele, como o JQGrid e Autocomplete, citados nesse post.

O método Ajax do JQuery faz uso de um evento chamado dataFilter, responsável por fazer qualquer tratamento nos dados retornados do servidor no momento em que a resposta chega, antes mesmo de ser repassado para qualquer plugin que esteja invocando o método. Nesse evento nós podemos fazer o tratamento necessário e retirar a propriedade “d”. Vejamos um código

   1: $.ajaxSetup({
   2:    dataFilter: function (data, type) {
   3:    
   4:        if (type == "json" || type == undefined) {
   5:            var msg = eval('(' + data + ')');
   6:            if (msg.hasOwnProperty('d'))
   7:                return JSON.stringify(msg.d);
   8:            else
   9:                return data;
  10:        }
  11:        else return data;
  12:    }
  13: });

Veja que nesse código, eu verifico o tipo do retorno, e se ele for JSON (ou se não foi especificado eu considero que é JSON) eu transformo em um objeto JavaScript normal (eval), verifico se tem o “d” e se tiver, utilizo o valor do “d” com retorno, se não tem o “d” ou não é JSON eu retorno o próprio valor retornado do servidor.

Para fazer funcionar, adicionei um referência ao arquivo “json2.js”, que pode ser encontrado no Google, e desta forma essa biblioteca fica responsável por transformar meu objeto sem o “d” em JSON novamente. Obs.: Essa biblioteca só é necessária em navegadores mais antigos.

Eu só preciso configurar esse método uma vez, no arquivo MasterPage ou Layout (para MVC), por exemplo, e todas as requisições já estarão configuradas para funcionar utilizando esse método.

Caso você queira apenas utilizar esse método em uma única requisição ajax, pode passá-lo como parâmetro do próprio método $.ajax().

E pronto! Seus plugins estarão funcionando no Asp.Net 3.5 ou superior, sem que você tenha que mudar qualquer código de plugin, ou seu próprio código “legado”.

Espero que possa ajudar como ajudou a mim. O código foi baseado em um código desse post do Encosia.

Abraços e até o próximo!

23. agosto 2011 20:45 by Frederico B. Emídio | Comments (1) | Permalink

Invocando PageMethods diretamente com JQuery    

Olá pessoal!

Apesar de já ter tratado de PageMethods em um dos meus primeiros posts, vou falar um pouco mais dele novamente, porque vejo que muita gente ainda não entendeu com funciona, o que torna difícil a utilização do mesmo de outra forma que não seja através do proxy que o próprio Asp.Net cria no JavaScript.

Para entender o básico, veja o primeiro post sobre o assunto, para eu não precisar repetí-lo aqui.

Resumidamente, adicionamos um PageMethod criando um método estático em uma página, e decorando esse método com o atributo [WebMethod]. E no JavaScript acessamos esse método através do proxy no JavaScript da seguinte forma: PageMethods.[NomeDoMetodo].

Basicamente o Asp.Net disponibiliza o método como um WebService, e o JavaScript acessa o mesmo através da classe XmlHttpRequest.

Mas por que muitas vezes não conseguimos acessar esses métodos através do JQuery? Alguns Plugins como JQGrid e Auto-Complete as vezes têm certa dificuldade de conseguir enviar o chamado ou processar a resposta. Vamos a alguns exemplos.

Vou fazer um código bem simples, meu PageMethod vai ser assim:

   1: [WebMethod]
   2: public static string ObterPessoa()
   3: {
   4:     return "Retorno do Server";
   5: }

E meu JavaScript:

   1: $().ready(function () {
   2:     $.ajax({
   3:         url: "Default.aspx/ObterPessoa",
   4:         success:retorno
   5:     });
   6: });
   7:  
   8: function retorno(data) {
   9:     alert(data.d);
  10: }

Veja que o código é bem simples. Estou utilizando o mínimo do método ajax do JQuery, ou seja, os demais valores estão sendo utilizados os valores Default. Vendo o código, o retorno deveria uma alerta do JavaScript com o valor “Retorno do Server”. Mas a resposta é: Nada! Exatamente, nada acontece, inclusive não retorna erro algum.

Nesse momento é que o desenvolvedor começa a passar as mãos na testa e falar: “Maldito Murphy!”.

Se olharmos as chamadas o Fiddler ou outra ferramenta que monitora os Requests, teremos algumas informações interessantes. Na minha IE Developer Toolbar tenho as seguintes informações (clique para ampliar):

image

Perceba que na última linha tem a minha chamada para o PageMethod, sem erro, mas com um código diferente (304). Lembre que códigos de erros são apenas os 4xx e 5xx, ou seja, de fato não houve erro.

O HTTP Return Code 304 indica que o arquivo (ou resource) solicitado não teve alteração, ou seja, o Browser pode obter o valor do cache se quiser, ou solicitar a página mesmo assim. Então pense, se aparentemente é a primeira vez que estamos chamando o método, por que ele está falando que já está em cache? Qual será o retorno? Vamos dar uma olhada no retorno (clique para ampliar):

image

Veja que é o próprio código da página, e não do PageMethod, ou seja, o Asp.Net, ao receber o Request, está entendendo que o que está sendo solicitado é a página e não o método.

Isso acontece porque estamos invocando o método com o verbo HTTP GET, e a primeira coisa que devemos saber é que PageMethods só funciona com HTTP POST!

Outra coisa que podemos ver na primeira imagem, é que o tipo da solicitação é “text/html” e o retorno do Asp.Net está de acordo, ou seja, o texto HTML da página.

Portanto, a segunda coisa que devemos saber é que para o PageMethod funcionar é que o tipo da requisição deve ser json ou xml.

Vamos mudar apenas essas duas coisa e ver o que acontece:

   1: $().ready(function () {
   2:     $.ajax({
   3:         type: "post",
   4:         contentType: "application/json", 
   5:         url: "Default.aspx/ObterPessoa",
   6:         success:retorno
   7:     });
   8: });
   9:  
  10: function retorno(data) {
  11:     alert(data.d);
  12: }

E os prints da Developer Toolbar:

image

Veja que algumas coisas mudaram:

Primeiro que o método agora é POST e o código do retorno é 200, ou seja, sucesso (OK). Além disso, o tipo da requisição também mudou, sendo agora application/json. Só o fato dessas informações terem mudado já é alguma coisa. Mas vamos ver o retorno:

image

E o alerta, como o esperado.

image

Assim já fazemos nosso PageMethod funcionar. Poderíamos terminar o post por aqui, mas tem mais algumas coisas que precisamos saber, por exemplo, como eu passo parâmetros?

Passando parâmetros para PageMethods com JQuery

Vou mudar meu PageMethod e meu JavaScript para passar parâmetros. A alteração é muito simples, preciso apenas passar meu dados no formato JSON, assim o Asp.Net vai saber converter os dados do requests para os parâmetros do método. Vamos lá:

   1: [WebMethod]
   2: public static string ObterPessoa(string nome, string idade)
   3: {
   4:    return string.Format("Nome {0}. Idade {1}",nome,idade);
   5: }

E o JavaScript:

   1: $().ready(function () {
   2:    $.ajax({
   3:        type: "post",
   4:        contentType: "application/json",
   5:        url: "Default.aspx/ObterPessoa",
   6:        success: retorno,
   7:        data: '{nome:"Frederico",idade:"25"}'
   8:    });
   9: });
  10:  
  11: function retorno(data) {
  12:    alert(data.d);
  13: }

Dessa forma conseguimos passar os dados, e podemos ver com o alerta que o retorno foi como o esperado:

image

Conclusão

Vimos que é possível invocar PageMethods do JQuery sem muito esforço. Precisamos apenas saber quais parâmetros passar na chamada. Muitas vezes nossos plugins não funcionam com PageMethods porque não sabemos quais parâmetros utilizar. Uma vez que já sabemos, fica fácil.

Tenha em mente que para um PageMethod funcionar, você deve invocá-lo:

  • Utilizando o verbo Post
  • Utilizando o Content-Type como application/json.

Isso já é suficiente para funcionar.

Se você vai invocar muitas vezes métodos através do Ajax do JQuery, você pode alterar os valores Defaults e invocar de forma mais sucinta, da seguinte forma:

   1: $().ready(function () {
   2:     $.ajaxSetup({
   3:         type: "post",
   4:         contentType: "application/json"
   5:     });
   6:     
   7:     //E chamar sem precisar configurar novamente.
   8:     $.ajax({
   9:         url: "Default.aspx/ObterPessoa",
  10:         success: retorno,
  11:         data: '{nome:"Frederico",idade:"25"}'
  12:     });
  13: });

Essa tática é muito boa para utilizar com plugins, afinal, você não consegue (ou não deve) alterar a chamada internas dos plugins, como o JQGrid, então assim você alterar o padrão, para que o plugin utilize o método $.ajax da forma que você deseja.

O interessante de utilizar JQuery, é que você não precisa utilizar o ScriptManager com EnablePageMethods igual a true, porque você não precisará do Proxy do JavaScript. Sua página ficará consideravelmente mais leve se você tiver vários PageMethods em uma página só.

Em breve publicarei um breve post para explicar o porquê do “.d” no retorno do PageMethods, o que também muitas vezes dificulta a utilização de PageMethods com plugins JQuery.

Bom pessoal, espero que ajude. Até o próximo.

16. agosto 2011 09:32 by Frederico B. Emídio | Comments (1) | Permalink

Como funciona o UpdatePanel?    

Olá pessoal!

Hoje vou falar de um tema bem simples do WebForm, mas que muitas pessoas utilizam errado, acreditando que estão fazendo a coisa certa. Vou falar do UpdatePanel.

O UpdatePanel é um controle do Asp.Net Ajax, utilizado para renderizar partes parciais de uma tela sem a necessidade de fazer um completo PostBack. Isso todos já sabem.

Para saber como utilizá-lo, é necessário conhecer algumas propriedades básicas:

Triggers – É uma coleção de controles que têm a capacidade de fazer o conteúdo do UpdatePanel atualizar. Existem dois tipos: Assíncronos e Síncronos. Em geral, quando utilizamos UpdatePanel utilizamos apenas os tipos Assincronos, ou seja, que atualizam sem realizar o PostBack. o Triggers do tipo Síncrono tem o comportamento normal, realizando o PostBack da página inteira.
ChildrenAsTriggers – Indica se os controles internos de um UpdatePanel automaticamente serão tratados como Triggers. Caso essa propriedade esteja como false, para que um botão interno do UpdatePanel dispare uma atualização parcial este deve ser colocado na lista de Triggers.
UpdateMode – Indica se a atualização do UpdatePanel vai sempre acontecer quando um evento assíncrono ocorrer ou apenas quando for invocado o método Update do UpdatePanel a partir do Code-behind.
RenderMode – Não é uma propriedade tão importante, mas não custa nada conhecer. Essa propriedade indica como será renderizado o controle referente ao UpdatePanel no HTML.  Os possíveis valores são: Block, que é o padrão, e renderizará o controle como DIV; e a outra opção é Inline, que renderizará um SPAN.

Além de conhecer essas propriedades, também temos que saber que para utilizar um UpdatePanel, e qualquer outro controle do Asp.Net Ajax, temos que utilizar o controle ScriptManager. Esse controle é responsável por adicionar à página todos os JavaScripts necessários para fazer funcionar as capacidades Ajax do site. Ele deve ser adicionado antes de qualquer controle Ajax.

Com isso, já poderíamos criar uma página totalmente Ajax, apenas utilizando um ScriptManager, mas será que isso está certo? O intuito desse post é mais responder essa questão do que qualquer outra coisa.

Como funciona UpdatePanel

Antes de vermos aos boas práticas de utilização do UpdatePanel, precisamos saber como ele funciona. Lembre-se, não existe mágica, e saber como funciona pode ajudar muito a resolver problemas que parecem coisa de Murphy.

Muitas pessoas acham que apenas o conteúdo interno do UpdatePanel é enviado ao Server, e isso é um grande erro. A primeira coisa que devemos saber é que ao atualizarmos uma região de uma página, todos os campos Forms de da página são enviado para o servidor. Isso inclui controles como Input, Select, etc; que por sua vez inclui todo o ViewState da página.

Acredite, sem enviar todos os campos para o servidor, incluindo o ViewState, não seria possível você ler os valores dos campos no CodeBehind. Isso deveria ser óbvio, mas já vi muitas pessoas descordarem nesse ponto.

O que muda é o retorno, que virá apenas o que deve ser atualizado. No retorno, o que altera naturalmente não é a página inteira, mas apenas o conteúdo do UpdatePanel.

Com isso em mente, ao utilizar UpdatePanel, você deve saber:

Não importa quão pequeno seja seu UpdatePanel, o envio para o Server vai ser sempre o mesmo.

Além disso, o ciclo de vida da página permanece inalterado, ou seja, todos os eventos serão invocado da mesma forma como seriam em um PostBack padrão. Portanto, se você clicar no botão btnSalvar, não será apenas o método btnSalvar_Click que será invocado, mas também o PageLoad e outros métodos necessários para renderização, como PreReder e Render dos controles envolvidos.

Para isso, o Asp.Net também enviará informações extras que controlam essas informações, como o controle clicado.

Vamos a um exemplo.

Vou utilizar o código do post sobre exportar para Excel, adicionando funcionalidades Ajax. Meu HTML ficaria da seguinte forma:

   1: <asp:ScriptManager ID="mng" runat="server">
   2: </asp:ScriptManager>
   3: <h2>Exportando para Excel</h2>
   4: <p>Nome:<asp:TextBox ID="txtNome" runat="server"></asp:TextBox></p>
   5: <p>Idade:<asp:TextBox ID="txtIdade" runat="server"></asp:TextBox></p>
   6: <p>Email:<asp:TextBox ID="txtEmail" runat="server"></asp:TextBox></p>
   7: <p>
   8: <asp:Button Text="Salvar" runat="server" ID="btnSalvar" OnClick="btnSalvar_Click" />
   9: </p>
  10: <asp:UpdatePanel runat="server" >
  11:     <ContentTemplate>
  12:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
  13:             OnPageIndexChanging="grid_PageIndexChanging">
  14:         </asp:GridView>
  15:     </ContentTemplate>
  16:     <Triggers>
  17:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  18:     </Triggers>
  19: </asp:UpdatePanel>
  20: <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />

Veja que apenas adicionei o UpdatePanel e o ScriptManager. Não mudei nada no CodeBehind. Com isso minha página já está funcionando com Ajax.

Apesar do meu UpdatePanel estar apenas na Grid, veja que o envio para o server, quando eu clicar no botão Salvar, enviará o conteúdo dos campos de cadastro e o ViewState. Veja a imagem do Fiddler (Clique para ampliar).

Fiddler do UpdatePanel

Na Imagem acima estou mostrando que o envio inclui todo o conteúdo da página, que será o mesmo se não tiver UpdatePanel, mas o retorno retorna só o que deve ser atualizado, que deve ser a Grid, além da atualização do ViewState.

A única diferença é que se não tiver UpdatePanel, a página seria enviada inteira, e isso incluiria todas as tags HTML (head, body, etc), que não são enviadas no update assíncrono.

Como utilizar corretamente um UpdatePanel

Agora que entendemos como funciona, devemos saber algumas boas práticas, e responder àquela pergunta: “Será que estou fazendo certo?”. Por exemplo, muitas pessoas têm uma tela enorme de cadastro, e acredita que apenas colocando um único UpdatePanel na página toda o problema está resolvido. Realmente resolve, mas não da melhor forma.

Nessas situações, as vezes para atualizar um simples Combo, todo o conteúdo do formulário é atualizado assincronamente. Isso deixa a página extremamente lenta, pois é enviado um enorme conteúdo para o serve, e o retorno, que poderia ser apenas uma linha de HTML, acaba sendo a página inteira.

Para isso, segue algumas dicas para fazer suas páginas rápidas e bem feitas com utilização UpdatePanel, e evitar diversos erros inexplicáveis.

  1. Não utilize UpdatePanels que englobem a página toda, a não ser que sua página realmente seja pequena e tenha realmente que atualizar toda de uma vez.
  2. Utilize em geral a propriedade ChildrenAsTrigger como false a maior parte do tempo, ao menos que saiba realmente que é necessário que todos os filhos sejam Triggers.
  3. Utilize mais de um UpdatePanel na sua página. Um para cada pequeno contexto que deve ser atualizado de acordo com a navegação do usuário.
  4. Controle quais controles devem atualizar cada UpdatePanel através da coleção de Triggers de cada UpdatePanel.
  5. Saiba que um controle pode ser Trigger de mais de um UpdatePanel.
  6. Saiba que um controle dentro de um UpdatePanel pode ser Trigger de outro UpdatePanel.
  7. Utilize com cuidado as regras acima, uma quantidade exagerada de UpdatePanels e Triggers em uma página pode deixá-la pesada, devido a quantidade de Scripts. Além disso, alguns poucos Browsers tem dificuldade de limpar memória, e a atualização de HTML pode causar um estouro de memória (acredito, isso é possível).

Bom, com essas dicas acredito que seu uso de UpdatePanel vai ficar mais tranquilo, e terá menos erros desconhecidos.

Espero que possa ajudar.

Até o próximo.

21. julho 2011 05:36 by Frederico B. Emídio | Comments (6) | Permalink

Download de arquivo com UpdatePanel    

Olá pessoal!

Agora, vou fazer um post bem rápido sobre um erro que ocorre quando é tentado realizar download em uma página com UpdatePanel.

Não é um post para detalhar o uso de UpdatePanel, isso você pode ver aqui, mas apenas um de seus comportamentos.

É um post simples, apenas para responder um pergunta: Não é possível fazer download com UpdatePanel? A resposta é: Sim, é possível. Veremos como logo abaixo.

Tomando como base o post sobre exportar para Excel, vou acrescentar a funcionalidade Ajax a ele um pouco diferente do que fiz no post de UpdatePanel.

Como explicado no post anterior, o UpdatePanel só deve estar na região que deve ser atualizada, e com certeza um arquivo para ser baixado não deve estar na região de atualização.

Vamos ao código!

Veja o código abaixo, extraído do post de UpdatePanel:

   1: <asp:ScriptManager ID="mng" runat="server"></asp:ScriptManager>
   2: <h2>Exportando para Excel</h2>
   3: <p>Nome:<asp:TextBox ID="txtNome" runat="server"></asp:TextBox></p>
   4: <p>Idade:<asp:TextBox ID="txtIdade" runat="server"></asp:TextBox></p>
   5: <p>Email:<asp:TextBox ID="txtEmail" runat="server"></asp:TextBox></p>
   6: <p><asp:Button Text="Salvar" runat="server" ID="btnSalvar" OnClick="btnSalvar_Click" /></p>
   7: <asp:UpdatePanel runat="server" >
   8:     <ContentTemplate>
   9:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
  10:             OnPageIndexChanging="grid_PageIndexChanging">
  11:         </asp:GridView>
  12:     </ContentTemplate>
  13:     <Triggers>
  14:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  15:     </Triggers>
  16: </asp:UpdatePanel>
  17: <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />

Apesar dessa página estar utilizando UpdatePanel, o Download do botão btnExportar vai funcionar. Por que? Por que a ação dele não é assíncrona. Ele não é Trigger de nenhum UpdatePanel.

Veja bem, quando tentamos baixar um arquivo dentro de um UpdatePanel, o conteúdo retornado para o Browser não é um HTML válido, logo, não será possível adicioná-lo dentro do UpdatePanel e o Asp.Net/ JavaScript reclama.

Se utilizarmos o botão Exportar dentro do UpdatePanel, teremos um erro, veja a seguir:

   1: <asp:UpdatePanel runat="server" >
   2:     <ContentTemplate>
   3:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
   4:             OnPageIndexChanging="grid_PageIndexChanging">
   5:         </asp:GridView>
   6:         <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />
   7:     </ContentTemplate>
   8:     <Triggers>
   9:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  10:     </Triggers>
  11: </asp:UpdatePanel>

Agora, na linha 6, o meu botão está dentro do UpdatePanel, e como ao clicar nele o Asp.Net Ajax vai preparar tudo para fazer Assíncrono, ao ter o retorno inválido (um Excel, por exemplo, não é um retorno válido) vai gerar o erro, que pode variar de acordo com a versão do Framework utilizado.

Mas então só botões que estão fora de um UpdatePanel podem fazer download? Lógico que não! Se você tiver o botão em uma GridView, que deve estar dentro de um UpdatePanel, e ele deve realizar um download, como fazer? Ou por qualquer motivo o seu botão tem que estar dentro do UpdatePanel? Simples, coloque o evento do botão, ou da Grid, como um Trigger Síncrono. Veja:

   1: <asp:UpdatePanel runat="server" >
   2:     <ContentTemplate>
   3:         <asp:GridView ID="grid" runat="server" AllowPaging="true" PageSize="5" AutoGenerateColumns="true"
   4:             OnPageIndexChanging="grid_PageIndexChanging">
   5:         </asp:GridView>
   6:         <asp:Button ID="btnExportar" Text="Exportar" runat="server" OnClick="btnExportar_Click" />
   7:     </ContentTemplate>
   8:     <Triggers>
   9:         <asp:AsyncPostBackTrigger ControlID="btnSalvar"  EventName="Click"/>
  10:         <asp:PostBackTrigger ControlID="btnExportar" />
  11:     </Triggers>
  12: </asp:UpdatePanel>

Agora meu botão, que está dentro do UpdatePanel, vai consegui realizar o Download, porque na linha 10 eu o adicionei como um Trigger de PostBack normal (Síncrono).

Caso eu não faça isso, terei o seguinte erro (Asp.Net 4):

Erro em tempo de execução do Microsoft JScript: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed.

A mensagem pode mudar, como disse, de acordo com o FrameWork utilizado. Poderia ser:

Microsoft JScript runtime error: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.

Como disse no post sobre UpdatePanel, não existe mágica. Internamente o Framework Ajax da Microsoft utilizar os objetos Ajax do Browser (XMLHttpRequest), assim como o Jquery faz, e através desses objetos não é possível realizar download. Você poderia transferir os bytes de um arquivo de forma assíncrona, mas não seria possível convertê-lo em arquivo, porquê o JavaScript, em geral, não tem permissão no Browser para realizar esse tipo de função (acessar IO, memória, etc). caso contrário seria muito fácil fazer vírus.

Bom, espero que ajude quem precise dessa ajuda.

Até o próximo.

20. julho 2011 21:09 by Frederico B. Emídio | Comments (10) | Permalink

Como utilizar validação com Asp.Net MVC    

Olá pessoal!

Hoje vou falar de uma tarefa importante no desenvolvimento de sites: Validação.

Na construção de site nós nunca podemos confiar nas informações que o usuário informa, devemos saber que qualquer pessoa pode errar uma digitação ou esquecer de digitar uma informação qualquer, e por isso é importante saber utilizar Validação nos formulários que criamos.

No Asp.Net MVC existem várias formas de realizar validações, mas hoje vou falar de apenas três:

  • Adicionando informações à propriedade ModelState
  • Utilizando DataAnnotations
  • Utilizando DataAnnotations com JQuery

O processo de validação no Asp.Net MVC ficou muito fácil, eu diria que ficou muito mais fácil que no Asp.Net WebForm, vamos então começar pelo básico:

Validando suas Views com ModelState

A validação no Asp.Net MVC sempre é feita através do estado do modelo (ModelState) recebido na Action de um Controller, o que muda é como esse estado é alterado, pois pode feito manualmente, como faremos agora, ou automaticamente, através de ActionFilter customizado ou automático.

A forma básica é você adicionar manualmente informações de erro no modelo ao ModelState, invocando o método AddModelError(chave,mensagemDeErro).

Vamos aos exemplos! Para começar vou criar meu modelo, bem simples, como em todos os meus outros exemplos: Pessoa

   1:  public class Pessoa
   2:  {
   3:      public string Nome { get; set; }
   4:      public string Email { get; set; }
   5:      public string Telefone { get; set; }
   6:      public int Idade { get; set; }
   7:  }

Naturalmente, para criar uma pessoa no banco de dados, algumas informações devem ser obrigatórias e outras devem seguir algum formato específico, para todos os nossos exemplos usaremos as mesmas validações:

  • O nome é obrigatório.
  • O e-mail é obrigatório.
  • O telefone é obrigatório.
  • O e-mail deve seguir o formatado texto@texto.texto
  • A idade é deve ser maior que 18.
  • O telefone deve seguir o formato ####-####

Vou criar duas Actions de nome Edicao, uma para renderizar a View inicial e outra para persistir os dados, essa segunda terá a responsabilidade de fazer a validação do Modelo, e adicionar os erros ao ModelState. Caso não tenha erro, apenas redirecionará o usuário para a página inicial (persistir em uma base de dados não vem ao caso).

Abaixo segue o código das Actions:

   1:  public ActionResult Edicao()
   2:  {
   3:      return View();
   4:  }
   5:   
   6:  [AcceptVerbs(HttpVerbs.Post)]
   7:  public ActionResult Edicao(Pessoa pessoa)
   8:  {
   9:      //Valida os dados no lado do servidor.
  10:      if(string.IsNullOrEmpty(pessoa.Nome))
  11:      ModelState.AddModelError("Nome","Nome é obrigatório");
  12:      
  13:      if (string.IsNullOrEmpty(pessoa.Email))
  14:      ModelState.AddModelError("Email", "Email é obrigatório");
  15:      else{
  16:      if (!Regex.Match(pessoa.Email,@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$").Success)
  17:      ModelState.AddModelError("Email", "E-mail inválido.");
  18:      }
  19:   
  20:      if (pessoa.Idade <= 18)
  21:      ModelState.AddModelError("Idade", "Idade deve ser maior que 18.");
  22:   
  23:      if (string.IsNullOrEmpty(pessoa.Telefone))
  24:      ModelState.AddModelError("Telefone", "Telefone é obrigatório.");
  25:      else
  26:      {
  27:      if (!Regex.Match(pessoa.Telefone, @"^\d{4}[-]{1}\d{4}$").Success)
  28:          ModelState.AddModelError("Telefone", "Preencha um telefone no formato ####-####");
  29:   
  30:      }
  31:      //Verifica se algum erro acima falhou.
  32:      if (!ModelState.IsValid)
  33:      {
  34:      return View();
  35:      }
  36:      else
  37:      {
  38:      //Se estive certo só redireciono para a página inicial.
  39:      return Redirect("/");
  40:      }   
  41:  }

O código é bem intuitivo, caso tenha adicionado alguma mensagem de erro, a propriedade IsValid do ModelState retornará false, e o modelo estará inválido, assim eu retorno minha mesma View, que exibirá as mensagens de erros. Minha View fica da seguinte forma:

   1:  <%using(Html.BeginForm()){ %>
   2:  <p>
   3:  <%=Html.LabelFor(Model=>Model.Nome)%>
   4:  <%=Html.TextBoxFor(Model=>Model.Nome)%>
   5:  <%=Html.ValidationMessage("Nome")%>
   6:  </p>
   7:  <p>
   8:  <%=Html.LabelFor(Model=>Model.Email)%>
   9:  <%=Html.TextBoxFor(Model=>Model.Email)%>
  10:  <%=Html.ValidationMessage("Email")%>
  11:  </p>
  12:  <p>
  13:  <%=Html.LabelFor(Model=>Model.Telefone)%>
  14:  <%=Html.TextBoxFor(Model=>Model.Telefone)%>
  15:  <%=Html.ValidationMessage("Telefone")%>
  16:  </p>
  17:  <p>
  18:  <%=Html.LabelFor(Model=>Model.Idade)%>
  19:  <%=Html.TextBoxFor(Model=>Model.Idade)%>
  20:  <%=Html.ValidationMessage("Idade")%>
  21:  </p>
  22:  <p>
  23:  <input type="submit" value="Salvar"/>
  24:  </p>
  25:  <%} %>

Veja que a única novidade da minha View a utilização do HTMLHelper ValidationMessage, passando o nome da chave que eu valido no servidor. Ao executar minha tela, caso passe informações inválidas, terei o seguinte resultado:

image

Ou assim:

image

E a sua validação está pronta para ser usada! Eu poderia utilizar um ValidationSummary, apenas utilizando o helper para isso, da seguinte forma: <%=Html.ValidationSummary()%>.

Vamos à nossa segunda forma de validar nosso modelo.

Validando suas Views com DataAnnotarions

DataAnnotations é uma forma de você adicionar validações ao seu modelo através de atributos, incorporada ao .Net 4. O interessante dessa estratégia é que não está ligada ao Asp.Net MVC, portanto, você pode utilizá-la tanto em Asp.Net, como em qualquer outra tecnologia .Net, como WCF. Isso é muito importante, pois você não precisa repetir validação em diversas partes do código, técnica conhecida como DRY (Don’t Repeat Yourself).

Como a validação é centralizada no modelo, nosso modelo sofrerá uma pequena alteração, assim como nossa Action, pois tiraremos a validação da Action e passaremos para o modelo. Nosso modelo ficará assim:

   1:  public class Pessoa
   2:  {
   3:      [Required(ErrorMessage = "O Nome é obrigatório.")]
   4:      public string Nome { get; set; }
   5:   
   6:      [Required(ErrorMessage = "O E-mail é obrigatório.")]
   7:      [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "E-mail inválido.")]
   8:      public string Email { get; set; }
   9:   
  10:      [Required(ErrorMessage = "O Telefone é obrigatório.")]
  11:      [RegularExpression(@"^\d{4}[-]{1}\d{4}$", ErrorMessage = "Telefone inválido. Informe ####-####.")]
  12:      public string Telefone { get; set; }
  13:   
  14:      [Range(18, Int16.MaxValue, ErrorMessage = "A idade deve ser maior que 18 anos.")]
  15:      public int Idade { get; set; }
  16:  }

Veja que existem vários tipos de atributos de Validação, e para todos eu posso informar uma mensagem de erro personalizada, através da propriedade ErrorMessage, além de cada um ter propriedades específicas de acordo com o tipo.

Como toda a lógica de validação passou para o modelo, nossa Action ficará bem mais simples:

   1:  [AcceptVerbs(HttpVerbs.Post)]
   2:  public ActionResult Edicao(Pessoa pessoa)
   3:  {
   4:      //O modelo já chega validado
   5:      if (!ModelState.IsValid)
   6:      {
   7:      return View();
   8:      }
   9:      else
  10:      {
  11:      //Se estive certo só redireciono para a página inicial.
  12:      return Redirect("/");
  13:      }
  14:  }

Veja como ficou simples a Action agora, e o resultado nas Views é o mesmo, não mudei nada no meu HTML, apenas adicionei um Summary, para ver que o resultado é o mesmo:

image

E mais uma forma de validação já está funcionando e pronta para publicar!

Mas espera aí! Existe um problema nessas duas formas de validação: Ela são realizadas no servidor. Ou seja, o usuário precisa ser submetido ao servidor para realizar a validação, o que pode prejudicar muito a experiência do usuário, e para isso termos a terceira forma de validação de hoje:

Utilizando DataAnnotations com JQuery para validação no Cliente

O mais interessante da utilização do JQuery com DataAnnotation é o impacto no código do servidor: Nada! Isso mesmo, não tem impacto nenhum no servidor para você adicionar validação no Asp.Net MVC no cliente. Vou fazer a seguinte alteração no HTML:

 

   1:  <% Html.EnableClientValidation();%>
   2:  <%using (Html.BeginForm())
   3:  { %>
   4:  <p>
   5:  <%=Html.LabelFor(Model=>Model.Nome)%>
   6:  <%=Html.TextBoxFor(Model=>Model.Nome)%>
   7:  <%=Html.ValidationMessageFor(Model => Model.Nome)%>
   8:  </p>
   9:  <p>
  10:  <%=Html.LabelFor(Model=>Model.Email)%>
  11:  <%=Html.TextBoxFor(Model=>Model.Email)%>
  12:  <%=Html.ValidationMessageFor(Model => Model.Email)%>
  13:  </p>
  14:  <p>
  15:  <%=Html.LabelFor(Model=>Model.Telefone)%>
  16:  <%=Html.TextBoxFor(Model=>Model.Telefone)%>
  17:  <%=Html.ValidationMessageFor(Model => Model.Telefone)%>
  18:  </p>
  19:  <p>
  20:  <%=Html.LabelFor(Model=>Model.Idade)%>
  21:  <%=Html.TextBoxFor(Model=>Model.Idade)%>
  22:  <%=Html.ValidationMessageFor(Model => Model.Idade)%>
  23:  </p>
  24:  <p>
  25:  <input type="submit" value="Salvar" />
  26:  </p>
  27:  <%} %>

As alterações na view foram pequenas, adicionei o Helper EnableClientValidation e mudei ValidationMessage por ValidationMessageFor, fora isso, foi necessário adicionar referência a alguns arquivos JS (JavaScript), você pode adicionar tanto no seu arquivo de Layout  (Master) ou na própria View. Os arquivos são os seguintes:

 

   1:  <script src="<%=Url.Content("~/Scripts/jquery-1.4.1.min.js")%>" type="text/javascript"></script>
   2:  <script src="<%=Url.Content("~/Scripts/jquery.validate.js")%>" type="text/javascript"></script>
   3:  <script src="<%=Url.Content("~/Scripts/MicrosoftMvcJQueryValidation.js")%>" type="text/javascript"></script>
 

Os arquivos que adicionei são os arquivo do próprio JQuery (a versão desatualizada que vem com o Visual Studio 2010, que você pode mudar para a atual), o plug-in Validate do Jquery e um arquivo fornecido pelo time do Asp.Net para fazer o meio campo entre a validação do Asp.Net com o JQuery de forma totalmente transparente. Esse arquivo pode ser baixado com o Asp.Net MVC Futures, clicando aqui.

O Resultado é o mesmo:

image

E assim sua validação já estará funcionando no cliente. O mais legal dessa abordagem é que você realizará a validação tanto no Cliente como no Servidor, sem repetir uma linha de código (DRY).

No servidor que eu digo não é como no WebForm, onde a validação de um Validator Control também funciona tanto no servidor como no cliente, porque naquele caso, é apenas no servidor Asp.Net, visto que a validação está na página. Nesse caso, a validação está no Modelo, logo, o mesmo código pode ser utilizado em uma classe de negócio, WCF, WWF, etc; sem replicação de código.

Existe uma validação nativa no lado do Cliente no MVC, mas preferi mostrar com o Jquery, porque é possível integrar outros plug-ins com a validação, que podem gerar efeitos bem interessantes à sua página.

Bom, acredito que seja o suficiente para você adicionar validação ao sua página, de forma bem clara sem ter que se esforçar muito.

Abaixo você pode baixar uma solução contendo três projetos, um com cada tipo de validação, para você ver que não existe grandes diferenças entra elas.

Abraços e até o próximo pessoal.

ValidatorsMVC.zip (1,00 mb)

27. fevereiro 2011 06:06 by Frederico B. Emídio | Comments (0) | Permalink

Utilizando Ajax com Asp.Net MVC    

Olá pessoal, hoje vou falar de um assunto que é de extrema importância para desenvolvimento Web atualmente: Ajax.

Para quem não sabe, Ajax é o acrônimo de Asynchronous Javascript And XML, e que na  prática nada mais é que a capacidade de postar informações ao servidor, sem a necessidade de enviar a página inteira, e receber apenas uma pequena informação, sem a necessidade de atualizar a página toda. Para saber mais, clique no link do nome.

Hoje em dia, Ajax virou praticamente um conceito de atualização parcial de páginas Web, digo isso, porque Ajax em si não é mais utilizado na grande maioria das vezes! É isso mesmo, se reparar no nome completo, o X do Ajax é XML, e normalmente não utilizamos XML para a comunicação, mas sim JSON, ou seja, o que utilizamos de fato é chamado muitas vezes em blog e artigos pela Web de AJAJ (Asynchronous Javascript And JSON).

A responsabilidade de fazer a comunicação de forma assíncrona é do navegador, através do objeto XMLHttpRequest (IE 7+, e demais browsers) ou do Microsoft.XMLHTTP (IE 5 e 6). No nosso caso, não precisamos saber disso, porque utilizaremos JQuery para encapsular toda a lógica de criação da comunicação.

Apesar de usarmos JQuery, nós poderiamos utilizar outros frameworks JavaScript para encapsular a lógica de comunicação. A própria Microsoft disponibilizar nos projetos Web o framework MicrosoftAjax.js, que é extremamente bom. Com esse framework, inclusive, você pode acessar funcionalidades do Asp.Net, como a parte de Authentication.

Bom, mas vamos ao que interessa.

Utilizando JQuery para fazer chamadas assíncronas em Asp.MVC

No Jquery existe uma forma básica de se comunicar assincronamente com o servidor, que é o método $.ajax. Este método contém todas as informações necessárias para uma comunicação, nele você pode definir a url, o método de retorno em caso de sucesso e em caso de falha, tipo do dado passado (onde você pode passar uma sério de Content-Type, como json, xml, text, etc), verbo HTTP (get, post, etc), e mais uma série de opções. Como não utilizaremos esse método, você pode clicar aqui para ver uma lista detalhada de opções.

Não utilizaremos esse método, porque existem dois métodos que abstraem bastante a utilização dele, e que são suficientes para a maioria das situações, são os métodos $.post e $.get.

Estes métodos basicamente invocam o método $.ajax, passando no parâmetro type o tipo post ou get. O único problema é que ele não retorna erro, ou seja, se tiver algum problema no seu código que faça ter algum erro, você vai ficar sem saber até quebrar um pouco a cabeça, o ideal é: Quando tiver algum comportamento estranho, mude o método para $.ajax, e defina o parâmetro error, assim poderá ver qual a mensagem de erro está sendo retornada do servidor.

Nosso site fará quatro testes:

  • Obter informação do servidor via Get
  • Enviar informação para o servidor via Get
  • Enviar informação para o servidor via Post através de parâmetros
  • Enviar informação para o servidor via Post através de um objeto.

Abaixo segue o código do Controller com suas Action:

   1:  public JsonResult ObterGet()
   2:  {
   3:      var json = new { Nome = "Nome do Servidor", Idade = 20 };
   4:      return Json(json, JsonRequestBehavior.AllowGet);
   5:  }
   6:   
   7:  public JsonResult EnviarGet(string nome, int idade)
   8:  {
   9:      var json = new { Nome = nome + " Servidor", Idade = idade + 1 };
  10:      return Json(json, JsonRequestBehavior.AllowGet);
  11:  }
  12:   
  13:  public JsonResult EnviarPost(string nome, int idade)
  14:  {
  15:      var json = new { Nome = nome + " Servidor", Idade = idade + 1 };
  16:      return Json(json);
  17:  }
  18:   
  19:  public JsonResult EnviarObjeto(Pessoa p)
  20:  {
  21:   
  22:      p.Nome += " Servidor";
  23:      p.Idade += 1;
  24:      return Json(p);
  25:  }

Os códigos são bem simples: Os métodos de GET (ObterGet e EnviarGet) precisam passar uma informação a mais no método Json pois o MVC força você a dizer que quer retornar uma chamada get, mesmo sabendo do risco que isso pode ser. É um risco porque um site de outro domínio pode invocar esse método utilizando GET, e se você não informar que isso aconteça, o ASP.NET vai barrar a chamada, retornado  a mensagem:

This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.

Como queremos testar o Get, então permitimos essa comunicação, adicionando o parâmetro JsonRequestBehavior.AllowGet. Para os métodos utilizados através de POST (EnviarPost e EnviarObjeto) não precisamos utilizar esse parâmetro.

Para o método EnviarObjeto funcionar, criei uma classe simples, que segue abaixo:

   1:  public class Pessoa
   2:  {
   3:      public string Nome { get; set; }
   4:      public int Idade { get; set; }
   5:  }

Para consumir esses métodos, criei o HTML abaixo:

<p>Nome:<input type="text" id="txtNome" /></p>
<p>Idade:<input type="text" id="txtIdade" /></p>
<input type="button" value="Enviar Get" onclick="enviarGet();"/>
<input type="button" value="Enviar Post" onclick="enviarPost();"/>
<input type="button" value="Enviar Objeto" onclick="enviarObjeto();"/>
<input type="button" value="Obter" onclick="javascript:obter();"/>

Sua visualização será essa:

image

E o Javascript da página será:

   1:  function obterCallback(retorno,status) {
   2:      $("#txtNome").val(retorno.Nome);
   3:      $("#txtIdade").val(retorno.Idade);
   4:  }
   5:  function obter() {
   6:      $.get("Ajax/ObterGet", obterCallback);
   7:  }
   8:   
   9:  function enviarGet() {
  10:      if ($("#txtNome").val() == "" || $("#txtIdade").val() == "") {
  11:          alert("Preencha todas os campos!");
  12:          return;
  13:      }
  14:      $.get("Ajax/EnviarGet",
  15:      { nome: $("#txtNome").val(), idade: $("#txtIdade").val() },
  16:         obterCallback
  17:      );
  18:  }
  19:   
  20:  function enviarPost() {
  21:      if ($("#txtNome").val() == "" || $("#txtIdade").val() == "") {
  22:          alert("Preencha todas os campos!");
  23:          return;
  24:      }
  25:      $.post("Ajax/EnviarJson",
  26:      { nome: $("#txtNome").val(), idade: $("#txtIdade").val() },
  27:         obterCallback
  28:      );
  29:  }
  30:   
  31:  function enviarObjeto() {
  32:      if ($("#txtNome").val() == "" || $("#txtIdade").val() == "") {
  33:          alert("Preencha todas os campos!");
  34:          return;
  35:      }
  36:      $.post("Ajax/EnviarObjeto",
  37:      { nome: $("#txtNome").val(), idade: $("#txtIdade").val() },
  38:         obterCallback
  39:      );
  40:  }

O JavaScript, utilizando o Jquery, é tão simples que nem precisaria de explicação, mas vamos lá: O primeiro parâmetro é o endereço da Action (Controller/Action), o segundo pode ser o método de retorno (Callback) ou os parâmetros que serão passados, caso seja definido parâmetro, o terceiro método é o método de retorno. Existe um quarto parâmetro que não estou utilizando, que seria para informar o tipo do retorno (json, xml, etc). No meu caso não preciso utilizá-lo.

Os parâmetros estou passando através de um objeto JSON, que o Jquery converte em querystring, caso seja GET ou coloca os parâmetros no corpo da mensagem HTTP, caso seja POST. Relembre essas regras aqui.

Para conseguir testar, basicamente eu altero o valor enviado para o servidor e retorno, preenchendo os campos com o novo valor, dessa forma posso ver que as informações estão realmente chegando no servidor e voltando.

Obervação: A Action que recebe as chamadas Assíncronas não precisa estar no mesmo Controller que retornou a View do formulário.

Conclusão

Quis mostrar nesse post como é simples utilizar Ajax juntando MVC e JQuery, tão simples quanto utilizar PageMethods no WebForms, porém, sendo mais rápido para primeira renderização (porque não precisa de ScriptManager para gerar um Proxy).

Obervação 2: No WebForms é possivel utilizar esses métodos do Jquery com os métodos estáticos da página marcados com o atributo WebMethod, dessa forma você não precisaria utilizar o ScriptManager para gerar o próximo PageMethods.

Acho importante esse tipo de conhecimento, pois hoje em dia não existe site ou sistema sem alguma funcionalidade Assícrona, e isso pode ser incluído nos conhecimentos básicos de Asp.Net MVC.

O Link para o exemplo está abaixo.

MvcAjax.zip (292,92 kb)

Bom, por hoje é isso. Até o próximo.


  
     
  
24. janeiro 2011 23:31 by Frederico | Comments (3) | Permalink

Asp.Net Validator Assíncrono    

Neste post passado falei como usar Validator com controles HTMLs puros, para facilitar algumas coisas quando se usa Ajax.

Hoje vou falar novamente sobre Validators, mas agora criando um customizado para necessidades que muitas vezes podemos achar no desenvolvimento de sites com Ajax: Validações Assíncronas.

Imagine um formulário de cadastro, totalmente com Ajax, e você gostaria de validar informações de negócio, onde muitas vezes é necessário fazer algum tipo de consulta em banco de dados, etc.

Vamos supor: Você quer saber, num formulário de cadastro de usuário, se o usuário digitado já existe na base de dados, e mostrar o erro no momento que o usuário digitar o usuário e trocar o foco do controle. Veja bem, eu sei que é possível fazer isso com simples Ajax e os eventos onblur do JavaScript, mas a idéia deste post é mostrar como criar novos controles Asp.Net Validator, e aproveitar funcionalidades como o Validator Callout.

Como funciona um Validator?

No post citado acima, expliquei rapidamente como funciona um Validator, agora vou explicar um pouco mais.

Todo Validator herda da classe System.Web.UI.WebControls.BaseValidator, nessa classe existe um método chamado AddAttributesToRender, o qual é responsável por renderizar os atributes que os códigos do lado do cliente usarão para validar as informações de um campo. Sempre que um Validator é implementado, é necessário renderizar atributos específicos de acordo com a função do Validator.

Estes atributos são necessários porque os Validator precisam renderizar as propriedades definidas no Server Side para o navegador do Cliente. Em geral, os Validators realizam suas validações ainda no JavaScript, por isso, o JavaScript precisa conhecer os controles que serão validados, as mensagens de erros, etc. Para isso, o Asp.Net utiliza o método RegisterExpandoAttribute , que pode ser encontrado tanto Page.ClientScript como no ScriptManager. No nosso caso, como estaremos usando este validators sempre com Ajax, vamos utilizar o método RegisterExpandoAttribute do ScriptManager.

Obs.: Para ficar mais correto, poderíamos utilizar um Padrão de Projeto conhecido como Adapter, onde ficaria encapsulado a utilização do método RegisterExpandoAttribute do Page.ClientScript ou do ScritptManager de forma transparente, mas isso vai além do intuito deste post, e para ficar mais simples, estou utilizando diretamente o do ScriptManeger.

E para que serve o método RegisterExpandoAttribute? Este método renderiza propriedades em objetos JavaScripts. Se for ver como o Validator do post anterior foi renderizado, veremos:

var MainContent_reqNome = document.all ? document.all["MainContent_reqNome"] : document.getElementById("MainContent_reqNome");
MainContent_reqNome.controltovalidate = "MainContent_txtNome";
MainContent_reqNome.errormessage = "Preencha o Nome";
MainContent_reqNome.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
MainContent_reqNome.initialvalue = "";

No exemplo acima, MainContent_reqNome é objeto JavaScript que representa o Validator, e controltovalidate, errormessage, evaluationfunction e initialvalue são os ExpandoAttribute.

Criando o AsyncValidator

Para criar Validator Assíncrono, iremos definir duas propriedades no lado do servidor para conseguirmos informações básicas do que o Usuário quer validar. A maioria das demais propriedades de um validator, como ControlToValidate, já estão implementadas na classe base BaseValidator.

	/// 
        /// Método estático na página que fará a validação.
        /// Este método deve estar decorado com o atributo WebMethod
        /// Sua assinatura deve ser:
        /// 
        /// public static bool NomeDoMetodo(string valor){}
        /// 
        /// 
        public string ServerValidationMethod
        {
            get
            {
                if (this.ViewState["ServerValidationMethod"] == null)
                    return "";

                return this.ViewState["ServerValidationMethod"].ToString();
            }
            set { this.ViewState["ServerValidationMethod"] = value; }
        }

        /// 
        /// Caso se deseje alterar o valor do campo definido na propriedade ControlToValidate, define-se um método no javascript com a seguinte assinatura function (sender,args).
        /// O parâmetro args contém a propriedade Value, que contem o valor do campo, que pode ser alterado.
        /// Neste caso, o valor assumido para ser enviado para o server é o valor alterado.
        /// 
        public string CustomValueFunction
        {
            get
            {
                if (this.ViewState["CustomValueFunction"] == null)
                    return "";

                return this.ViewState["CustomValueFunction"].ToString();
            }
            set { this.ViewState["CustomValueFunction"] = value; }
        }

 

O código acima define duas propriedades. Acredito que a propriedade que merece um comentário extra é a ServerValidationMethod. Essa propriedade armazena o nome de um PageMethod que é o responsável por fazer a validação no lado do Server. Por padrão ele recebe a informação que está no campo a ser validado, porém, essa informação pode ser alterada antes de ser enviada ao servidor, pelo método JavaScript definido na propriedade CustomValueFunction.

Para fazer estas informações definidas no servidor ficarem disponíveis no cliente, sobrescrevemos o método citado acima (AddAttributesToRender), e renderizamos as duas proriedades e algumas outras:

protected override void AddAttributesToRender(HtmlTextWriter writer)
        {
            
            base.AddAttributesToRender(writer);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "evaluationfunction", "AsyncValidatorEvaluateIsValid", false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "isvalidating", "0", false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "myclientid", this.ClientID, false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "resultado", "true", false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "servervalidationmethod", ServerValidationMethod, false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "customvaluefunction", CustomValueFunction, false);
            ScriptManager.RegisterExpandoAttribute(this, this.ClientID, "ultimovalor", "", false);
            
        }

A maioria dos atributos renderizados são de uso do JavaScript que utilizaremos para fazer a validação, mostrado abaixo. Apenas um merece uma especial atenção: evaluationfunction.

O atributo evaluationfunction é necessário para todos os Validators do Asp.Net WebForm, este atributo armazena o nome do método JavaScript que realiza a validação no lado do cliente. Os scripts ClientSide do WebForm chamam sempre o método definido neste atributo ao executar a validação ao clicar em um botão de submit.. Em um RequiredFieldValidator por exemplo, este atributo contém o nome do método RequiredFieldValidatorEvaluateIsValid, este é o método que verifica se um controle é preenchido ou não.

No nosso caso, como o exemplo mostra, o nosso método responsável por validar o campo, ou seja, chamar o PageMethod criado pelo usuário do Validator responsável pela validação de fato será o AsyncValidatorEvaluateIsValid.

Nosso código JavaScript deve ficar da seguinte forma:

 function AsyncValidatorEvaluateIsValid(val,resultado,userContext,methodName) {
    
    var value = ValidatorGetValue(val.controltovalidate);
    
    //Possíveis valores para isvalidating:
    //0 : Não está validando e deve ir ao server fazer a validação
    //1 : Está na validação, esperando resposta do server, não deve ser executado nada
    //2 : Retornou da validação, deve ser retornado o valor armazenado no retorno
    
    if(val.isvalidating==1){
        return true;
    }
    
    if(val.isvalidating==2){
        val.isvalidating=0;
        return val.resultado; 
    }
    
      
    if(val.customvaluefunction.length>0){
        var args = { Value:value};  
        eval(val.customvaluefunction + "(val, args);");
        value = args.Value;
    }
       

    if(val.servervalidationmethod.length==0) return true;

    val.isvalidating = 1;
    
    eval('PageMethods.'+val.servervalidationmethod+'(value,function (valorRetorno,userContext,methodName){'+
    'val.resultado = valorRetorno;val.isvalid = valorRetorno;val.isvalidating = 2;val.evaluationfunction(val);'+
    '});');
       
     return true;
 
}

O código acima tem toda a lógica necessária para conseguir chamar o PageMethod definido na propriedade do Validator, aguardar o retorno, e informar se o validator foi válido ou não. A lógica não é das melhores, mas ajuda a exemplificar como deve funcionar um validator.

É importante informar que pelo fato de estarmos utilizando um PageMethod, precisamos adicionar um ScriptManager na página e habilitar a utilização de PageMethods, com a propriedade EnablePageMethods definida como true. Naturalmente, esta é só uma das formas de se validar informações assíncronas, foi a escolhida aqui por ser a mais simples.

Apenas com os códigos acima já podemos dizer que o Validator está pronto, mas você pode me perguntar: “Mas se eu quiser distribuir meu Validator, vou ter que distribuir um JavaScript junto?”. A resposta é :”Só se você quiser”.

Para quem não sabe, no .Net é possível colocar arquivos Embedded em DLLs. Para isso, basta colocarmos a propriedade Build Action do arquivo JavaScript como Embedded Resource, desta forma, ao compilar a DLL, o JavaScript será incluído dentro da DLL.

Para carregarmos este arquivo junto no momento do Load do Validator, apenas precisamos sobrescrever o método OnPreRender do Validator:

	protected override void OnPreRender(EventArgs e)
 {
    base.OnPreRender(e);
    ScriptManager.GetCurrent(this.Page).EnablePageMethods = true;
    Page.ClientScript.RegisterClientScriptResource(this.GetType(),
    "AssyncValidator.AsyncValidator.js");

 }

O uso do Validator é como o de qualquer outro, e pode ser usado com ValidatorCallout por exemplo.

Espero que este post possa ajudar você em validações com Web Forms e Ajax.

Até o próximo.

18. agosto 2010 00:50 by Frederico | Comments (1) | Permalink

Sobre mim

Minha Imagem

Meu nome é Frederico Batista Emídio, trabalho com desenvolvimento de sistemas profissionalmente a oito anos, porém, já faço sites pessoais a pelo menos dez anos.

Para saber mais clique aqui.

Páginas

Calendário

<<  novembro 2017  >>
seteququsedo
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

Visualizar posts em um Calendário
Sigua @fredemidio

MCP Asp.NET