Evolution of the NuGet PackageReference

NuGet is probably the best thing that happened to .NET dependencies. Before this great time, we all had to copy or worse, commit a bunch of assemblies into source control – just so we could build our projects. And for library developers… well, we had to find someone to host our libraries.

When NuGet was born, we could just throw our libraries up on nuget.org and be done with it. We could tweet about it and then anyone could open the package manager and click install. The repository stayed binary-free and the library started growing in users. Great for everyone.

Both NuGet and MSBuild have evolved much over time, and there have been a few different incarnations of the simple, but complex, feature of including a NuGet package into the project file. It took a few tries, but we are at the best so far with PackageReference. But, before we get there, let us recap some of the past ideas…


There was a downside to the first NuGet: it scratched around in your .csproj and added a few elements that sometimes got mixed up. Let’s take a simple project, and add SkiaSharp:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Reference Include="SkiaSharp">
    <None Include="packages.config" />
  <Import Project="..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets" Condition="Exists('..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets')" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    <Error Condition="!Exists('..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets'))" />

Our beautiful project file just got a bit mutilated… Still, it is not that bad, right? Well, what happens if you use a few packages? You get a project file that has quite a few elements that basically pull in a bunch of assemblies and add a few targets – there is just clutter.

Although this is all correct MSBuild file, the packages are really managed by that packages.config file. The IDE and NuGet use that file to track the installed packages and also to update the project file with the assembly references and target imports.

You can read more about the packages.config file here.


Along came a spider (named .NET Core) and sat down besIDE her…

The guys working on .NET Core also had had enough of this, (they may have gone a bit too far and removed all XML) so they came up with project.json. Although they threw the .csproj right out of the window, they had a cool idea when it came to NuGet packages: don’t touch the project file.

Initially, project.json was just for ASP.NET Core apps, but they also used it for (and still do) UWP apps. Then, they decided to let other project types use it. If we were to install the SkiaSharp package into a project.json file, we would end up with something like this:

  "dependencies": {
    "SkiaSharp": "1.58.1"
  "frameworks": {
    "net46": { }
  "runtimes": {
    "win": { }

This WAS way neater than the big, clunky .csproj, and – best of all – NuGet managed itself. No need for assembly references and importing .targets. Also, because NuGet never touched the .csproj, it could never get it wrong.

The downside to project.json was simply the fact that it was no longer MSBuild. There were other project types, such as .xproj that would pull in the project.json and then make it work with MSBuild, but now we have even more files and overhead. The problem of less was undone by the new file type.

You can read more about the project.json file here.


So, the experts got together and decided to come up with a plan – a format to rule them all. They took the idea that NuGet needs to be MSBuild and that it must not touch what it shouldn’t. It needs to be minimal, but still powerful.

This is what they came up with:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PackageReference Include="SkiaSharp" Version="1.58.1" />

No, your browser is not still loading. That simple, single line does it all. There is no additional packages.config or project.json file. That goes directly in the .csproj like any .NET framework reference (such as when including System.IO.Compression).

I could write a big puff piece about how awesome this is, but the whole point is to not have that puff. I really love this new format, and I hope that you will too. I have tested this with many projects, including Xamarin projects. It all works, and it should – it is part of MSBuild, and Xamarin uses MSBuild. This is one of the cool features about having a MSBuild-based project: if MSBuild supports it, then so does your project.

You can read more about the PackageReference format here. And, there is some more information about using PackageReference in any project type here.


Using this new PackageReference format makes life so much easier, but there are some things that you will need before you switch over all those projects:

  • MSBuild v15
  • NuGet v4.x

If you want to use an IDE, the best still are:

  • Visual Studio 2017
  • Visual Studio for Mac v7.1

The current set of requirements is just that you need to be using MSBuild v15 (comes with Visual Studio) and NuGet v4.x (also comes with Visual Studio). If you need the NuGet command line tool, you can get it here.

By default, Visual Studio will use the packages.json format, but you can change this in the options. Navigate to “Tools” > “Options…”, then “NuGet Package Manager” > “General”:

NuGet Package Manager

See more information about using PackageReference in any project type here.


Right now this magic works on Windows and macOS without anything special. Linux is a bit different… As it has a case-sensitive file system, and NuGet is a little inconsiderate: it downloads the packages as lowercase, but then tries to include the real case.

If you look at the ~/.nuget/packages directory, you will see a skiasharp directory, but when you try and compile, you will get this error:

error : The package SkiaSharp with version 1.58.1 could not be found in /home/matthew/.nuget/packages/. Run a NuGet package restore to download the package.

Exciting stuff! But no worries, until NuGet/MSBuild fixes this, we can just tell Mono to ignore case sensitivity using the MONO_IOMAP environment variable:

> export MONO_IOMAP=all
> msbuild


> MONO_IOMAP=all msbuild

That was mostly painless. Still, we can set that environment variable per build and not affect any other apps. And, this is just a tiny bit of “work” so that we can get really awesome, clean .csproj files.

So, is there anything stopping you from using the PackageReference in your projects? No! Transition Now!

6 thoughts on “Evolution of the NuGet PackageReference

  1. What’s stopping me is the fact that Package Reference format does not support importing of msbuild tasks when referencing a NuGet package with such.


  2. Cool information, thanks! Quick question: Does this work with traditional .NET Framework projects (not-dot-net-core)? I guess I should try myself before asking… :p


  3. LOL, almost 2 years later and Package Reference still doesn’t support install/uninstall scripts, drop-in content or configuration (via xdt). Nothing preventing people from switching…? Hahahaha


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s