Clique aqui para ler este artigo em pdf imagem_pdf.jpg

msdn03_capa.jpg

Clique aqui para ler todos os artigos desta edição

 

F.A.Q.

Como resolver o uso de nome de classes idênticos em Namespaces XML, XmlNodeList, XMLReader e muito mais

por Aaron Skonnard

 

 

 

P Estou desenvolvendo um Web Service público que fornecerá uma interface única para todos os sistemas internos da empresa. Como resultado, criei vários Web Services intermediários que, por sua vez, estão ligados ao Web Service público. Quando uso o mesmo nome de classe em qualquer um desses Web Services, recebo uma mensagem de erro ao buscar o arquivo WSDL, embora o código seja compilado sem problemas. Como resolvo isso?

 

R O problema que você está enfrentando diz respeito a um choque de namespaces XML. Quando você trabalha com Web Services ASP.NET, precisa ter em mente que existem dois tipos de namespaces em jogo. O primeiro tipo é um namespace do Microsoft® .NET Framework — usado no C#, no Visual Basic® .NET e em outras linguagens .NET para fazer a distinção entre os tipos no momento da compilação. O segundo tipo é um namespace XML — usado em documentos XML para fazer a distinção entre tipos XML, elementos e atributos durante o processamento de documentos XML.

Quando você escreve uma classe Web Service em ASP.NET, usa sempre os dois tipos de namespace. Assim, é importante compreender a diferença entre os dois e saber como é feito o mapeamento entre eles. Considere a seguinte classe: 

 

namespace Foo

{

   public class MyService : WebService {

   ... // WebMethods go here

}

 

O namespace do Framework denomina-se Foo e contém uma classe única chamada MyService, que por sua vez conterá alguns WebMethods. Como eu não especifiquei explicitamente nenhum namespace XML, a infra-estrutura usará automaticamente http://tempuri.org como o namespace XML do serviço. Definitivamente, você não quer usar esse namespace, e sim um namespace exclusivo, de sua própria escolha. Portanto, quando você for para a página de documentação .asmx, verá uma mensagem de aviso a esse respeito, juntamente com uma descrição útil sobre como corrigir o problema.

O namespace XML da classe Web Service deve ser atribuído explicitamente através do atributo [WebService], conforme ilustrado a seguir:

 

namespace Foo

{

   [WebService(Namespace="urn:example-org:myservice")]

   public class MyService : WebService {

      •••

   }

}

Se você compilar o projeto e retornar à página de documentação .asmx, o aviso não será mais mostrado, pois o projeto assumirá que você agora sabe o que está fazendo. Todas as classes que você usar dentro dessa classe serão então associadas a esse namespace XML, independentemente de seus namespaces originais no .NET Framework, a menos que você atribua explicitamente um namespace XML diferente para elas. Por exemplo, considere a Listagem 1, que contém algumas novas definições de classe usadas na classe MyService.

Listagem 1 –Classe MyService

namespace Foo

{

   [WebService(Namespace="urn:example-org:myservice")]

   public class MyService : WebService {

      [WebMethod]

      public Bar.MyClass GetBarMyClass() {

         return new Bar.MyClass();

      }

      [WebMethod]

      public Baz.MyClass GetBazMyClass() {

         return new Baz.MyClass();

      }

}

 

namespace Bar {

    public class MyClass {

       •••

    }

}

 

namespace Baz {

    public class MyClass {

       •••

    }

}

Como os dois tipos MyClass são usados na classe MyService, eles são automaticamente incluídos no namespace XML do serviço, neste caso urn:example-org:myservice. E, como os nomes de classe também são os mesmos neste caso (MyClass), ocorre uma duplicação no namespace XML. Como isso é ilegal no XML Schema, a infra-estrutura .asmx gera uma exception quando detecta esse tipo de situação em tempo de execução. Entretanto, o código é compilado sem problemas, como você verificou, pois os dois tipos MyClass estão localizados em namespaces diferentes no C#.

O compilador não conhece as semânticas do WSDL nem o XML Schema adicional que trazem esse problema à tona. Entretanto, a infra-estrutura .asmx é esperta o suficiente para detectar essas questões na primeira vez em que executa o reflection na classe.

 

 Por exemplo, se você for para a página de documentação .asmx, verá a mensagem de erro descrita a seguir, descrevendo o problema:

 

Types Bar.MyClass and Baz.MyClass both use the XML type name, MyClass,

from namespace urn:example-org:myservice.

 

Essa mensagem de erro também explica que você precisa efetuar mapeamentos adicionais do Framework ao XML para resolver o problema:

 

Use XML attributes to specify a unique XML name

and/or namespace for the type.

 

Em outras palavras, se você realmente precisar usar essas duas classes diferentes que, coincidentemente, têm o mesmo nome, mas significam coisas diferentes, precisará colocá-las em diferentes namespaces XML. É possível executar essa tarefa usando os atributos [XmlType] e [XmlRoot], conforme ilustra a Listagem 2.

Listagem 2 – Atributos XmlType e XmlRoot

namespace Bar {

    [XmlType(Namespace="urn:bar")]

    [XmlRoot(Namespace="urn:bar")]

    public class MyClass {

       •••

    }

}

 

namespace Baz {

    [XmlType(Namespace="urn:baz")]

    [XmlRoot(Namespace="urn:baz")]

