A última intervenção que fiz no motor do PontoNetPT permitiu-me encontrar mais um problema relacionado com o ID dos controlos (não sei se a questão dos ID é uma paixão ou uma obcessão mas a verdade é que é um tema recorrente).
Tal como expliquei antes, o motor do PontoNetPT está compilado para NetFX 1.1 mas o servidor actual não possuir esta versão instalada, i.e., estamos a correr uma aplicação 1.1 sobre a 2.0 ;-).
Este não é um cenário recomendado pois a versão 2.0 quebrou alguns comportamentos da 1.1 … mas como estamos perante um cenário de transição é aceitável.
Relativamente aos controlos de validação houve alterações na forma como a informação é agregada no lado do cliente:
Cenário NetFx 1.1
Toda a informação necessária para realizar a validação no lado do cliente é guardada em atributos do elemento Html que representa o validador.
Cenário NetFx 2.0+
A mesma informação passou a ser renderizada através de ExpandoAttributes, i.e., passou a existir um objecto javascript que agrega esta informação.
À primeira vista esta alteração parece não trazer problemas, passando apenas por uma estratégia diferente de guardar a informação necessária, no entanto, esconde um problema relacionado com a convenção de nomenclatura usada nos atributos de elementos Html e nas variáveis de javascript.
O problema
Com a migração da aplicação deixou de ser prossivel adicionar comentários aos posts, isto porque todas as *.aspx que tinham validadores disparavam um erro de javascript.
O primeiro erro ocorria sempre na seguinte declaração:
var ctl00_pageBody-2_RequiredFieldValidator1 = document.all ? document.all["ctl00_pageBody-2_RequiredFieldValidator1"] : document.getElementById("ctl00_pageBody-2_RequiredFieldValidator1");
Olhando com atenção o erro é óbvio, o caracter ‘-‘ é um operador de javascript e como tal não pode ser usado no nome de uma variável.
Tal como referi, na NetFx 1.1 este objecto não existia e toda a informação era guardada sob a forma de atributos do elemento Html que representava o validador.
Por outro lado, não encontro nenhuma boa razão para justificar a atribuição do valor “pageBody-2” ao ID de um controlo, no entanto este é um valor absolutamente válido e não viola nenhuma regra que eu conheça (ou melhor … não violava … a partir de agora passou a violar).
A solução
Bom, a solução é simples … alterar o ID por forma a não conter o caracter ‘-‘.
No meu caso, e porque o mundo é invariavelmente mais complicado, o ID era gerado dinamicamente num método privado de uma classe base interna …
A única solução prática foi fazer override ao método PreRender da página e substituir o padrão ‘_pageBody-‘ por ‘_pageBody_‘.
Eu sei que esta é uma solução feia e nada performante ... eu não gosto dela … mas sendo uma solução de curto prazo até à migração para CS é um compromisso aceitável.
As Soluções
Existem duas abordagens para resolver esta questão, tanto podemos forçar os validadores a renderizar atributos Html como podemos remover os caracteres indesejados do ID.
A primeira é naturalmente a melhor, principalmente nesta situação que temos uma aplicação antiga, no entanto é uma alteração global da aplicação.
Instruir os validadores a renderizar atributos Html
Os validadores .Net estão preparados para operar num modo ‘Legacy’, mas para habilitar este funcionamento é necessário alterar os modo de funcionamento da aplicação. Para habilitar este funcionamento alteramos o web.config da seguinte forma:
<system.web>
<xhtmlConformance mode="Legacy"/>
Embora esta seja a solução que prefiro, optei por usar a outra alternativa simplesmente porque não conheço o suficiente para avaliar o impacto global que esta alteração pode envolver. O que eu procurava era uma alteração cirúrgica para resolver um problema específico e localizado sem envolver efeitos colaterais.
Alterar o ID dos controlos
Esta abordagem requer a normalização do ID de todos os controlos que contribuem para a hierarquia de cada validador, para desta forma evitar problemas de nomenclatura Javascript. É de esperar que desta forma o tempo dispendido seja mais elevado, no entanto, não é espectável qualquer problema.
Bom, essa espectativa acabou por não se cumprir, sendo que encontrar o local exacto onde este ID estava a ser criado foi uma tarefa maçadora e demorada.
Acabei por descobrir o que o ‘pageBody-2’ era um UserControl, carregado dinamicamente e cujo ID era também ele gerado dinamicamente. Infelizmente, esta manipulação do ID era realizada num método privado de uma classe base interna.
A única solução prática foi fazer override ao método PreRender da página e substituir o padrão ‘_pageBody-‘ por ‘_pageBody_‘.
Eu sei que esta é uma solução feia e nada eficiente... eu não gosto dela … mas sendo uma solução de curto prazo até à migração para CS é um compromisso aceitável.
A reter
Nunca usar no ID de controlos Server-Side caracteres que, tal como o ‘-‘, tenham sigificado especial em javascript. Desta forma vão evitar surpresas desagradáveis.
Como alguns de vocês já devem ter reparado, tenho andado ocupado com a migração da comunidade.
Este é um desafio que o meu grande amigo Paulo Morgado me lançou, e sabem como é, um bom desafio não dá para resistir.
O desafio é simples .... migrar o PontoNetPT de .Text para CS, mas como todos os desafios é vencido em pequenas etapas.
Neste caso as etapas são as seguintes e como sabem não foram ainda todas ultrapassadas:
- Migrar o PontoNetPT para o novo servidor e garantir o pleno funcionamento
- Testar metodologias de migração de conteúdos entre o .Text v0.91 e o CS
- Instalar CS
- Migrar todos os conteúdos para CS e descontinuar o .Text
Naturalmente, cada uma destas etapas apresenta dificuldades distintas.
Relativamente ao 1. o principal problema esteve relacionado com o runtime do ASP.NET. O João Cardoso migrou a aplicação para o novo servidor mas a compilação da aplicação resultava sempre no seguinte erro:
/Skins/blue/Controls/EntryList.ascx(1): error ASPPARSE: Ambiguous match found.
[AmbiguousMatchException]: Ambiguous match found.
at System.RuntimeType.GetField(String name, BindingFlags bindingAttr)
at System.Web.UI.Util.GetNonPrivateFieldType(Type classType, String fieldName) [...]
Eu e o Paulo, já tinhamos visto alguns erros semelhantes a este e desconfiamos de imediato que a aplicação estava a ser compilada em vb.NET e não C#.
"Reflectindo" um pouco sobre o assunto encontramos este código no componente que dava erro:
public class EntryList : BaseControl
{
[...]
private EntryCollection entries;
protected Repeater Entries;
[...]
A classe EntryList é usada pelo UserControl EntryList.
Embora alguns erros tenham sido ultrapassados removendo todas as referências "vb" este problema persistia. A análise do Paulo mostrou o seguinte:
- se existirem apenas membros não públicos com o mesmo nome embora com capitalização diferente o método System.RuntimeType.GetField devolve uma AmbiguousMatchException
- se um dos membros com mesmo nome for publico o método consegue executar com sucesso
Graças a esta análise não foi dificil ultrapassar esta questão, bastando para tal criar uma classe que estende a EntryList e expõe como membro público a propriedade que de facto queremos que o Runtime resolva correctamente e esconder a propriedade da base. A classe é a seguinte:
public class EntryList : global::Dottext.WebUI.Controls.EntryList
{
public new Repeater Entries
{
get { return base.Entries; }
set { base.Entries = value; }
}
}
Naturalmente, foi necessário alterar o UserControl para passar a usar esta classe 'kittada' e não a original.
Caros amigos,
Já está disponivel a funcionalidade "Export to BlogML" nas opções de administração.
Aconselho a que cada um de vós use esta funcionalidade e guarde o xml gerado como backup.
Informem-me se tiverem algum problema ou quiserem partilhar o sucesso ;-)