Executando serviço em Background na infraestrutura do Asp.Net    

Olá pessoal!

Hoje eu vou abordar um tema não muito ortodoxo: As possíveis formas de “hospedar” um serviço na infraestrutura do Asp.Net.

Entenda como serviço nesse contexto uma tarefa que roda de tempos em tempos para realizar uma tarefa qualquer, que não dependa de uma ação do usuário, como o disparo de e-mails de newsletter, ou o que for. Não entenda serviço aqui como um WebService.

Digo que não é ortodoxo porque nesse caso, você deveria hospedar seu serviço em um WindowsService ou Console Application que pudesse ser iniciado pelo scheduler do Windows, pois essas são as formas normais de criar serviços que rodam por tempo indeterminado e que executam tarefas de tempos em tempos.

Mas então, porque criar um “serviço” na estrutura do Asp.Net? A resposta é simples: para as opções citadas acima, você precisa ter um servidor dedicado ou um Azure, o que muitas vezes não temos condições de ter, porque o projeto simplesmente não paga uma infraestrutura dessas. Muitas vezes hospedamos nossos sites em servidores baratos que não nos fornece nada mais que o IIS para hospedar nosso site. (Eu acredito que essa seja a realidade da grande maioria dos desenvolvedores).

Ao longo deste post, vou mostrar algumas formas de fazer esses “serviços” funcionarem, e expor os prós e os contras de cada um.

Vamos lá, mãos a obra!

Exemplo 1 – Criando nova Thread no Application Start

A primeira forma que vou demonstrar é bem simples. Vamos criar uma nova Thread na aplicação Asp.Net no momento que ela for iniciada, e essa Thread terá a lógica para manter o serviço sempre ativo e a própria lógica do processamento, o exemplo do código está abaixo:

   1: protected void Application_Start()
   2: {
   3:     AreaRegistration.RegisterAllAreas();
   4:     RegisterGlobalFilters(GlobalFilters.Filters);
   5:     RegisterRoutes(RouteTable.Routes);
   6:     ThreadPool.QueueUserWorkItem(Executar);
   7: }
   8:  
   9: public void Executar(object state)
  10: {
  11:     while (true)
  12:     {
  13:         //Lógica do Serviço
  14:         Thread.Sleep(20000);
  15:     }
  16: }

O código é bem simples. Na linha 6 eu crio uma nova Thread e  a lógica do “serviço” está no método Executar. Todo esse código está no Global.asax.

Prós:

  • Criação bem simples
  • Os usuários do site não são diretamente afetados pelo processo.

Contras:

  • Uma exceção não tratada em uma Thread que não está associada a um Request derrubará todo o processo.
  • Se você estiver em um WebFarm, você poderá criar a Thread em vários servidores, fazendo múltiplas instância do seu processo ser iniciado e mais de uma tarefa pode realizar o mesmo processamento.
  • O AppDomain do seu site pode cair por uma série de motivos e levar junto seu serviço, eventualmente corrompendo dados.

Exemplo 2 – Realizando o processamento em um Request

Essa segunda forma também é simples, porém eu a acho extremamente fraca e feia, mas não deixa de ser uma possibilidade. Ela verifica a necessidade de rodar um serviço em todos os Requests que chegam ao site, neste caso validado no momento de renderizar uma View sempre utilizada. Meu código está no meu arquivo de Layout do Asp.Net MVC (MasterPage se for WebForm):

   1: @functions{
   2:     public bool DeveExecutar(){
   3:         //Lógica para verificar se deve executar ou não, validando algo em cache ou banco de dados, por exemplo
   4:         return true;
   5:     }
   6: }
   7:  
   8: @if (DeveExecutar())
   9: {
  10:     //Lógica do serviço, pode ser em uma nova thread
  11: }

Acima estou mostrando um código em Razor onde eu preciso verificar se está na hora de executar o serviço, podendo fazer isso de diversas formas possíveis, como dito no comentário da linha 3. E também seguindo o comentário da linha 10, para não onerar demais o usuário com o processamento do serviço, você pode iniciar uma Thread nova para realizar sua tarefa.

Prós:

  • Para não falar que é nenhum, é ligeiramente fácil para desenvolver, especialmente em WebForm, que seria apenas codificar uma lógica semelhante à mostrada acima no Page Load da MasterPage.

Contras:

  • Todos os do Exemplo 1.
  • Por depender de Requests, se o seu site não tiver nenhum Request num grande período de tempo, o serviço não será executado.
  • Por interceptar um Request, o usuário poderá perceber que o site está lendo, pois o Response não está voltando tão rápido como deveria.

Exemplo 3 – Realizando o processamento em um Request – Modo 2

O exemplo 3 tem toda a lógica igual ao do Exemplo 3 e praticamente os mesmos Prós e Contras, a única coisa que muda seria a forma de desenvolver. Nesse exemplo, estou tratando o Evento BeginRequest do Global.asax.

   1: protected void Application_BeginRequest()
   2: {
   3:    if (DeveExecutar())
   4:    {
   5:        //Lógica do serviço, pode ser em uma nova thread
   6:    }
   7: }
   8:  
   9: public bool DeveExecutar()
  10: {
  11:    //Lógica para verificar se deve executar ou não, validando algo em cache ou banco de dados, por exemplo
  12:    return true;
  13: }

Esse modo de fazer tem ainda mais um contra:

  • O evento BeginRequest é chamado para qualquer Request, ou seja, para cada Recurso (imagem, js, etc) chamado, não só o Controller, esse método será chamado, o que pode crescer consideravelmente o tempo para carregar uma página.

Até agora vimos que há muito mais problemas do que soluções para criar esse tipo de serviços, vamos mais um pouco a fundo nos problemas, e assim depois poderemos procurar uma solução um pouco mais satisfatória que as definidas acima.

Motivos pelos quais o AppDomain pode cair

No último contra do Exemplo 1, eu comentei que o AppDomain pode cair e causar problemas para sua Thread, vamos ver alguns caso que isso pode acontecer.

