NuGet and Library Development

Concept

NuGet is .NET's package manager. A package (.nupkg, a zip file) contains compiled assemblies per target framework, metadata (id, version, authors, license, description, dependencies) and optionally documentation, analyzers and build files.

<PackageReference Include="System.IO.Hashing" Version="10.0.12" /> in the .csproj; added with dotnet add package

dotnet restore (run automatically by build/run/test) downloads packages into the global packages folder (%USERPROFILE%\.nuget\packages) Transitive dependencies

Dependencies of your dependencies are restored automatically; dotnet list package --include-transitive shows them Package sources nuget.org by default; nuget.config adds private feeds (a folder, file share, Azure Artifacts, GitHub Packages)

Semantic versioning MAJOR.MINOR.PATCH[-prerelease]: breaking change → major, new feature → minor, fix → patch Version ranges

Version="10.0.12" means *≥ 10.0.12* (lowest applicable wins); [1.2,2.0) is an explicit range Central package management

Directory.Packages.props with <ManagePackageVersionsCentrally> keeps versions consistent across many projects Lock files

RestorePackagesWithLockFile for reproducible restores in CI

Restore audits packages against the vulnerability database (NU1901–NU1904 warnings); dotnet list package --vulnerable

Packages can be author-signed (dotnet nuget sign with a code-signing certificate) and are repository-signed by nuget.org; dotnet nuget verify checks signatures — part of supply-chain trust

dotnet pack creates the .nupkg; dotnet nuget push pkg.nupkg --source <feed> --api-key <key> publishes it Library Design Checklist

Target the lowest framework your consumers need (net8.0;net10.0, or netstandard2.0 for very broad reach) via <TargetFrameworks>.

Keep the public API small, documented (<GenerateDocumentationFile>) and stable; breaking changes require a major version.

Avoid heavy dependencies; each one becomes your consumers' transitive dependency.

Add a README, license expression, repository URL, Source Link and symbols (.snupkg) for debuggable packages.

Why It Matters

Teams share signal decoders, protocol stacks and test utilities as internal packages on a private feed, and every application depends on dozens of public packages. Versioning and vulnerability auditing are part of software supply-chain security.

Syntax

dotnet add package System.IO.Hashing --version 10.0.12
dotnet pack -c Release -o ./feed
dotnet nuget push ./feed/MyLib.1.2.0.nupkg --source https://my.feed/v3/index.json --api-key $KEY
dotnet list package --include-transitive --vulnerable --outdated

Example

46.1 Create a library package, publish it to a local feed, and consume it

The library project with package metadata and one dependency:

A nuget.config in the consuming project adds the local folder as a package source (a private feed):

The consuming console app's Program.cs:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

    <!-- NuGet package metadata -->
    <PackageId>SampleApp.Units</PackageId>
    <Version>1.2.0</Version>
    <Authors>SampleApp</Authors>
    <Description>Signal conversion and CRC helpers for vehicle diagnostics.</Description>
    <PackageTags>automotive;can;diagnostics</PackageTags>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <!-- a dependency: consumers get it transitively -->
    <PackageReference Include="System.IO.Hashing" Version="10.0.12" />
  </ItemGroup>

</Project>
using System.IO.Hashing;

namespace SampleApp.Units;

/// <summary>Conversions between raw CAN signal values and physical values.</summary>
public static class Signals
{
    /// <summary>physical = raw * factor + offset</summary>
    public static double ToPhysical(long raw, double factor, double offset) => raw * factor + offset;

    /// <summary>raw = (physical - offset) / factor, rounded to the nearest integer.</summary>
    public static long ToRaw(double physical, double factor, double offset) => (long)Math.Round((physical - offset) / factor);

    /// <summary>CRC-32 (IEEE) of a flash block, via the System.IO.Hashing package.</summary>
    public static uint Crc32Of(ReadOnlySpan<byte> data) => Crc32.HashToUInt32(data);
}
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!-- a local folder works as a private feed; so does a file share, Azure Artifacts or GitHub Packages -->
    <add key="local" value="../feed" />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>
using SampleApp.Units;

