NuGet
Manage NuGet Dependencies with Lock Files and Package Consumers
This article is part of our series on NuGet at Scale, also available as a chapter in our free, downloadable eBook.
It worked when I built it—or the classic—well, it works on my machine. You’ve probably heard (or said) these yourself, usually right before an endless spiral of debugging and frustrated sighs. More often than we’d like, NuGet packages and their dependencies are the sneaky culprits behind these headaches.
NuGet package dependencies might seem simple, but under the hood, they can get real messy, real fast. If you’re not keeping an eye on things, NuGet might resolve them in a way that leads to unwanted NuGet packages being pulled into your code, maybe even into production.
In this article, I’ll explain how unwanted NuGet packages can sneak into your code, how to use Lock Files to ensure that your applications only use your approved NuGet packages, and how to use Package Consumers to quickly identify which applications are using a specific package so you can update them when necessary.
The Unintended Side Effects of Dependency Resolution
Most NuGet packages depend on other packages, meaning every time you install one, you’re also growing a whole dependency tree that must be resolved for your project to build.

As you can see, things can get tricky fast—especially since even the wrong version of the right package will prevent dependency resolution. This is typically handled using version ranges.
Version ranges let you define a range of versions your package will accept. Super helpful for sorting out dependency trees—but they can come with some unintended side effects if you’re not careful.

Let’s say you are using the package Oracle.ManagedDataAccess.Core 23.8.0. This package is dependent on the System.DirectoryServices.Protocols package. To resolve this dependency, a version range is used to tell Oracle.ManagedDataAccess.Core to accept any version of System.DirectoryServices.Protocols that is greater than or equal to version 6.0.2.
Pretty convenient, right? It is, but it comes at a cost!

Take a look at the version history for the System.DirectoryServices.Protocols package. Version 9.0.6 dropped seven days ago (as of the time of writing), with version 10.0.0 is already in the works, and version 6.02 a lot further down the list. Since the version range for Oracle.ManageDataAccess.Core is set to accept any version north of 6.0.2 inclusive, your application would automatically include a new package—perhaps, an unwanted one.
Letting in unexpected third-party packages opens the door to two common issues:
1. Builds that suddenly break and leave you saying, “But it worked yesterday!”
2. Unsafe packages sneaking into your code.
Version ranges definitely make dependency resolution easier, but they can introduce chaos if you’re not careful. The good news? You can keep things under control with lock files.

Best Practice: Use Lock Files for Repeatable Builds
Lock files basically “lock down” every package version in your whole dependency tree at the moment you create the file. They give you a full snapshot of all the NuGet packages your app is using.
Why is this so helpful?
When the NuGet client installs dependencies at build time, it grabs the latest version it can find. That might not match what you originally intended when you were developing. If you don’t lock those versions down, your build could end up different the time you—or someone else on your team—builds it.
To enable the use of lock files with NuGet, set the MSBuild property RestorePackagesWithLockFile in your project file:
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
If this property is set, NuGet restore will generate a lock file – packages.lock.json file at the project root directory. Besides locking in the NuGet package versions, the lock file will also show all the packages your application depends on.

Lock files solve the massive headache of unpredictable NuGet package versions sneaking into production… but they don’t eliminate the problems with dependencies completely.
Use Lock Files in .NET 9 for Increased Reliability
And get this—in .NET 9, lock files become even more powerful thanks to an updated NuGet restore algorithm. This new resolver was completely rewritten to better handle complex networks of dependencies, building dependency graphs more efficiently and deterministically to significantly improve restore performance and memory usage—basically, you get faster, identical restores every time—and that’s especially valuable for your enterprise-scale apps and project-crammed repositories.
Now locked-mode restores are super reliable, since running RestorePackagesWithLockFile along with the --locked-mode flag, the NuGet CLI sticks to dependency versions specified in your lock file, failing anything that’s out of sync. This prevents unexpected changes from sneaking into your builds, making lock files a must-use feature in .NET 9 for scaling teams and projects.
Watch out for New Dependencies
Remember, dependencies can—and often will—have dependencies. And these dependencies can change over time, meaning that when you update, you can find yourself with totally different packages than before. And yeah—those packages might bring along some license issues, vulnerabilities, and quality risks.
The best way to mitigate this risk is by setting up a package approval workflow that only allows approved packages to be used by developers and build servers.
Watch out for New Vulnerabilities
Even after you’ve got NuGet lock files in place and a solid workflow to stick to approved packages, there’s always a chance a new vulnerability pops up in one of your dependencies.
This vulnerability could then impact any applications that use it. So what can you do?
You could manually inspect the lock files of all your applications to see which ones are using the unwanted dependency. That’s a lot of repositories to check out and a lot of searching.
Another option that pairs well with lock files is NuGet Audit. Introduced in .NET 8 and improved in .NET 9, this feature scans your project’s dependencies—including transitive ones—for known vulnerabilities, pulling data from the GHSA. Since lock files provide an exact snapshot of every package and version, using them makes NuGet Audits results far more reliable. With a lock file in place, you’ll know exactly which vulnerable packages are being flagged, without worrying about inconsistencies after restores.
But you can also use ProGet’s Package Consumer Feature to do this automatically.
Best Practice: Use Package Consumers to Track Dependencies
ProGet’s Package Consumers feature shows all the applications that are “consuming” or using a specific package. So let’s say the package dependency in question is System.DirectoryServices.Protocols 9.0.6.
After building your application, ProGet’s Package Consumers feature kicks in, using pgscan to scan the build output, track down exactly which package versions your app is using, and then publish that data—plus your app’s name and version—back to ProGet.

With Package Consumers, we can see that System.DirectoryServices.Protocols 9.0.6 is being used in the applications ProGetExtension, MyApplication, and ChocoMintoApp. Now you know exactly which apps will be impacted—and can jump in to make the fixes, fast!
By the way… If you were wondering about that System.DirectoryServices.Protocols package, it’s not just an example for theoretical purposes. Back when System.DirectoryServices.Protocols was in version 5.0.0, it was downloaded around 17.6M times before it was discovered to have a vulnerability, and the download number has risen to 30.8M since then!

Anyone with an app using a package dependent on version 5.0.0 now has a vulnerable package in their code, and anyone in that position definitely wants to know which applications make use of this package as quickly as possible. Of course, that’s exactly why we created package consumers 😀.
NuGet in the Enterprise
NuGet packages and dependency issues go hand in hand—an endless network of packages within packages almost guarantees a conflict somewhere, leading to broken apps and teams of frustrated developers.
Utilizing NuGet lock files will reduce dependency conflicts, but it’s not enough, since versions ranges mean vulnerable packages can still slip through. Luckily, ProGet’s package consumers let you track which of your apps are using those packages, letting you clean up problems fast.
A ton of info, right? Be sure to save this page somewhere for future reference! Or check out our free eBook, “NuGet at Scale”. It has everything in this article, and more on managing NuGet packages in your organization, from challenges with CI/CD & NuGet, to creating a package approval workflow! Download your free copy now!