<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sang Au's Blog]]></title><description><![CDATA[Sang Au - That's me! Daddy, Husband, Coder, Photographer]]></description><link>https://auvansang.me</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 22:15:47 GMT</lastBuildDate><atom:link href="https://auvansang.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Centralized Package Management in .NET Core]]></title><description><![CDATA[💡
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 ...]]></description><link>https://auvansang.me/centralized-package-management-in-net-core</link><guid isPermaLink="true">https://auvansang.me/centralized-package-management-in-net-core</guid><category><![CDATA[.NET]]></category><category><![CDATA[Nuget]]></category><category><![CDATA[msbuild]]></category><category><![CDATA[package management]]></category><dc:creator><![CDATA[Sang Au]]></dc:creator><pubDate>Thu, 17 Apr 2025 10:40:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744887034756/24b06b49-14ba-4203-ae36-e0d87685ace3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">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.</div>
</div>

<h3 id="heading-problem">Problem ❗</h3>
<p>In multi-project .NET solutions, developers face the following challenges:</p>
<ul>
<li><p><strong>Inconsistent package versions</strong>: Different projects reference different versions of the same package.</p>
</li>
<li><p><strong>Version conflicts</strong>: Resolving version mismatch issues is time-consuming and error-prone.</p>
</li>
<li><p><strong>Duplication</strong>: Updating package versions across multiple <code>.csproj</code> files is tedious.</p>
</li>
</ul>
<p>Without centralized management, maintenance becomes complex, especially in microservice architectures or layered applications.</p>
<h3 id="heading-solution">Solution 💡</h3>
<p>Centralized Package Management (CPM) allows developers to define package versions in a single <code>Directory.Packages.props</code> file. Introduced in .NET 5 and enhanced in later versions, this feature improves maintainability and avoids duplication.</p>
<p>With CPM, you:</p>
<ul>
<li><p>Define package versions once.</p>
</li>
<li><p>Consume packages in project files without repeating versions.</p>
</li>
<li><p>Maintain consistency across all projects in your solution.</p>
</li>
</ul>
<h3 id="heading-how-it-works">How It Works ⚙️</h3>
<p>The core idea of CPM is to separate <strong>versioning</strong> from <strong>referencing</strong>. Here's how it works:</p>
<ol>
<li><p>Create a <code>Directory.Packages.props</code> file in the solution root.</p>
</li>
<li><p>Add package versions using <code>&lt;PackageVersion&gt;</code> elements.</p>
</li>
<li><p>Reference packages in project files without specifying the version.</p>
</li>
<li><p>MSBuild automatically merges the version from the centralized file during build.</p>
</li>
</ol>
<p><strong>Example</strong> <code>Directory.Packages.props</code><strong>:</strong></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageVersion</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Serilog"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"2.12.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageVersion</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.Extensions.Logging"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"7.0.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p><strong>Example</strong> <code>MyApp.csproj</code><strong>:</strong></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Serilog"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.Extensions.Logging"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<h3 id="heading-benefit">Benefit 🎯</h3>
<p>Using CPM brings several key advantages:</p>
<ul>
<li><p><strong>Consistency</strong>: All projects use the same package versions.</p>
</li>
<li><p><strong>Maintainability</strong>: Update versions in one place.</p>
</li>
<li><p><strong>Reduced Conflicts</strong>: Eliminate ambiguity in dependency resolution.</p>
</li>
<li><p><strong>Cleaner Project Files</strong>: Project files focus on purpose, not versions.</p>
</li>
<li><p><strong>Support for Transitive Pinning</strong>: Control transitive package versions explicitly.</p>
</li>
</ul>
<h3 id="heading-implement">Implement 🚀</h3>
<p>Follow these steps to enable CPM in your solution:</p>
<ol>
<li><p><strong>Create the configuration file</strong>:</p>
<p> Crate the <code>Directory.Packages.props</code> at the root folder of the solution</p>
<pre><code class="lang-bash"> touch Directory.Packages.props
</code></pre>
</li>
<li><p><strong>Add package versions</strong>:</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">Project</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">PackageVersion</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"AutoMapper"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"12.0.0"</span> /&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">PackageVersion</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"FluentValidation"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"11.5.1"</span> /&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
</li>
<li><p><strong>Update</strong> <code>.csproj</code> <strong>files</strong>: Remove <code>Version</code> attributes from <code>&lt;PackageReference&gt;</code> elements.</p>
</li>
<li><p><strong>Build the solution</strong>: MSBuild will now resolve versions from <code>Directory.Packages.props</code>.</p>
</li>
</ol>
<h3 id="heading-advanced">Advanced 🧠</h3>
<h4 id="heading-overriding-package-versions">📌 Overriding Package Versions</h4>
<p>In some cases, you may want to override the centralized version in a specific project. To do this, explicitly specify the version using <code>VersionOverride</code> in the project file:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Serilog"</span> <span class="hljs-attr">VersionOverride</span>=<span class="hljs-string">"2.11.0"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
</code></pre>
<blockquote>
<p>Note: MSBuild uses the closest version definition when resolving dependencies. A version declared in a <code>.csproj</code> will override what's in <code>Directory.Packages.props</code>.</p>
</blockquote>
<h4 id="heading-global-package-references">🌐 Global Package References</h4>
<p>You can define <strong>global package references</strong> in the <code>Directory.Packages.props</code> file using <code>&lt;GlobalPackageReference&gt;</code> instead of <code>&lt;PackageReference&gt;</code>. This ensures certain packages are included in all projects without manually adding them to each <code>.csproj</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">GlobalPackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"StyleCop.Analyzers"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"1.2.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<blockquote>
<p>Global package references are added to the PackageReference item group with the following metadata:</p>
<ul>
<li><p><code>IncludeAssets="Runtime;Build;Native;contentFiles;Analyzers"</code><br />  This ensures that the package is only used as a development dependency and prevents any compile-time assembly references.</p>
</li>
<li><p><code>PrivateAssets="All"</code><br />  This prevents global package references from being picked up by downstream dependencies.</p>
</li>
</ul>
</blockquote>
<p>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.</p>
<h3 id="heading-best-practices">Best Practices ✅</h3>
<ul>
<li><p><strong>Place</strong> <code>Directory.Packages.props</code> <strong>at the solution root</strong> to apply it broadly.</p>
</li>
<li><p><strong>Avoid version duplication</strong> across other <code>.csproj</code> files.</p>
</li>
<li><p><strong>Commit the centralized file to version control</strong>.</p>
</li>
<li><p><strong>Use clear versioning policies</strong>, such as pinning to stable releases.</p>
</li>
<li><p><strong>Use</strong> <code>dotnet list package --outdated</code> to track outdated packages centrally.</p>
</li>
<li><p><strong>Test builds after every version bump</strong>.</p>
</li>
</ul>
<h3 id="heading-conclusion">Conclusion 🧾</h3>
<p>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.</p>
<h3 id="heading-references">References 📚</h3>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management">Microsoft Docs: Central Package Management</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild?view=vs-2022">MSBuild Docs</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/nuget/">NuGet Docs</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>