Ontem decidi efectuar alguns testes com o novo controlo XmlDataSource. Uma vez que precisava de obter todos os dados, achei por bem utilizar o controlo TreeView pois este controlo permite estabelecer relacionamentos de data binding com fontes de dados hierárquicas. O primeiro passo que dei consistiu em construir um documento de XML simples:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <Alunos xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Alunos.xsd">
3 <Aluno>
4 <Nome>João</Nome>
5 <Morada>Funchal</Morada>
6 </Aluno>
7 <Aluno>
8 <Nome>Rita</Nome>
9 <Morada>Lisboa</Morada>
10 </Aluno>
11 <Aluno>
12 <Nome>Pedro</Nome>
13 <Morada>Porto</Morada>
14 </Aluno>
15 </Alunos>
Como é possível verificar através do excerto anterior, estamos na presença de um documento muito simples de XML. O próximo passo foi construir um form simples com dois controlos: uma TreeView e um controlo do tipo XmlDataSource:
14 <form id="form1" runat="server">
15 <asp:TreeView runat="server" DataSourceID="source" ID="tree">
16 </asp:TreeView>
17 <asp:XmlDataSource ID="source" runat="server" DataFile="Alunos.xml"/>
18 </form>
Ao utilizar o browser, reparei que não era bem isto que eu queria. A TreeView mostrava todos os nós contidos no documento; contudo, todos os nós de XML eram representados pelo respectivo nome. Achei por bem dar uma olhadela à documentação e foi aí que descobri o elemento TreeNodeBinding. Parecia ser a solução para o meu problema. Modifiquei então o código para o seguinte:
<asp:TreeView runat="server" DataSourceID="source" ID="tree">
<DataBindings>
<asp:TreeNodeBinding DataMember="Nome" TextField="innertext" Depth="2" />
<asp:TreeNodeBinding DataMember="Morada" TextField="innertext" Depth="2" />
</DataBindings>
</asp:TreeView>
<asp:XmlDataSource ID="source" runat="server" DataFile="Alunos.xml">
</asp:XmlDataSource>
Ao abrir o IE, deparo-me com uma excepção! Após ler a mensagem de erro, sou levado a pensar que o controlo TreeView não consegue encontrar a propriedade InnerText. Ao ver isto fiquei, como seria de esperar, de boca aberta! Fui novamente à documentação e verifiquei que os elementos do tipo XmlNode possuem de facto a propriedade InnerText. Mas então o que se estará a passar? Voltei a ler a documentação sobre o elemento TreeNodeBinding. Ao fazer scroll, reparei que existia um exemplo em que era feito o mapeamento com um documento de XML. Perfeito (pensei eu na altura)! Ao tentar analisar o exemplo, reparei que o mapeamento estava a ser executado entre o atributo do nó de XML e a propriedade TextField do elemento TreeNodeBinding. Mal podia acreditar no que estava a ver! É que de, acordo com a documentação, isto não poderia estar a acontecer (recordar que segundo a documentação, os valores contidos na propriedade TextField são utilizados indicar a propriedade do elemento a partir da qual iria ser obtido o valor; se é assim, como é que eles estavam a colocar lá o nome do atributo que estava contido no nó). Resolvi experimentar o exemplo fornecido. Para grande espanto meu, funcionou. Mas então o que se estará a passar?Foi aí que tive de recorrer aquela grande ferramenta (.Net Reflector) para tentar perceber o que se está a passar. Ao analisar o código relativo à classe XmlDataSource, podemos comprovar que esta classe fornece dois tipos de views: XmlDataSourceView e XmlHierarchicalDataSourceView .Cada uma delas é utilizada em situações diferentes (a primeira é utilizada quando temos controlos de visualização que esperam os dados no formato tabular; a segunda é utilizada por parte de controlos que trabalham com dados hierárquicos). Nesta situação, a vista utilizada era a associada a dados hierárquicos (por outras palavras, estavamos a utilizar a classe XmlHierarchicalDataSourceView).O procedimento de obtenção de dados baseia-se na execução do método Select desta classe. Ao observar o método Select, cheguei à conclusão que este retorna um elemento do tipo XmlHierarchicalEnumerable (a partir do nome já dá para perceber que esta classe implementa o interface IEnumerable - na verdade, implementa o interface IHierachicalEnumerable, que herda de IEnumerable...enfim, pormenores). É aqui que começa a ocorrer a "magia" responsável pelo comportamento descrito acima. Em vez de retornar a lista de elementos do tipo XmlNode que a classe contém quando é evocado o método GetEnumerator, esta classe opta por construir uma nova lista (ArrayList) com elementos do tipo XmlHierarchicalData (cada elemento deste tipo é responsável por conter um nó do tipo XmlNode). Mas então porquê utilizar esta classe (XmlHierarchicalData)? aha! Ainda bem que perguntam...Na verdade, esta classe implementa o interface ICustomTypeDescriptor. A implementação deste interface permite personalizar o processo de obtenção de propriedades através da utilização de um TypeDescriptor (sim, é verdade! Internamente, a classe TreeView tenta obter as propriedades contidas em cada nó de XML através da utilização de um TypeDescriptor). Assim, basta olharmos para a implementação do método ICustomTypeDescriptor.GetProperties para termos a noção real do que se está passar: esta classe está modificar as propriedades associadas a cada nó! Em vez das tradicionais propriedades descritas na documentação associada ao XmlNode, a classe está a devolver os nomes dos atributos contidos no nó (por isso é que o sample da MSDN funciona!). Para além disso, a classe adicionar três valores especiais: #InnertText, #Value e #Name.aha! Agora estamos a chegar mais perto da verdade :)Claro que a classe recorre a uma classe interna (XmlHierarchyDataPropertyDescriptor) para encapsular cada PropertyDescritpor definido desta forma. Aliás, convém referir que é esta classe que será responsável por devolver o valor da propriedade especificada no campo TextField do exemplo anterior. Ao analizar esta classe, pude verificar que os valores especiais #InnertText, #Name e #Value são mapeados nas propriedades InnerText, Name e Value da classe XmlNode. E cá está a solução para o problema inicial (como especificar que o binding deve obter o texto contido no nó!). Basta utilizar o valor especial #innertext, conforme o sample seguinte ilustra:
<asp:TreeView runat="server" DataSourceID="source" ID="tree">
<DataBindings>
<asp:TreeNodeBinding DataMember="Nome" TextField="#innertext" Depth="2" />
<asp:TreeNodeBinding DataMember="Morada" TextField="#innertext" Depth="2" />
</DataBindings>
</asp:TreeView>
<asp:XmlDataSource ID="source" runat="server" DataFile="Alunos.xml"/>
É óbvio que para descobrir este tipo de informação levei algum tempo. Será que era mesmo necessário perder tanto tempo a perceber o que se estava a passar? Bem, eu sei que não iria resistir a tentar perceber o mecanismo interno de binding...contudo, desta vez queria apenas utilizar o controlo XmlDataSource associado a um controlo TreeView. A minha questão é a seguinte: Porque é que a documentação não fala nestes valores especiais? Devemos ser bruxos ou a Microsoft está interessada em promover o .Net Reflector?
posted on Saturday, February 12, 2005 6:30 PM