In a nutshell, you have to pay attention to what namespaces are defined. My problem was I forgot to consider the default namespace. These two topics on MSDN explain it in more detail: Working with XML Namespaces and Scope of Default Namespaces in C#.
To illustrate the problem precisely, suppose you have the following XML:
XML Listing 1
<contacts>
<contact>
<name>name</name>
</contact>
</contacts>
Then with this C# code you can get the name of the contact.
Code Listing 1
XDocument xdoc = XDocument.Load(MapPath("~/App_Data/XMLFile.xml"));
var nameQuery1 = xdoc.Descendants("contact");
foreach (XElement nameElement in nameQuery1)
{
Response.Write(nameElement.Element("name").Value);
}
Now suppose the XML is defined with a default namespace such as this:
XML Listing 2
<contacts xmlns="http://travelmarx.blogspot.com">
<contact>
<name>name</name>
</contact>
</contacts>
In this case Code Listing 1 would not return the name element because it doesn’t take in account the default namespace. The following code will work:
Code Listing 2
XNamespace ns = "http://travelmarx.blogspot.com";
var nameQuery2 = xdoc.Descendants(ns + "contact");
foreach (XElement nameElement in nameQuery2)
{
Response.Write(nameElement.Element(ns + "name").Value);
}
Now, suppose there is an additional namespace declared with a prefix defined such as this:
XML Listing 3
<contacts xmlns="http://travelmarx.blogspot.com" xmlns:an="http://anothernamespace">
<contact>
<name>name</name>
<an:note>note</an:note>
</contact>
</contacts>
Possible C# code to get the note element is:
Code Listing 3
XNamespace ns2 = "http://anothernamespace";
var nameQuery3 = xdoc.Descendants(ns + "contact");
foreach (XElement nameElement in nameQuery3)
{
Response.Write(nameElement.Element(ns2 + "note").Value);
}
The code examples above are just for illustration purposes. You should make the code more robust, e.g. using try/catch clauses, for your particular application.
The following XML is an excerpt from the Messenger Connect AllContacts ATOM feed. It is followed by code to read it. It runs in the context of a web page, but the code can be easily taken out and used in other scenarios.
XML Listing 4
<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://bay.apis.live.net/V4.1/cid-3333333333333333/" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="http://www.w3.org/2005/Atom">
<title type="text">AllContacts</title>
<id>uuid:1623d873-0cc4-45ac-bea0-8c09fad60370;id=9850</id>
<updated>2010-09-13T20:57:55Z</updated>
<link rel="self" type="application/atom+xml;type=feed" title="self" href="Contacts/AllContacts"/>
<link rel="edit" type="application/atom+xml;type=feed" title="edit" href="Contacts/AllContacts"/>
<entry p3:reserved=" " p4:etag="0001-01-01T00:00:00.0000000" xmlns:p4="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:p3="http://schemas.microsoft.com/ado/2007/08/dataservices">
<id>urn:uuid:F2E3AWHYXZYUVHICHHNFXIXKHQ</id>
<title type="text">Cosentino Luparello</title>
<updated>2010-08-05T19:11:17Z</updated>
<link rel="self" type="application/atom+xml;type=entry" title="self" href="Contacts/AllContacts/F2E3AWHYXZYUVHICHHNFXIXKHQ"/>
<link rel="edit" type="application/atom+xml;type=entry" title="edit" href="Contacts/AllContacts/F2E3AWHYXZYUVHICHHNFXIXKHQ"/>
<category term="Contact" label="Contact" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<content type="application/xml">
<p4:properties xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<p3:Emails>
<p3:element>
<p3:Type>None</p3:Type>
</p3:element>
</p3:Emails>
<p3:FirstName>Cosentino</p3:FirstName>
<p3:FormattedName>Cosentino Luparello</p3:FormattedName>
<p3:LastName>Luparello</p3:LastName>
<p3:Locations>
<p3:element>
<p3:City>Palermo</p3:City>
<p3:Type>None</p3:Type>
</p3:element>
</p3:Locations>
<p3:PhoneNumbers/>
<p3:Urls/>
</p4:properties>
</content>
</entry>
</feed>
Code Listing 4
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Linq" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Web.UI" %>
<%@ Import Namespace="System.Xml.Linq" %>
<script runat="server">
public class ContactName
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
// Atom namespace.
XNamespace ns = "http://www.w3.org/2005/Atom";
// Dataservices namespace, p3.
XNamespace nsp3 = "http://schemas.microsoft.com/ado/2007/08/dataservices";
// Dataservices metadata namespace, p4.
XNamespace nsp4 = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
XDocument xdoc = XDocument.Load(MapPath("~/App_Data/data.xml"));
Output("Query 1: Selecting content node and then iterating on it to find FirstName and LastName.");
// Select the content nodes directly.
var contentQuery = xdoc.Descendants(ns + "content");
// Iterate over the collection of content nodes.
foreach (XElement contentElement in contentQuery)
{
string firstName = contentElement.Descendants(nsp3 + "FirstName").FirstOrDefault().Value;
string lastName = contentElement.Descendants(nsp3 + "LastName").FirstOrDefault().Value;
Output(firstName + " " + lastName);
}
Output();
Output("Query 2: Selecting FirstName and LastName as string.");
var firstNameQuery = xdoc.Descendants(nsp3 + "FirstName").Select(entry => entry.Value + " " + entry.ElementsAfterSelf(nsp3 + "LastName").First().Value);
foreach (string firstNameEntry in firstNameQuery)
{
Output(firstNameEntry);
}
Output();
Output("Query 3: Selecting FirstName and LastName as ContactName object.");
var contactNameQuery = xdoc.Descendants(nsp4 + "properties").Select(entry => new ContactName
{
FirstName = entry.Element(nsp3 + "FirstName").Value,
LastName = entry.Element(nsp3 + "LastName").Value
});
foreach (var contactNameEntry in contactNameQuery)
{
Output(contactNameEntry.FirstName + " " + contactNameEntry.LastName);
}
Output();
Output("Query 4: Query 3 + filtering results.");
var contactNameFilteredQuery = xdoc.Descendants(nsp4 + "properties")
.Where(entry => entry.Element(nsp3 + "FirstName").Value.ToLower().StartsWith("c"))
.Select(entry => new ContactName
{
FirstName = entry.Element(nsp3 + "FirstName").Value,
LastName = entry.Element(nsp3 + "LastName").Value
});
foreach (var contactNameFilteredEntry in contactNameFilteredQuery)
{
Output(contactNameFilteredEntry.FirstName + " " + contactNameFilteredEntry.LastName);
}
}
protected void Output()
{
Output("");
}
protected void Output(string txt)
{
Label1.Text += txt + "<br/>";
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Reading Windows Live Contact Atom Feed</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Using LINQ extension methods and lambda expressions.<br />
<asp:Label ID="Label1" runat='server'></asp:Label>
</div>
</form>
</body>
</html>
I've been Googling and trying to understand how parsing with a namespace works for over 2 hours but failed, thanks for this great article, I've totally understood how it works now.
ReplyDelete