Cada vez mais o uso de arquivos XML se mostra necessário no nosso dia a dia. Projetos de integração de sistemas, construção de projetos multiplataformas e até projetos que disponibilizam serviços via Cloud Computing nos obrigam cada vez mais dominarmos as técnicas de construção de Web Services que lidam com arquivos do tipo XML.

Nesse contexto, as técnicas de Serialização se mostram muito úteis no nosso cotidiano, pois é o processo de transformar um objeto (geralmente uma instância de uma classe) em uma sequência de bytes, para persistir na memória, num banco de dados ou em um arquivo. O objetivo é salvar o estado de um objeto para poder recriá-lo quando necessário. Para recriar um objeto utilizamos a desserialização, que é o processo inverso ao de Serialização, ou seja, transformar uma sequência de bytes em um objeto, como mostra a Figura 1.

Processo de Serialização
Figura 1. Processo de Serialização

O objeto é serializado em um fluxo, que pode transportar não apenas dados, mas informações sobre o tipo do objeto, como seu nome, versão, cultura, e assembly. Esse fluxo pode ser armazenado em um banco de dados, em um arquivo, ou na memória.

A serialização permite que o desenvolvedor salve o estado de um objeto e o recrie conforme necessário, fornecendo o armazenamento dos objetos, bem como intercâmbio de dados. Através da serialização, um desenvolvedor pode executar ações como enviar o objeto para um aplicativo remoto por meio de um serviço da Web, passando um objeto de um domínio para outro,ou através de um firewall como uma sequência XML.

Neste artigo construiremos uma ferramenta que poderá serializar objetos para XML.

Vale ressaltar que o .NET Framework oferece muitas maneiras de implementarmos nossos códigos customizados de serialização/desserialização através do Namespace System.Runtime.Serialization. No nosso artigo utilizaremos outro Namespace – System.Xml.Serialization, que disponibiliza um objeto específico para serialização de objetos em XML. Caso o leitor tenha curiosidade em obter maiores informações o site da MSDN possui diversos materiais a respeito.

Agora que sabemos um pouco sobre Serialização, vamos iniciar a construção de nossa ferramenta.

No Visual Studio crie uma nova solução de nome Custom Serializer, como mostra a Figura 2.

Criação da solução
Figura 2.Criação da solução

Com a solução criada adicione um projeto do tipo Class Library de nome Utils e um projeto do tipo Console Application de nome TestProject, como vemos na Figura 3.

Criação do projeto
Figura 3. Criação do projeto

Vamos começar a codificação:inicialmente vamos construir uma classe que será utilizada para criar o objeto que iremos Serializar/Desserializar. No .NET é necessário indicar nas classes que construímos que elas podem ser serializadas. Essa indicação é feita através do uso do atributo Serializable, que fica no Namespace System. Esse atributo deverá indicar ao Runtime que a classe pode ser serializada.

No projeto TestProject crie uma nova classe de nome Cliente, como demonstra a Listagem 1.


using System;
namespace TestProject
{
    [System.Serializable]
    publicclass Cliente
    {
        private int _id;
        private string_nomeCompleto;
        private int _cpf;
        private string_email;
        private string_senha;
 
        public int Id
        {
            get { return _id; }
            set { _id = value; }
        }
        public stringNomeCompleto
        {
            get { return_nomeCompleto; }
            set { _nomeCompleto = value;}
        }
        public int Cpf
        {
            get { return _cpf; }
            set { _cpf = value; }
        }
        public string Email
        {
            get { return _email;}
            set { _email = value;}
        }
        public string Senha
        {
            get { return _senha;}
            set { _senha = value; }
        }
 
    }
}

Listagem 1. Classe Cliente

Repare que utilizamos o atributo System.Serializable acima da declaração da classe. No mais a classe é bastante simples, contendo apenas campos privados e propriedades.

Agora iniciaremos a codificação de nossa ferramenta. No projeto Utils crie uma nova classe de nome GenericSerializer.cs. A ideia é ter uma ferramenta que serialize qualquer tipo de objeto criado, ou seja, aqueles que serão instâncias de nossas classes. Seguindo esse pensamento, a classe GenericSerializer.cs deverá ser capaz de ler em tempo de execução e até em tempo de design, qualquer tipo de objeto que definirmos. Para isso utilizamos os chamados Parameters Type, que são usados em definições de classes genéricas. Na Listagem 2 segue a definição de nossa classe.


public class GenericSerializer<T>
Listagem 2. Classe GenericSerializer.cs

Quando definimos uma classe genérica podemos aplicar restrições (Constraint) aos tipos que a classe receberá como argumento, como mostra a Listagem 3.


public class GenericSerializer<T> where T : new()
Listagem 3. Restrições ao tipo

Na Listagem 4 restringimos o uso de nossa classe para instâncias de classes que possuem um construtor público vazio, ou seja, sem parâmetros.


public Cliente() 
{ 

}
Listagem 4. Restrição ao construtor public vazio

