.Net Xml Serialization, Parte 2
Vimos anteriormente os princípios básicos da Serialização em Xml, as classes necessárias para definir a estrutura do Xml, as que são necessárias para a serialização propriamente dita, e finalmente como estruturar uma classe para que possa ver serializável.
Vamos agora ver em mais detalhe a serialização de objectos de e para disco, os utilitário necessários para criar objectos a partir de um Xml Schema (XSD) e vice-versa.
Inicialmente para o exemplo apresentado, foi criado uma string com o Xml resultante da serialização, este Xml está de acordo com o que foi especificado na classe, ou seja é na classe e através de atributos que se define entre outras coisas o seguinte:
- Quais as propriedades a serem serializadas.
- Que propriedade são atributos ou elementos do xml.
- Quais os seus nomes a atribuir a essas propriedades ou elementos.
- Que elementos e atributos são obrigatórios.
Uma vez não temos um Xml Schema, não é possível verificar à priori se o Xml produzido está de acordo com uma especificação, nesta situação a única forma de validarmos essa conformidade é recorrendo a um bloco try\catch no momento em que o objecto é instanciado a partir do xml, porém esta solução está longe de ser a ideal, uma vez que o Xml pode e deve ser validado a partir de um Schema.
Mesmo ao nível do próprio processo de desenvolvimento, pode ser mais útil criar a definição de uma classe a partir de um Xml Schema existente, uma vez que a maioria dos processos de negócio de hoje implicam a troca de documentos em formato Xml, podemos olhar para o Schema na sua forma mais simplista como o contrato a que esses documentos devem obedecer, pelo que é de todo o interesse que seja o Schema a servir de ponto de partida para criar as classes utilizadas na execução desses processos de negócio.
Para este exemplo vamos usar a “xsd.exe” tool, esta tool faz parte do SDK da.Net Framework, e permite como referido anteriormente criar um XSD Schema a partir de uma classe ou conjunto de classes, e também o inverso, criar a classe ou classes a partir do XSD schema, existem outra ferramentas para este efeito algumas das quais bastante mais poderosas, mas pela sua simplicidade e uma vez que já vem com a Framework é a eleita para este exemplo.
Vejamos as seguintes classes, tal como os nomes indicam a classe SalesOrder representa o cabeçalho de uma encomenda e a classe SalesOrderItem uma linha dos itens dessa mesma encomenda:
Para criar um XSD Schema a partir de uma classe é necessário identificar em primeiro lugar o tipo do objecto, esse é o tipo a partir do qual o Schema vai ser gerado, é também necessário indicar qual a assembly onde está definido esse tipo, para tal usamos o seguinte comando:
xsd.exe -t:SalesOrder SerializationSampleTwo.exe
Este comando vai dar origem a um ficheiro chamado schema0.xsd, esse ficheiro contém a especificação do Schema a que o Xml produzido pela serialização das classes SalesOrder e SalesOrderItem deve obedecer:
Como se pode ver existem propriedades de ambas as classes que estão definidas como atributos e também propriedade que tem nomes diferentes entre a classe e o schema, de acordo com a definição criada pelos diversos atributos na classe. Estes atributos fazem parte do namespace System.Xml.Serialization.
Agora o inverso, partir de um XSD schema e gerar as classes. Para criar um schema pode-se utilizar o Visual Studio, ou o Xml Spy, este ultimo disponibiliza á algum tempo uma versão gratuita, o seu download pode ser feito a partir do seguinte url: http://www.altova.com/support_freexmlspyhome.asp.
O schema que vamos usar para criar as classes é o seguinte:
Para criar as classes a partir de um schema, basta indicar qual o nome do schema, existem mais parâmetros que podem ser passados, tais como o namespace ou a linguagem, neste exemplo vamos usar o mais básico possível, o comando é o seguinte:
xsd.exe /classes schema.xsd
O Visual Studio 2005, automaticamente cria as classes a partir de um schema, para tal basta que se adicione o schema ao projecto, que ele de imediato cria as classes respectivas.
As classes criadas são as seguintes:
Um primeiro reparo, o elemento Items que no schema está definido como uma sequência de elementos CatalogItems é criado como um Array.
49 public partial class CatalogItems
50 {
51 private CalalogItem[] calalogItemField;
52
53 /// <remarks/>
54 [XmlElement("CalalogItem")]
55 public CalalogItem[] CalalogItem
56 {
57 get { return calalogItemField; }
58 set { calalogItemField = value; }
59 }
60 }
Se o tipo de operações a fazer com os dados envolve apenas leitura, o Array serve perfeitamente, porém o que acontece na maior parte das vezes, é a necessidade de retirar e\ou adicionar elementos, para isso o melhor é utilizar uma generic Collection ao invés de um Array, deste modo temos a facilidade de retirar e adicionar elementos sem a preocupação de estar permanentemente a redimensionar o Array.
17 public partial class CatalogItems
18 {
19 private Collection<CalalogItem> calalogItemField;
20
21 /// <summary>
22 /// Initializes a new instance of the <see cref="CatalogItems"/> class.
23 /// </summary>
24 public CatalogItems()
25 {
26 calalogItemField = new Collection<CalalogItem>();
27 }
28
29 /// <remarks/>
30 [XmlElement("CalalogItem")]
31 public Collection<CalalogItem> CalalogItem
32 {
33 get { return calalogItemField; }
34 set { calalogItemField = value; }
35 }
36
37 /// <summary>
38 /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
39 /// </summary>
40 /// <returns>
41 /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
42 /// </returns>
43 public override string ToString ()
44 {
45 return calalogItemField.Count.ToString();
46 }
47 }
Outro reparo a fazer, é o facto de todas as classes estarem num único ficheiro, mas isso não é nada que o ReSharper não possa resolver ;).
Há que ter também em atenção o seguinte, por defeito na classe Catalog a collection de Items (CatalogItems) está com o valor null, esta situação pode provocar algums problemas caso se queira utilizar os items sem que exista uma instancia da collection, o melhor é criar desde logo no construtir da classe Catalog a instancia dos items, deste modo é possível desde logo adicionar ou remover os mesmos. Outros elementos que também não estão instanciados são o Validity e o Factory, pelo que aqui o reparo é o mesmo, contudo a decisão de inicializar estes elementos depende do modo como o developer os vai tratar, ou da definição das regras de negócio.
38 public Catalog()
39 {
40 itemsField = new CatalogItems();
41 validityField = new Validity();
42 factoryField = "Sample";
43 }
Além da ferramenta standard da Framework existem no mercado uma série de outras ferramentas para este mesmo efeito, uma delas pode inclusive ser obtida a partir da Microsoft, o seu nome é XSDObjectGen, é uma versão melhorada da ferramenta standard, mas uma rápida perquisa no Google por “xsd schema generator” vai retornar bastantes ferramentas.
A class utilizada neste exemplo para a serialização permitem gravar uma instancica de um objecto para um ficheiro xml, e criar uma instancia desse objecto a partir de um ficheiro xml.
7 public static class FileSerializer
8 {
9 #region Public Methods
10
11 /// <summary>
12 /// Serializes the specified source.
13 /// </summary>
14 /// <param name="source">The source.</param>
15 /// <param name="fileName">Name of the file.</param>
16 public static void Serialize(object source, string fileName)
17 {
18 // Create the xml serializer, the serializer needs to know the type
19 // of the object that will be serialized
20 XmlSerializer xmlSerializer = new XmlSerializer(source.GetType());
21
22 // Create a XmlTextWriter to write the xml object source, we are going
23 // to define the encoding in the constructor
24 using (XmlTextWriter writer = new XmlTextWriter(fileName, Settings.Encoding))
25 {
26 // Save the state of the object into the stream
27 xmlSerializer.Serialize(writer, source);
28 // Flush the stream
29 writer.Flush();
30 }
31 }
32
33 /// <summary>
34 /// Deserializes the specified object string.
35 /// </summary>
36 /// <param name="fileName">Name of the file.</param>
37 /// <param name="type">Type of the object.</param>
38 /// <returns></returns>
39 public static object Deserialize(string fileName, Type type)
40 {
41 // The object to be returned
42 object source;
43
44 using (XmlTextReader reader = new XmlTextReader(fileName))
45 {
46 // Create the serializer, we must provide the object type to the constructor
47 XmlSerializer xmlSerializer = new XmlSerializer(type);
48 // Deserialize the object
49 source = xmlSerializer.Deserialize(reader);
50 }
51 // Return the new object
52 return source;
53 }
54
55 #endregion
56 }
O aspecto final é este:
De uma forma muito simples é possivel editar o conteúdo do objecto Catalog e dos seus items, gravar e abrir o ficheiro Xml resultante da serialização, ou seja podemos persistir o objecto para um ficheiro e recriar esse mesmo objecto a partir desse ficheiro gravado anteriormente. Esta técnica é particularmente útil para transferir um objecto, ou para por exemplo gravar o estado de um objecto depois de uma mensagem de erro, de modo a permitir a analise do seu conteúdo e o porquê desse erro, ao persistir o objecto para um ficheiro é ainda possível utilizar esse mesmo ficheiro num processo de integração recorrendo por exemplo ao Biztalk Server, mais uma vez a utilização da serialização depende apenas das necessidades e imaginação de cada um.