Quantcast
Channel: Fairway Technologies
Viewing all articles
Browse latest Browse all 53

NuGet Config File Transformation Causes Duplicate Entries On Update

$
0
0

Problem Overview

First off NuGet is great.  It has made life easier for me as a developer, and now, as an author for the open source generic repository SharpRepository.  It provides a great distribution channel, especially for providing updates.

The NuGet packages for SharpRepository add configuration elements to your Web.Config or App.config to make your life easier and allow for a configuration file way of setting up your repository.  NuGet provides a very simple configuration file transformation process when installing or updating a package. (For more information on it check here.) Basically it merges in your web.config.transform data into your web.config file adding the entries that don’t already exist.

Here is the issue I came across recently.  The SharpRepository.Ef5Repository NuGet package includes the following web.config.transform file:

<configuration>
	<sharpRepository>
	  <repositories>
		 <repository name="ef5Repository" connectionString="DefaultConnection" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository" />
	  </repositories>
	</sharpRepository>
</configuration>

After doing the initial install of this package, NuGet will add the <sharpRepository> section close to the bottom of your Web.config file since it didn’t exist previously.  Then as you start your development, you realize that you are using a connection string named “MyConnectionString” (because you hate naming things apparently), so you do the logical thing and change the repository connectionString value in your Web.config file like so:

	<sharpRepository>
	  <repositories>
		 <repository name="ef5Repository" connectionString="MyConnectionString" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository" />
	  </repositories>
	</sharpRepository>

Great, everything is working.  Then sometime down the road, you notice there is a brand new shiny version of SharpRepository.Ef5Repository available (due to new amazing features of course, no way there are any bugs to fix, not possible), so you update your package via NuGet and click run to start up your project, only to be shown an exception due to a duplicate key.  So what happened?

When NuGet runs the configuration file transformation it merges in the <repository> element again because the one from the package does not match the one that is in the Web.config file exactly due to the change of the connectionString value.  So the new file looks like this after the update:

	<sharpRepository>
	  <repositories>
		<repository name="ef5Repository" connectionString="MyConnectionString" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository" />
		<repository name="ef5Repository" connectionString="DefaultConnection" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository" />
	  </repositories>
	</sharpRepository>

So what can we do about it?

In order to get this fixed, I needed to hack together a workaround until NuGet does an upgrade to their XML transformations and maybe includes Visual Studio Web.config Transforms, which are more powerful.  I took advantage of the fact that during installation and upgrade of packages you can provide an Install.ps1 PowerShell script that will run after the files have been copied and the configuration file transformation is done.

Time to break out my non-existent PowerShell skills and put together a script to fix the problem.  When the script is run we may or may not have a duplicate element, but if we do, it will get added below the one that was edited and should be kept.  I also wanted to be able to have this work against both Web.config and App.config files since either one could have the problem depending on the type of project being used.

Each SharpRepository package that touches the configuration files has a version of this script, so my goal was to make it so that the majority of the code is the same from script to script and we just needed to setup which element to look for and clean up.  Here is what I came up with.

# Install.ps1
param($installPath, $toolsPath, $package, $project)

# Path Settings (since this script is used across multiple packages we only need to change these settings in each usually)
$parentPath = "configuration/sharpRepository/repositories"
$keyPath = "repository[@name='ef5Repository']"

Function Clean-Dups-In-Config-File ($localPath, $parentNodePath, $nodePath)
{
	$xml = New-Object xml

	# load config as XML
	$xml.Load($localPath)

	# select parent node to call the RemoveChild on later
	$parentNode = $xml.SelectSingleNode($parentNodePath)

	# select the nodes
	$nodes = $xml.SelectNodes($parentNodePath + "/" + $nodePath)

	if ($nodes.Count -gt 0) 
	{
		# if there are multiple repository names with the same key, then the NuGet update added the additional ones and they will be last
		#	so loop through those nodes, excluding the first one, and remove them
		$i = 1;
		while ($i -lt $nodes.Count) 
		{
			$parentNode.RemoveChild($nodes.Item($i))
			$i++
		}

		# save the config file
		$xml.Save($localPath)
	}
}

# find the Web.config/App.config file if there is one
$items = $project.ProjectItems | where {$_.Name -eq "Web.config" -or $_.Name -eq "App.config" }
Foreach ($item in $items)
{
	# find its path on the file system
	$localPath = $item.Properties | where {$_.Name -eq "LocalPath"}

	# clean duplicate entries
	Clean-Dups-In-Config-File $localPath.Value $parentPath $keyPath
}

This works great so far and cleans up the duplicates.


Viewing all articles
Browse latest Browse all 53

Trending Articles