XML Files

XML Namespace Collisions, XmlNodeList and Deserialization, and More

Aaron Skonnard

Code download available at:XMLFiles0307.exe(350 KB)

Q I am developing a public Web Service that will provide a single interface to all of my organization's internal systems. As a result, I have built several intermediary Web Services that, in turn, get wrapped by the public Web Service. If I use the same class name in any of these Web Services, I get an error when browsing to the WSDL file although it compiles just fine. How do I resolve this?

Q I am developing a public Web Service that will provide a single interface to all of my organization's internal systems. As a result, I have built several intermediary Web Services that, in turn, get wrapped by the public Web Service. If I use the same class name in any of these Web Services, I get an error when browsing to the WSDL file although it compiles just fine. How do I resolve this?

A The problem you're running into has to do with an XML namespace collision. When working with ASP.NET Web Services, you have to realize that there are two types of namespaces in play. The first type is a namespace in the Microsoft® .NET Framework—a namespace that's used in C#, Visual Basic® .NET, and other .NET-targeted languages for distinguishing among types at compile time. The second type is an XML namespace—a namespace that's used in XML documents to distinguish between XML types, elements, and attributes when processing XML documents.

A The problem you're running into has to do with an XML namespace collision. When working with ASP.NET Web Services, you have to realize that there are two types of namespaces in play. The first type is a namespace in the Microsoft® .NET Framework—a namespace that's used in C#, Visual Basic® .NET, and other .NET-targeted languages for distinguishing among types at compile time. The second type is an XML namespace—a namespace that's used in XML documents to distinguish between XML types, elements, and attributes when processing XML documents.

When you write an ASP.NET Web Service class, you're always using both types of namespaces so it's important to understand the difference between the two and how they map to each other. Consider the following class:

namespace Foo { public class MyService : WebService { ... // WebMethods go here }

The Framework-friendly namespace in this case is called Foo and it contains a single class called MyService, which is going to contain some WebMethod operations. Since I didn't explicitly specify an XML namespace, the infrastructure will automatically use https://tempuri.org as the service's XML namespace. You definitely don't want to use this namespace but rather a unique one of your choosing. Hence, when you browse to the .asmx documentation page, you'll see a warning about this along with a helpful description of how to fix the problem.

The XML namespace of the Web Service class must be explicitly assigned through the [WebService] attribute, as illustrated here:

namespace Foo { [WebService(Namespace="urn:example-org:myservice")] public class MyService : WebService { ••• } }

If you compile the project and browse back to the .asmx documentation page, the warning goes away because it assumes you know what you're doing now. All of the classes you use within this class now will also be associated with this XML namespace, regardless of their namespace in the .NET Framework, unless you explicitly assign them a different XML namespace of their own. For example, consider Figure 1, which contains a few new class definitions used in the MyService class.

Figure 1 MyService Class

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 { ••• } }

Since the two MyClass types are used in the MyService class, they're automatically included in the service's XML namespace, in this case urn:example-org:myservice. And since the class names are the same in this case (MyClass), it causes a duplicate in the XML namespace. Since this is illegal in XML Schema, the .asmx infrastructure automatically throws an exception when such situations are detected at run time. However, the code compiles fine, as you noted, since the two MyClass types are in different namespaces in C#.

The poor compiler has no idea about the additional XML Schema and WSDL semantics that cause this problem to surface. The .asmx infrastructure is smart enough, however, to detect these issues when it first performs reflection on the class. For example, if you browse to the .asmx documentation page, you'll see the following error information describing the problem:

Types Bar.MyClass and Baz.MyClass both use the XML type name, MyClass, from namespace urn:example-org:myservice.

The error message also explains that you need to perform additional Framework-to-XML mappings to resolve the situation:

Use XML attributes to specify a unique XML name and/or namespace for the type.

In other words, if you really need to use these two different classes that happen to have the same name but mean different things, you need to put them in different XML namespaces. You can accomplish this using the [XmlType] and [XmlRoot] attributes, as illustrated in Figure 2.

Figure 2 XmlType and XmlRoot Attributes

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 { ••• } }

