PackageReference

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The C# Reference bibliography lists the reference material consulted while preparing these pages.

PackageReference is the item that declares a NuGet dependency in an SDK-style project. Its Version is covered on Package Versions and Ranges; this page covers everything else — which parts of a package flow where, the per-reference switches, multi-targeting and the pruning that .NET 10 added — with NuGet 7.9.0 in .NET SDK 10.0.402.

The basics

<ItemGroup>
  <PackageReference Include="Humanizer.Core" Version="2.14.1" />
  <PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
</ItemGroup>

A Condition on $(TargetFramework) restricts a reference to one framework of a multi-targeted project — Ledgerline uses exactly this for its netstandard2.0 polyfills. In a repository with Central Package Management the Version moves to Directory.Packages.props (Central Package Management).

Controlling assets

Three metadata decide which asset groups of a package (How Package Installation Works) the project uses and which flow to projects or packages that depend on it:

Metadata Meaning Default

IncludeAssets

assets this project consumes

all

ExcludeAssets

assets this project does not consume

none

PrivateAssets

assets consumed here but not flowed to dependents

contentfiles;analyzers;build

Values: compile, runtime, contentFiles, build, buildMultitargeting, buildTransitive, analyzers, native, none, all.

The effect is clearest on a packed library. Pa references three packages:

<ItemGroup>
  <PackageReference Include="Ledgerline.Money" Version="2.0.0" />
  <PackageReference Include="Humanizer.Core" Version="2.14.1" PrivateAssets="all" />
  <PackageReference Include="Ledgerline.Rates" Version="1.0.0" ExcludeAssets="compile" />
</ItemGroup>
dotnet pack -o out && unzip -p out/Pa.1.0.0.nupkg Pa.nuspec
<dependencies>
  <group targetFramework="net10.0">
    <dependency id="Ledgerline.Money" version="2.0.0" exclude="Build,Analyzers" />
    <dependency id="Ledgerline.Rates" version="1.0.0" exclude="Compile,Build,Analyzers" />
  </group>
</dependencies>
  • Default — Ledgerline.Money is a dependency of the package, with Build,Analyzers excluded: the default PrivateAssets keeps build logic and analyzers from flowing to consumers.

  • PrivateAssets="all" — Humanizer.Core disappeared from the package’s dependencies. That is right for build-time-only packages (analyzers, Source Link, Microsoft.Build.* for tasks), and wrong here: Pa calls Humanizer at run time, so every consumer would fail with FileNotFoundException.

  • ExcludeAssets="compile" — excluded for this project and recorded as excluded for consumers too.

Per-reference switches

<ItemGroup>
  <PackageReference Include="Humanizer.Core" Version="2.14.1" GeneratePathProperty="true" />
  <PackageReference Include="Ledgerline.Legacy" Version="1.0.0" NoWarn="NU1701" />
  <PackageReference Include="Ledgerline.Money" Version="2.0.0" Aliases="MoneyV2" />
</ItemGroup>
  • GeneratePathProperty creates $(Pkg<Id with . replaced by _>) pointing at the package’s folder, for build logic that needs a file from a package:

    dotnet msbuild -getProperty:PkgHumanizer_Core
    ~/.nuget/packages/humanizer.core/2.14.1
  • NoWarn on the item suppresses a NuGet warning for that package only — the NU1701 of the net48-only package disappeared from the restore while NU1701 for any other package would still be reported.

  • Aliases puts the package’s assemblies under an extern alias (extern alias MoneyV2;) instead of the global namespace, for two packages that define the same type names.

  • IsImplicitlyDefined is set by SDK authors on references an SDK adds automatically: it tells NuGet tooling the version is controlled by the SDK, so the Visual Studio package manager and dotnet package update do not offer upgrades for it.

AssetTargetFallback

When a package has no assets compatible with the project’s framework, AssetTargetFallback lists frameworks to try instead. For .NET projects the SDK sets it to the .NET Framework versions, which is how Ledgerline.Legacy (only lib/net48) restored into a net10.0 project — with NU1701 naming every fallback framework (Dependency Resolution). Adding to it (<AssetTargetFallback>$(AssetTargetFallback);…</AssetTargetFallback>) is rarely the right fix; a compatible package version is.

Duplicate frameworks

From NuGet 7.6 (SDK 10.0.300) a project can list the same framework twice under different aliases in TargetFrameworks — for example to build against two versions of a dependency — and NuGet keeps the graphs apart; invalid alias combinations are reported as NU1018 and NU1019 (Target Frameworks and Runtime Identifiers).

Package pruning

Many packages that once filled gaps — System.Text.Json, System.Memory, System.Collections.Immutable — are part of the shared framework in modern .NET. Pruning (on by default for net10.0 and later) removes such packages from the restore graph when the framework already provides them. Ledgerline.Json is a netstandard2.0 package that depends on System.Text.Json 8.0.5 and System.Memory 4.5.5:

dotnet restore --force -p:RestoreEnablePackagePruning=true
dotnet restore --force -p:RestoreEnablePackagePruning=false
pruning=true  -> Ledgerline.Json/1.0.0
pruning=false -> Ledgerline.Json/1.0.0, System.Memory/4.5.5, System.Text.Json/8.0.5

(the packages in the assets file’s net10.0 target). With pruning, the old System.Text.Json 8.0.5 never reaches the build — and no vulnerability audit warning about it can appear either. A direct reference to a framework-provided package is not pruned but reported:

~/ledgerline/samples/Deps/Deps.csproj : warning NU1510: PackageReference System.Text.Json will not be pruned. This package is automatically available and does not need to be referenced explicitly. Remove the PackageReference item.

PrunePackageReference items (normally supplied by the SDK) list what is pruned and up to which version. The .NET 10 what’s-new page presents pruning under the NuGet Audit feature, but it is a restore feature: it changes the graph that is built, not only what is audited.

PackageDownload

PackageDownload downloads a package into the global-packages folder without referencing it — no assets, no dependencies — for build steps that need its files (a code generator, a native tool). Its version must be exact:

<PackageDownload Include="Ledgerline.Money" Version="[1.5.0]" />

restored with no package in the target graph and the download recorded in the assets file ('version': '[1.5.0, 1.5.0]'). Written as Version="1.5.0", the restore failed without any NuGet message:

~/.dotnet/sdk/10.0.402/NuGet.targets(198,5): error MSB4181: The "RestoreTask" task returned false but did not log an error. [~/ledgerline/samples/Deps/Deps.csproj]

Common mistakes

  • PrivateAssets="all" on a runtime dependency of a library — it vanishes from the package.

  • Referencing System.Text.Json and friends in net10.0 projects — NU1510; remove the reference.

  • Project-wide NoWarn for a package-specific warning — use the item’s NoWarn.

  • A non-exact PackageDownload version — MSB4181 with no explanation.