Project.json all the things

One of the less known features of Visual Studio 2015 is that it is possible to use project.json with any project type, not just "modern PCL's," UWP projects, or xproj projects. Read on to learn why you want to switch and how you can update your existing solution.

Background

Since the beginning of NuGet, installed packages were tracked in a file named packages.config placed alongside the project file. The package installation process goes something like this:

Determine the full list of packages to install, walking the tree of all dependent packages Download all of those packages to a \packages directory alongside your solution file Update your project file with correct libraries from the package (looking at \lib\TFM If the package contains a build directory, add any appropriate props or targets files found Create or update a packages.config file along the project that lists each package along with the current target framework

Terms

TFM - Target Framework Moniker. The name that represents a specific Platform (platforms being .NET Framework 4.6, MonoTouch, UWP, etc.)

- Target Framework Moniker. The name that represents a specific Platform (platforms being .NET Framework 4.6, MonoTouch, UWP, etc.) Short Moniker - a short way of referring to a TFM in a NuGet file (e.g., net46). Full list is here.

- a short way of referring to a in a NuGet file (e.g., net46). Full list is here. Full Moniker - a longer way of specifying the TFM (e.g., .NETPortable,Version=v4.5,Profile=Profile111). Easiest way to determine this is to compile and let the NuGet error message tell you what to add (see below).

Limitations

The above steps are roughly the same for NuGet up to and including the 2.x series. While it works for basic projects, larger, more complex projects quickly ran into issues. I do not consider the raw number of packages that a project has to be an issue by itself - that is merely showing oodles of reuse and componentization of packages into small functional units. What does become an issue are the UI and the time it takes to update everything.

As mentioned, because NuGet modifies the project file with the relative location of the references, every time you update, it has to edit the project file. This is slow and can lead to merge conflicts across branches.

Furthermore, the system is unable to pivot on different compile-time needs. With many projects needing to provide some native support, NuGet v2.0 had no way of providing different dependencies based on build configuration.

One more issue surfaces with the use of "bait and switch" PCLs. Some packages provide a PCL for reference purpose (the bait), and then also provide platform-specific implementations that have the same external surface area (the switch). This enables libraries to take advantage of platform specific functionality that's not available in a portable class library alone. The catch with these packages is that to function correctly in a multi-project solution containing a PCL and an application, the application must also add a NuGet reference to all of the packages its PCL libraries use to ensure that the platform-specific version winds up in the output directory. If you forget, you'll likely get a runtime error due to an incomplete reference assembly being used.

NuGet v3 and Project.json to the rescue

NuGet 3.x introduces a number of new features aimed at addressing the above limitations:

Project files are no longer modified to contain the library location. Instead, an MSBuild task and target gets auto-included by the build system. This task creates references and content-file items at build time enabling the meta-data values to be calculated and not baked into a project file. Per-platform files can exist by using the runtimes directories. See the native light-up section in the docs for the details.

Packages are now stored in a per-user cache instead of alongside the solution. This means that common packages do not have to be re-downloaded since they'll already be present on your machine. Very handy for those packages you use in many different solutions. The MSBuild task enables this as the location is no longer baked into the project file.

Reference assemblies are now more formalized with a new ref top-level directory. This would be the "bait" assembly, one that could target a wide range of frameworks via either a portable- or dotnet or netstandard TFM. The implementation library would then reside in \lib\TFM . The version in the ref directory would be used as the compile-time reference while the version in the lib directory is placed in the output location.

top-level directory. This would be the "bait" assembly, one that could target a wide range of frameworks via either a or or TFM. The implementation library would then reside in . The version in the directory would be used as the compile-time reference while the version in the directory is placed in the output location. Transitive references. This is a biggie. Now only the top-level packages you require are listed. The full chain of packages is still downloaded (to the shared per-user cache), but it's hidden in the tooling and doesn't get in your way. You can continue to focus on the packages you care about. This also works with project-to-project references. If I have a bait-and-switch package reference in my portable project, and I have an application that references that portable library, the full package list will be evaluated for output in the application and the per-architecture, per-platform assemblies will get put in the output directories. You no longer have to reference each package again in the application.

It is important to note that these features only work when a project is using the new project.json format of package management. Having NuGet v3 alone isn't enough. The good news is that we can use project.json in any project type with a few manual steps.

Using project.json in your current solution

You can use project.json in your current solution. There are a couple of small caveats here:

Only Visual Studio 2015 with Update 1 currently supports project.json . Xamarin Studio does not yet support it but it is planned. That said, Xamarin projects in Visual Studio do support project.json . If you're using TFS Team Build, you need TFS 2015 Update 1 on the build agent in addition to VS 2015 Update 1. Some packages that rely on content files being placed into the project may not work correctly. project.json has a different mechanism for this, so the package would need to be updated. The workaround would be to manually copy the content into your project file. All projects in your solution would need to be updated for the transitive references to resolve correctly. That's to say that an application using NuGet v2/ packages.config won't pull in the correct transitive references of a portable project reference that's using project.json .

With that out of the way, lets get started. If you'd like to skip this and see some examples, please look at the following projects that have been converted over. These are all libraries that have a combination of reference assemblies, platform specific implementations, test applications and unit tests, so the spectrum of scenarios should be covered there. They have everything you need in them:

One last note before diving deep: make sure your .gitignore file contains the following entries:

*.lock.json

*.nuget.props

*.nuget.targets

These files should not generally be checked in. In particular, the .nuget.props/targets files will contain a per-user path to the NuGet cache. These files are created by calling NuGet restore on your solution file.

Diving deep

As you start, have the following blank project.json handy as you'll need it later:

This represents an empty project.json for a project targeting .NET 4.5.2. I'm using the short moniker here, but you can also use the full one. The string to use here is the thing you'll likely hit the most trouble with. Fortunately, when you're wrong and try to build, you'll get what's probably the most helpful error message of all time:

Your project is not referencing the ".NETPortable,Version=v4.5,Profile=Profile111" framework. Add a reference to ".NETPortable,Version=v4.5,Profile=Profile111" in the "frameworks" section of your project.json, and then re-run NuGet restore.

The error literally tells you how to fix it. Awesome! The fix is to put .NETPortable,Version=v4.5,Profile=Profile111 in your frameworks section to wind up with something like:

The eagle-eyed reader will notice that the first example had a runtimes section with win in it. This is required for a desktop .NET Framework projects and for projects where CopyNuGetImplementations is set to true like your application (we'll come back that in a bit), but is not required for other library project types. If you have the runtimes section, then there's rarely, if ever, a reason to have both the supports section too.

The easiest way to think about this:

For library projects, use supports and not runtimes

and not For your application project, (.exe, .apk, .appx, .ipa, website) use runtimes and not supports

and not If it's a desktop .NET Framework project, use runtimes for both class libraries and your application

for both class libraries and your application If it's a unit test library executing in-place and you need references copied to its output directory, use runtimes and not supports

Now, take note of any packages with the versions that you already have installed. You might want to copy/paste your packages.config file into a temporary editor window.

The next step is to remove all of your existing packages from your project. There are two ways to do this: via the NuGet package manager console or by hand.

Using the NuGet Package Manager Console

Pull up the NuGet Package Manager Console and ensure the drop-down is set to the project you're working on. For each package in the project, uninstall each package with the following command:

Uninstall-Package <package name> -Force -RemoveDependencies

Repeat this for each package until they're all gone.

By Hand

Delete your packages.config file, save the project file then right-click the project and choose "Unload project". Now right-click the project and select Edit. We need to clean up a few things in the project file.

At the top of the project file, remove any .props files that were added by NuGet (look for the ones going to a \packages directory.

files that were added by NuGet (look for the ones going to a directory. Find any <Reference> element where the HintPath points to a NuGet package library. Remove all of them.

element where the points to a NuGet package library. Remove all of them. At the bottom of the file, remove any .targets files that NuGet added. Also remove any NuGet targets or Tasks that NuGet added (might be a target that starts with the following line <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> ).

files that NuGet added. Also remove any NuGet targets or Tasks that NuGet added (might be a target that starts with the following line ). If you have any packages that contain Roslyn Analyzers, make sure to remove any analyzer items that come from them.

Save your changes, right click the project in the solution explorer and reload the project.

Adding the project.json

In your project, add a new blank project.json file using one of the templates above. Ensure that the Build Action is set to None (should be the default). Once present, you might need to unload your project and reload it for NuGet to recognize it, so save your project, right-click your project and unload it and reload it.

Now you can either use the Manage NuGet Packages UI to re-add your packages or add them to the project.json by hand. Remember, you don't necessarily have to re-add every package, only the top-level ones. For example, if you use Reactive Extensions, you only need Rx-Main , not the four other packages that it pulls in.

Build your project. If there are any errors related to NuGet, the error messages should guide you to the answer. Your project should build.

What you'll notice for projects other than desktop .NET executables or UWP appx's, is that the output directory will no longer contain every referenced library. This saves disk space and helps the build be faster by eliminating extra file copying. If you want the files to be in the output directory, like for unit test libraries that need to execute in-place, or for an application itself, there's two extra steps to take:

Unload the project once more and edit it to add the following to the first <PropertyGroup> at the top of the project file: <CopyNuGetImplementations>true</CopyNuGetImplementations> . This tells NuGet to copy all required implementation files to the output directory. Save and reload the project file. You'll next need to add that runtimes section from above. The exact contents will depend on your project type. Rather than list them all out here, please see the Zeroconf or xUnit for Devices for the full examples. For an AnyCPU Desktop .NET project win is sufficient

is sufficient For Windows Store projects, you'll need more

Once you repeat this for all of your projects, you'll hopefully still have a working build(!) but now one where the projects are using the rich NuGet v3 capabilities. If you have a CI build system, you need to ensure that you're using the latest nuget.exe to call restore on your solution prior to build. My preference is to always download the latest stable version from the dist link here: https://dist.nuget.org/win-x86-commandline/latest/nuget.exe .

Edge Cases

There may be some edge cases you hit when it comes to the transitive references. If you need to prevent any of the automatic project-to-project propagation of dependencies, the NuGet Docs can help.

In some rare cases, if you start getting compile errors due to missing System references, you may be hitting this bug, currently scheduled to be fixed in the upcoming 3.4 release. This happens if a NuGet package contains a <frameworkAssembly /> dependency that contains a System.* assembly. The workaround for now is to add <IncludeFrameworkReferencesFromNuGet>false</IncludeFrameworkReferencesFromNuGet> to your project file.

What this doesn't do

There is often confusion between the use of project.json and its relation to the DNX/CLI project tooling that enables cross-compilation to different sets of targets. Visual Studio 2015 uses a new project type ( .xproj ) as a wrapper for these. This post isn't about enabling an existing .csproj or .vbproj project type (the one most people have been using on "regular") projects to start cross-compiling. Converting an existing project to use .xproj is a topic for another day and not all project types are supported by .xproj .

What this does do is enable the NuGet v3 features to be used by the existing project types today. If you have a .NET 4.6 desktop project, this will not change that. Likewise if your project is using the Xamarin Android 6 SDK, this won't alter that either. It'll simply make package management easier.

Acknowledgments

I would like to thank Andrew Arnott for his persistence in figuring out how to make this all work. He explained it to me as he was figuring it out and then recently helped to review this post. Thanks Andrew! A shout out is also due to Scott Dorman and Jason Malinowski for their valuable feedback reviewing this post.