O Asp.Net pode derrubar seu AppDomain pelos seguintes motivos, entre outros:

  • Quando você modifica o Web.Config do site o Asp.Net vai realizar o recycle do AppDomain.
  • O IIS vai por conta própria reciclar todo o processo a cada 29 horas, derrubando todos os AppDomain sob ele.
  • Em muitos servidores, é comum o IIS está configurado para derrubar o Application Pool depois de algum período de inatividade, ou seja, se depois de 20 minutos, por exemplo, sem receber um Request, o AppDomain pode ser derrubado.

Nesse caso, você pode estar se perguntando: Esses problemas não podem ocorrer também para Requests normais? O AppDomain não pode cair ou ser reciclado durante a execução de um Request?

Bom, quando o Asp.Net/IIS resolve derrubar/reciclar um AppDomain, ele procura descarregar todos os Requests pendentes e dar um tempo para eles terminarem seus trabalhos. Ele dá esse tempo para os códigos que ele sabe que estão rodando, e normalmente os código que ele “sabe” são os códigos de Requests padrões.

Basicamente, o problema apontado no Exemplo 1 com o AppDomain é que o Asp.Net/IIS não sabe que o seu código está rodando, por não ser um Request normal..

Informando ao Asp.Net que você tem um código especial rodando

Depois de ver tanto problema, vamos começar a estudar uma solução.

Existe uma forma fácil de informar ao Asp.Net sobre o seu código. No namespace  System.Web.Hosting  existe uma classe chamada HostingEnvironment que segundo o MSDN:

Provides application-management functions and application services to a managed application within its application domain

Ou seja, a classe te fornece funções e serviços para gerenciar uma aplicação dentro de um AppDomain.

Dessa classe, precisamos conhecer, para o nosso objetivo, apenas dois métodos: RegisterObject e UnregisterObject.

A função desses métodos é registrar e desregistrar um objeto a lista de “Códigos conhecidos” do Asp.Net.

Esses métodos recebem como parâmetro apenas um objeto que implementa a interface IRegisteredObject, que contém apenas um método, conforme snippet abaixo:

   1: public interface IRegisteredObject
   2: {
   3:     void Stop(bool immediate);
   4: }

Quando o Asp.Net vai derrubar o AppDomain, ele primeiro invoca o método Stop de todos os objetos registrados naquele AppDomain. Em geral ele chama o método duas vezes, primeiro com o parâmetro immediate igual a false, o que te dá um tempo para terminar o seu trabalho, tempo esse que é um total de 30 segundos para todos os objetos registrados terminarem o que estão fazendo, e uma segunda vez é chamado após esses 30 segundos passarem, e desta vez com o parâmetro igual a true, o que seguinifica que você precisa terminar seu trabalho agora, porque seu AppDomain está prestes a sumir do mapa.

O segredo é: Uma vez que o Asp.Net chama o método Stop do seu objeto, você deve impedir que esse método retorne até que seu trabalho tenha terminado. Quando o trabalho terminar, você deve desregistrar seu objeto.

Com essa explicação, vamos ao quarto exemplo:

Exemplo 4 – Criando o serviço em um objeto registrado no Asp.Net

Esse exemplo é melhor que todos os anteriores, pois continua sendo simples, e alguns dos contras são superados:

   1: public class Servico : IRegisteredObject
   2: {
   3:    private readonly object _lock = new object();
   4:    private bool _derrubando;
   5:    public Servico()
   6:    {
   7:        HostingEnvironment.RegisterObject(this);
   8:    }
   9:    public void Stop(bool immediate)
  10:    {
  11:        lock (_lock)
  12:        {
  13:            _derrubando = true;
  14:        }
  15:        HostingEnvironment.UnregisterObject(this);
  16:    }
  17:    public void Executar()
  18:    {
  19:        while (true)
  20:        {
  21:            lock (_lock)
  22:            {
  23:                if (_derrubando)
  24:                {
  25:                    return;
  26:                }
  27:                //Lógica do serviço
  28:            }
  29:            Thread.Sleep(60000);
  30:        }
  31:    }
  32: }

Veja que adicionei um Lock no método Stop e dessa forma impeço que esse método retorne caso esteja no meio de uma execução do método Executar.

No Global.asax eu chamo a minha classe do serviço:

   1: protected void Application_Start()
   2: {
   3:     AreaRegistration.RegisterAllAreas();
   4:     RegisterGlobalFilters(GlobalFilters.Filters);
   5:     RegisterRoutes(RouteTable.Routes);
   6:     ThreadPool.QueueUserWorkItem(state => new Servico());
   7: }

Veja que na linha 6 crio uma Thread nova para o meu objeto, mas agora ele estará registrado no Asp.Net e seu fim não será tão repentino como seria antes.

Agora meus Prós e Contras ficarão da seguinte forma:

Prós:

  • Os usuários do site não são diretamente afetados pelo processo.
  • Terei mais controle do tempo de vida do meu “serviço”

Contras:

  • Uma exceção não tratada em uma Thread que não está associada a um Request derrubará todo o processo.
  • Se você estiver em um WebFarm, você poderá criar a Thread em vários servidores, fazendo multiplas instância do seu processo ser iniciado e mais de uma tarefa pode realizar o mesmo processamento.
  • Para utilizar o RegisterObject é necessário que o site esteja rodando com FullTrust

Essa abordagem seria a mais próxima do ideal. Temos ainda o problema das Exceções, que você deve ter cuidado, e a questão do WebFarm, que você terá que fazer um controle mais manual, mas que foge do objetivo desse post, mas agora temos ao menos mais controle sobre o AppDomain.

Também temos um novo problema, que é a necessidade do FullTrust. Porém, até servidores baratos aceitam FullTrust e muitos frameworks que utilizamos no desenvolvimento também exige FullTrust, então acredito que esse é um problema secundário.

É importante falar ainda que o AppDomain pode cair por outros problemas criticos, e você não será avisado, o que pode ainda corromper seus dados no serviço. Por exemplo, o AppDomain por cair se alguém tropeçar no cabo de força ou se o Windows mostrar uma tela azul, ou por qualquer outro motivo desastroso. Porém, esses riscos você também pode enfrentar em Windows Services normais.

Exemplo 5 – Apenas uma abordagem alternativa ao Exemplo 4

