Sdk: Moving Dependencies to csproj is a bad idea

Created on 19 Oct 2016  路  29Comments  路  Source: dotnet/sdk

I've seen this blog entry (https://blogs.msdn.microsoft.com/dotnet/2016/10/19/net-core-tooling-in-visual-studio-15) that details moving the dependencies to the XML inside of csproj and I think this is a really bad idea. I am for moving most of what is inside project.json to csproj, but the great experience of editing the project.json for dependencies is going to slow people down, even if you do support intellisense. It's just going to push people to use the PowerShell extensions or the VS Nuget UI both of which are a big step back.

This means we're going from the simple and sublime:

"Microsoft.AspNetCore.Mvc": "1.0.0"

to:

 <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.0" />

This is even a step back from the XML file from prior versions since the csproj has to be reloaded to make these changes. Please reconsider this.

My suggestion is to keep the dependencies as a nuget.json or packages.json instead.

Most helpful comment

Personally, I'm happy to see NuGet finally become a first-class citizen in the .NET project/build system. The real issue is that MSBuild needs to support additional file formats besides just XML...

All 29 comments

I have 2 projects, same output path.
Is there something to prevent me from adding 2 different versions of same lib? Like Paket which does global dependency resolving (with support for groups where I explicitely have to state that I need that).

@shawnwildermuth VS 15 is finally going to be able to edit the project file without unloading. https://twitter.com/davkean/status/787038131656589312

@dasMulli I don't think that mitigates the other downsides...

never claimed it would. but it unblocks the dev workflow for working without ui and custom msbuild work which is all i need.

@shawnwildermuth good catch and agreed. Definitely a step back, .NET Core is supposed to be cross-platform and the json format is definitely the winner there and in terms of brevity.

@TheOnlyRealTodd

.NET Core is supposed to be cross-platform and the json format is definitely the winner there

The old project.json could be used on Windows, Linux and Mac. The new MSBuild-based csproj will be usable on Windows, Linux and Mac. I don't see how does that make the old format any more cross-platform than the new one.

Personally, I'm happy to see NuGet finally become a first-class citizen in the .NET project/build system. The real issue is that MSBuild needs to support additional file formats besides just XML...

Typing a version number in an XML file is no more a "bad idea" than typing a version number in a JSON file. There is no significant difference, and the semantics of how to use this functionality in a text editor or VS2017 are identical to how you use project.json in a text editor or VS2015.

May be spam but let me just dump my thoughts on this here.
Background: When the news broke on moving away from project.json (april/may?), i was devastated. Loved that thing and somehow still do. Now to the "yes, but" arguments that i have run into:

Language / Format

Language and format preferences are highly subjective. There are many reasons to favour YAML, JSON, XML, F#/C# build scripts, Package.swift ore something completely different for various scenarios. It doesn't _really_ matter and there are many up- and downsides for each approach (e.g. i had a few merge conflicts in project.json files just with comma needed when adding a new line to a json object).
I don't think there can ever be a _logical_ / deterministic decision on this.

Fixed descriptive schema vs Extensibility

Build frameworks usually fall somewhere between "we define what you can choose from" and "we give you some tools to build your own".
Project.json is definitely more the first one. It has _some_ capabilities and ways you can configure it. It's only extension points are script invocations. While that is ok for a lot of tasks, it has two major drawbacks: It relies on the shell of the platform so you need to work with the minimum intersection of cmd/bash/zsh/etc and you have no integration with the build itself while running the commands - you have no access to the build system's model and can't communicate back to it other than a success/failure state via exit code.
Frameworks like gulp or grunt on the other hand let you run your own code to set up the build pipeline and inject custom actions on the way. Want to analyse all JS objects, parse the parameter names of all constructor functions out and append DI metadata to the build output so minifying won't kill you? no problem. just insert a step where appropriate.
Project.json can't do stuff like that. But MSBuild can. I can easily write before-build targets that can emit <Compile Include="obj/$(Configuration)/AutoGeneratedAwesomeness.cs" /> elements. Or embedded resources, simple files to put next to the output or whatever else i can think of to scare my coworkers. See CoreCLR/CoreFx: they have logic to decide between including Foo.Windows.cs and Foo.Unix.cs based on custom build arguments.
The interesting part is that i can get extensions via NuGet now. Just like Microsoft.NET.Sdk is just a NuGet with *.targets and *.props files, i can just as easily make my own or consume 3rd party packages to get awesome build and deployment targets. Some have complained about missing publish-to-azure / webdeploy functionality in the dotnet CLI. such features could easily be brought in via NuGet. Also, the VS docker integration just adds targets and props files to your project and adds them to your solution. Some of that could also be moved to a NuGet.
My best example for this is the current CoreRT integration. It basically is "add this line to your csproj and then call dotnet build3 /t:LinkNative". Previously, the CLI had to know how CoreRT works and implement the necessary logic for dotnet build --native.