    public class MyClass {

       •••

    }

}

O atributo [XmlType] controla o nome do namespace e o nome do tipo de XML Schema, enquanto o atributo [XmlRoot] controla o nome da declaração do elemento raiz mapeado para cada tipo. As várias declarações de atributo que mostrei colocam cada tipo MyClass em um namespace XML diferente, evitando assim a colisão de nome que causou o problema. Agora, se você for para a página de documentação .asmx, tudo funcionará adequadamente e a documentação será exibida.

Nos casos em que duas classes realmente representam a mesma coisa, convém eliminar uma delas e ter apenas uma classe à qual fazer referência a partir de diferentes serviços no seu sistema. Nesse caso, você ainda deveria especificar o namespace XML do tipo, usando os atributos [XmlType] e [XmlRoot], para que ele não seja automaticamente incorporado no namespace XML do serviço no qual for usado. Esse procedimento permite manter a identidade do tipo em documentos XML.

 

P Estou trabalhando em um Web Service e gostaria de retornar um conjunto de nós a partir de um de seus métodos. Originalmente, eu pretendia retornar apenas um XmlNodeList, mas ele não se parece com um XML serializável. Qual é a melhor maneira de se fazer isso?

 

R O XmlNodeList não possui um método Add, o que torna impossível para o XmlSerializer desserializar uma instância dele. No entanto, o XmlSerializer pode serializar objetos XmlNode. Ele também pode serializar arrays de qualquer tipo serializável. Como XmlNodeList é basicamente apenas um array de objetos XmlNode, não é difícil imaginar como você poderia fazer isso através de algum código que traduza objetos XmlNodeList em objetos de array XmlNode. Por exemplo, observe o WebMethod, que retorna um array de XmlNodes para o cliente:

 

[WebMethod]

public XmlNode[] GetNodes()

{

    XmlDocument doc = new XmlDocument();

    XmlNodeList nl = doc.SelectNodes("//*");

    XmlNode[] nodes = new XmlNode[nl.Count];

    for (int i=0; i<nl.Count; i++)

        nodes[i] = nl[i];

    return nodes;

}

 

Seria bastante fácil generalizar esse método em uma classe XmlNodeArray, que efetua automaticamente a tradução enquanto é construída:

 

public class XmlNodeArray

{

    public XmlNode[] nodes;

    public XmlNodeArray() {}

    public XmlNodeArray(XmlNodeList nl)

    {

        nodes = new XmlNode[nl.Count];

        for (int i=0; i<nl.Count; i++)

            nodes[i] = nl[i];           

    }

}

 

Em seguida, você pode retornar qualquer objeto XmlNodeList simplesmente empacotando-o com um novo XmlNodeArray, conforme é mostrado a seguir:

 

[WebMethod]

public XmlNodeArray GetMoreNodes()

{

    XmlDocument doc = new XmlDocument();

    return new XmlNodeArray(doc.SelectNodes("//*"));

}

 

P Existe alguma implementação XmlReader que seja suportado por um XpathNavigator subjacente?

 

R Infelizmente, o System.Xml não inclui um adaptador XmlReader para o XPathNavigator. Entretanto, é muito fácil escrever um adaptador como esse, derivando uma nova classe a partir de XmlReader que delegue chamadas para um cursor XPathNavigator subjacente. Veja a Listagem 3.

  Também é possível escrever um adaptador que faça o caminho inverso, ou seja, adaptar um XPathNavigator para um XmlReader subjacente. Esse procedimento lhe permite efetuar consultas XPath em um stream forward-only.

 

Listagem 3 – Adaptador NavigatorReader

namespace Developmentor.Xml

{

  using System;

  using System.Xml;

  using System.Xml.XPath;

  using System.IO;

  using System.Collections;

 

  public class NavigatorReader : XmlReader

  {

    private XPathNavigator cursor;

    ...

    public NavigatorReader(XPathNavigator nav)

    {

      this.cursor = nav;

      this.cursor.MoveToRoot();

    }

 

    public override XmlNodeType NodeType

    {

      get

      {

        // mapeia os valores do XPathNodeType para o XmlNodeTypes

        switch(cursor.NodeType)

        {

        case XPathNodeType.Root:

          return XmlNodeType.Document;

        case XPathNodeType.Element:

          if (IsEndElement)

            return XmlNodeType.EndElement;

          else

            return XmlNodeType.Element;

        case XPathNodeType.Attribute:

          return XmlNodeType.Attribute;

        case XPathNodeType.Text:

          return XmlNodeType.Text;

        case XPathNodeType.Comment:

          return XmlNodeType.Comment;

        case XPathNodeType.ProcessingInstruction:

          return XmlNodeType.ProcessingInstruction;

        case XPathNodeType.Whitespace:

          return XmlNodeType.Whitespace;

        case XPathNodeType.SignificantWhitespace:

          return XmlNodeType.SignificantWhitespace;

        default:

          return XmlNodeType.None;

        }

      }

    }

 

    public override string Name

    {

      get

      {

        return cursor.Name;

      }

    }

 

    public override string LocalName

    {

      get

      {

        return cursor.LocalName;

      }

    }

 

    public override string NamespaceURI

    {

      get

      {

        return cursor.NamespaceURI;

      }

    }

 

    ... // O código restante foi omitido.

}