Nesse exemplo venho apenas mostrar um alternativa aos Loops com While(true) que estou utilizando, mas a solução é ainda a do Exemplo 4. Nesse exemplo vou utilizar um Timer no  lugar do While(true). Acredito que essa abordagem seja um pouco mais elegante.

 

   1: using System;
   2: using System.Threading;
   3: namespace Exemplo5
   4: {
   5:     public static class MeuTimer
   6:     {
   7:         private static readonly Timer _timer = new Timer(OnTimerElapsed);
   8:         private static readonly Servico _servico = new Servico();
   9:         public static void Start()
  10:         {
  11:             _timer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(60000));
  12:         }
  13:         private static void OnTimerElapsed(object sender)
  14:         {
  15:             _servico.Executar();
  16:         }
  17:     }
  18: }

Só preciso mudar uma linha no meu Global.asax:

   1: protected void Application_Start()
   2: {
   3:     AreaRegistration.RegisterAllAreas();
   4:     RegisterGlobalFilters(GlobalFilters.Filters);
   5:     RegisterRoutes(RouteTable.Routes);
   6:     MeuTimer.Start();
   7: }

Agora na linha 6 eu apenas chamo o método Start do meu Timer, e o meu serviço rodará de tempos em tempos de acordo com o meu Timer.

Conclusão

Vimos algumas alternativas para utilizar serviços na infraestrutura do Asp.Net, mesmo sabendo que essa deva ser a última opção, é bom saber quais alternativas temos. Nitidamente, eu prefiro utilizar a última abordagem, ela é a mais madura de todas.

É importante saber também que essas soluções funcionam tanto em WebForm como em MVC.

Por hoje é isso pessoal.

Segue o fonte dos exemplos:

Valeu!

Obs.: Esse post foi inspirado neste outro post

29. February 2012 23:32 by Frederico B. Emídio | Comments (0) | Permalink

Testando seu código JavaScript com QUnit    

Olá pessoal!

Depois de muito tempo sem escrever, volto falando de um dos meus temas preferidos: JavaScript!

Hoje vou falar de um tema complicado mesmo para linguagens compiladas como C# ou Java, para uma linguagem de script esse tema é ainda mais complicado. Como o título já mostra, vou falar de Testes em JavaScript.

Não pretendo aqui falar de todos os conceitos de Teste abordado para tantas linguagens e de seus inúmeros benefícios, mas apenas apresentar uma boa ferramenta que facilita muito a validação do seu código JavaScript. De qualquer forma, não posso deixar de falar que validar código não é o único benefício dos testes, na realidade, na minha opinião, um dos principais benefícios de criar código para testar seu código é melhorar o design do mesmo, deixando ele mais claro e organizado, além de facilitar futuras refatorações.

Organizar código JavaScript é uma tarefa difícil, e o teste facilita muito isso.

Então vamos lá, conhecendo o QUnit

QUnit é a uma ferramenta de teste criada pela equipe do JQuery, para testar sua biblioteca, que mais tarde foi exposta ao mundo para quem quiser utilizar. Só por ser utilizado pela equipe do JQuery já é um motivo para também querermos adotar essa suite de teste para nossos projetos, afinal, não é fácil encontrar erros na biblioteca do JQuery.

QUnit pode ser utilizada para testar código JavaScript puro ou mesmo o comportamento do DOM. Por exemplo, os plugins do JQuery são testados com QUnit, e não somente os códigos de lógica e classes.

Como qualquer ferramenta de teste, o QUnit é perfeito para teste de regressão, afinal, uma vez que um bug é encontrado, você pode criar um teste para validar a existência dele, e após corrigi-lo, verificar a correção, rodando novamente o teste toda vez que você trabalhar sobre o código.

Você não precisa instalar nada de especial para rodar o QUnit, é necessário apenas referenciar o script do Qunit, que pode ser encontrado aqui, e rodar seus testes no Browser.

Vamos ao código!

Utilizando o QUnit

Para utilizar o QUnit, você deve referenciar o script que realiza os testes e o CSS que dá o layout básico da página que mostra os resultados do teste, além de um HTML padrão para organizar os testes, da seguinte forma:

.

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3: <head>
   4:     <title></title>  
   5:     <link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen" />
   6:     <script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
   7:   
   8: </head>
   9: <body>
  10:     <h1 id="qunit-header">
  11:         QUnit example</h1>
  12:     <h2 id="qunit-banner">
  13:     </h2>
  14:     <div id="qunit-testrunner-toolbar">
  15:     </div>
  16:     <h2 id="qunit-userAgent">
  17:     </h2>
  18:     <ol id="qunit-tests">
  19:     </ol>
  20:     <div id="qunit-fixture">
  21:         test markup, will be hidden</div>
  22: </body>
  23: </html>

Explicando o código HTML acima: na tag HEAD adiciono o CSS e o Script necessários para funcionar os testes, e no Body as tags (h1, h2 e div) com id qunit-header, banner, testrunner-toolbar e qunit-userAgent são para criar um layout bonito e para auxiliar na execução dos testes, você não deve mexer nessas tags. A tag com id qunit-tests é onde será exibido os resultados dos seus testes, você também não deve adicionar conteúdo a essa tag. Por fim a tag com id qunit-fixture você deve utilizar no caso de testar algo com HTML, e não somente com código JavaScrit puro. É onde você adiciona HTML de teste. O conteúdo dessa tag não é exibido no browser.

O resultado do código acima é algo como a imagem abaixo:

image

Testando o código

Vamos fazer um código simples para testar. A classe abaixo será utilizada:

   1: var Calculadora = function () {
   2:     this.somar = function (a, b) {
   3:         return a + b;
   4:     }
   5:     this.subtrair = function (a, b) {
   6:         return a / b;
   7:     }
   8: };

Para utilizar o QUnit, precisamos conhecer alguns métodos básicos da biblioteca:

module (nome) – Para separar os testes em módulos

test (nome,testMethod) – Método que executa o teste, o primeiro parâmetro recebe o nome do método e o segundo é um método anonimo responsável por realizar o teste. Esse método tem um overload, podendo receber no segundo parâmetro, chamado expected, a quantidade de assets esperados dentro do método definido no parâmetro testMethod.

