ASP .NET - Manutenção de dados em arquivos XML


Olhando para o passado, 5 ou 7 anos atrás, podemos ver como as ferramentas para desenvolvimento de software evoluíram. Basta comparar o tempo que era gasto para realizar uma tarefa simples como tratar arquivos XML com o tempo que se gasta hoje para se ter uma idéia; citei o exemplo dos arquivos XML porque eles serão o foco deste artigo.

Particularmente a plataforma .NET evoluiu muito e atualmente apresenta uma nova suíte de APIs para XML construídas com base nos padrões DOM, XPath, XSD e XSLT. As classes XML da plataforma .NET oferecem um desempenho muito bom e possuem um modelo de programação bem familiar que estão vinculadas às APIs de acesso a dados ADO .NET.

As classes XmlWriter, XmlReader e XmlNavigator e as classes que derivam dela, incluindo XMLTextReader e XMLTextWriter encapsulam um número de funcionalidades que permitem um grande aumento de desempenho e produtividade substituindo tarefas que antes tinham que ser construídas manualmente e com muito trabalho.

Neste artigo eu vou mostrar como gerenciar arquivos XML realizando operações de seleção, inclusão, alteração e exclusão de informações em arquivos XML usando as novas funcionalidades da plataforma .NET com a linguagem C#.

Para acompanhar o exemplo deste artigo você pode usar o Visual Web Developer 2008 Express Edition ou o Visual Studio 2008.

Definição de requisitos, leiaute e configuração

Vou iniciar apresentando o leiaute do formulário web principal do nosso web site representado pela página Default.aspx:

Figura 1.0 - leiaute da página Default.aspx

Temos aqui um formulário web simples contendo os seguintes controles:

O GridView apresenta as seguintes definições para os campos: Autor, Titulo e Conteudo;

Figura 2.0 - Definições para os campos gerenciados

O objetivo é gerenciar informações sobre poesias como autor, título e conteúdo permitindo a seleção, inclusão, alteração e exclusão de informações.

Nota: No exemplo eu estou tratando poemas de autoria do famoso poeta Fernando Pessoa.

Efetuar tal tarefa usando um banco de dados com os recursos do ASP .NET é uma tarefa simples e você têm a sua disposição uma suíte de ferramentas e opções como ADO .NET, SQLDataSource, ObjectDataSource, LINQ , etc. que lhe permitem tratar o caso de diversas formas.

A novidade é que vamos gerenciar estas informações a partir de um arquivo xml chamado poemas.xml que esta na pasta App_Data e que contém a seguinte estrutura:

<?xml version="1.0" encoding="utf-8"?>
<poetas>
  <poemas>
    <autor></autor>
    <titulo></titulo>
    <conteudo></conteudo>
  </poemas>
  <poemas>
</poetas>			

Vamos então mostrar como usar os recursos da plataforma .NET para efetuar a manutenção das informações do arquivo poemas.xml realizando operações de seleção, inclusão, alteração e exclusão de dados.

Vamos iniciar abrindo o VWD 2008 Express e criando um novo web site a partir do menu File->New web site;

Em Templates selecione ASP .NET Web Site, escolha a linguagem C# e informe o nome manuXML para o web site clicando em OK;

Agora vamos criar a pasta App_Data em nosso projeto clicando com o botão direito do mouse sobre o nome do web site e selecionando a Add ASP .NET Folder => App_Data;

Em seguida vamos criar o arquivo xml poemas.xml na pasta App_Data clicando com o botão direito sobre a pasta e selecionando Add New Item;

A seguir informe o nome poemas.xml e clique em Add;

Defina a estrutura do arquivo conforme já mostramos

<?xml version="1.0" encoding="utf-8"?>
<poetas>
  <poemas>
    <autor></autor>
    <titulo></titulo>
    <conteudo></conteudo>
  </poemas>
  <poemas>
</poetas>

Agora vamos criar o leiaute do formulário web conforme o leiaute e definições da figura 1.0 (Figura 1.0 - leiaute da página Default.aspx) e figura 2.0 mostradas anteriormente.

Já temos tudo pronto: o leiaute e as definições da página Default.aspx e o arquivo poemas.xml.

Gerenciando informações em arquivos XML

Vamos iniciar definindo o código do evento Load da página Default.aspx que deverá carregar os dados do arquivo poemas.xml e preencher o GridView  conforme a figura abaixo:

Nota:  Especifique a diretiva using no espaço para nome System.Xml para que não seja necessário qualificar declarações código:  using System.Xml;

Carregando dados

Temos abaixo o código do evento Load:

  protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            carregaDadosXML();
            this.Session["Incluir"] = 0;
        }
    }

Eu defini uma variável de sessão Incluir e atribui um valor inicial igual a 0. Essa variável irá controlar a inclusão de dados. Veremos abaixo o porque desta definição.

