Debugging a .NET Assembly Delivered as a NuGet Package

If you’ve ever had to debug a .NET library that’s deployed via NuGet, you’ve probably hit this loop:

  1. Change code in the library

  2. Build

  3. Pack

  4. Publish (or at least produce a .nupkg)

  5. Update the consuming app to the new version

  6. Restore

  7. Rebuild

  8. Finally debug

  9. Repeat… forever

It works, but it’s slow and it pollutes your package feed (or your local folder feed) with “debug build” versions that you’ll never want again.

There’s a faster workflow that’s great for tight iteration: replace the DLL directly inside the NuGet global packages cache (.nuget cache) and rebuild your consuming project. Done right, you can iterate in seconds without constantly re-versioning packages.

This post walks through the approach, how the .nuget cache is laid out, what to watch out for, and a couple of tips to keep it painless.

The NuGet global packages cache: what it is and why it matters

When you install a package, NuGet downloads and extracts it into a global folder called the global packages folder (often referred to as “the NuGet cache”, though NuGet has multiple caches).

By default, it lives at:

  • Windows: C:/Users/<you>/.nuget/packages

  • Linux/macOS: ~/.nuget/packages

Inside that folder, packages are stored like this:

 .nuget/packages/<packageid>/<version>/<package files...>
 

So if you depend on Contoso.WidgetEngine version 2.3.1, you’ll likely find:

~/.nuget/packages/contoso.widgetengine/2.3.1/
 

And inside there, you’ll see the lib/ folder (and maybe runtimes/, build/, etc.) exactly as shipped in the .nupkg.

That matters because your consuming project typically resolves reference assemblies from here during restore/build.

The “replace the DLL” workflow in one sentence

Build the library DLL locally → copy it over the DLL inside the package’s lib/<TFM>/ folder in .nuget/packages → rebuild the consuming project. This avoids:

  • generating a new .nupkg

  • changing the version

  • editing PackageReferences

  • restoring new packages every time

Step-by-step: replacing the DLL in .nuget/packages

1) Identify the exact package + version you’re consuming

In the consuming project, check the PackageReference:

 <ItemGroup>
<PackageReference Include="Contoso.WidgetEngine" Version="2.3.1" />
</ItemGroup>
 

You must match:

  • package id

  • version

  • target framework that the consuming app uses

2) Find the package in the global packages folder

Navigate to:

C:Users<you>.nugetpackagescontoso.widgetengine2.3.1

(or ~/.nuget/packages/...)

Inside, look for lib/:

 lib/net8.0/Contoso.WidgetEngine.dll
 

3) Build your library locally (Debug configuration)

Build your library project so you have an updated DLL:

bin/Debug/<tfm>/YourLibrary.dll

4) Copy the DLL into the cache folder

Replace the package’s DLL with your freshly built one:

From:

bin/Debug/net8.0/Contoso.WidgetEngine.dll
 To:
.nuget/packages/contoso.widgetengine2.3.1/libnet8.0

(Tip: keep a backup of the original files if you want to revert quickly)

5) Rebuild the consuming project

Now rebuild the consuming project (or the full solution). In many cases you do not need to restore again—just rebuild.

If you’re using Visual Studio, a Rebuild is usually enough. From CLI:

dotnet build

At this point, your app is referencing the same package/version, but the actual bits on disk are your modified assembly.

Why you often need a rebuild (and why sometimes it still “doesn’t update”)

The consuming project may copy assemblies to output

Most apps will copy dependencies to bin/Debug/... at build time. If the consuming app already copied an older version earlier, you want to force it to refresh.

What helps:

  • Rebuild (not just Build)

  • deleting the consuming app’s bin/ and obj/ folders

  • ensuring the file timestamp changes on the replaced DLL (copying usually does)

NuGet restore won’t overwrite your modified DLL

Restore generally treats the global packages folder as immutable for that version. It won’t “fix” your manual changes unless you explicitly clear caches or force re-download.

That’s good for this workflow—but it can also surprise you later when you forget you modified it.

When this approach is the right tool (and when it isn’t)

Great for:

  • quick iteration while debugging package internals

  • validating a fix before you bother packaging it properly

  • investigating production behavior when the package was the culprit

Not great for:

  • sharing the debug build with teammates (their cache won’t match yours)

  • CI/build servers (they should stay deterministic)

  • long-term usage (it’s easy to forget you patched your cache)

This is a local developer productivity trick, not a distribution strategy.

Cleaning up: how to revert back to “real NuGet”

When you’re done debugging, you’ll likely want to undo your local patch.

Options:

  1. Restore the original DLL from a backup (fastest if you made one)

  2. Delete the package version folder and restore again
    Example:

    • delete .nuget/packages/contoso.widgetengine/2.3.1/

    • run dotnet restore

  3. Clear relevant NuGet caches (more aggressive; usually not needed)

Practical tips to make this painless

Keep your library build output aligned with the package TFM

If the package provides net6.0 and net8.0 assemblies, replace the one your consumer actually uses.

Automate the copy step with a script

Even a tiny script beats manual drag-and-drop. Common patterns:

  • a PowerShell script that copies the DLL to the right lib/<tfm> folder

  • a post-build target in the library project (use carefully)

Add a big reminder to yourself

It’s easy to forget you’ve “hot-patched” your .nuget cache and later wonder why behavior doesn’t match what’s in source control. A sticky note in the repo README or a small script named PATCH-NUGET-CACHE.ps1 can save you.

Summary

If you’re repeatedly debugging a .NET library delivered as a NuGet package, you don’t need to bump package versions for every debug build.

A fast iteration loop is:

  1. Build the library locally (Debug)

  2. Replace the DLL inside:
    ~/.nuget/packages/<id>/<version>/lib/<tfm>/

  3. Rebuild the consuming project

  4. Debug normally

It’s simple, effective, and dramatically faster than packaging and versioning every time—just remember to clean up when you’re done.