NuGet
multiple projects
solutions
dependency management
package management

NuGet for solutions with multiple projects

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In a .NET solution with multiple projects, NuGet works at the project level, not at the solution level in the abstract. That sounds obvious, but it explains most confusion: every project has its own package references, and the solution is mainly the place where you coordinate versions, restore behavior, and shared build conventions.

Start with PackageReference

Modern .NET projects normally declare NuGet dependencies directly in the project file.

A simple example:

xml
1<Project Sdk="Microsoft.NET.Sdk">
2  <PropertyGroup>
3    <TargetFramework>net8.0</TargetFramework>
4  </PropertyGroup>
5
6  <ItemGroup>
7    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
8  </ItemGroup>
9</Project>

If three projects need the same package, each project usually gets its own PackageReference. NuGet restore then resolves the right dependency graph for each build.

That is important because different projects in the same solution may have different roles:

  • a web app
  • a class library
  • a test project
  • a console tool

Not every project should reference every package just because they live in the same solution.

Centralize Versions with Directory.Packages.props

When several projects need the same package version, repeating the version string in every .csproj quickly becomes noisy and error-prone. The modern answer is central package management.

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

xml
1<Project>
2  <ItemGroup>
3    <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
4    <PackageVersion Include="Serilog" Version="4.0.0" />
5  </ItemGroup>
6</Project>

Then each project can reference the package without repeating the version:

xml
<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" />
</ItemGroup>

This keeps versions consistent across the solution while still letting each project opt into only the packages it actually uses.

Reference Projects, Not Internal DLL Packages

Inside one solution, your own code should usually be connected with project references, not private NuGet packages.

Example:

xml
<ItemGroup>
  <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>

Use ProjectReference when:

  • the projects are developed together
  • they are versioned together
  • you want local changes to flow through during development

Use a NuGet package for an internal library only when you want an explicit publish and consume workflow across repositories, teams, or release boundaries.

Restore and Build the Whole Solution

NuGet restore can run on the whole solution, which is how you keep a multi-project workspace reproducible.

bash
dotnet restore MySolution.sln
dotnet build MySolution.sln

This restores all project dependencies and resolves transitive packages according to each project graph.

For example, if WebApp references CoreLibrary, and CoreLibrary references Dapper, WebApp may get access to that dependency transitively at build time depending on project type and asset flow. You should still keep direct dependencies explicit when your project actually uses them. That makes intent clearer and avoids surprises.

Handling Different Package Needs Across Projects

In a real solution, package usage is rarely uniform.

Example structure:

  • 'MyApp.Web needs ASP.NET Core packages'
  • 'MyApp.Core needs validation or serialization libraries'
  • 'MyApp.Tests needs xUnit and test helpers'

That means the right setup is usually not "install this package on the whole solution." It is "install this package on the projects that need it, while managing versions centrally where useful."

A test project might look like this:

xml
1<ItemGroup>
2  <PackageReference Include="Microsoft.NET.Test.Sdk" />
3  <PackageReference Include="xunit" />
4  <PackageReference Include="xunit.runner.visualstudio" />
5</ItemGroup>

Those packages belong in the test project only, even if the solution contains many projects.

Packing Your Own Project as a NuGet Package

If one project in the solution should become a reusable package for other solutions, you can pack it directly.

bash
dotnet pack src/MyLibrary/MyLibrary.csproj -c Release

That creates a .nupkg artifact you can publish to NuGet.org or to a private feed. This is separate from using NuGet to manage third-party dependencies inside the current solution.

Common Pitfalls

The biggest mistake is treating the solution as if it owned one shared package list for all projects. NuGet restore works through project dependency graphs, so package references still belong to individual projects.

Another issue is mixing old packages.config habits with modern PackageReference guidance. Current .NET projects are generally simpler and easier to manage with PackageReference and central package management.

Teams also over-share dependencies. Installing a package into every project "just in case" creates clutter and can make the dependency graph harder to maintain.

Finally, do not confuse project references with NuGet packaging strategy. Use ProjectReference for code that is developed together, and reserve internal NuGet packages for code that needs a deliberate published-version boundary.

Summary

  • NuGet dependencies are declared per project, even inside one solution.
  • Use PackageReference for modern .NET package management.
  • Use Directory.Packages.props to centralize package versions across projects.
  • Use ProjectReference for projects that are built together in the same solution.
  • Pack a project as a NuGet package only when you need a published artifact for external consumption.

Course illustration
Course illustration

All Rights Reserved.