26

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?

sebastiaan
  • 5,870
  • 5
  • 38
  • 68

1 Answers1

45

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.

Itamaram
  • 719
  • 7
  • 17
  • Fantastic, works like a charm, even included in a NuGet package! It's indeed a hack but it's exactly what I needed. Thanks! – sebastiaan Oct 14 '13 at 10:17
  • I thought this was going to be a weird quirk that I could find no easy workaround for but this worked perfectly! – Rush Frisby Jun 25 '14 at 20:50
  • For the unintall, I just removed the section's config and the section itself, by name:
    – Dan Csharpster Sep 22 '15 at 15:57
  • 1
    I tried @Sebastiaans original solution (below) with Visual Studio 2015 and it still does not work. So I went with @Itarmaram's answer above. `` – PBMe_HikeIt Nov 09 '15 at 21:36
  • 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. ` ` – Jeremy Nov 30 '16 at 23:56
  • I handled an empty app.config by adding a dummy element before the @Itamaram's example and removing that element at the end. – Jon Norton Jun 29 '17 at 18:25