A few months back I was migrating a .Net solution with 110 projects from .Net 4.5 to .Net 4.6.2 as well as upgrading the Azure SDK. Each project came with its own set of NuGet packages. As you can guess, there were a decent number of packages that needed to be upgraded. Given that the whole solution required testing after the upgrade, then was as good a time as any to bring everything up to date.
When it was time to merge this branch back to the main development branch, there were a number of conflicts, mostly in app.config files belonging to class library projects. Virtually every class library project in the solution had an app.config which didn’t sit right with me. It was something I’d often noticed but never really given any thought to, until now that it was causing some pain.
app.config file in a class library project do anything?
In a nutshell, no, but with a caveat. Configuration files have application wide scope, not assembly wide scope (ignoring machine configurations and publisher policy files). Even though the configuration files contain assembly binding redirects and other important things, they’re really not used (unless you have code to load settings from a local configuration file). It’s one of those things that you know the answer too, but there is a shadow of a doubt in there.
So why do these files appear?
NuGet doesn’t just randomly create these files. NuGet only does what it is told to do. If the package creator added config transforms to the package, they will be applied regardless of the target project type.
The transforms are mostly used to add or modify an existing configuration file to add things such as:
- Configuration elements e.g. adding an enterprise library package will most likely add some sections to your config file.
- Application settings.
- Dependent assemblies and possibly assembly binding redirects.
If a NuGet package is added to a project that doesn’t contain a configuration file, e.g. your lovely class library, a shiny new configuration file will be added.
You would think that it should be simple enough to say that any projects of type Class Library can be ignored. However, projects such as those housing Azure Worker Roles would be skipped and they certainly require a configuration file.
During my research in to this topic, I came across a StackOverflow post which really doesn’t help clarify things and is most certainly a source of confusion. The answer in question references this specific sentence in an Redirecting Assembly Versions MSDN article:
You can redirect compile-time binding references to .NET Framework assemblies, third-party assemblies, or your own app’s assemblies.
It may just be a simple misunderstanding of how assembly unification works. My interpretation of the above sentence is that an assembly binding re-direct, which is defined in a
*.config file, allows you to override any binding that was used at compile-time, during run-time. The version of the assembly that was used at compile time is based on a number of factors and is largely irrelevant to the actual running of the application (think of the scenario where a different version of some assembly is present in the GAC as well as local to the application).
If you need any more proof, this note in the MSDN article on Managing Application Settings (.NET) should clear it up.
Because there is no configuration file model for class libraries, application settings do not apply for Class Library projects. The exception is a Visual Studio Tools for Office DLL project, which can have a configuration file.
What’s the solution?
Well there isn’t really. At least not a clean, simple solution that works in all scenarios. NuGet packages run configuration transformations to solve issues. Simply ignoring them could lead to unexpected behaviour.
There isn’t really a one-size-fits-all approach to handling this. In an ideal world, NuGet would be smart enough to know which projects can be skipped when it comes to config transforms, but that’s a rather brittle approach.
This is something I may well explore further, but from the top of my head, it feels like the best bet is to create a Visual Studio extension or even and MSBuild target. The intent being that a whitelisted set of projects in the solution keep their *.config files.
Ignore the Files in Source Control
An obvious solution would be to create a general rule in your version control system that ignores *.config files for all but a select few of your projects. If you’re using Git, this could be added to your
.gitignore file and shared across the team. The downside is that it wouldn’t prevent the config files from being referenced by the solution, resulting in dead links after a commit.
Visual Studio Extension
Creating a Visual Studio extension that runs a cleanup for you could be a nice way to manage the problem. In the scenario where a solution contains a large number of projects, and you run a NuGet package upgrade that touches a large number of projects, having an extension that is invoked on demand could be a nice workaround.
This post touches on the topic of binary versioning and discovery, which is way beyond the scope of this post. If you want to dive in to that, you can start by taking a look at the MSDN article on How the Runtime Locates Assemblies and How to: Enable and Disable Automatic Binding Redirection. Also look in to Side-by-Side versioning (SxS) and Strong-Named Assemblies.
The caveat I mentioned at the start of this post comes in to a specialised scenario. When I first started looking in to this topic, I read the following MSDN article.
It’s stated that app.config values local to class libraries can be considered at compile time… That makes no sense given the JIT nature of the .Net platform. Initially I was caught off-guard by the talk of ‘ISOLATION_AWARE_ENABLED’ which is a hangover from the way old COM days and doesn’t really apply to .Net. If you want to go down that rabbit hole, check out the MSDN article Registration-Free Activation of .NET-Based Components.