XDT Transform: InsertBefore - Locator Condition is ignored
Asked Answered
F

1

27

I have a web.config file in which I need to either insert the <configSections /> element or manipulate children of that node if it already exists.
If it already exists I don't want to insert it again (obviously, as it is only allowed to exist once).

Normally, that would not be a problem, however:

If this element is in a configuration file, it must be the first child element of the element.

Source: MSDN.

So if I use xdt:Transform="InsertIfMissing" the <configSections /> element will always be inserted after any existing child elements (and there are always some), violating the above restriction of it having to be the first child element of <configuration />

I attempted to make this work in the following way:

 <configSections
    xdt:Transform="InsertBefore(/configuration/*[1])"
    xdt:Locator="Condition(not(.))" />

Which works perfect, if the <configSections /> element doesn't already exist. However, the condition I've specified seems to be ignored.

In fact, I've tried a few conditions like:

Condition(not(/configuration[configSections]))
Condition(/configuration[configSections] = false())
Condition(not(/configuration/configSections))
Condition(/configuration/configSections = false())

Finally, out of desperation, I tried:

Condition(true() = false()) 

It still inserted the <configSections /> element.

It is important to note that I'm trying to include this in a NuGet package, so I will be unable to use a custom transform (like the one AppHarbor uses).

Is there any other clever way to get my element in the right place only if it doesn't yet exist?

To test this out, use AppHarbors config transform tester. Replace the Web.config with the following:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="initialSection" />
  </configSections>
</configuration>

And Web.Debug.config with the following:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

  <configSections
    xdt:Transform="InsertBefore(/configuration/*[1])"
    xdt:Locator="Condition(true() = false())" />

  <configSections>
    <section name="mySection" xdt:Transform="Insert" />
  </configSections>

</configuration>

The result will show two <configSections /> elements, the one containing "mySection" being the first, as specified in the InsertBefore Transform. Why was the Locator Condition not taken into account?

Faraway answered 11/9, 2013 at 9:10 Comment(1)
I'm experiencing a similar issue with runtime/assemblyBinding/dependentAssembly nodes.Cleaning
B
46

So after facing the same issue, I came up with a solution. It's not pretty nor elegant, but it works. (At least on my machine)

I just split the logic into 3 different statements. First, I add an empty configSections at the correct position (first). Then I insert the new config to the last configSections, which would be the new one if it is the only one, or a previously existing one otherwise. Lastly I remove any empty configSections elemnt which might exist. I'm using RemoveAll for no good reason, you should probably use Remove.

The overall code looks like so:

<configSections xdt:Transform="InsertBefore(/configuration/*[1])" />
<configSections xdt:Locator="XPath(/configuration/configSections[last()])">
    <section name="initialSection" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
</configSections>
<configSections xdt:Transform="RemoveAll" xdt:Locator="Condition(count(*)=0)" />

The question which still remains unanswered is why Locator conditions are not taken into account for InsertBefore. Or why I can't handle an empty match set for InsertBefore, because that would allowed me to do fun things such as

//configuration/*[position()=1 and not(local-name()='configSections')]

Which to be honest is a much clearer way of doing what I want to achieve.

Bellini answered 27/9, 2013 at 1:53 Comment(6)
Fantastic, works like a charm, even included in a NuGet package! It's indeed a hack but it's exactly what I needed. Thanks!Faraway
I thought this was going to be a weird quirk that I could find no easy workaround for but this worked perfectly!Harumscarum
For the unintall, I just removed the section's config and the section itself, by name: <?xml version="1.0"?> <configuration xmlns:xdt="schemas.microsoft.com/XML-Document-Transform"> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" xdt:Transform="Remove" xdt:Locator="Match(name)" /> </configSections> <unity xmlns="schemas.microsoft.com/practices/2010/unity" xdt:Transform="Remove"> </unity> </configuration>Cloutier
I tried @Sebastiaans original solution (below) with Visual Studio 2015 and it still does not work. So I went with @Itarmaram's answer above. <configSections xdt:Transform="InsertBefore(/configuration/*[1])" xdt:Locator="Condition(not(.))" />Watusi
I have been using this approach for several months now and it worked great until I came across a project with an empty app.config file. <?xml version="1.0" encoding="utf-8" ?> <configuration> </configuration>Promiscuity
I handled an empty app.config by adding a dummy element before the @Itamaram's example and removing that element at the end.Foldaway

© 2022 - 2024 — McMap. All rights reserved.