Reducing Package Dependencies with project.json

This article covers what you need to know about reducing your package dependencies when authoring project.json libraries. By the end of this article, you will learn how to compose your library such that it only uses the dependencies it needs.

Why it's Important

.NET Core is a product made up of NuGet packages. An essential package is the .NETStandard.Library metapackage, which is a NuGet package composed of other packages. It provides you with the set of packages that are guaranteed to work on multiple .NET implementations, such as .NET Framework, .NET Core and Xamarin/Mono.

However, there's a good chance that your library won't use every single package it contains. When authoring a library and distributing it over NuGet, it's a best practice to "trim" your dependencies down to only the packages you actually use. This results in a smaller overall footprint for NuGet packages.

How to do it

Currently, there is no official dotnet command which trims package references. Instead, you'll have to do it manually. The general process looks like the following:

  1. Reference NETStandard.Library version 1.6.0 in a dependencies section of your project.json.
  2. Restore packages with dotnet restore (see note) from the command line.
  3. Inspect the project.lock.json file and find the NETSTandard.Library section. It's near the beginning of the file.
  4. Copy all of the listed packages under dependencies.
  5. Remove the .NETStandard.Library reference and replace it with the copied packages.
  6. Remove references to packages you don't need.

You can find out which packages you don't need by one of the following ways:

  1. Trial and error. This involves removing a package, restoring, seeing if your library still compiles, and repeating this process.
  2. Using a tool such as ILSpy or .NET Reflector to peek at references to see what your code is actually using. You can then remove packages which don't correspond to types you're using.

Example

Imagine that you wrote a library which provided additional functionality to generic collection types. Such a library would need to depend on packages such as System.Collections, but may not at all depend on packages such as System.Net.Http. As such, it would be good to trim package dependencies down to only what this library required!

To trim this library, you start with the project.json file and add a reference to NETStandard.Library version 1.6.0.

{
    "version":"1.0.0",
    "dependencies":{
        "NETStandard.Library":"1.6.0"
    },
    "frameworks": {
        "netstandard1.0": {}
     }
}

Next, you restore packages with dotnet restore (see note), inspect the project.lock.json file, and find all the packages restored for NETSTandard.Library.

Here's what the relevant section in the project.lock.json file looks like when targeting netstandard1.0:

"NETStandard.Library/1.6.0":{
    "type": "package",
    "dependencies": {
        "Microsoft.NETCore.Platforms": "1.0.1",
        "Microsoft.NETCore.Runtime": "1.0.2",
        "System.Collections": "4.0.11",
        "System.Diagnostics.Debug": "4.0.11",
        "System.Diagnostics.Tools": "4.0.1",
        "System.Globalization": "4.0.11",
        "System.IO": "4.1.0",
        "System.Linq": "4.1.0",
        "System.Net.Primitives": "4.0.11",
        "System.ObjectModel": "4.0.12",
        "System.Reflection": "4.1.0",
        "System.Reflection.Extensions": "4.0.1",
        "System.Reflection.Primitives": "4.0.1",
        "System.Resources.ResourceManager": "4.0.1",
        "System.Runtime": "4.1.0",
        "System.Runtime.Extensions": "4.1.0",
        "System.Text.Encoding": "4.0.11",
        "System.Text.Encoding.Extensions": "4.0.11",
        "System.Text.RegularExpressions": "4.1.0",
        "System.Threading": "4.0.11",
        "System.Threading.Tasks": "4.0.11",
        "System.Xml.ReaderWriter": "4.0.11",
        "System.Xml.XDocument": "4.0.11"
    }
}

Next, copy over the package references into the dependencies section of the library's project.json file, replacing the NETStandard.Library reference:

{
    "version":"1.0.0",
    "dependencies":{
        "Microsoft.NETCore.Platforms": "1.0.1",
        "Microsoft.NETCore.Runtime": "1.0.2",
        "System.Collections": "4.0.11",
        "System.Diagnostics.Debug": "4.0.11",
        "System.Diagnostics.Tools": "4.0.1",
        "System.Globalization": "4.0.11",
        "System.IO": "4.1.0",
        "System.Linq": "4.1.0",
        "System.Net.Primitives": "4.0.11",
        "System.ObjectModel": "4.0.12",
        "System.Reflection": "4.1.0",
        "System.Reflection.Extensions": "4.0.1",
        "System.Reflection.Primitives": "4.0.1",
        "System.Resources.ResourceManager": "4.0.1",
        "System.Runtime": "4.1.0",
        "System.Runtime.Extensions": "4.1.0",
        "System.Text.Encoding": "4.0.11",
        "System.Text.Encoding.Extensions": "4.0.11",
        "System.Text.RegularExpressions": "4.1.0",
        "System.Threading": "4.0.11",
        "System.Threading.Tasks": "4.0.11",
        "System.Xml.ReaderWriter": "4.0.11",
        "System.Xml.XDocument": "4.0.11"
    },
    "frameworks":{
        "netstandard1.0": {}
    }
}

That's quite a lot of packages, many of which which certainly aren't necessary for extending collection types. You can either remove packages manually or use a tool such as ILSpy or .NET Reflector to identify which packages your code actually uses.

Here's what a trimmed package could look like:

{
    "dependencies":{
        "Microsoft.NETCore.Platforms": "1.0.1",
        "Microsoft.NETCore.Runtime": "1.0.2",
        "System.Collections": "4.0.11",
        "System.Linq": "4.1.0",
        "System.Runtime": "4.1.0",
        "System.Runtime.Extensions": "4.1.0",
        "System.Runtime.Handles": "4.0.1",
        "System.Runtime.InteropServices": "4.1.0",
        "System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
        "System.Threading.Tasks": "4.0.11"
    },
    "frameworks":{
        "netstandard1.0": {}
     }
}

Now, it has a smaller footprint than if it had depended on the NETStandard.Library metapackage.

Note

Starting with .NET Core 2.0, you don't have to run dotnet restore because it's run implicitly as part of dotnet build or dotnet run. It's still a valid command in certain scenarios where doing an explicit restore makes sense, such as continuous integration builds in Visual Studio Team Services.