A rotina carregaDadosXML() é quem vai acessar o arquivo poemas.xml e carregar o GridView, vejamos o seu código abaixo:

    /// <summary>
    /// carrega os dados xml
    /// </summary>
    private void carregaDadosXML()
    {
        DataSet ds = new DataSet();
        ds.ReadXml(Server.MapPath(@"App_Data\poemas.xml"));
        if (ds.Tables.Count > 0)
        {
            this.GridView1.DataSource = ds;
            this.GridView1.DataBind();
        }
    }

Como pretendemos gerenciar os dados do arquivo xml devemos criar uma estrutura em memória com as informações do arquivo poemas.xml. Fazemos isso criando um objeto DataSet e em seguida usando o método ReadXML() que para ler o conteúdo do arquivo na pasta App_Data.

Nota:  O método ReadXML possui diversas sobrecargas. Veja detalhes em: http://msdn.microsoft.com/pt-br/library/system.data.dataset.readxml.aspx

Poderíamos criar uma instância de um objeto XmlTextReader e preenchê-lo com o arquivo XML. Normalmente, a classe XmlTextReader é usada quando é necessário acessar o XML como dados brutos, sem a sobrecarga de um DOM. Dessa maneira, a classe XmlTextReader fornece um mecanismo mais rápido para leitura de XML.

 A classe XmlTextReader possui construtores diferentes para especificar a localização dos dados XML. O código a seguir cria uma instância da classe XmlTextReader e carrega o arquivo poemas.xml.

XmlTextReader reader = new XmlTextReader ("App_Data\poemas.xml");

Após criar o objeto XmlTextReader, use o método Read para ler os dados XML. O método Read continua se movendo pelo arquivo XML seqüencialmente até atingir o final do arquivo, onde o método Read retornará um valor "False."

while (reader.Read())
{
    Console.WriteLine(reader.Name);
}
Console.ReadLine();

Em seguida verificamos e o dataset foi preenchido e exibimos o resultado no GridView.

Limpando o formulário

Vamos mostrar agora o código associado ao evento Click do botão Limpar:

  protected void btnLimpar_Click(object sender, EventArgs e)
    {
        this.TextBox1.Text = "";
        this.TextBox2.Text = "";
        this.TextBox3.Text = "";
        this.TextBox1.Text = "NOVO";
        this.TextBox1.Focus();
        Session["Incluir"] = 1;
    }

Este código apenas limpa o conteúdo das caixas de texto do formulário web. Lembre-se que eu defini uma variável de sessão chamada Incluir no evento Load e atribui um valor inicial zero. Ao limpar o conteúdo das caixas de texto eu estou alterando o seu valor para 1. Dessa forma eu posso controlar a inclusão da seguinte forma. O usuário somente poderá incluir valores após limpar as caixas de texto ou seja quando o valor da variável de sessão Incluir for igual a 1.

Se o usuário tentar incluir uma informação irá receber uma mensagem solicitando que os campos sejam limpos.

Incluindo dados

Vejamos então o código do evento Click do botão Incluir:

protected void btnIncluir_Click(object sender, EventArgs e)
    {
        if ((int)Session["Incluir"] == 1)
        {
            if (TextBox1.Text.Equals("") || TextBox2.Text.Equals("") || TextBox3.Text.Equals(""))
            {
                this.RegisterClientScriptBlock("alertmessage", "<script>alert('Preencha os campos do formulário.')</script>");
            }
            else
            {
                //define um documento XML e carrega o seu conteúdo 
                XmlDocument xmldoc = new XmlDocument();
                xmldoc.Load(Server.MapPath(@"App_Data\poemas.xml"));

                //Cria um novo elemento poemas  e define os elementos autor, titulo e conteudo
                XmlElement novoelemento = xmldoc.CreateElement("poemas");
                XmlElement xmlAutor = xmldoc.CreateElement("autor");
                XmlElement xmlTitulo = xmldoc.CreateElement("titulo");
                XmlElement xmlConteudo = xmldoc.CreateElement("conteudo");
                //atribui o conteúdo das caixa de texto aos elementos xml
                xmlAutor.InnerText = this.TextBox1.Text.Trim();
                xmlTitulo.InnerText = this.TextBox2.Text.Trim();
                xmlConteudo.InnerText = this.TextBox3.Text.Trim();
                //inclui os novos elementos no elemento poemas
                novoelemento.AppendChild(xmlAutor);
                novoelemento.AppendChild(xmlTitulo);
                novoelemento.AppendChild(xmlConteudo);
                //inclui o novo elemento no XML
                xmldoc.DocumentElement.AppendChild(novoelemento);
	//Salva a inclusão no arquivo XML
                xmldoc.Save(Server.MapPath(@"App_Data\poemas.xml"));
                this.Session["Incluir"] = 0;
                //exibe os dados no GridView
                carregaDadosXML();
            }
        }
        else
        {
            this.RegisterClientScriptBlock("alertmessage", "<script>alert('Limpe os campos do formulário para incluir um novo item.')</script>");
        }
    }

