If you’re working with just a single project in your solution, this article might not apply to youโfeel free to skip it. Thanks for stopping by!
Why Do You Need Directory.Packages.props?
Imagine you’re building a large .NET solution with dozens of projects: web APIs, class libraries, worker services, and more. Updating a shared NuGet packageโlike Entity Framework Core or Newtonsoft.Jsonโmeans hunting down every .csproj file, bumping the version manually, and praying you didn’t miss one. It’s like trying to update the spice levels in a massive pot of chili by stirring each individual bean. Tedious, error-prone, and a recipe for inconsistency.
Enter Directory.Packages.props: your one-stop shop for centralizing NuGet package versions across the entire solution. One file to rule them all! This MSBuild file lives at the root of your repository and lets you declare package versions globally. Individual projects then reference packages without specifying versionsโthey automatically pull from the central file.
This approach shines in team environments or monorepos, ensuring everyone uses the same versions, simplifying upgrades, and reducing “works on my machine” bugs caused by version drift.
How to Set It Up
Getting started is straightforward. You have two options:
- Manual Creation: Create a file named
Directory.Packages.propsin the root of your solution (right next to your.slnfile). - Via .NET CLI: Use the built-in command for a quick scaffold:
dotnet new packageprops
This generates the file with some placeholder content.
Defining Package Versions Centrally
Open Directory.Packages.props and populate it with <PackageVersion> items inside an <ItemGroup>. Here’s a clean example:
<Project>
<PropertyGroup>
<!-- Enable central package management (more on this below) -->
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>
Think of this file as the master recipe book in a busy kitchen. The head chef (you) lists exact ingredient amounts (package versions). Line cooks (your projects) just say, “Add EntityFrameworkCore,” and grab the pre-measured amount from the central pantryโno guessing needed.
Referencing Packages in Your .csproj Files
In each project file, drop the version attribute from <PackageReference>. MSBuild will resolve it from the central file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
</Project>
Build your solution, and NuGet restores using the centralized versions. Smooth as butter!
Overriding Versions in Specific Projects
Sometimes, a project needs a different versionโmaybe a legacy library requires an older package. No problem! Use the VersionOverride attribute on <PackageReference>:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" VersionOverride="7.0.0" />
</ItemGroup>
</Project>
This overrides only for that project while keeping the central file intact. It’s like allowing one dish in the kitchen to use a custom spice blend without rewriting the whole menu.
Gotcha Alert: Don’t forget to enable central management globally! Add
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>in a<PropertyGroup>at the solution level (e.g., inDirectory.Packages.propsor a sharedDirectory.Build.props). Without it, versions won’t resolve centrally, leading to restore errors. Also, ensure your .NET SDK is 6.0+โthis feature landed there.
Going Further: Managing Transitive Dependencies
Transitive dependencies (packages pulled in by your direct packages) can sneak in version conflicts. Enable transitive pinning to lock them down centrally:
In Directory.Packages.props:
<PropertyGroup>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
Now, you can pin transitive versions like this:
<ItemGroup>
<PackageVersion Include="Some.Transitive.Package" Version="1.2.3" />
</ItemGroup>
This prevents indirect upgrades from breaking your build. Real-world win: Upgrading a logging library won’t silently bump a shared serializer to an incompatible version.
Transitive pinning is like a family budget app that auto-locks subcategory spending. Your main “groceries” budget is set, but it also caps “snacks” to avoid overspendingโkeeping the whole household (solution) in harmony.
Wrapping Up
Directory.Packages.props transforms package management from a scattered chore into a streamlined, centralized process. It’s a game-changer for multi-project solutions, promoting consistency and saving hours on upgrades.
Start small: Add it to your next solution, centralize a few key packages, and watch the magic unfold.


Leave a Reply