Quando um código cliente tenta usar a classe genérica usando um tipo que não é permitido pela Constraint definida, o resultado é um erro em tempo de compilação.

Na Tabela 1 encontramos todos os tipos de Constraints que podemos utilizar na construção de classes genéricas.

Constraint Descrição
where T: struct O “Type Argument” precisa ser um “Value Type”. Qualquer “Value Type” pode ser especificado, exceto o tipo “Nullable”..
where T : class O “type argument” precisa ser um “reference type”- Classe, interface, delegate, ou array type.
where T : new() O “type argument” precisa ter um construtor vazio público. Quando usado com outras Constraints, essa constraint precisa ser declarada por ultimo.
where T : O “type argument” precisa ser uma derivação de uma Base Class especificada.
where T : O “type argument” precisa ser uma Interface ou implementar uma. Múltiplas “interface constraints” podem ser especificadas. Essa Constraint pode também ser genérica.
where T : U O “type argument” fornecido para “T”, precisa ser ou derivar o argumento fornecido por “U”.
Tabela 1. Tipos de Constraints

Agora que aprendemos um pouco sobre Type Parameters, vamos definir nossa classe genérica com o código da Listagem 5.


public class CustomSerializer<T> here T : new()
{

}
Listagem 5. Classe genérica

O método que codificaremos agora será o responsável por serializar nossos objetos, ou seja, persistir um objeto numa string representando um XML. O objeto principal deste método é o XmlSerializer, pois ele contém o método de serialização pronto para usar. Como já definimos o Type Parameter que a classe receberá, o método em questão apenas receberá como parâmetro um objeto do tipo definido na declaração da classe. Na Listagem 6 segue o código completo do método.


