Nowadays, every non-trivial software project depends on a number of external dependencies. Good software design is all about modularity, and that’s one of the reasons behind NuGet’s success in the .NET world. NuGet is the famous, well-integrated package manager for .NET with hundreds of thousands of public packages. Modern .NET projects utilize NuGet packages a lot, and the tool just got better. The NuGet team has just released the central package management feature.
Let’s have a look at what it’s all about!
What Is Central Package Management and Why Do We Need It?
Central Package Management is exactly what its name suggests. It’s a way of controlling versions of NuGet packages in a centralized location. This solution follows the modern pattern of using
Directory.*.props files to share the settings between multiple projects.
In this case, the file is called Directory.Packages.props.
Before this feature was available, we had to set each NuGet package version in each project one by one. This was tedious and error-prone – even with the ‘Manage Packages for Solution’ feature of Visual Studio. Package version conflicts are annoying when they break our builds, but they are a whole different level of hell when they fail at runtime.
Having the versions of dependencies in one place guarantees that all projects use the same thing. And since it’s just one file, it’s much faster to edit.
So, let’s see how to start using it!
How to Use Central Package Management
First of all, we need to add the Directory.Packages.props file into our repository. The root folder of the solution is a good place to start:
<Project> <ItemGroup> <PackageVersion Include="Newtonsoft.Json" Version="13.0.1" /> <PackageVersion Include="Serilog" Version="1.59.0" /> </ItemGroup> </Project>
Adding this file should then enable the central management for all the projects unless they opt-out.
However, at the time of writing, this feature is only available in the preview version of Visual Studio (17.2). Until it is officially released, we need to opt-in to enable this feature. In order to do that, we need to add
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> to our project files. Alternatively, we can use the Directory.Build.props file to control this setting centrally, as we’ve described in our article about setting the C# version for all projects.
Next, let’s adjust the project files to handle centrally managed packages:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Newtonsoft.Json" /> </ItemGroup> </Project>
As we can see, the project file has only the reference to the package name – it does not contain the Version attribute.
It’s important to notice, that if we decide to use Central Package Management for a project, we cannot add NuGet packages in the traditional way. If we attempt to do that, we will see an NU1008 error:
Notice also the subtle difference between the project file and the Directory.Packages.props – the latter specifies
PackageVersion elements, not
PackageReference. This can be tricky if we just copied the packages section from the project file.
That’s it – building our project now should trigger proper NuGet resolution. The Preview version of Visual Studio can act a bit flaky – sometimes it helps to restart and delete the hidden .vs folder if it gets stuck.
Fine-Tuning the Central Package Management
In some cases, we might need a bit more granular control over the versions of packages. One of the ways of achieving it is to declare the
VersionOverride attribute on the package reference in the project file:
<ItemGroup> <PackageReference Include="Newtonsoft.Json" /> <PackageReference Include="Serilog" VersionOverride="2.11.0" /> </ItemGroup>
In this case, the current project uses the Serilog package in version 2.11. NuGet will ignore the version specified in Directory.Packages.props.
Another way of adding more granularity to the package resolution logic is to use more Directory.Packages.props files. The version resolution mechanism applies the file that is closest to the project that is being resolved. This means a subfolder of a repository can have its own Directory.Packages.props file, and the projects in this subfolder and below will have that file applied:
Root |-- Directory.Packages.props |-- Directory.Build.props |-- NuGet.config |-- MySolution.sln |-- MyProject |-- MyProject.csproj |-- Subfolder |-- Directory.Packages.props |-- ProjectInSubfolder.csproj
However, only one file applies to a project. This means that packages are declared in the top-level Directory.Packages.props file don’t apply in projects where a subfolder Directory.Packages.props file exists.
In order to use them, we need to define them separately!
Central Package Management is a very nice addition to the NuGet ecosystem. It simplifies dependency management, which can be quite challenging in complex projects. Unfortunately, at the time of writing, there is no GUI support for this feature. Luckily, there are plans to include that at some point, so the experience will be even better.