Friday, November 13, 2009

MSBuild + XMLUpdate + XPath + Namespaces

Today I came across an issue trying to use an XMLUpdate statement against the web.config in our team deployment build, and while the Googling did end up helping me resolve the issue, it didn't turn up any direct results that really showed/explained what I needed to do. So what would Brian Boitano do? Well, besides using his magical fire breath to save a maiden, he'd probably decide to dig up the rotting corpse of his blog and fill the void himself. So here we are!


Anyways, check out the relevant chunk of my config file (sensitive info obfuscated, of course):

xml version="1.0"?>

<configuration>

...

<openaccess xmlns="http://www.telerik.com/OpenAccess">

<references>

<reference assemblyname="OurAssembly.Name" configrequired="True" />

</references>

<connections>

<connection id="OurDatabase">

<databasename>OurDB</databasename>

<servername>.\SQLEXPRESS</servername>

<integratedSecurity>True</integratedSecurity>

<backendconfigurationname>mssqlConfiguration</backendconfigurationname>

<connectionParams></connectionParams>

</connection>

</connections>

<backendconfigurations>

<backendconfiguration id="mssqlConfiguration" backend="mssql">

<mappingname>mssqlMapping</mappingname>

<logging.logEventsToTrace>False</logging.logEventsToTrace>

<logging.logEvents>verbose</logging.logEvents>

<lockTimeout>5000</lockTimeout>

<logging.logEventsToSysOut>False</logging.logEventsToSysOut>

</backendconfiguration>

</backendconfigurations>

</openaccess>

...


</configuration>

As you can see, we're using the Telerik OpenAccess ORM tools. My goal was to insert some values into the connectionParams node specific to our deployed application scenario, so I originally had a statement in the team build proj file that looked like this:


<XmlUpdate XmlFileName="%(WebConfig.FullPath)"

XPath="//configuration/openaccess/connections/connection[@id='OurDatabase']/@connectionParams"

Value="AttachDbFileName=$(DropLocation)\$(LabelName)\$(Configuration)\_PublishedWebsites\$(WebProjectName)\OurDB.mdf" />

Unfortunately, it didn’t work. After some digging I discovered that the xmlns attribute on the openaccess node messes with XPath, and that I could use a Namespace attribute to help solve that problem, but nothing really showed how exactly to do it. I used a little trial and error and came up with the solution as seen here:


<XmlUpdate XmlFileName="%(WebConfig.FullPath)"

Prefix="n"

Namespace="http://www.telerik.com/OpenAccess"

XPath="//configuration/n:openaccess/n:connections/n:connection[@id='OurDatabase']/n:connectionParams"

Value="AttachDbFileName=$(DropLocation)\$(LabelName)\$(Configuration)\_PublishedWebsites\$(WebProjectName)\OurDB.mdf" />


So as a way of explanation, we define the namespace we're hunting for and assign it a prefix. Then, in the XPath, we use the prefix for any node that falls within that namespace, but not for the nodes before it takes effect (such as configuration).


All of this info is out there on the web in pieces, and I'm sure smarter folks than I can put it together quickly and move on, but hopefully this post will help the other goobers like me who would otherwise take an hour or two to put all the pieces of the puzzle together.


Faithfully yours,

Code Baboon

2 comments:

Mike said...

What was that about Brian Boitano voiding himself on the maiden?

John said...

Thanks. This was a big help. I used this to help update an NHibernate (for version 2.1) connection string in config files:



<ItemGroup>

<ConfigVar Include="NHConnStr">
<XPath>//configuration/nh:hibernate-configuration/nh:session-factory/nh:property[@name='connection.connection_string']</XPath>
<Namespace>urn:nhibernate-configuration-2.2</Namespace>
<Prefix>nh</Prefix>
</ConfigVar>


<ConfigVar Include="ConnStr">
<XPath>configuration/connectionStrings/add[@name='DBConnStr']/@connectionString</XPath>
<Namespace></Namespace>
<Prefix></Prefix>
</ConfigVar>

<ConfigVar Include="PathToDir">
<XPath>configuration/appSettings/add[@key='PathToDir']/@value</XPath>
<Namespace></Namespace>
<Prefix></Prefix>
</ConfigVar>

</ItemGroup>


<Target Name="RunConfig" Outputs="%(ConfigVar.Identity)">

<PropertyGroup>
<RunConfig_CurrValue></RunConfig_CurrValue>
</PropertyGroup>

<XmlRead Prefix="%(ConfigVar.Prefix)" Namespace="%(ConfigVar.Namespace)" XPath="%(ConfigVar.XPath)" XmlFileName="$(InputConfig)">
<Output TaskParameter="Value" PropertyName="RunConfig_CurrValue"/>
</XmlRead>


<XmlUpdate
Prefix="%(ConfigVar.Prefix)"
Namespace="%(ConfigVar.Namespace)"
XPath="%(ConfigVar.XPath)"
XmlFileName="$(DropConfig)"
Value="$(RunConfig_CurrValue)" />

</Target>