Querying Xml with LINQ - Don't forget the namespace
I’ve been working with a colleague on parsing a Visual Studio project file using LINQ to effectively create a DOM of the file.
The first thing we tried to do was get a list of all the references from the file. It seemed like a fairly easy problem to solve but for some reason nothing was getting returned:
XDocument projectFile = XDocument.Load(projectFilePath.Path); var references = from itemGroupElement in projectFile.Descendants("ItemGroup").First().Elements() select itemGroupElement.Attribute("Include").Value;
We are selecting all the occurrences of ‘ItemGroup’, taking the first occurrence, getting all the elements inside it (i.e. all the Reference elements) and then selecting the value of the ‘Include’ attribute. A fragment of the csproj file is as follows:
<ItemGroup> <Reference Include="System" /> <Reference Include="System.Core"> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> ... </ItemGroup>
After several hours of trial and error it turned out that we just needed to include the namespace of the file when querying. The new and now working code looks like this:
XNamespace projectFileNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; XDocument projectFile = XDocument.Load(projectFilePath.Path); var references = from itemGroupElement in projectFile.Descendants(projectFileNamespace + "ItemGroup").First().Elements() select itemGroupElement.Attribute("Include").Value;
There are two quite clever things going on with the way this is done
1) There is an implicit type conversion defined on XNamespace which allows us instatiate it using a string. 2) The addition(+) operator has been overloaded on XNamespace so that it can combine the namespace with the local name (‘ItemGroup’). This is described in more detail here.