NuGet Versioning Hell
In this post I'd like to point out why proper versioning of your .NET assemblies is important and how easy it is to get into NuGet versioning hell. First, a bit of a background how .NET versioning works.
If you have ever looked into
AssemblyInfo.cs you've noticed those two tags:
[assembly: AssemblyVersion("220.127.116.11")] [assembly: AssemblyFileVersion("18.104.22.168")]
or, if you are using the new .NET Core project model with msbuild the project structure is slightly different, i.e. the version information is contained within the
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <AssemblyVersion>22.214.171.124</AssemblyVersion> <FileVersion>126.96.36.199</FileVersion> <Version>188.8.131.52</Version> </PropertyGroup> </Project>
These are the only three elements you need to describe a proper version, but follow me below.
I'm not going to go into details on how
project.json works as this is slightly outdated and was a horrible project system not compatible with existing .NET world (this is just my opinion of course).
From what I've seen everyone just ignores those leaving the defaults, use uses in a terribly wrong way. And that's absolutely fine if you are writing an end-user application, you won't get any kind of problems. It only matters if you are a library developer.
In essense, there are three markers which you should care about:
Let's say we have an application v1.2.3 (actually application version doesn't play any role here) which references a library called
common v1.0.1. It also references a
Library v1.1.0, however this library depends on
common v1.0.2. Once you made those references and start your application it will crash horribly with an error similar tho this:
System.IO.FileLoadException: Could not load file or assembly 'common, Version=184.108.40.206, Culture=neutral, PublicKeyToken=8dea52f811e261ef' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) File name: 'common, Version=220.127.116.11, Culture=neutral, PublicKeyToken=8dea52f811e261ef' blah-blah-blah
One of the ways to solve this (if you own the
library code) is to update the dependency to the same version as your application uses. This will break other applications using the libarary in the same way though. Or update your application to reference v1.0.2, although the next update of the
library will crash your app again.
Let's go back to our version markers and analyze what they are for:
The values of all of these can be completely different and don't have to match.
You've probably guessed that the developers of the libraries used AssemblyVersion to stamp their libraries with actual versions like 1.0.2, 1.0.3 etc. Hence the runtime conflict.
When you are developing a library the simple rule to follow is to use AssemblyVersion only to mark a Major version of your product. This simply means that when it doesn't change, any rolling upgrades to the library don't break the functionality i.e. fully backward compatible with previous versions. This way you tell the runtime to use the same version, even if actual version of your library changes. Only change AssemblyVersion when you are releasing major upgrades which require breaking changes to the users's code.
Always mark the library's FileVersion with the actual version of your library, so it at least gives a hint to a user what they are on. I would strongly recomment stamping PackageVersion with the same marker so achieve some consistencly. I can't think of a case at the moment when they should be different, other than to completely confuse your users.
Now if we follow those conventions you'll get something like this (av - AssemblyVersion, fv - FileVersion):
common can be upgraded as many times as required, but as long as AssemblyVersion stays the same (1.0.0) all the dependencies are happy.
Hope you learned something new today.
These are my courses you should definitely check out on Pluralsight: