Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

XML parsing, Namespaces and Web.Config in ASP.NET 2.0


:P
On this page:

I just spent a few hours backfitting my wwAppConfiguration class which is part of the West Wind Web Store, so it works properly with .NET 2.0 and the new config file format that ASP.NET 2.0 uses by default.

 

Actually ASP.NET 2.0 includes improved configuration section access that supports read and write access, but the wwAppConfiguration class still provides a number of useful features above and beyond what ASP.NET 2.0 provides, the strongest feature (IMHO) being the fact that you can get a strongly typed interface for your configuration settings.

 

Anyway, even if ASP.NET 2.0 addressed all those needs there’s still the issue of backwards compatibility. A 1.x app upgraded to a 2.x project ends up with the new configuration file format.

 

Version 2.0 broke the original implementation with something as simple as adding a namespace reference to the ASP.NET web.config file:

 

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

 

This namespace reference is added for VS.NET to be able to provide a configuration schema to web.config editing, but the namespace definitely affects any manual XML parsing anybody might have been doing on the web.config file.

 

Seems simple enough, but this actually broke any of the code that is not using the ConfigSections manager classes directly. Anything using the XML DOM directly broke based on this innocuous namespace reference addition.

 

Whenever it comes to dealing with default namespaces in XML documents I feel like a complete idiot because I can never remember where you need to explicitly have to apply the prefix and where it’s not required. I end up going through a bunch of trial and error to get this right.

 

Is it just me, or shouldn’t a non prefixed namespace automatically be treated like the default namespace without having to explicitly redeclare it? I remember this same issue popping up when MSXML 4.0 came out and broke backwards compatibility with older MSXML parsers for this same logic. The logic is WC3 correct, but that doesn't make it right or logical <g>... Bitch, bitch, bitch...

 

The problem here is that the XMLDOM parser doesn’t automatically use a default namespace like the one above and push it down into the childnodes of this document. Instead you have to assign the namespace explicitly and assign a namespace prefix to it, then actually use that prefix explicitly on any nodes referenced by XPATH query. If you have existing code runs XPATH queries without namespace references at all (because there weren’t any explicitly assigned namespaces previously) cleaning up this code is quite a mess.

 

Although no namespace prefix is used, not using a namespace prefix on any XPATH queries results in nodes not being found. So where things like this worked before:

 

XmlNode Node = Dom.DocumentElement.SelectSingleNode(

                    ConfigSection + "/" +

                    "add[@key='" + Field.Name + "']",

                    null);

 

you now have to explicitly use a namespace object, and prefix on every element expression. Here’s what this looks like:

 

XmlDocument Dom = new XmlDocument();

 

try

{

      Dom.Load(Filename);

}

catch

{

    // *** Can't open or doesn't exist - so create it

      if (!this.WriteKeysToConfig(Filename) )

            return;

 

    // *** Now load again

      Dom.Load(Filename);

}

 

// *** Retrieve XML Namespace information to assign default

// *** Namespace explicitly.

this.GetXmlNamespaceInfo(Dom);

 

XmlNode Node = Dom.DocumentElement.SelectSingleNode(

                    this.XmlNamespacePrefix + ConfigSection + "/" +

                    this.XmlNamespacePrefix + "add[@key='" + Field.Name + "']",

                    XmlNamespaces);

// … do something with the node..

 

XmlNamespaces is a an XmlNamespacesManager object which picks up all of the namespaces of the XML document. Basically the idea is that you can add the ‘default’ namespace to the existing namespaces and assign a prefix to it. So whenever a DOM is loaded the following code does the trick:

 

/// <summary>

/// Used to load up the default namespace reference and prefix

/// information. This is required so that SelectSingleNode can

/// find info in 2.0 or later config files that include a namespace

/// on the root element definition.

/// </summary>

/// <param name="Dom"></param>

private void GetXmlNamespaceInfo(XmlDocument Dom)

{

    // *** Load up the Namespaces object so we can

    // *** reference the appropriate default namespace

    if (Dom.DocumentElement.NamespaceURI == null || Dom.DocumentElement.NamespaceURI == "")

    {

        this.XmlNamespaces = null;

        this.XmlNamespacePrefix = "";

    }

    else

    {

        if (Dom.DocumentElement.Prefix == null || Dom.DocumentElement.Prefix == "")

            this.XmlNamespacePrefix = "ww";

        else

            this.XmlNamespacePrefix = Dom.DocumentElementPrefix;

 

        XmlNamespaces = new XmlNamespaceManager(Dom.NameTable);

        XmlNamespaces.AddNamespace(this.XmlNamespacePrefix,  

                                   Dom.DocumentElement.NamespaceURI);

 

        this.XmlNamespacePrefix += ":";

    }

}

 

Ok, my XML may have been rusty but it took quite a while to figure this part out. Maybe there’s an easier way to do this, but I couldn’t get this to work any other way.

 

Note that I have to deal with the two scenarios of where there is a namespace provided and where there isn’t separately. The consistency of how this works in the DOM really sucks. Luckily this wrapper takes care of the logistics since null for the namespace manager and empty for the prefix work fine. The other alternative would have been to attach a namespace explicitly.

 

Once this code was in place then there was still the job left of going through the document and cleaning up all the references and making sure that the namespace manager is used and the prefix applied to all element references.

 

Adding nodes in turn DOESN’T require the namespace prefix, but it does require the namespace reference. Here’s what the code looks like to create a new configuration section:

 

private XmlNode CreateConfigSection(XmlDocument Dom,string ConfigSection)