expect (total) – Define a quantidade de asserts esperados em um testMethod. É utilizado no caso de não ser utilizado o parâmetro expeted do método test.

Além do métodos acima, também têm os métodos responsáveis pelos Asserts dos testes, abaixo listo os principais:

ok(resultado, mensagem) – Realiza uma validação booleana do parâmetro resultado e exibe a mensagem como resultado.

equal(valor_real,valor_esperado, mensagem). Valida se o Valor Real é igual ao Valor Esperado.

notEqual(valor_real,valor_esperado, mensagem). Valida se o Valor Real NÃO é igual ao Valor Esperado.

Para testar meu código acima, vou fazer o seguinte código de teste:

   1: module("Testes de Somar");
   2: test("Testar Soma", 1, function () {
   3:    var calc = new Calculadora();
   4:    equal(calc.somar(1, 2), 3, "O resultado deve ser 3");
   5: });
   6:  
   7: test("Testar Soma Com Erro", 1, function () {
   8:    var calc = new Calculadora();
   9:    notEqual(calc.somar(2, 2), 3, "O resultado não pode ser 3");
  10: });

O resultado do código acima seria como a imagem abaixo:

image

Caso meu código tenha algum erro, o resultado seria como a imagem abaixo, mostrando inclusive o local onde ocorre o erro:

image

O código é bem simples, mas vou fazer uma pequena explicação.

No método test eu passo o nome do teste e a quantidade de assert que eu quero fazer no teste. Se eu definir um número de assert diferente do número que eu executo, o teste vai falhar (os possíveis métodos de assert do QUnit está acima). Por último parâmetro eu passo o método responsável por realizar os testes. O QUnit verifica os asserts dentro deste método, e se todos estiverem de acordo com o esperado, os testes passarão.

E se eu quiser testar um HTML? Bom vamos aos códigos abaixo:

Primeiro vou adicionar referência aos scripts do JQuery e JQuery.UI:

   1: <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
   1:  
   2: <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js">
</script>

Vou criar um JQuery Ui Widget simples, e testar se meu código está funcionando conforme o esperado:

   1: (function ($, undefined) {
   2:     $.widget('teste.bolder', {
   3:     _create: function () {
   4:  
   5:         $(this.element).append("<p class='bolder'/>");
   6:     }
   7: });
   8: } (jQuery));

O que meu código faz é apenas adicionar uma tag P com uma classe “bolder” ao controle selecionado pelo JQuery, ou seja, se eu fizer:

