Why does my .NET Standard NuGet package trigger so many dependencies?

.NetNuget.Net Standard

.Net Problem Overview


I've been mucking about with a .NET Standard project and NuGet. I've got a working project and have uploaded it to NuGet.org. My project targets .NET Standard 1.3, which should support .NET Framework 4.6 and .NET Core 1.0.

But when I tried to add my project (via NuGet) to a fresh .NET Framework 4.6 project, the dependencies resolved to 47 packages! They're all system libraries and appear to be dependencies of either Microsoft.NETCore.Platforms or NETStandard.Library 1.6.1. (Gist of full PM output.)

My project only imports (using) a handful of libraries, none of which I added manually; i.e. they're all libraries that "came with" .NET Standard. These libraries are:

  1. System
  2. System.Text
  3. System.Reflection
  4. System.Linq
  5. System.Collections.Generic;

The thing is, I decided to make my project target .NET Standard because I wanted it to work seamlessly across .NET Framework and .NET Core applications. I thought the whole point of Standard was to set a minimum level of compatibility. By extension, I suppose I had assumed (perhaps erroneously) that libraries like System.Console would be automatically available in either Core or Framework.

I didn't notice anything like this when I tested my Standard project as a dependency in a Framework and Core project within the same solution, so I'm suspicious that this might be a NuGet thing.

What's really going on here? And how can I make my .NET Standard library available on NuGet without a huge list of dependencies?

Is it a problem with the way I've specified my NuGet package? Or have I fundamentally misunderstood something?

.Net Solutions


Solution 1 - .Net

You haven't done anything wrong, this is expected to happen. If you want nothing more than your own DLL being added to a new .NET Framework project, you have to target .NET Standard 2.0 for your library wait for a .NET Framework version that natively supports both the API and assembly versions - which is going to be 4.7.2 (while .NET Framework 4.7.1 supports all the APIs, there were bugs with how some assemblies are versioned and so the tooling (VS 2017 15.5+) will add additional assemblies to fix that).

What you are seeing are side effects of how .NET Standard is built and the support for the supported frameworks is implemented. This is also different based on the .NET Standard version you target and the tooling used to reference the library package.

In .NET Standard < 2.0, you reference the NETStandard.Library meta-package which in turn references additional (System.*) packages. Those packages contain the reference assemblies that make up the ".NET Standard Contract" - a set of APIs and the assembly names + versions.

When the NuGet package you create for .NET Standard 1.0-1.6 is then referenced by an application, these individual packages don't bring in the reference assemblies but rather implementation assemblies for the framework that the application targets.

For .NET Core, these match the assemblies that are already part of the runtime so the DLL files won't end up next to the built application. This changed however when a new set of packages was released for .NET Core 1.1 (NETStandard.Library version 1.6.1). This resulted in applications built for .NET Core 1.0 ending up getting newer implementation assemblies that were meant to be included in .NET Core 1.1 (luckily, 1.1 was then made the "long-term support" version since that sparked a discussion about which assemblies are part of the LTS promise).

On .NET Framework these libraries (with some exceptions like System.Net.Http) don't do much - they just forward to the system assemblies. So for example the "contract" defines that System.Object is defined in a System.Runtime.dll assembly. So the System.Runtime.dll file you end up with in a .NET Framework application contains a System.Runtime.dll that contains type forward to .NET Framework's mscorlib.dll. .NET Core already contains a different System.Runtime.dll that does something different for that platform. This mechanism allows for a single DLL file to work on both platforms since those type forwards and additional implementations assure the same "contract" (types + assemblies + assembly versions) working on both implementations.

.NET Standard 2.0 aimed to reduce the number of packages and DLLs being necessary and also to remove requiring updates to NETStandard.Library whenever a new .NET Core version is released.

So for .NET Standard 2.0 and .NET Core 2.0, the NETStandard.Library package only brings reference assemblies for compiling code to a project, but the resulting NuGet package no longer depends on this package. So when you create a library targeting .NET Standard 2.0 and publish it, it will have no NuGet dependencies (unless you add additional ones).