Primeiro verificamos se a nossa variável de sessão Incluir tem o valor 1. Se o valor for diferente exibiremos a mensagem : 'Limpe os campos do formulário para incluir um novo item'.

this.RegisterClientScriptBlock("alertmessage", "<script>alert('Limpe os campos do formulário para incluir um novo item.')</script>");

Estamos fazendo isso usando o método RegisterClientScriptBlock que permite que você coloque uma função JavaScript no topo da página.

A partir da ASP.NET 2.0 usa a nova propriedade Page.ClientScript para registrar e colocar funções JavaScript em suas páginas ASP.NET, os métodos Page.RegisterStartUpScript e Page.RegisterClientScriptBloc da versão 1.0/1.1 da plataforma .NET podem ser considerados obsoletos pois agora você tem que fornecer o conjunto de parâmetros chave/script para registrar scripts do lado do cliente.

Para detalhes veja o meu artigo:   ASP.NET - Messagebox

Em seguida usamos o mesmo recurso para exibir uma mensagem ao usuário caso as caixas de textos não forem preenchidas.

this.RegisterClientScriptBlock("alertmessage", "<script>alert('Preencha os campos do formulário.')</script>");

Satisfeitas estes critérios começamos o processo de inclusão de uma nova informação no arquivo poemas.xml.

Estamos criando uma instância da classe XmlDocument que representa o documento XML e contém um método Load para carregar o documento a partir de um arquivo, fluxo ou XmlReader.

                XmlDocument xmldoc = new XmlDocument();
                xmldoc.Load(Server.MapPath(@"App_Data\poemas.xml"));


A seguir criamos novos elementos usando o método CreateElement da classe XmlDocument.

                //Cria um novo nó poemas e define os elementos autor, titulo e conteudo para este nó
                XmlElement novoelemento = xmldoc.CreateElement("poemas");
                XmlElement xmlAutor = xmldoc.CreateElement("autor");
                XmlElement xmlTitulo = xmldoc.CreateElement("titulo");
                XmlElement xmlConteudo = xmldoc.CreateElement("conteudo")

Embora esse método cria o novo objeto no contexto do documento, ele automaticamente não adicionar o novo objeto à árvore de documento. Para adicionar o novo objeto, você deve explicitamente chamar um dos métodos inserir do nó.

A seguir estamos atribuindo os valores informados na caixa de texto aos elementos criados usando a propriedade InnerText que obtêm ou define valores concatenados do nó e todos os seus nós filhos.

                //atribui o conteúdo das caixa de texto aos elementos criados
                xmlAutor.InnerText = this.TextBox1.Text.Trim();
                xmlTitulo.InnerText = this.TextBox2.Text.Trim();
                xmlConteudo.InnerText = this.TextBox3.Text.Trim();

Agora incluímos os elementos criados ao nó poemas usando o método AppendChild que inclui um novo nó ao fim da lista de nó filhos para o nó atual.

               //inclui os elementos no novo nó
                novoelemento.AppendChild(xmlAutor);
                novoelemento.AppendChild(xmlTitulo);
                novoelemento.AppendChild(xmlConteudo);

Incluímos então o novo nó poemas ao nó raiz do documento XML.

                //inclui o novo elemento no XML
                xmldoc.DocumentElement.AppendChild(novoelemento);

Finalmente salvamos o documento XML para o arquivo especificado e exibimos os dados no GridView.

//Salva a inclusão no arquivo XML
                xmldoc.Save(Server.MapPath(@"App_Data\poemas.xml"));
                this.Session["Incluir"] = 0;
                //exibe os dados no GridView
                carregaDadosXML();

Alterando dados

Veremos agora o código do evento Click do botão Alterar:

   protected void btnAlterar_Click(object sender, EventArgs e)
    {
        if (selectIndex == -1)
        {
            this.RegisterClientScriptBlock("alertmessage", "<script>alert('Selecione um item para alteração.')</script>");
        }
        else
        {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(Server.MapPath(@"App_Data\poemas.xml"));
            
            XmlNode xmlnode = xmldoc.DocumentElement.ChildNodes.Item(selectIndex);
            xmlnode["autor"].InnerText = this.TextBox1.Text.Trim();
            xmlnode["titulo"].InnerText = this.TextBox2.Text.Trim();
            xmlnode["conteudo"].InnerText = this.TextBox3.Text.Trim();
            xmldoc.Save(Server.MapPath(@"App_Data\poemas.xml"));
            carregaDadosXML();
        }
    }

