NuGet is one of the best things that happened to .NET, to make it easier to share and distributed shared components. NuGet has become Microsoft’s main inclusion mechanism (and it looks to be come even more prominent in ASPvNext which eschews binary deployments for NuGet packages). And there are a bazillion third party components you can now get through NuGet. While it’s not all unicorns and rainbows, NuGet has been one of the nicest additions to .NET to move the platform forward.
Personally I also use NuGet to distribute a host of Westwind libraries as part of the Westwind.Toolkit. I update those libraries frequently and it’s made it super easy to get new versions out and distributed to existing users, as well for my own work. Whether internal or external it’s a nice way to push components into applications.
Publishing and Dependency Management
However, when it comes to publishing NuGet packages I’ve had more than a little trouble getting my dependencies to come in with the proper version. Basically when you create a component that has a dependency on another component you can specify a version number or number range that you are depending on.
For example, here is the Westwind.Data nuget which depends on Westwind.Utilities and depends on version 2.50 and later:
<dependency id="Westwind.Utilities" version="[2.50,3.0)" />
<dependency id="EntityFramework" version="6.1.0" />
This basically says to depend on version 2.50 or higher up to version 2.99 (anything under 3.0) of Westwind.Utilities.
Now when I read the rather cryptic page that describes the dependency version management here:
My interpretation of the expression above based on that documentation would be – load version the highest version as long as it’s lower than 3.0. But that’s not actually what happens. In fact, quite the opposite occurs: Rather than loading the latest version, the smallest possible version is used, so even though the current version on NuGet for Westwind.Utilities is at 2.55, if you add this NuGet it’ll load version 2.50.
Here’s an article that describes NuGet’s default behavior (thanks to @BrianDukes):
It would be really helpful if the behavior described in the second article was also provided in the first. Duh! ‘Cause you know, that’s kind of crucial information that’s not freaking obvious!
To demonstrate: This gets much worse if you use the following syntax (which I accidentally used at first):
<dependency id="Westwind.Utilities" version="[,3.0)" />
which looks like it would mean to load any version lower than 3.0. But in fact it’ll load the lowest version which is 1.27, which is a totally incompatible version (2.0 had breaking changes). So at the very least it’s always best to include a lower boundary version, but even if I specifed:
<dependency id="Westwind.Utilities" version="[2.0,3.0)" />
it would work, but this would always load 2.0 unless a higher version already existed in the project.
I’m not sure why anybody would ever use this option when you explicitly provide high and low version constraints? When would you ever want to load an old component knowing that it would always load the old one? This seems rather pointless.
It seems the only option I really have here to get the latest version to load is to explicitly provide the latest version that is current and provide in the lower version explicitly and match it to the current version:
<dependency id="Westwind.Utilities" version="[2.55,3.0)" />
That way at least I get the latest version that was available at the time the component was created.
But the downside to that is that older versions that might already be in the project would not be allowed and you’d end up with a version conflict unless all components are upgraded to this latest version.
You can see how this gets ugly really quick. This is not all NuGet’s fault BTW – this is really a versioning issue, but it hits when the components are installed and requested in the first place and the point of entry where the pain occurs happens to be NuGet. The issue is really component dependency versioning and runtime binding, where a single application might have multiple dependencies on the same assembly with different versions. There are no easy answers here and I don’t want to get into this argument because that’s an endless discussion – and hopefully this will be addressed much better in the new ASPvNext stack that seems to allow for side by side execution of the same assemblies (via Roslyn magic perhaps)?
Lowest Common Denominator and Overriding
NuGet by default loads the lowest possible version it can match. In this case that’s 2.50, even though 2.55 is available. There’s a bit of discussion on why this somewhat unintuitive behavior occurs. Summarized, it amounts to this: lower versions are safer and avoid pulling in newer versions of components that might break existing applications/components that depend on the same component. It avoids pulling in newer versions in unconditionally.
This behavior can be explicitly overridden by explicitly running a component install with an extra switch:
which amounts to this:
PM> install-package westwind.utilities -DependencyVersion Highest
That’s nice, but as a package author that still leaves you unable to force the latest version even if you explicitly delimit your version range as I’ve done above *and* you want to support older versions as well for backwards compatible loading.
NuGet should be smarter than that!
I can see the argument of making sure projects are not broken by components automatically revving to a new higher version.
But it seems to me that NuGet should be smart enough to detect if you’re installing a component with a dependency for the first time when you have an upper version constraint and in that case it should install the LATEST version allowed of that component. There’s no point to add a new component with an old version UNLESS there’s a conflict with an existing package that is already installed in the project.
As it stands today, the version range features don’t really behave the way I would expect them to work. The ranges don’t really apply a range of versions to install, but rather act as a restraint to ensure you’re not stepping on an existing component by checking that an already installed component is in the version range allowed. But as for installation, the default to the lowest version pretty much ensures that NuGet always installs the lowest version, which is pretty lame if you think about it.
If the component is already installed and it’s in the range that’s valid then I can understand leaving the existing component alone and that would be the correct behavior in order to not accidentally screw up dependencies. We can then manually do the
to get the component to the latest version in that scenario.
But in the case where the dependency is loaded for the very first time, it makes no sense to load the oldest version… Discuss.
I'll be at DevIntersection in Vegas this fall giving sessions on ASP.NET Core with Angular and Localization. Thinking of coming? Use discount code STRAHL and save a few bucks. If you do be sure to stop by and say hello!
Other Posts you might also like