public string GenSerializer(T Obj)
{
    XmlSerializer customSerializer = null;
    MemoryStream ms1 = null;
    XmlTextWriter xmlWriter = null;
    XmlDocument xmlDoc = null;
    XmlNodeList emptyNodes = null;
    MemoryStream ms2 = null;
    string ret = "";
    try
    {
        //definição do tipo de objeto que o XmlSerializer trabalhará.
        customSerializer = new XmlSerializer(typeof(T));

        //instanciando um MemoryStrem que receberá o resultado da serialização
        using (ms1 = new MemoryStream())
        {
            xmlWriter = new XmlTextWriter(ms1, Encoding.UTF8);
            customSerializer.Serialize(xmlWriter, Obj);

            //instanciando um outro MemoryStream que será utilizado para criar
            // em tempo de execução um XmlDocument
            using (ms2 = (MemoryStream)xmlWriter.BaseStream)
            {
                ms2.Position = 0;
                xmlDoc = new XmlDocument();
                xmlDoc.Load(ms2);

                //Verifica se houve algum erro na serialização que possa fazer  que o 
                XmlDocument esteja nulo ou vazio
                if (xmlDoc != null && xmlDoc.HasChildNodes)
                {
                    //Esse bloco de comando remove nodes vazios do XmlDocument
                    emptyNodes = xmlDoc.SelectNodes(@"//*[not(node())]");
                    if (emptyNodes.Count > 0)
                    {
                        for (int i = emptyNodes.Count - 1; i >= 0; i--)
                        {
                            emptyNodes[i].ParentNode.RemoveChild(emptyNodes[i]);
                        }
                    }

        //bloco que retira alguns nodes indesejados
        ret = xmlDoc.InnerXml;
        ret = ret.Replace(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance        "", "").Trim();
        ret = ret.Replace(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema        "", "").Trim();
        ret = ret.Replace("<?xml version=\"1.0\" encoding=\"utf-8\"?
        >", "").Trim();


                }
            }
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        customSerializer = null;
        ms1 = null;
        xmlWriter = null;
        xmlDoc = null;
        emptyNodes = null;
        ms2 = null;
    }
    return ret;
}
Listagem 6. Método XmlSerializer

O resultado da serialização será uma string representando um XML. Portanto o retorno do método é uma string. Inicialmente instanciamos um objeto XmlSerializer passando o Parameter Type definido na declaração da classe, depois utilizamos um XmlTextWriter e um MemoryStream, para conseguir utilizar o Método Serialize do XmlSerializer. Posteriormente criamos outro MemoryStrem para adicionarmos na memória um XmlDocument que receberá o resultado da serialização. Depois do XmlDocument preenchido, capturamos a propriedade InnerXml, que retorna uma string representando o XML. Para finalizar o método excluímos nodes vazios do XML (se houver) assim como outros nodes que não necessitamos.

Vale ressaltar que todo MemoryStream criado precisa ser fechado. Por isso utilizamos o bloco Using que fecha automaticamente o MemoryStream, quando o runtime chega no final do bloco Using.

Desserialização

Agora iniciaremos a codificação do método de desserialização, ou seja, o método que transforma um XML em um objeto.

O método receberá uma string XML e criará um objeto à partir dela. Ele, assim como o método de serialização, utilizará um XmlSerializer, que é um objeto que encapsula funcionalidades de serialização e deserialização. Porém o resultado dessa desserialização do XmlSerializer é um objeto do tipo object, ou seja, não é um tipo definido. Mas como estamos utilizando uma classe genérica, que recebe um Type Parameter, podemos fazer com que esse método retorne um tipo definido – T, como mostra a Listagem 7.


public T GenDeserializer(string pXML)
        {
            UTF8Encoding encoding = null;
            XmlDocument xmlDoc = null;
            MemoryStream ms1 = null;
            XmlSerializer customSerializer = null;
            T Obj = default(T);
            try
            {
                xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(pXML);

                encoding = new UTF8Encoding();
                using (ms1 = new MemoryStream(encoding.GetBytes(xmlDoc.InnerXml)))
                {
                    ms1.Position = 0;
                    customSerializer = new XmlSerializer(typeof(T));
                    Obj = (T)customSerializer.Deserialize(ms1);
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                encoding = null;
                xmlDoc = null;
                ms1 = null;
                customSerializer = null;
            }
            return Obj;
        }
Listagem 7. Método T

O método recebe uma string representando um XML e carrega um XmlDocument na memória de acordo com a string recebida. Uma vez carregado o XmlDocument, o transformamos em bytes utilizando um MemoryStream. Com o MemoryStream, podemos chamar o método de desserialização. Como dissemos o método de desserialização do XmlSerializer, retorna um object, portanto precisamos fazer um parse para converter esse object para o tipo que queremos – T.

É isso, terminamos a codificação de nossa classe genérica para Serialização e Desserialização de objetos. Vamos passar agora para os testes de nossa ferramenta.

Testes da aplicação

No projeto TestProject que é um Console Application, vamos codificar a classe Program.cs, que já criada por default.

Primeiramente adicione ao TestProject uma referência ao projeto Utils. Dentro do método main vamos instanciar um objeto do tipo Cliente e preencher suas propriedades, como mostra a Listagem 8.


  static void Main(string[] args)
  {
            Cliente cliente = new Cliente();
            cliente.Id = 10;
            cliente.NomeCompleto = "Cliente Teste";
            cliente.Cpf = 500986026;            
            cliente.Email = "emailcliente@cliente.com";
            cliente.Senha = "senha123";

   }
Listagem 8. Método main

Agora vamos criar uma instância da classe GenericSerializer com o código da Listagem 9. Lembrando que ao instanciar devemos passar um tipo como Type Parameter. Como vamos trabalhar com a classe Cliente, consequentemente esse tipo será Cliente.


GenericSerializer<Cliente> customSer = new     GenericSerializer<Cliente>();
Listagem 9. Instância da classe GenericSerializer

Agora vamos testar o método de serialização. Instancie uma string de nome xml que receberá o resultado da serialização, como mostra a Listagem 10.


string xml = customSer.GenSerializer(cliente);
Listagem 10.Instância de XML

O leitor vai reparar que em tempo de design o intelisense do Visual Studio indica o tipo de objeto esperado pelo método GenSerializer, devido ao fato de na instanciação da classe GenericSerializer definimos que o Type Parameter será do tipo Cliente, como mostra a Figura 4.

GenericSerializer
Figura 4. Classe GenericSerializer

Agora vamos colocar na tela o conteúdo da serialização e testar o método de desserialização, como mostra a Listagem 11.


Console.WriteLine(xml);
Console.ReadKey();
Console.Clear();
Cliente clienteX = customSer.GenDeserializer(xml);
Console.WriteLine(clienteX.NomeCompleto + " " + clienteX.Id);
Console.ReadKey();
Listagem 11. Método de desserialização

Vamos agora rodar o programa e verificar na tela o resultado, como mostra a Figura 5.

Resultado da serialização
Figura 5. Resultado da serialização

Como podemos ver, a serialização foi concluída com sucesso, sendo que a string XML foi gerada corretamente. A estrutura do XML é a apresentada na Listagem 12.


<Cliente>
<Id>10</Id>
<NomeCompleto>Cliente  Teste</NomeCompleto>
<Cpf>500986026</Cpf>
<Email>emailcliente@cliente.com</Email>
<Senha>senha123</Senha>
</Cliente>
Listagem 12. Estrutura do XML
Resultado da Desserialização
Figura 6. Resultado da Desserialização

Vemos também que o sistema realizou a desserialização corretamente exibindo a propriedade NomeCompleto, do objeto clienteX corretamente, como vimos na Figura 6. Seria interessante o leitor debugar a aplicação de teste e verificar o valor da string xml e do objeto clienteX, para verificar o correto funcionamento de nossa ferramenta.

Para o leitor que queira se aprofundar um pouco mais sobre o objeto XmlSerializer, deixo uma dica: deem uma olhada nos atributos contidos no Namespace System.Xml.Serialization.