double rpm = Signals.ToPhysical(raw: 0x1F40, factor: 0.125, offset: 0);
Console.WriteLine($"engine speed: {rpm} rpm");
Console.WriteLine($"raw for 90 degC (factor 1, offset -40): {Signals.ToRaw(90, 1, -40)}");
Console.WriteLine($"CRC-32 of '123456789': 0x{Signals.Crc32Of("123456789"u8):X8}");

Expected Result

The commands and their real output (the demo folder is shown as C:\demo; the source files above were copied into place between the commands):

dotnet pack wrote feed/SampleApp.Units.1.2.0.nupkg silently (-v q). The consumer never referenced System.IO.Hashing directly, yet it appears as a transitive package, and the CRC-32 of "123456789" is 0xCBF43926, the standard check value for CRC-32.

> dotnet pack SampleApp.Units -c Release -o feed -nologo -v q
> dotnet new console -n DiagTool -o DiagTool
The template "Console App" was created successfully.

Processing post-creation actions...
Restoring C:\demo\DiagTool\DiagTool.csproj:
  Determining projects to restore...
  Restored C:\demo\DiagTool\DiagTool.csproj (in 79 ms).
Restore succeeded.
> dotnet add DiagTool package SampleApp.Units --version 1.2.0
info : X.509 certificate chain validation will use the default trust store selected by .NET for code signing.
info : X.509 certificate chain validation will use the default trust store selected by .NET for timestamping.
info : Adding PackageReference for package 'SampleApp.Units' into project 'C:\demo\DiagTool\DiagTool.csproj'.
info : Restoring packages for C:\demo\DiagTool\DiagTool.csproj...
info :   CACHE https://api.nuget.org/v3/vulnerabilities/index.json
info :   CACHE https://api.nuget.org/v3-vulnerabilities/2026.09.26.05.43.06/vulnerability.base.json
info :   CACHE https://api.nuget.org/v3-vulnerabilities/2026.09.26.05.43.06/2026.09.29.23.43.22/vulnerability.update.json
info : Package 'SampleApp.Units' is compatible with all the specified frameworks in project 'C:\demo
…

Common Mistakes

Referencing DLLs directly (<Reference HintPath=...>) instead of packages.

Floating versions (1.*) in production — non-reproducible builds.

Ignoring NU190x vulnerability warnings.

Publishing breaking changes as a minor/patch version.

Committing API keys for nuget push; storing credentials for private feeds in a committed nuget.config.

Different projects in one solution using different versions of the same package.

Best Practices

Semantic versioning, changelogs and Source Link for every internal package.

Central package management for multi-project solutions.

Package source mapping (<packageSourceMapping>) so internal package ids can only come from the internal feed (prevents dependency confusion attacks).

Run dotnet list package --vulnerable in CI.

Interview Relevance

Medium: "what is a transitive dependency?", "how does NuGet choose a version?", "how do you host private packages?", "what is semantic versioning?", "what is dependency confusion?".

Practice Questions

Multi-target the library for net8.0;net10.0 and inspect the lib folders inside the .nupkg (it is a zip).

Publish version 1.3.0 with a new method and update the consumer with dotnet add package.

Convert a two-project solution to central package management.

Add package source mapping so SampleApp.* comes only from the local feed.

Quick Quiz

Q1. (Multiple choice) A bug fix that doesn't change the public API should increment: A) Major B) Minor C) Patch D) Nothing Answer: C.

Q2. (True/False) A consumer of SampleApp.Units must add System.IO.Hashing itself.

Answer: False. It is restored transitively (verified by dotnet list package --include-transitive).

Q3. (Predict the output) <PackageReference Include="X" Version="2.1.0" /> — which versions are acceptable to NuGet?

Answer: 2.1.0 or higher (a minimum version); NuGet picks the lowest available version that satisfies all constraints.

Q4. (Find the bug) A team's internal package Contoso.Can is suddenly resolved from nuget.org with version 99.0.0.

Answer: Dependency confusion: a public package with the same id and a higher version won. Use package source mapping and reserved prefixes.

Coding Challenge

Write the Directory.Packages.props file and the matching .csproj item for central package management of System.IO.Hashing 10.0.12.

<!-- Directory.Packages.props (solution root) -->
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="System.IO.Hashing" Version="10.0.12" />
  </ItemGroup>
</Project>

<!-- in each .csproj: no Version attribute -->
<ItemGroup>
  <PackageReference Include="System.IO.Hashing" />
</ItemGroup>