$(“div#teste”).bolder();

Um div vazio deve ficar assim:

<div><p class=”bolder”/></div>

Vamos ao teste. Primeiro devo alterar a tag com id qunit-fixture:

   1: <div id="qunit-fixture">
   2:     <div id="teste"></div>
   3: </div>

Após fazer isso, crio meu código de teste:

   1: test("Testando widget Bolder", 1, function () {
   2:    //Crio o controle executando o widget
   3:   $("#teste").bolder();
   4:   //faço um teste básico para ver  se fpo criado como o esperado.
   5:   equal($("p.bolder").length, 1, "Testando criação");
   6: });

É um teste bem simples, mas dá para ver como podemos fazer coisas mais complexas se quisermos. Lembre-se: A equipe do JQuery teste seu código dessa forma!

O resultado do meu teste está abaixo:

image

Bom, acredito que com isso você já consegue codificar seus métodos de testes de JavaScript. É só aplicar os mesmo conceitos que você já utiliza para testar seu código C#.

Para mais informações sobre o QUnit, visite o site do JQuery específico para essa ferramenta.

Abraços e até o próximo!

25. January 2012 00:03 by Frederico B. Emídio | Comments (0) | Permalink

Trabalhando com Escopo em JavaScript–Palavra chave ‘With’    

Olá pessoal!

Voltando a falar novamente de JavaScript, vou fazer um rápido post sobre uma palavra chave que dificilmente se vê por ai, a palavra with.

Todos nós já sabemos que o JavaScript tem uma forma um pouco diferente de trabalhar com escopos, e que chaves não têm o mesmo comportamento que têm em C#, por exemplo, não definindo escopo de forma alguma.

Veja o código abaixo:

   1: for (var i=0; i<3; ++i)
   2: {
   3:        var num = i;
   4:        setTimeout(function() { alert(num); }, 10);
   5: }
   6: alert(num);

Em C#, por exemplo, o num da linha 6, daria erro, por não estar definido, porém, o valor dele em JavaScript estará válido. No final da execução do código acima, o resultado será quatro alertas com o mesmo valor: 2. Isso porque no JavaScript, por padrão, uma variável tem contexto global, e os valores estão naturalmente definidos com a ordem de execução, e não com o momento ou local que elas aparecem no código.

Se eu fizer uma pequena alteração no código, atribuindo um valor fora do FOR, mesmo a variável dentro do método anônimo, dentro do setTimeout, que por sua vez está dentro do FOR será afetada:

   1: for (var i=0; i<3; ++i)
   2: {
   3:        var num = i;
   4:        setTimeout(function() { alert(num); }, 10);
   5: }
   6: var num = 9;
   7: alert(num);

Agora os quatro alertas que aparecem serão 9.

Dentro de funções, porém, isso não acontece. Por exemplo, o seguinte código daria um erro ao invocar o método metodoB, pois no contexto dele o metodoB não tem a variável num definida:

   1: function metodoA(){
   2:     var num = 2;
   3: }
   4:  
   5: function metodoB(){
   6:     alert(num);
   7: }
   8:  
   9: metodoA();
  10: metodoB();

Utilizando a palavra-chave With para definir escopo

Bacana, agora que já passamos por uma brevíssima introdução a escopo no JavaScript, vamos ver como utilizar a palavra chave With. Talvez você nunca tenha vista essa palavra chave, mas em alguns casos ela pode ser muito útil.

O que o With faz é definir que tudo que for criado dentro do seu bloco, terá um escopo novo, mesmo que seja dentro de um loop, por exemplo, se utilizarmos o primeiro código com um block with, ele ficaria assim:

   1: for (var i=0; i<3; ++i)
   2: {
   3:     with({num : i}){  
   4:        setTimeout(function() { alert(num); }, 10);
   5:    }
   6: }
   7: var num = 10;
   8: alert(num);

Agora os alertas seriam um diferente do outro, seria primeiro um 10, e depois um 0, 1 e 2. Dentro do próprio bloco poderíamos criar mais variáveis que elas não compartilhariam escopo.

Uma outra forma de utilizar o with é dizendo qual é o escopo dentro dele, como fazemos com o with da linguagem VB, por exemplo:

   1: var Pessoa = function(){
   2:     
   3:     this.Nome = "Frederico";
   4:     this.Idade = 25;
   5:  
   6:     this.Escrever = function(){
   7:  
   8:         with(this){
   9:             alert(Nome + " - " + Idade);
  10:         }
  11:     }
  12: }
  13:  
  14: var p = new Pessoa();
  15: p.Escrever();

Veja que legal: dentro do bloco with, estou definindo que o escopo da variável é o this, então não preciso utilizar this.Nome e this.Idade para acessar as variáveis da minha classe!

Agora imagina o quanto de código isso vai poupar quando você for fazer grandes interfaces com JavaScript, utilizando muito JQuery, Knockout, etc. Lembre que cada palavra no JavaScript pode mudar muito o tamanho do seu arquivo, o que faz uma bela diferença na Web.

Bom, era isso pessoal, espero que essa dica os ajude!

Abraços e até o próximo!

22. October 2011 09:32 by Frederico B. Emídio | Comments (0) | Permalink

Asp.Net MVC – Preenchando campos da View a partir do JavaScript (JQuery, KnockoutJS, etc)    

Olá Pessoal!

Atualmente, todo bom desenvolvedor se preocupa com a experiência do usuário. Não é a toa que bibliotecas JavaScript, como JQuery, tem se popularizado tanto. Nós queremos dar a melhor experiência para o usuário e o único jeito é abusar de JavaScript e Ajax.

Porém, quando começamos a fazer uma interface muito rica, normalmente fazemos todo o controle da View no JavaScript, e acabamos fazendo muitos request para preencher uma única página.

Por exemplo, se eu estiver utilizando KnockoutJS, e minha página for um pouco mais complexa, meu ViewModel começará a ficar extenso. Veja por exemplo esse tutorial do próprio site do KnockoutJS, que apesar de ser simples, já demanda um pouco mais de JavaScript. Num exemplo real, o JavaScript fica imenso.

E como recuperar os dados do servidor e fazer esse Model ser renderizado na página? Muitas vezes fazemos como era possível no WebForms:

  1. Renderizamos a Página
  2. Requisitamos os dados via Ajax
  3. Retornamos os dados.
  4. Preenchemos o ViewModel

O problema é que essa solução, além de fazer dois Request, no mínimo, faz a página “piscar” geralmente, afinal, ela aparece momentaneamente sem os dados, e depois os dados aparecem.

Por exemplo, imagine o simples modelo abaixo:

   1: <script src="../../Scripts/knockout-1.3.0beta.js" type="text/javascript"></script>
   1:  
   2: <div data-bind="with: Autor">
   3:     <p>
   4:         Nome : <span data-bind="text: Nome"></span>
   5:     </p>
   6:     <p>
   7:         E-mail: <span data-bind="text: Email"></span>
   8:     </p>
   9:     <h2>
  10:         Posts</h2>
  11:     <ul data-bind="foreach:Posts">
  12:         <li><span data-bind="text:Titulo"></span>
  13:     </li></ul>
  14: </div>
  15: <script type="text/javascript"> 
  16:  
  17:     var meuModelo = {
  18:         Autor: {
  19:             Nome: "Frederico",
  20:             Email: "teste@teste.com",
  21:             Posts: [
  22:                     { Titulo: "Post 1" },
  23:                     { Titulo: "Post 2" },
  24:                     { Titulo: "Post 3" },
  25:                     { Titulo: "Post 4" }
  26:                 ]
  27:         }
  28:     } 
  29:  
  30:     ko.applyBindings(meuModelo);    
</script>

Caso eu queira receber os dados do servidor, no formato JSON, a primeira idéia que viria na cabeça seria a seguinte:

Criar uma Action que retorna os dados do banco de dados:

   1: public JsonResult ObterDados()
   2: { 
   3:    //Buscando no Banco de dados
   4:    var model = new
   5:    {
   6:        Nome = "Frederico",
   7:        Email = "teste@teste.com",
   8:        Posts = new object[]{
   9:            new {Titulo = "Post 1"},
  10:            new {Titulo = "Post 2"},
  11:            new {Titulo = "Post 3"},
  12:            new {Titulo = "Post 4"}
  13:        }
  14:    }; 
  15:    return Json(model, JsonRequestBehavior.AllowGet);
  16: } 

E na minha View, deveria invocar essa Acion de forma assíncrona:

   1: <script type="text/javascript"> 
   2: //faço outro request
   3: $.getJSON("Home/ObterDados", function (dados) {
   4:     //Preencho meus campos.
   5:     ko.applyBindings({ Autor: dados });
   6: });
   7: </script> 

Bom, na minha opinião, a solução ideal vem a seguir:

Renderizando JSON com ajuda do Json Helper

Apesar dos exemplos estarem com Knockout, utilizar o JSONHelper pode ajudar em qualquer situação, afinal, praticamente todos os plugins JQuery fazem uso de JSON.

A solução é muito simples, você não precisa criar duas Action para renderizar a página (Uma Index que apenas retorna a View, e outra que retorna os dados do Model assincronamente). Na própria Index você retorna os dados, como se fosse preencher os campos normalmente, mas na View renderiza os dados como JSON. Veja:

Na Action padrão eu retorno os dados:

   1: public ActionResult Index()
   2: {
   3:    ViewBag.Message = "Welcome to ASP.NET MVC!"; 
   4:    //Buscando no Banco de dados
   5:    var model = new
   6:    {
   7:        Nome = "Frederico",
   8:        Email = "teste@teste.com",
   9:        Posts = new object[]{
  10:            new {Titulo = "Post 1"},
  11:            new {Titulo = "Post 2"},
  12:            new {Titulo = "Post 3"},
  13:            new {Titulo = "Post 4"}
  14:        }
  15:    }; 
  16:    return View(model);
  17: } 

E na View utilizo o helper @Json:

   1: <script type="text/javascript">
   2: var meuModelo = {
   3:     //Faço o retorno da Action ser transformado em JSON
   4:     Autor: @Html.Raw(@Json.Encode(Model))
   5:     //Aqui poderia ter meus outros métodos e propriedades do ViewModel
   6: }
   7: //Preencho meus campos.
   8: ko.applyBindings(meuModelo);
   9: </script> 
 

E o resultado é o mesmo, porém, com apenas um Request, e com o código ligeiramente mais limpo:

image

Veja como o HTML é gerado, o JSON está renderizado como o esperado:

image

Apesar de não ter utilizado nenhum controle Input (textbox), a solução funciona da mesma forma com esses controles.

Bom pessoal, por hoje era isso. Espero que possa te ajudar.

Abraços!

9. September 2011 12:00 by Frederico B. Emídio | Comments (0) | Permalink

Utilizando JQGrid com WebForms    

Olá pessoal!

Muitas pessoas têm dificuldades de utilizar plugins JQuery com WebForms. Principalmente porque desenvolvedor WebForm muitas vezes sabe usar apenas ServerControls, desconhecendo muitos conceitos básicos de Web em sí. Não é a toa que fiz uma série de post nesse blog mostrando conceitos de Web com WebForms.

Geralmente, quando uma pessoa vê exemplos de JQuery, eles estão em PHP, e a pessoa acredita que não funcionaria com WebForms, ou mesmo que saiba que seja possível, não sabe como adaptar para WebForm. Obs.: Geralmente esse problema não acontece com quem desenvolve com Asp.Net MVC.

Nos últimos post, mostrei alguns conceitos que podem ajudar, todos relacionados a PageMethods, como esse e esse. Se você não leu nenhum deles, ou está caindo diretamente nesse post, aconselho fortemente a ler pelo menos esse: Invocando PageMethods diretamente com JQuery.

Se você entender bem esse post que citei, nem precisará desse para utilizar o JQGrid ou qualquer outro plugin JQuery.

Então vamos lá!

Nesse post, vou utilizar as funcionalidades básicas do JQGrid, como Listar, Pesquisar e Editar/Adicionar.

O mais básico, e que todo mundo utiliza, é o Listar. Vou fazer o seguinte no meu HTML/JavaScript:

   1: <script type="text/javascript">
   2:     $().ready(function () {
   3:          $.ajaxSetup({
   4:                dataFilter: function (data, type) {
   5:                    if (type == "json" || type == undefined) {
   6:                        var msg = eval('(' + data + ')');
   7:                        if (msg.hasOwnProperty('d'))
   8:                            return JSON.stringify(msg.d);
   9:                        else
  10:                           return data;
  11:                   }
  12:                   else return data;
  13:               }
  14:            });
  15:  
  16:         $("#tbGrid").jqGrid({
  17:             mtype: "post",
  18:             url: '/Default.aspx/Listar',
  19:             datatype: "json",
  20:             ajaxGridOptions: { contentType: 'application/json; charset=utf-8' },
  21:             colNames: ['ID', 'Nome', 'Idade', 'E-mail'],
  22:             colModel: [
  23:                        { name: 'PessoaID', index: 'PessoaID', width: 55 },
  24:                        { name: 'Nome', index: 'Nome', width: 150 },
  25:                        { name: 'Idade', index: 'Idade', width: 100 },
  26:                        { name: 'Email', index: 'Email', width: 150 }
  27:                    ],
  28:             rowNum: 10,
  29:             rowList: [10, 20, 30],
  30:             pager: '#pager',
  31:             sortname: 'PessoaID',
  32:             viewrecords: true,
  33:             sortorder: "desc",
  34:             caption: "JQGrid com WebForms",
  35:             serializeGridData: function (dados) {
  36:                 return JSON.stringify(dados)
  37:             },
  38:             jsonReader: { repeatitems: false, id: "PessoaID" }
  39:  
  40:         });
  41:         $("#tbGrid").jqGrid('navGrid', '#pager', { edit: false, add: false, del: false });
  42:     });
  43: </script>
  44: <table id="tbGrid">
  45: </table>
  46: <div id="pager">
  47: </div>

Até ai não fiz nada de mais, adicionei configurações padrões na Grid. Adicionei quatro colunas que virão do meu PageMethod, que está na página Default.aspx com o nome Listar.

Como será que deve ficar meu PageMethod para aceitar a requisição dessa Grid? Se não sabe, ai vai uma dica: Utilizando o Fiddler ou o DevelopToolBar, você pode ver qual HTTP Request o Grid está fazendo. Verificando quais campos estão lá você saberá que são os mesmos parâmetros que deve ter no seu PageMethod.

No meu caso, o JQGrid mandou os seguintes campos:

{"_search":false,"nd":1314831637255,"rows":10,"page":1,"sidx":"PessoaID","sord":"desc"}

No meu caso, o que importa é :

  • rows – Representa a quantidade de linha por página.
  • page – Indica a página atual.
  • sidx – Indica o campo pelo qual a Grid está ordenada. Sidx significa Search Index
  • sord – Indica a ordem que o campo definido em sidx deve ser ordenado.

Nesse caso, se meu PageMethod tiver esses campos já será o suficiente. Abaixo segue a implementação dele:

   1: [WebMethod]
   2: public static Reader<Pessoa> Listar( string sidx, string sord, int page, int rows)
   3: {
   4:     var modelo = new Modelo();
   5:     var lista = modelo.Pessoas.ToList();
   6:     var reader = new Reader<Pessoa>(lista,page,rows);
   7:     return reader;
   8: }

Como ele faz a busca, muda de solução para solução. No meu caso eu estou realizando a pesquisa em um SQLServer CE com EntityFramework. Minha classe Reader é responsável por realizar uma “paginação” nos registros retornados e retornar os campos de acordo com o JSONReader da JQGrid. Todo o código fonte dele está abaixo, apenas por curiosidade, visto que você não precisa dele para fazer seu JQGrid funcionar, ele apenas ajuda:

   1: public class Reader<T>
   2: {
   3:    /// <summary>
   4:    /// Número de páginas retornadas no registro
   5:    /// </summary>
   6:    public int total { get; set; }
   7:    /// <summary>
   8:    /// Página atual que a Grid exibirá
   9:    /// </summary>
  10:    public int page { get; set; }
  11:    /// <summary>
  12:    /// Número total de registros na base.
  13:    /// </summary>
  14:    public int records { get; set; }
  15:    /// <summary>
  16:    /// Lista com cada registro.
  17:    /// </summary>
  18:    public List<T> rows { get; set; }
  19:  
  20:    /// <summary>
  21:    /// Construtor recebe a lista e faz a paginação virtual.
  22:    /// </summary>
  23:    /// <param name="lista">Lista com os registros</param>
  24:    /// <param name="paginaAtual">Página atual.</param>
  25:    /// <param name="totalPorPagina">Total de registros por páginas</param>
  26:    public Reader(List<T> lista,int paginaAtual, int totalPorPagina)
  27:    {
  28:  
  29:        var totalRegistros = lista.Count();
  30:        page = paginaAtual;
  31:        var ini = (page - 1) * totalPorPagina;
  32:        var fim = totalRegistros > totalPorPagina ? totalPorPagina : totalRegistros;
  33:        
  34:        if (ini > 0)
  35:        {
  36:            fim = totalPorPagina;
  37:            fim = fim - 10 >= 0 ? totalRegistros % totalPorPagina : fim;
  38:        }
  39:        var totalPags = totalRegistros / totalPorPagina;
  40:        if (totalRegistros % totalPorPagina > 0)
  41:        {
  42:            totalPags++;
  43:        }
  44:        total = totalPags;
  45:        page = page;
  46:        records = totalRegistros;
  47:        rows = lista.ToList().GetRange(ini, fim);
  48:    }
  49: }

Com os códigos acima minha Grid já está funcionando:

image

Se, por exemplo, eu mudo a quantidade de registros por página, a Grid vai passar os novos valores nos parâmetros do PageMethod, e minha pesquisa retornará a nova quantidade de registros por página:

{"_search":false,"nd":1314832264231,"rows":"20","page":1,"sidx":"PessoaID","sord":"desc"}

E se eu quiser utilizar os campos de pesquisa do JQGrid? Será que o PageMethod já está preparado? Vamos ver. Vou buscar pelo usuário de nome “Frederico” na lista da Grid, vamos ver o que a JQGrid envia para o servidor utilizar o Fiddler ou o IEDeveloperToolbar, ou qualquer ferramenta de sua vontade. Utilizando a “Lupa” da Grid e selecionando o campo na popup que aparece:

image

O que ele envia é o seguinte:

{"_search":true,"nd":1314832456208,"rows":"20","page":1,"sidx":"PessoaID","sord":"desc", "searchField":"Nome","searchString":"Frederico","searchOper":"eq","filters":""}

Veja que alguns campos foram adicionados e outros estão com novos valores, os campos que teremos que adicionar em nosso PageMethod são:

  • _seach – Indica se está sendo realizando uma pesquisa ou se é apenas a listagem do dados
  • searchField – Indica por qual campo está sendo pesquisado.
  • searchString – Indica qual é o valor pesquisado.
  • searchOper – Indica qual é o operado utilizado para a pesquisa.

Vou alterar meu PageMethod  para ficar da seguinte forma:

   1: [WebMethod]
   2: public static Reader<Pessoa> Listar(GridParams param)
   3: {
   4:     var modelo = new Modelo();
   5:     List<Pessoa> lista;
   6:  
   7:     if (param._search)
   8:     {
   9:         var operador = new Func<string,string,string>((op,valor) =>
  10:         {
  11:              int saida;
  12:              bool isNum = Int32.TryParse(valor, out saida);
  13:             switch(op){
  14:                 case "ne":
  15:                     return isNum? string.Format(" <>{0}", valor): string.Format(" <>\"{0}\"", valor);
  16:                 default:
  17:                     return isNum ? string.Format(" = {0}", valor) : string.Format(" = \"{0}\"", valor);
  18:                 }
  19:         });
  20:  
  21:         var sb = new StringBuilder();
  22:         sb.Append(param.searchField);
  23:         sb.Append(operador(param.searchOper,param.searchString));
  24:         lista = modelo.Pessoas.Where(sb.ToString()).ToList();
  25:     }
  26:     else{
  27:         lista = modelo.Pessoas.ToList();
  28:     }
  29:     
  30:     var reader = new Reader<Pessoa>(lista, param.page, param.rows);
  31:     return reader;
  32: }

Primeira mudança importante é que troquei todos os parâmetros por uma classe. A classe encapsula todos aqueles campos que a JQGrid envia para o servidor, independente do Request. Apesar de ser bem simples, segue o código:

   1: public class GridParams
   2: {
   3:     public string sidx;
   4:     public string sord;
   5:     public int page;
   6:     public int rows;
   7:     public bool _search;
   8:     public string searchField;
   9:     public string searchString;
  10:     public string searchOper;
  11: }

Criei essa classe para receber todos os parâmetros porque o Asp.Net não aceita WebServices/PageMethods com parâmetros opcionais ou com Overloads, então criei um método com apenas um parâmetro, que pode ter todos os campos definidos ou não. Visto que a JQGrid utiliza o mesmo método para listar e pesquisar, essa é a única solução.

O resto é apenas a lógica do método, que você pode mudar com a sua no lugar, a minha é só para exemplo. O segredo está em validar o parâmetro _search, se ele for true quer dizer que o usuário está realizando uma pesquisa, e não apenas carregando a lista. Para simular, criei apenas lógica necessária para o usuário usar um Campo como critério igual ou diferente do valor que for fornecido.

Com isso minha lógica já estará funcionando.

Como mudei o parâmetro do PageMethod, preciso apenas alterar um evento na configuração da Grid, para encapsular o nome do parâmetro:

   1: serializeGridData: function (dados) {
   2:    return JSON.stringify({param: dados})
   3: },

Esse evento é invocado pela Grid logo antes da requisição ser enviada para o servidor, então eu coloco os parâmetros dentro do campo param. E pronto! sua Grid está funcionando perfeitamente com Asp.Net WebForm.

Para finalizar, vamos fazer a Grid conseguir criar e alterar registros

Adicionando  e Alterando registro com JQGrid

Antes de mais nada, devo habilitar algumas configurações no Pager da grid, fazendo que o botão de adicionar e editar apareça:

   1: $("#tbGrid").jqGrid('navGrid', '#pager', { edit: true, add: true, del: false });

E alterar o ColModel para dizer como será a edição de cada campo:

   1: colModel: [
   2:    { name: 'PessoaID', index: 'PessoaID', width: 55, editable: false, editoptions: { readonly: true, size: 10} },
   3:    { name: 'Nome', index: 'Nome', width: 150, editable: true, editoptions: { size: 50} },
   4:    { name: 'Idade', index: 'Idade', width: 100, editable: true, editoptions: { size: 2 }, editrules: {integer:true} },
   5:    { name: 'Email', index: 'Email', width: 150, editable: true, editoptions: { size: 50 }, editrules: { email: true} }
   6: ],

Depois adicionar a opção para definir o caminho do PageMethod responsável por salvar o registro:

   1: editurl: "/Default.aspx/Salvar"

Também devemos alterar algumas configurações da Grid para conseguir tratar o JSON, porque infelizmente o JQuery tem um problema ao converter os dados para JSON automaticamente, então nós mesmos temos que fazer isso. Para isso, vamos alterar algumas informações padrões da Grid:

   1: $.extend($.jgrid.edit,
   2: {
   3:     ajaxEditOptions: { contentType: "application/json;charset=utf-8" },
   4:     serializeEditData: function (data) {
   5:         return JSON.stringify(data);
   6:     }
   7: });

Feito isso, não precisamos mais fazer nada no JavaScript, apenas preparar o PageMethod. que você verá que é autoexplicativo:

   1: [WebMethod]
   2: public static void Salvar(string Nome, int Idade, string Email, string oper, string id)
   3: {
   4:     var modelo = new Modelo();
   5:     if (oper == "add")
   6:     {
   7:         var pessoa = new Pessoa
   8:         {
   9:             Nome = Nome,
  10:             Idade = Idade,
  11:             Email = Email
  12:         };
  13:         modelo.Pessoas.Add(pessoa);
  14:     }
  15:     else
  16:     {
  17:         var pessoa = modelo.Pessoas.Where("PessoaID = " + id).ToList()[0];
  18:         pessoa.Nome = Nome;
  19:         pessoa.Idade = Idade;
  20:         pessoa.Email = Email;
  21:     }
  22:     modelo.SaveChanges();
  23: }

E pronto!

Com isso você já pode testar a alteração e inclusão clicando nos botões que aparecem na barra da Grid:

Alterando registro com JQGrid X WebForms

Criando registro com JQGrid X WebForms

É isso pessoal, com o que foi passado nesse post você já consegue ir muito além. Você viu que tudo é possível com a estrutura que o Asp.Net fornece para o WebForm.

Espero que isso ajude. Muitas pessoas já me perguntaram sobre JQGrid com WebForm, isso deve ajudar muita gente.

Abraços e até o próximo!

2. September 2011 00:39 by Frederico B. Emídio | Comments (5) | Permalink

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. August 2011 20:45 by Frederico B. Emídio | Comments (2) | 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. August 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. July 2011 05:36 by Frederico B. Emídio | Comments (1) | 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. July 2011 21:09 by Frederico B. Emídio | Comments (5) | Permalink

Asp.Net MVC 4 – Novidades para CSS e JavaScript    

Olá pessoal!

Hoje vou fazer um post bem rápido, apenas para comentar sobre a nova versão do Asp.Net MVC.

Para quem não sabe, a equipe do Asp.Net já está planejando a nova versão do Asp.Net MVC, e maiores detalhes podem ser encontrados aqui.

Para planejar as melhorias, eles definiram cinco guidelines, e no momento oportuno falarei de cada uma delas. Essas Guidelines vão de publicação e temas para Mobile à publicação na de sites na Nuvem.

O que achei interessante, e quis escrever esse post para comentar, é um item de publicação de site extremamente útil. Apesar de eles falarem que são apenas planos, e que qualquer coisa pode mudar, acho interessante comentar.

Eles estão planejando realizar um Build Integrado para CSS e JavaScript. Uma coisa que sempre senti falta no momento de compilar Web Applicattion.

A idéia seria, no momento da compilação do seu site, realizar a combinação de todos os seus arquivos CSS e JavaScript em apenas um (Um de cada tipo, naturalmente), e após realizar a combinação, fazer a minificação, ou seja, tirar todos os espaços desnecessários e comentários, além de alterações de nomes de algumas variáveis e funções, para diminuir consideravelmente o tamanho do arquivo.

Isso faria seu site carregar bem mais rápido, pois iria diminuir o número de requests por recursos do site (seria apenas um para cada tipo de recurso), além de diminuir o tempo de downloads, graças ao arquivo minificado (minified). Se você utilizar o Fiddler, por exemplo, para ver como é gasto o tempo de download de suas páginas, verá que esses arquivos realmente fazem diferença no load da página.

Como todos sabem, já existem ferramentas de minified disponíveis na internet, mas fazer todo esse trabalho manualmente para cada arquivo, e eventualmente combinar esses arquivos é uma tarefa bem enfadonha, e essa nova feature do Asp.Net MVC seria de muita ajuda.

Vamos torcer para que o time realmente implemente essa funcionalidade, porque acredito que é tipo de anuncio que dá brilhos nos olhos, mas é esquecido durante o processo de desenvolvimento. Afinal, as implicações de alterar e combinar arquivos automaticamente são inúmeras, e não são apenas boas implicações, afinal, alteraria nome de arquivo, o que teria que validar todas as referências em arquivos aspx ou cshtml, etc.

Vamos aguarda para ver o que o time do Asp.Net mostrará nos próximos meses. Todas as novidades serão mostradas nesse blog.

Abraços e até o próximo.

19. July 2011 04:54 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

<<  May 2012  >>
MoTuWeThFrSaSu
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

Visualizar posts em um Calendário
Sigua @fredemidio

MCP Asp.NET