Introdução: Já faz um bom tempo que não posto nada e aqui uma explicação: Transferi-me para São Paulo e ate ajustar tudo esta sendo bem mais demorado do que previa...
Costumo sempre dar uma pesquisa pela web sobre AJAX para ver como a comunidade vem absorvendo esta funcionalidade que hoje é indispensável para qualquer aplicativo web. Um post em um blog me chamou atenção hoje. Não pelo seu conteúdo, mas pela dúvida gerada dentro de um cenário que dificilmente acontece. Como lá só tinha o problema que apontava para o AJAX , sem a causa e explicação dos motivos resolvi escrever este pequeno post para compartilhar com a comunidade alguns fundamentos e conhecimento sobre este cenário . Afinal não adianta nada trazer problemas sem soluções e a devida pesquisa , isso pode leva a outra conclusão.Qual é o cenário que estamos falando : Você esta criando um aplicativo web habilitado para usar Ajax. A aplicação criada esta correta, o arquivo de configuração esta perfeito e o servidor também este corretamente configurado com os assemblys do AJAX devidamente registrado no GAC. Ao executar o aplicativo aparece a mensagem de “Sys is undefined”. Verificando a Data do servidor você percebe que a Data esta errada (com alguns anos para trás...). A solução imediata é simples : É só corrigir a Data, porem o que chamou atenção foi justamente a dúvida deixada :
“Mas por que diabos recusar uma data desatualizada, só porque ele não existia na época? Tudo bem que sabe-se lá porque alguém precisaria disso, mas e se eu precisar do meu servidor com a data em 2002 ?”
A dúvida é mais que justificada! porem não existe nenhuma relação com a existência ou não do produto na época.
A idéia deste post é entender melhor o que esta acontecendo, como funciona o AJAX ASP. NET e alguns outros detalhes do Framework .NET . Ao final descobrir as respostas e dar um “workarround” para este cenário caso seja necessário.
Para se chegar à explicação iremos precisar usar uma ferramenta(que inclusive já citei neste blog) como fundamentais para qualquer desenvolvedor: http://linhadecodigo.com.br/cs2/blogs/fcerqueira/archive/2007/02/22/588.aspx
“Refletor (Embora muitos achem que a intenção é descompilar e ver o código fonte, estas ferramentas tem uma tarefa mais nobre : Facilitar o entendimento do .NET framework,Ajudar a melhorar a performance e reduzir os cast, Uma grande fonte de aprendizado”
A mensagem de Erro para o usuário : “Microsoft JScript runtime error: 'Sys.WebForms.PageRequestManager' is null or not an object”Esta mensagem normalmente ocorre quando não temos instalado (diga-se registradas) as dlls do AJAX. Mas esta mensagem (neste nosso caso) é uma conseqüência e não origem do erro uma vez que tudo esta registrado de forma correta.Entendendo melhor: O AJAX é formado por um conjunto de bibliotecas que estão no lado do servidor e no lado do cliente. Quando tornamos nossos aplicativos habilitados para Ajax as requisições são interceptadas pela biblioteca que esta no servidor que executa o devido tratamento, inclusive incluído na página que retorna para o cliente os script necessários (Veja o exemplo abaixo) :...
O ScriptResource.axd é um recurso introduzido no Framework 2.0 que permite trabalhar com os resouces anexados no Assembly . Sua estrutura é desta forma :
WebResource.axd?d=recurso & t =Tempo. "d" significa o recurso da Web solicitada que vem encripitado e o "t" é a data/hora para o assembly solicitado. O parâmetro “t” ajuda a identificar se houve alterações feitas o recurso.
Para quem desejar saber um pouco mais, abaixo um artigo no MSDN que trata deste assunto :http://support.microsoft.com/kb/910442/pt-br
A mensagem de Erro de origem: Agora conhecendo melhor esta premissa de como os script são gerados e enviados, podemos partir para a origem do problema, testando a execução dos script que retorna a mensagem real de origem abaixo :
Specified argument was out of the range of valid values.Parameter name: utcDate
...Exception Details: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.Parameter name: utcDate
...Stack Trace:
[ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: utcDate]
System.Web.HttpCachePolicy.UtcSetLastModified(DateTime utcDate) +3299747
System.Web.HttpCachePolicy.SetLastModified(DateTime date) +47
mpletedSynchronously) +64
...
Ok, até aqui quase nenhuma novidade. Apenas identificamos a origem do erro, mas ainda restam duas perguntas: Porque deu este erro? Como posso contornar o problema mantendo a data do servidor (mesmo que desatualizada)? Vamos então continuar para termos as respostas JPorque deu este erro ?
Para responder vamos usaremos dois recursos: O Refletor (para ver a lógica e codigo que o framework utiliza) e o código fonte do AJAX.NET . Para quem não sabe o código fonte do AJAX.NET esta disponível para estudo e para debug em :http://www.microsoft.com/downloads/details.aspx?FamilyID=ef2c1acc-051a-4fe6-ad72-f3bed8623b43&DisplayLang=en
O erro apresentado é no método UtcSetLastModified da classe System.Web.HttpCachePolicy.
Usando o Refletor temos os seguintes códigos abaixo: public void SetLastModified(DateTime date)
{
DateTime utcDate = DateTimeUtil.ConvertToUniversalTime(date); this.UtcSetLastModified(utcDate);}
private void UtcSetLastModified(DateTime utcDate){ utcDate = new DateTime(utcDate.Ticks - (utcDate.Ticks % 0x989680L));
if (!this._isLastModifiedSet || (utcDate > this._utcLastModified)) { this.Dirtied(); this._utcLastModified = utcDate; this._isLastModifiedSet = true; }}
O Texto em vermelho grifado é o trecho de código responsável pela mensagem de erro, ou seja: caso a data passada seja maior que a data corrente ocorre uma exception. Mas que Data é essa que é passada como parâmetro? Agora precisamos usar o segundo recurso de analise: O Código fonte do AJAX ASP.NET. Como estamos tratando uma requisição de resource já explicada, se buscarmos pelo titulo dos arquivos iremos encontrar um arquivo de nome: ScriptResourceHandler.cs e dentro dele temos o método abaixo :private static DateTime GetLastWriteTime(Assembly assembly) { string codeBase = GetCodeBaseWithAssert(assembly); Uri codeBaseUri = new Uri(codeBase); if (!codeBaseUri.IsFile) return DateTime.MinValue; string localPath = codeBaseUri.LocalPath; FileIOPermission p = new FileIOPermission(FileIOPermissionAccess.Read, localPath); p.Assert(); return File.GetLastWriteTime(localPath);}Analisando o código o leitor poderá perceber que o parâmetro passado é a data do assembly que possui o resouce a ser incluído.Explicando o motivo de erro apresentado:De um lado temos um assembly que foi gerado com uma determinada Data, e de outro lado temos a Data corrente do servidor . Como a Data do assembly é superior a data Corrente do servidor temos uma inconsistência que é verificada pelo Framework! Ou seja, o erro gerado não é do AJAX e sim do próprio .NET framework que executa a verificação de data.
Respondendo a segunda pergunta: Caso seja necessário ter um servidor com data desatualizada e ainda assim executar assemblys que foram gerados com data superior a data corrente ?Como se trata de um cenário extremante atípico precisamos fazer um “workarround” (contornar o problema) . A forma mais simples é modificar a data do assembly e para isso podemos usar váriso aplicativos para este fim, um deles é o File Touch utility http://www.jddesign.f2s.com/touchpro.htm.
Conclusão :
Posso usar webparts com ajax ?Minha resposta SIM.Para usar webparts com ajax não preciso fazer nada ?Minha resposta e não, você precisa fazer sim! Infelizmente nem todas as funcionalidades de webparts foram implementadas , tanto é que ela não faz parte da versão RTM e sim da versão “Future” . Veja as limitações (sem fazer nada) em
http://forums.asp.net/thread/1545256.aspx ou em http://blogs.msdn.com/mharder/archive/2007/01/23/webparts-and-asp-net-ajax-1-0.aspxEntão só posso Ajax com a versão “FUTURE” ?Minha resposta e não, PODE IR ALEM, VOCE PODE USAR WEBPARTS COM A VERSÃO ASP.NET AJAX 1.0!!!!!!!!!!!!!!!!!.
E posso usar Ajax com Webparts em produção?
Minha resposta e não, mas por quê? Por que embora as funcionalidades de Drag and Drop passem a funcionar com as soluções que demonstro mais abaixo, ainda precisa ser amadurecida e melhorada, inclusive se pensando em cross-browser. O Objetivo aqui é compartilhar o conhecimento e mostrar que é possível fazer funcionar webparts com Ajax , quem acompanhou a evolução lembra que durante os ctps a compatibilidade entres os browses veio depois , como em qualquer amadurecimento de código.Mas como é possível? Já li que isso é mito uma lenda não é fato....Minha resposta é que leia ate o final e depois tire a conclusão do que é lenda e que fato e pode ser feito, e conclua você mesmo, afinal não estou aqui para ficar demostrando o que não funciona e sim para compartilhar conhecimento e aprender junto com a comunidade , ou seja com vocês.
Mas esta solução é sua? Não. Estava trabalhando em uma solução para este cenário, porem meu trabalho foi abreviado por encontrar uma solução bem mais adiantada e seguindo o mesmo raciocínio que o meu.
Bem antes de mostrar como é a mágica, vamos entender o que não funciona:
Uma das principais funcionalidades de usar webparts é você poder mover as webparts entre as webzones. Uma das coisas desagradáveis era que quando fazia isso para cada webparts que você movia era executado um postback e página era toda carregada. Com a chegada do AJAX isso pareceu ter sido resolvido de uma forma muito elegante não ocorrendo mais o postback, tornando a usabilidade bastante atraente. Infelizmente esta funcionalidade com o Ajax foi retirada nas versões betas e também na versão RTM 1.0, sendo deixada para ser implementada em versões futuras. Quando colocamos as webparts e o wepartmananger dentro do updatepanel o drag and drop não funciona corretamente. Mas porque não funciona? É aqui que começa a mágica. Se observar o comportamento quando esta dentro do updatepanel ira perceber que funciona na primeira vez e depois quando é feito o “Partial render” pelo scriptmananger não é possível mais executar o drag and drop. Este efeito indesejado esta centrado em um único componente: O webpartmananger.
O webpartmananger que é responsável por gerar e registrar os scripts com as funcionalidades. Este script é que são os responsáveis pela funcionalidade Drag and drop, os Verbos dos menus e outras funcionalidades das webparzones.Desta forma então será o webpartmananger que teremos que modificar para que o webparts funcione corretamente.
Mas porque precisamos modificar ele (webpartmananger) ? Porque quando colocamos ele dentro do updatepanel durante o primeiro render ele carrega os script e executa as funcionalidades, mas depois do primeiro “Partial render” o Webpartmananger não consegue mas registrar os scripts e “se perde” criando exatamente o comportamento já descrito de apenas executar somente 1 vez, isso se explica porque o responsável por isso é o scriptmananger quando trabalhamos com AJAX. Simples não é..... São estes conceitos que ajudam a você a resolver cenários onde existe pouca documentação e recursos existentes.
Vocês já leram também pelo meu blog que os validators também tem problemas quando usando dentro do updatepanel (principalmente dentro de templates de grids que estão dentro dos updatepanels) . A solução para os validtors foi mapear as classes responsáveis pelos controles para outra classe que resolver o problema. A solução para webparts é feita da mesma forma! Justamente por este caminho que estava trabalhando quando me deparei com outra solução feita por outro desenvolvedor segundo exatamente esta linha!
Mas o que deve ser feito?
Primeiro criar uma classe que herde do WebpartMananger e sobre-escrever o RenderClientScript (para se ter o controle do se se escreve na pagina). Com isso registramos os scripts pelo System.Web.UI.ScriptManager e não mais pelo System.Web.UI.ClientScriptManager. Esta simples mudança faz com que os script registrados sejam executados a cada refresh do updatepanel que é controlado pelo scriptmananger.
Estava bem próximo disso quando encontrei esta solução que estou descrevendo. Para completar a solução será necessário mapear as classes do System.Web.UI.WebControls.WebParts.WebPartManager para a classe que esta sendo criada da mesma forma que foi feita a solução para os validators.<configuration> tagType="System.Web.UI.WebControls.WebParts.WebPartManager" mappedTagType="Sample.Web.UI.WebParts.WebPartManager, Sample.Web.UI.WebParts" /> E o código ? Vamos a explicação de algumas partes dele :
Como já falamos a classe herda de System.Web.UI.WebControls.WebParts.WebPartManager e o trecho abaixo mostra como esta sendo registrado o script necessário junto ao ScriptManager e depois durante a statup para garatir o resfresh do updatepanel.
Public Class WebPartManager Inherits System.Web.UI.WebControls.WebParts.WebPartManager
Protected Overrides Sub RegisterClientScript() If Me.CheckRenderClientScript Then System.Web.UI.ScriptManager.RegisterClientScriptResource(Me, GetType(System.Web.UI.WebControls.WebParts.WebPartManager), "WebParts.js") System.Web.UI.ScriptManager.RegisterStartupScript(Me, Me.GetType, Me.ID & "_Script", Me.Script, True) End If End Sub
Abaixo parte de o script necessário ser executado durante o startup.
Private ReadOnly Property Script() As String Get Dim colorConverter As New System.Web.UI.WebControls.WebColorConverterDim _clientScript As String = String.Format("__wpm = new WebPartManager();{0}" & _ "__wpm.overlayContainerElement = document.getElementById('{2}___Drag');{0}" & _ "__wpm.personalizationScopeShared = {1};{0}" & _"var zoneElement;{0}var zoneObject;{0}", ControlChars.CrLf, Me.Personalization.CanEnterSharedScope.ToString.ToLower, Me.ClientID) For Each z As WebPartZone In Me.Zones…… Return _clientScript End Get End Property
Você deve estar se perguntando como é possível saber que código escrever para se chegar a este resultado ... Aqui Tb não existe nenhuma lenda nem nenhum “super guru” ..rs.rs, quem acompanha meu blog em http://linhadecodigo.com.br/cs2/blogs/fcerqueira deve se lembra de uma dica que dei da nova versão do :
“Refletor 5.0 - Uma ferramenta indispensável” (http://linhadecodigo.com.br/cs2/blogs/fcerqueira/comments/588.aspx)
No blog disse
“Embora muitos achem que a intenção é descompilar e ver o código fonte, estas ferramentas tem uma tarefa mais nobre : Faciliar o entendimento do .NET framework. Ajudar a melhorar a performance e reduzir os cast Uma grande fonte de aprendizado”
E ai esta a outra parte da mágica, aprender mais sobre o funcionamento vendo como é realizado as rotinas.
Uma pergunta final que pode ser feita : Esta solução é crossbrowser ?
Infelizmente não é, por enquanto esta solução esta compatível apenas com o Internet Explorer , porem mostra que com um pouco de conhecimento pode-se se chegar a uma solução. O legal de compartilhar conhecimento é poder mostrar os avanços, os caminhos . Compartilhando estes caminhos e códigos outros desenvolvedores aprendem melhor o funcionamento e pode evoluir a solução até chegar a uma maturidade de código que permita o uso em usa plenitude
Onde posso pegar todo o código fonte ?
Esta em um thread no fórum www.asp.net : http://forums.asp.net/thread/1621227.aspx Quem inica a thead e o propio autor da classe, a ele que devemos dar o grande parabens . De minha parte estou apenas ajudando a divulgar e compartilhar com vocês.A todos um grande abraço