Convergence of build system and package management

NuGet and MSBuild haven't had much integration until now. It worked somehow if you build with devenv thanks to NuGet's vsix package. If you built project.json based things with it, you have probably run into restore.dg. This temporary file is a manifestation of all the problems a separated package / build system can have. I don't fully understand the internals and i don't really care. I want things to just work.
For JS, there are npm and bower. Both specific to what you want to do with either the library you're pulling or the code you're writing. Can be a bit of a mess when you are new to web / node development.
For Java development, people mostly use maven or gradle. Both have a deep understanding of and integration with package management. It is fairly easy to add a dependency and have it build.
How does that work with NuGet and csproj? If you install a NuGet package in the packages.config world, it's a mess. NuGet will add the package you need to your packages.config, install the dll-references / targets / custom stuff into the csproj. If packages use a custom install script, it may be impossible to completely uninstall a packet. The migration guide for packages.config to project.json has just too many manual steps including editing your csproj files.
At build time / CI server, you then need to figure out how to actually restore the packages. Turns out the best way is to have a nuget.exe somewhere and point it to the packages.config files or the solution file. MSBuild itself can't do "restore on build", that's again a VS feature you'd have to use via devenv.exe somehow (tell me if i'm wrong on this!).
In the new world (current CI builds of the dotnet cli), msbuild does everything. dotnet restore has the same effect as dotnet msbuild /t:Restore. It just translates the arguments.
So, if NuGet packages are a primary development tool and the build pipeline shall handle it, it makes sense to merge the files and not have NuGet edit the other.

Integration into existing MSBuild infrastructure

Building large project is a pain in the a*, independent of the languages / tools used. Setting up complex msbuild "solutions" isn't easy but if you have that running, it's awesome. And most of the infrastructure in .NET is msbuild-based. If I want to build an installer for a windows app, i create a .wixproj project. That references a csproj as <ProjectReference>, allowing me to use the output information for that project that MSBuild provides to create an installer configuration. If any new project type comes out i can add it too, as long as it is msbuild-based. I can't reference a project.json out of a .wixproj.
Btw, how do you currently build a project containing 20 project.json-based projects? Either call dotnet build 20 times or have an msbuild solution that includes all 20 .xproj files. That will also get better if it's just a csproj and the dotnet cli is able to build .sln files.

Improvements to existing projects

At our company, we moved some libraries and application to project.json just to get the integrated NuGet pack support and transitive package references. Much easier that maintaining .nuspecfiles. That is one example of features that all C# solutions instantly get with the move from project.json to msbuild.

Future possibilities

While csproj files are getting a lot better with all the recent updates (more defaults, less clutter), there are still additional options that have been suggested to "make msbuild great again":

  • JSON as alternative to XML for all msbuild project types. (try to parse a *proj file as XML and JSON, use whichever succeeds).
  • Drop the PropertyGroup / ItemGroup + Condition mess and think of something new.
    While that is theoretically possible and worth investigating, I'd much rather have a solution soon that works than waiting another year for tooling changes.

Ok i went a little too off-topic..
TL;DR, msbuild and nuget are now two integral parts of the same thing - a C# library or application. As such, i think they should be in the same file. Whatever the format of that file.

@llehn I was intrigued by your question and blogged this post.

For the record, it is possible (just tried it out) to move all package references to a separate file. Not sure how tooling is going to like that when managing references via UI but it restores/builds/runs just fine like this:

packages.props:

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <PackageReference Include="Microsoft.NETCore.App" Version="1.0.1" />
    <PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161024-1" />
  </ItemGroup>
</Project>

TestProject.csproj:

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>netcoreapp1.0</TargetFrameworks>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="**\*.cs" />
    <EmbeddedResource Include="**\*.resx" />
  </ItemGroup>
  <Import Project="packages.props" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

With upcoming dotnet ref [add/rem] command (#4521), the general experience of dealing with references (and not needing to edit the csprojs) would be improved IMO.

A related thread is here dotnet/roslyn-project-system#639 (ability to add reference from nested directory via glob).

Dotnet Ref is interesting but not a solution. Not all dev's want to keep a console open to run commands if they can just edit a simple file.

I've started implementing project.json and several other methods of specifying references over at https://github.com/aL3891/CustomPackageReferences if you're interested. Early days but the concept works at least :) its a bit rougher than the "real" thing but its close. also, intellisense still works for project.json in the current vs builds, so that's kind of nice

XML versus JSON is a personal preference, it's not a flaw in the design of anything.

I personally like both approaches, they are both compact and get the job done.