The [XmlType] attribute controls the name of the XML Schema type name and namespace while the [XmlRoot] attribute controls the name of the root element declaration mapped to each type. The various attribute declarations that I've shown place each MyClass type in a different XML namespace, thus avoiding the name collision that caused the problem. Now if you browse to the .asmx documentation page, everything should work just fine and the documentation will appear.

In cases where two classes really represent the same thing, you should probably eliminate one of them and just have one class that you reference from the different services in your system. In this case, you should still specify the type's XML namespace using the [XmlType] and [XmlRoot] attributes so it's not automatically incorporated into the XML namespace of the service where it's used. Doing so allows you to maintain the identity of the type within XML documents. This topic is discussed in detail at Sharing Types.

Q I'm working on a Web Service and want to return a nodeset from one of its methods. I had originally intended to just return an XmlNodeList, but it doesn't look like it's XML serializable. What's the best way to do this?

Q I'm working on a Web Service and want to return a nodeset from one of its methods. I had originally intended to just return an XmlNodeList, but it doesn't look like it's XML serializable. What's the best way to do this?

A XmlNodeList doesn't contain an Add method, so it's impossible for XmlSerializer to deserialize an instance of it. XmlSerializer, however, can serialize XmlNode objects. It can also serialize arrays of any serializable type. Since XmlNodeList is essentially just an array of XmlNode objects, it's not hard to imagine how you could accomplish this with some code that translates XmlNodeList objects into XmlNode array objects. For example, check out the following WebMethod that returns an array of XmlNodes to the client:

[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; }

A XmlNodeList doesn't contain an Add method, so it's impossible for XmlSerializer to deserialize an instance of it. XmlSerializer, however, can serialize XmlNode objects. It can also serialize arrays of any serializable type. Since XmlNodeList is essentially just an array of XmlNode objects, it's not hard to imagine how you could accomplish this with some code that translates XmlNodeList objects into XmlNode array objects. For example, check out the following WebMethod that returns an array of XmlNodes to the client:

[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; }

It would be easy enough to generalize this in a class called XmlNodeArray that automatically performs the translation when it's constructed:

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]; } }

Then you can return any XmlNodeList object by simply wrapping it with a new XmlNodeArray, as illustrated here:

[WebMethod] public XmlNodeArray GetMoreNodes() { XmlDocument doc = new XmlDocument(); return new XmlNodeArray(doc.SelectNodes("//*")); }

Q Is there an XmlReader implementation that sits over an underlying XPathNavigator?

Q Is there an XmlReader implementation that sits over an underlying XPathNavigator?

A Unfortunately, System.Xml doesn't include an XmlReader adapter for XPathNavigator. It's fairly easy to write such an adapter, however, by deriving a new class from XmlReader that delegates calls to an underlying XPathNavigator cursor. I wrote one of these as part of a previous installment of The XML Files. The code was in the download for that column, but Figure 3 shows it again.

A Unfortunately, System.Xml doesn't include an XmlReader adapter for XPathNavigator. It's fairly easy to write such an adapter, however, by deriving a new class from XmlReader that delegates calls to an underlying XPathNavigator cursor. I wrote one of these as part of a previous installment of The XML Files. The code was in the download for that column, but Figure 3 shows it again.

Figure 3 NavigatorReader Adapter

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 { // map the XPathNodeType values to the XmlNodeType values 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; } } ... // remainder ommitted }

It's also possible to write an adapter that goes the other way, where you want to adapt an XPathNavigator to an underlying XmlReader. This gives you the benefit of being able to perform XPath queries on a forward-only stream. I've provided each of these examples for download from the link at the top of this article.

Send your questions and comments for Aaron to  xmlfiles@microsoft.com.

Aaron Skonnard is an instructor/researcher at DevelopMentor, where he develops the XML and Web Service-related curriculum. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000).