The logic of what "support libraries" to bring in when consuming a .NET Standard library was moved to the tooling that is used during build. So when a library that contains a reference to a netstandard.dll is added to a .NET Framework project, the tooling will then add necessary support DLLs based on the version of .NET Framework being used. This was done for .NET Standard 2.0 as well as .NET Standard 1.5+ since .NET Framework 4.6.1 was retroactively made compatible with .NET Standard 2.0 (was 1.4 previously) through these kinds of DLL files. The same tooling also makes sure that even if NuGet packages are somehow brought in to such an application project, any .NET Standard implementation libraries brought in via NuGet are removed from the build. So if you reference a .NET Standard 1.0 NuGet package that was built when .NET Core 1.0 was released, all its NuGet dependencies are trimmed out and you get the support libraries shipped with the build tooling instead.

The idea was that .NET Framework 4.7.1 would contain all the necessary assemblies "inbox" so that a netstandard.dll, System.Runtime.dll etc. are part of .NET Framework and any .NET Standard 1.0-2.0 DLL file would "just work", the problem was that these "inbox" dll files had a too low version number for some assemblies so libraries would fail to load - this was fixed by changing the tooling again to include DLL files with higher version numbers as support libraries which in turn forward to the "inbox" .NET Framework assemblies. This is planned to be fixed in .NET Framework 4.7.2.

Solution 2 - .Net

I just ran into this problem as well. The blog post you linked in a comment to Martin Ullrich's answer led me to a solution that worked for me: Using NuGet multi-targeting. By changing:

<TargetFramework>netstandard1.0</TargetFramework>

to

<TargetFrameworks>netstandard1.0;netstandard2.0;net45</TargetFrameworks>

in the project .csproj file. This causes the project to be built separately for each target framework and the resulting NuGet package only depends on NETStandard.Library for netstandard1.0. Since NuGet chooses the net45 binaries for any full .NET Framework version, this avoids the unnecessary dependencies when installing the package.

Solution 3 - .Net

You Can Pick .Net 4.7.2, that will solve annoying dependencies

for more info: https://weblog.west-wind.com/posts/2019/Feb/19/Using-NET-Standard-with-Full-Framework-NET

Solution 4 - .Net

If you're on .NET 4.6 and you're trying to figure out which ones you need to deploy, search your CSPROJ file for \System. (not a regex) - they are the ones in packages that need to be copied with your app, the rest should be framework DLLs.

To test this theory get rid of them in your local build and run that to make sure the deployed version won't break...

  • In the bin folder, do dir /b System*.dll > textfile.txt to get a list of the DLLs.
  • inserted "DEL " in front of all the names,
  • I also found Microsoft.Win32.Primitives.dll & netstandard.dll which weren't needed in 4.6 either.
  • and save it as a .CMD file - uh, not in the bin folder ok?
  • add it as a post-build process. $(ProjectDir)DeleteSuperfluousSystemDlls.cmd

I'd love to get off .NET 4.6 but AutoCAD breaks badly when the framework is too modern for it.

edit...

Here is some copy-paste ...

Step 1 - in your CSPROJ file ...

<!--https://stackoverflow.com/questions/2387456/msbuild-exec-task-without-blocking/21181071#21181071-->
  <!--Launch a Process in Parallel-->
  <UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <!--The file path is the full path to the executable file to run-->
      <FilePath ParameterType="System.String" Required="true" />
      <!--The arguments should contain all the command line arguments that need to be sent to the application-->
      <Arguments ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[
  System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo(FilePath, Arguments);
  processStartInfo.UseShellExecute = true;
  System.Diagnostics.Process.Start(processStartInfo);
  ]]></Code>
    </Task>
  </UsingTask>
  <Target Name="AfterBuild">
    <ExecAsync FilePath="$(ProjectDir)\Deployment\DeleteSuperfluousSystemDlls.cmd" Arguments="$(TargetDir)" />
  </Target>

Step 2. The batch file ...

Edit the list created by dir /b System*.dll > textfile.txt to look a lot like

del %1Microsoft.Win32.Primitives.dll
del %1netstandard.dll
del %1System.AppContext.dll
del %1System.Collections.Concurrent.dll
del %1System.Collections.dll
del %1System.Collections.NonGeneric.dll
del %1System.Collections.Specialized.dll
del %1System.ComponentModel.dll

but don't forget to remove the ones actually need so they don't get deleted.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTom WrightView Question on Stackoverflow
Solution 1 - .NetMartin UllrichView Answer on Stackoverflow
Solution 2 - .NetKevinoidView Answer on Stackoverflow
Solution 3 - .NetCorrMView Answer on Stackoverflow
Solution 4 - .NetCAD blokeView Answer on Stackoverflow