Centralized Package Management in .NET Core

Sang AuSang Au
4 min read
๐Ÿ’ก
Managing NuGet package versions across multiple projects in a .NET solution can quickly become chaotic. Developers often duplicate package references and risk introducing version conflicts. Centralized Package Management (CPM) in .NET solves this by declaring package versions in one place. This article walks through why and how to use CPM effectively in .NET Core.

Problem โ—

In multi-project .NET solutions, developers face the following challenges:

  • Inconsistent package versions: Different projects reference different versions of the same package.

  • Version conflicts: Resolving version mismatch issues is time-consuming and error-prone.

  • Duplication: Updating package versions across multiple .csproj files is tedious.

Without centralized management, maintenance becomes complex, especially in microservice architectures or layered applications.

Solution ๐Ÿ’ก

Centralized Package Management (CPM) allows developers to define package versions in a single Directory.Packages.props file. Introduced in .NET 5 and enhanced in later versions, this feature improves maintainability and avoids duplication.

With CPM, you:

  • Define package versions once.

  • Consume packages in project files without repeating versions.

  • Maintain consistency across all projects in your solution.

How It Works โš™๏ธ

The core idea of CPM is to separate versioning from referencing. Here's how it works:

  1. Create a Directory.Packages.props file in the solution root.

  2. Add package versions using <PackageVersion> elements.

  3. Reference packages in project files without specifying the version.

  4. MSBuild automatically merges the version from the centralized file during build.

Example Directory.Packages.props:

<Project>
  <ItemGroup>
    <PackageVersion Include="Serilog" Version="2.12.0" />
    <PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>
</Project>

Example MyApp.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <PackageReference Include="Serilog" />
    <PackageReference Include="Microsoft.Extensions.Logging" />
  </ItemGroup>
</Project>

Benefit ๐ŸŽฏ

Using CPM brings several key advantages:

  • Consistency: All projects use the same package versions.

  • Maintainability: Update versions in one place.

  • Reduced Conflicts: Eliminate ambiguity in dependency resolution.

  • Cleaner Project Files: Project files focus on purpose, not versions.

  • Support for Transitive Pinning: Control transitive package versions explicitly.

Implement ๐Ÿš€

Follow these steps to enable CPM in your solution:

  1. Create the configuration file:

    Crate the Directory.Packages.props at the root folder of the solution

     touch Directory.Packages.props
    
  2. Add package versions:

     <Project>
       <ItemGroup>
         <PackageVersion Include="AutoMapper" Version="12.0.0" />
         <PackageVersion Include="FluentValidation" Version="11.5.1" />
       </ItemGroup>
     </Project>
    
  3. Update .csproj files: Remove Version attributes from <PackageReference> elements.

  4. Build the solution: MSBuild will now resolve versions from Directory.Packages.props.

Advanced ๐Ÿง 

๐Ÿ“Œ Overriding Package Versions

In some cases, you may want to override the centralized version in a specific project. To do this, explicitly specify the version using VersionOverride in the project file:

<ItemGroup>
  <PackageReference Include="Serilog" VersionOverride="2.11.0" />
</ItemGroup>

Note: MSBuild uses the closest version definition when resolving dependencies. A version declared in a .csproj will override what's in Directory.Packages.props.

๐ŸŒ Global Package References

You can define global package references in the Directory.Packages.props file using <GlobalPackageReference> instead of <PackageReference>. This ensures certain packages are included in all projects without manually adding them to each .csproj:

<Project>
  <ItemGroup>
    <GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0" />
  </ItemGroup>
</Project>

Global package references are added to the PackageReference item group with the following metadata:

  • IncludeAssets="Runtime;Build;Native;contentFiles;Analyzers"
    This ensures that the package is only used as a development dependency and prevents any compile-time assembly references.

  • PrivateAssets="All"
    This prevents global package references from being picked up by downstream dependencies.

This is particularly useful for analyzers, logging frameworks, or shared utilities. It ensures the package is used only during development and is not referenced at compile-time or exposed to downstream consumers, reducing duplication and enforcing standard tooling.

Best Practices โœ…

  • Place Directory.Packages.props at the solution root to apply it broadly.

  • Avoid version duplication across other .csproj files.

  • Commit the centralized file to version control.

  • Use clear versioning policies, such as pinning to stable releases.

  • Use dotnet list package --outdated to track outdated packages centrally.

  • Test builds after every version bump.

Conclusion ๐Ÿงพ

Centralized Package Management in .NET Core simplifies how you handle dependencies across multiple projects. It reduces maintenance costs, enforces consistency, and streamlines development workflows. By moving version declarations to a single file, teams can spend less time managing dependencies and more time building features.

References ๐Ÿ“š

0
Subscribe to my newsletter

Read articles from Sang Au directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sang Au
Sang Au

That's me! Daddy, Husband, Coder, Photographer