{

 

    // *** Create the actual section first and attach to document

    XmlNode AppSettingsNode = Dom.CreateNode(XmlNodeType.Element,

        ConfigSection,Dom.DocumentElement.NamespaceURI);

 

    XmlNode Parent = Dom.DocumentElement.AppendChild(AppSettingsNode);

 

      // *** Now check and make sure that the section header exists

      if (ConfigSection != "appSettings")

      {

            XmlNode ConfigSectionHeader = Dom.DocumentElement.SelectSingleNode(this.XmlNamespacePrefix + "configSections",

                    this.XmlNamespaces);

            if (ConfigSectionHeader == null)

            {

                  // *** Create the node and attributes and write it

                  XmlNode ConfigSectionNode = Dom.CreateNode(XmlNodeType.Element,

                "configSections",Dom.DocumentElement.NamespaceURI);

 

            // *** Insert as first element in DOM

                  ConfigSectionHeader = Dom.DocumentElement.InsertBefore(ConfigSectionNode,

                Dom.DocumentElement.ChildNodes[0]);

            }

                             

            // *** Check for the Section

            XmlNode Section = ConfigSectionHeader.SelectSingleNode(this.XmlNamespacePrefix + "section[@name='" + ConfigSection + "']",

            this.XmlNamespaces);

           

        if (Section == null)

            {

                  Section = Dom.CreateNode(XmlNodeType.Element,"section",

                null);

                 

            XmlAttribute Attr =Dom.CreateAttribute("name");

                  Attr.Value = ConfigSection;

                  XmlAttribute Attr2 = Dom.CreateAttribute("type");

            Attr2.Value = "System.Configuration.NameValueSectionHandler,System,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";

                  Section.Attributes.Append(Attr);

                  Section.Attributes.Append(Attr2);

                  ConfigSectionHeader.AppendChild(Section);

            }

      }

 

      return Parent;

}

 

Note that reading requires the namespace prefix, but writing does not. Note that that each of the write operations writes the document’s namespace into the new nodes.

 

I doubt anybody will find this post very interesting, but I’m posting it for my own sake, so I can find it again in a few months the next time I run into a namespace issues with XML documents. <g>

 


The Voices of Reason


 

Very Useful BLOG!!
July 28, 2005

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

Trying to edit the web.config file during an installation in a custom Web Setup Project proves very difficult, as there seem to be a bug in VS Beta 2: When passing the fully qualified path of the web.config file to WebConfigurationManager.OpenWebConfiguration(), an error is thrown and the "easy-to-use" Configuration Manager is suddenly useless. As was the standard XML parsers, as you described in your blog. However the workaround you've described solves the problem perfectly! Nice job mister! :-) Thanx
-bjorn gustafson

Daniel
November 12, 2005

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

Try an another way:

// modify XmlDocument
this.DocumentElement.Attributes["xmlns"].Value = "";
this.LoadXml(this.DocumentElement.OuterXml);

And you can use "standart" SelectSingleNode queries like this:
SelectSingleNode("configuration/system.diagnostics/switches/add[@name='MyTraceSwitch']")

kurt
January 09, 2006

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

Very helpful. I encountered this with .net 2.0 and used your solution as a workaround. Thanks!

Luis Ramirez
May 22, 2006

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

I have the same problem than you. Thanks a lot for posting. Your article help me a lot.

Nj
November 21, 2006

# Help Needed

Can anyone tell how to retrieve the namespaces that i have written in my web.config file into my code page.. :(

Stephen Adam
January 15, 2008

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

"if (Dom.DocumentElement.NamespaceURI == null || Dom.DocumentElement.NamespaceURI == "")"

What's wrong with String.IsNullOrEmpty, just a minor note.

thanks for an awesome post.

Cheers

Steve

Rick Strahl
January 15, 2008

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

Code that in part predates .NET 2.0 <s>...

nick durcholz
January 28, 2009

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

There is a different way to get a constant xpath expression to work with or without a default namespace.

        [TestMethod]
        public void Bar()
        {
            const string XPATH = "/ns:xml/ns:foo/text()";

            XmlDocument xd = new XmlDocument();
            xd.LoadXml("<xml><foo>bar</foo></xml>");
            XmlNamespaceManager xmlNamespaces = new XmlNamespaceManager(xd.NameTable);
            //NamespaceURI could be anything including String.Empty
            xmlNamespaces.AddNamespace("ns", xd.DocumentElement.NamespaceURI ?? "");
            Assert.AreEqual("bar", xd.SelectSingleNode(XPATH, xmlNamespaces).Value);

            xd = new XmlDocument();
            xd.LoadXml("<xml xmlns=\"test:namespace\"><foo>bar</foo></xml>");
            xmlNamespaces = new XmlNamespaceManager(xd.NameTable);
            //NamespaceURI could be anything including String.Empty
            xmlNamespaces.AddNamespace("ns", xd.DocumentElement.NamespaceURI ?? "");
            Assert.AreEqual("bar", xd.SelectSingleNode(XPATH, xmlNamespaces).Value);
        }


Beats doing string concatenation to build an xpath expression IMO....

shailesh patel
February 17, 2010

# re: XML parsing, Namespaces and Web.Config in ASP.NET 2.0

If you want to modify web.config programatically, why can't you use WebConfigurationManager class ? I think you can easily modify any elements of web.config.

http://msdn.microsoft.com/en-us/library/system.web.configuration.webconfigurationmanager.aspx

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2025