Managing dependencies across multiple projects can get unwieldy, especially as the project count grows. NuGet’s Central Package Management (CPM) feature aims to simplify this by letting developers define package versions in a single location. With CPM, you avoid the redundancy of specifying package versions in each project individually. Here, we’ll explore how to set up and use CPM effectively in your C# solutions.
What is Central Package Management?
Historically, NuGet packages were managed directly in project files using <PackageReference /> tags or through packages.config. While effective for single projects, scaling this across multiple projects makes version management and dependency consistency challenging. CPM addresses these issues by allowing you to manage dependencies centrally using a Directory.Packages.props file, starting with NuGet 6.2.
CPM is supported by:
- Visual Studio 2022 version 17.2 or higher
- .NET SDK 6.0.300 or higher
nuget.exeversion 6.2.0 or higher
To use CPM, ensure that your build environments meet these version requirements, as older tooling will not recognize CPM configurations.
Enabling Central Package Management
To get started, create a Directory.Packages.props file in the root of your repository. Then, set the ManagePackageVersionsCentrally MSBuild property to true and define your dependencies using <PackageVersion /> elements.
Example setup for Directory.Packages.props:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="Dapper" Version="2.0.78" />
</ItemGroup>
</Project>
This file specifies package versions that will apply to all projects in your repository. In individual project files, you can reference these packages without specifying the version, as shown below.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Dapper" />
</ItemGroup>
</Project>
This approach centralizes version control, making updates easy by simply modifying Directory.Packages.props.
Rules for Directory.Packages.props
CPM’s central file, Directory.Packages.props, follows a few location-specific rules. Only one Directory.Packages.props file is evaluated per project. If multiple Directory.Packages.props files exist, the file closest to the project’s directory is used. This provides flexibility for complex repositories, allowing different solutions or project groups to have different sets of central package definitions.
Example folder structure:
mathematicaCopy codeRepository
├── Directory.Packages.props
├── Solution1
│ ├── Directory.Packages.props
│ └── Project1
└── Solution2
└── Project2
Here, Project1 uses Solution1/Directory.Packages.props while Project2 uses the root-level Directory.Packages.props.
Getting Started with CPM
To adopt CPM across your repository:
- Create a
Directory.Packages.propsfile at the root of your repository. - Set
ManagePackageVersionsCentrallyto true in thePropertyGroupofDirectory.Packages.props. - Define
<PackageVersion />items in this file for each dependency. - Reference packages using
<PackageReference />in each project file, without specifying a version.
Advanced CPM Features
Transitive Pinning
CPM allows for transitive pinning, where transitive dependencies (dependencies of dependencies) can be upgraded or overridden centrally. Enable this by setting the CentralPackageTransitivePinningEnabled property to true:
<PropertyGroup>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
Overriding Package Versions
Sometimes, a project may need a different version of a package than the one specified centrally. You can override the version using the VersionOverride property in the project’s PackageReference:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" VersionOverride="12.0.1" />
</ItemGroup>
</Project>
Disabling CPM for a Project
If a particular project needs independent package management, you can disable CPM by setting ManagePackageVersionsCentrally to false:
<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
Global Package References
For packages that are essential across all projects in a repository (such as build tools or analyzers), you can use global package references. These references should be added in Directory.Packages.props and are specified with IncludeAssets and PrivateAssets metadata to ensure they remain as development dependencies only:
<Project>
<ItemGroup>
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.5.109" />
</ItemGroup>
</Project>
CPM Automation
Doing this manually for large solution with many dependencies can be a very time-consuming prospect. To automate the transition of your .NET projects to Central Package Management (CPM) using the directory-packages-props-converter CLI tool, you can follow these step-by-step instructions:
Prerequisites
- Install the .NET SDK if you haven’t already, as it’s required to run the CLI tool. Use version 6.2 or higher, as CPM support is built into NuGet starting from this version.
- Install the
directory-packages-props-converterCLI tool if it isn’t available on your machine. You can get that tool from its GitHub repo.
Step-by-Step Migration Guide
1. Navigate to Your Project Directory
Open a terminal or command prompt and navigate to the root directory of your project or solution. This is where the Directory.Packages.props file will be created.
<code>cd /path/to/your/project</code>
2. Run the Directory-Packages-Props-Converter Tool
Use the following command to generate a Directory.Packages.props file based on the existing package references in your projects:
directory-packages-props-converter migrate
This command scans all .csproj files in your solution, consolidates package references, and creates a Directory.Packages.props file at the root of your repository.
3. Verify and Adjust the Generated Directory.Packages.props File
Open the newly created Directory.Packages.props file and check that all required package versions are correctly specified under <PackageVersion> elements. The file should contain each unique package, along with its specified version, which is now centrally managed.
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<!-- Other packages here -->
</ItemGroup>
</Project>
4. Remove Version Attributes from Project Files
The CLI tool should automatically remove Version attributes from <PackageReference> elements in your individual .csproj files. If any are left, manually remove them to ensure each <PackageReference> element refers to the centralized version in Directory.Packages.props.xml
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
</Project>
5. Test the Project
Run a build and restore to confirm that all dependencies are resolved through CPM:
dotnet restore
dotnet build
Ensure there are no errors or missing packages.
6. Commit the Changes
Once everything is working correctly, commit the Directory.Packages.props file along with any modified .csproj files to your version control system.
Wrapping Up
Central Package Management with NuGet simplifies dependency management for multi-project solutions, allowing you to manage versions in a single file. With the ability to set global package references, handle version overrides, and enable transitive pinning, CPM offers robust tools to streamline project dependencies.