Aqui destacamos o uso da propriedade DocumentElement que obtêm a raiz XmlElement para o documento; e a propriedade ChildNodes que contêm uma lista de nós de todos os filhos os elementos.

Estamos obtendo os valores para os elementos do índice indicado que é capturado a partir da seleção de uma linha  do controle GridView.

Para obter o valor do índice definimos a propriedade selectIndex conforme o código abaixo:

 internal int selectIndex
    {
        get
        {
            if (this.Session["Page_selectIndex"] == null)
                return -1;
                return Int32.Parse(this.Session["Page_selectIndex"].ToString());
        }
        set
        {
            this.Session["Page_selectIndex"] = value;
        }
    }

A palavra-chave Internal é um modificador de acesso para tipos de membros que são acessíveis somente no mesmo assembly. Em C# as classes que não possuírem uma modificador de acesso, são por default internal.

Com isso estamos obtendo o valor do índice selecionado a partir do controle GridView ou retornando o valor -1 e atribuindo o valor a partir da sessão.

Após obter os valores das caixas de texto salvamos o XML para o documento poemas.xml:

  xmldoc.Save(Server.MapPath(@"App_Data\poemas.xml"));

Para obter o índice atual do GridView quando o usuário selecionar uma linha temos que definir o código abaixo no evento SelectedIndexChanged:

  protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
    {
        selectIndex = this.GridView1.SelectedIndex;
        procuraDadosXml(selectIndex);
        this.Session["Incluir"] = 0;
    }

Note que chamamos a rotina procuraDadosXml(indice) passando o índice selecionado. É esta rotina que irá buscar os dados no documento poemas.xml exibindo-os nas caixas de texto do formulário. Veja o código da rotina:

  private void procuraDadosXml(int selectedIndex)
    {
        XmlDocument xmldoc = new XmlDocument();
        xmldoc.Load(Server.MapPath(@"App_Data\poemas.xml"));

        XmlNodeList xmlnodelist = xmldoc.DocumentElement.ChildNodes;       
        XmlNode xmlnode = xmlnodelist.Item(selectedIndex);

        this.TextBox1.Text = xmlnode["autor"].InnerText;
        this.TextBox2.Text = xmlnode["titulo"].InnerText;
        this.TextBox3.Text = xmlnode["conteudo"].InnerText;
    }

A classe XmlNodeList representa uma coleção ordenada de nós. Estamos obtendo a coleção de nós do documento XML e em seguida selecionando o nó referente ao índice selecionado.

Quando o usuário clica no botão Alterar os novos valores são salvos para o arquivo XML:

 xmldoc.Save(Server.MapPath(@"App_Data\poemas.xml"));

Excluindo dados

O código associado ao evento Click do botão Excluir é dado a seguir:

   protected void btnExcluir_Click(object sender, EventArgs e)
    {
        if (selectIndex == -1)
        {
           this.RegisterClientScriptBlock("alertmessage", "<script>alert('Selecione um item para excluir.')</script>");
        }
        else
        {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(Server.MapPath(@"App_Data\poemas.xml"));

            XmlNode xmlnode = xmldoc.DocumentElement.ChildNodes.Item(selectIndex);
            xmlnode.ParentNode.RemoveChild(xmlnode);

            xmldoc.Save(Server.MapPath(@"App_Data\poemas.xml"));
            carregaDadosXML();
            this.TextBox1.Text = "";
            this.TextBox2.Text = "";
            this.TextBox3.Text = "";
        }
    }

 

Para excluir os dados de um nó XML usamos o método RemoveChild() passando o nó desejado.

Dessa forma temos a implementação de todas as rotinas necessárias para gerenciar as informações de um arquivo XML. Talvez a interface pudesse ser melhorada e um tratamento de erros decente pudesse ser implementado. Tarefas que deixo a seu cargo para exercitar o aprendizado.

Veja abaixo a figura do web site em execução e a estrutura do arquivo poemas.xml após algumas inclusões:

<?xml version="1.0" encoding="utf-8"?>
<poetas>
  <poemas>
    <autor>Fernando Pessoa</autor>
    <titulo>Tabacaria</titulo>
    <conteudo>Não sou nada.
Nunca serei nada.
Não posso querer ser nada.
À parte isso, tenho em mim todos os sonhos do mundo.</conteudo>
  </poemas>
  <poemas>
    <autor>Fernando Pessoa</autor>
    <titulo>poesias coligidas</titulo>
    <conteudo>Eu amo tudo o que foi,
Tudo o que já não é,
A dor que já me não dói,
A antiga e errônea fé,
O ontem que dor deixou,
O que deixou alegria
Só porque foi, e voou
E hoje é já outro dia.</conteudo>
  </poemas>
</poetas>

 

Pegue o projeto completo aqui : manuXML.zip

Eu sei é apenas ASP .NET, mas eu gosto...

Referências:


José Carlos Macoratti