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

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