Liking is valanced familiarity. XML has gotten a bad wrap and thus we're projecting that negative perception of XML onto this being a "bad" way to reference packages. If you give it a chance and think about the benefits (that this accomplishes the same thing and is still compact) - you can learn to like either approach.

Neither format is "better" - JSON for example doesn't support commenting, and instead of angle bracket soup, it's curly bracket soup.

In either approach I'm happy that we:

  • Have dependencies listed in project files instead of packages.config, and that we can support transitive dependencies.
  • Don't have to list files explicitly.
  • Can edit the project file live.
  • Can multi-target!
  • Have the equivalent of npm install --save (dotnet add package)
  • Have quick completion via tools like ReSharper to add the PackageReference for me and import the namespace, so I just type code and don't worry about the project file format.
  • Lots of things I don't care about are swept under a rug (the Sdk package)
  • I can still use Package Manager UI and Install-Package to add PackageReferences
  • Deduplicating packages.config, nuspec, AssemblyInfo, csproj, etc into one version of the truth in the csproj file

These are marked improvements, so kudos and thank you to the developers behind all of this, you've truly eliminated things that are a PITA.

Personally, I'm happy to see NuGet finally become a first-class citizen in the .NET project/build system. The real issue is that MSBuild needs to support additional file formats besides just XML...

@bricelam What is the value proposition for additional formats? Seems like a giant investment just to appease people that don't want to get over personal biases.

Don't know/don't care. Syntax is irrelevant to me too.

But it's theoretically possible. The input to the MSBuild engine is just an object model, so however you can instantiate those objects will work.

The csproj file editing wouldn't be bad if two things happened:

  • Intellisense would work when adding a reference in the csproj file
  • If the csproj file showed up in the Solution Explorer in VS2017 so we could just double click it. Right-clicking and finding the "Edit Project File" isn't nearly as easy.

@shawnwildermuth In the meantime, open up the Environment > Keyboard settings and create a shortcut key to Project.EditProjectFile (i used CTRL + SHIFT + E, CTRL + SHIFT + P). Then just select the project file and use your shortkey.

One of the biggest confusion points for me right now is how to persist nuget dependencies to a file without wanting to compile any own C# code. I want to use a DLL from a nuget dependency in a PowerShell module on PowerShellGallery. I have no C# code, I just want dependencies. With any other package manager like npm, I can create a package.json and run npm install. It seems like project.json and packages.config worked the same. Now it seems like I have to introduce the concept of a C# project and use the dotnet CLI, which then compiles an empty project, creating trash files everywhere. Should I just continue to use packages.config?

For IntelliSense for PackageReference, install this extension: https://marketplace.visualstudio.com/items?itemName=ms-madsk.ProjectFileTools.

install this extension

Will it get OOTB support?

I personally favor PackageReference over packages.config for the following reasons (note that these are not necessarily technical constraints, they're defaults from VS):

  • System-wide package cache instead of package folder per solution
  • Implicit dependencies don't get added to the project (please note that this can lead to some undesired results as well, this is one of the reasons Yarn was developed as alternative to NPM with it's packages.lock functionality)
  • No duplicate (possibly even conflicting) reference to a package (I.e., packages.config referencing package-1.2.3, project file referencing DLL from package-2.0.0)
  • Only a single line in one file for a package reference (even when there's 5 DLL's in the package)
  • No filesystem reference to packages (even though it's usually a relative reference, it requires maintenance when moving (on filesystem) a project to a different level in the solution hierarchy.

I must say I really like the .props solution to keep packages (/ package configuration) in a separate file, would be nice if VS would support this OOTB.

@shawnwildermuth I agree with you no more. I still can not see a justified reason for using XML over JSON in the official documentation. My reason for JSON is quite simple : JSON format is more web friendly. I sincerely hope the @.NET Core team will consider to turn it back to project.json again in .NET Core 3.0 in which I do not mind to do the migration again no joke no scam no sarcasm at all.

Sorry folks, we have no intention of moving back to project.json. PackageReference is here is to stay.

For those using the new bits, having <PackageReference/> as items is quite powerful. Here's a few of the things you can do with them:

Share package references across a solution
Condition them
Unify package versions across a solution

@davkean, this is awesome, thanks!

Small feedback:

What would be really nice is if the package management UI also works with version sharing feature.
With Maket solution, when we update EntityFramework package using UI (in both projects that are referencing it):

image

it should update Directory.Build.targets, instead of csproj.

Moreover, as long as we have <PackageReference Update="EntityFramework" Version="6.1.2"/> in Directory.Build.targets, package manager UI doesn't realize that csproj now explicitly has version 6.2.0 and keep showing if there is an update available (for both projects).

I am going to go ahead and close this issue as, as @davkean stated above, we have no plans on rollback on it.

Was this page helpful?
0 / 5 - 0 ratings