In Part One, I went over a bit of the history and reasoning behind .Net Core and all this cross-platform business, but I will admit to skipping to the middle a bit. While choosing what platform to target is a crucially important piece of the puzzle, and one bit of information that I wanted to clear up first, it certainly was a bit like showing somebody how to shoot a 3-pointer without mentioning that they have to dribble the ball in order to get there.
Dribbling The Ball
What is all this project.json talk about? I thought that projects were defined as .sln and .csproj files. Why do we need yet another project format?
Well, if you've ever done a merge or two in the version control system of your choice (mine's git), you know that almost any change at all to .csproj files turns into a conflict. And not only that, but it turns into a conflict that will occasionally be auto-merged into something completely unintended and/or unintelligible. I work on a team of 10 and at least once a week or so a piece of a .csproj file gets merged away by accident.
A similar problem exists for our NuGet bretheren, the packages.config file. Let's face it: XML files are really a version control system's worst nightmare. And with a large scale system that may have 20+ projects, manually handling 20 .csproj conflicts and then 20 packages.config conflicts can really drag down productivity. Rebases or merges shouldn't take hours.
Write Me a Quick Node
In the ASP.Net team's zeal to compete with web service powerhouse Node, they did a pretty smart thing. They stole Node's project configuration JSON format.
OK, so, actually, the format isn't that close. But, you can see where they received their inspiration from. The project.json file that ASP.Net 5 introduces is also the de-facto project format going forward, at least for DNX tooling based projects. No word has come down yet on whether VS2016/17 will switch to DNX tooling by default, but if I was a betting man, I would venture to say that it will be. Time will tell.
VS2015 and .Net 4.6 are already using Roslyn, the C# built compiler for, you guessed it, C#. That's right, folks. The first shot across the bow 2 years ago was Microsoft's announcement that the next C# compiler would itself be written in C#, and that compiler would be open source for all the world to see.
How Does It All Fit?
You see, Roslyn enabled the team to begin to move cross-platform. Since the compiler was now C#, the only thing needed was to create a small, cross-platform CLR/DLR VM runtime. While Mono has a very complete full .Net framework implementation, the .Net team wanted to create a smaller code surface, break up the library layers, and allow developers to pick and choose their framework dependencies in a much more granular way, allowing for better control and isolation when new revs come out.
So now we have a cross platform runtime (coreclr) as well as a cross-platform core framework (corefx or .Net Core). The only thing needed was cross-platform tooling, and the team delivered.
Show Me The Goods, Already.
I've waxed historical here for enough time, so let's get down to the main course. Project.json is a JSON file (as you may have guessed) that takes the place of .csproj files for defining C# projects. It also takes the place of two other files: NuGet's packages.config and .nuspec.
Unlike .csproj formats, there is no requirement to directly specify all of the .cs files that will contribute to compilation. This is an important distinction. Under project.json, all .cs files underneath the root where the project.json is placed will automatically be included. This means that no longer will you run into goofy merge conflicts where VS has changed the order of some files in the .csproj XML. By convention, everything will be included.
However, this convention also means that if you had your projects structured in a non-standard way before, with references to files in folders above the project folder, you'll need to work around and/or restructure your project.
Additionally, at present, the name of the NuGet package that this tooling will create is directly influenced by the name of the folder under which the project.json sits. So, if your project folder is named Basketball.Dribble and the project.json is in that folder, the package that will be built will be Basketball.Dribble.nupkg. It sounds like this restriction may be lifted in RC2, but I'm not entirely sure yet. To be safe, I would recommend following this convention.
The .Net team recommends structuring your DNX solution folders as such (and is doing the same):
SolutionFolder/
|- src/
|- SolutionFolder.Project1/
|- project.json
src/
|- .cs files
|- SolutionFolder.Project2/
|- project.json
src/
|- .cs files
Structure, Structure, Structure
What does this project.json file look like? Well, let me start by showing a very simple example, then we'll delve into what it means.
{
"version": "1.0.0-beta-1",
"description": "A great little program that dribbles a number of virtual basketballs.",
"authors": ["Matt Nischan"],
"dependencies": {
"Newtonsoft.Json": "7.0.1",
"Basketball.Dribble.Core": ""
},
"frameworks": {
"net46": {
"frameworkAssemblies": {
"System.Linq": "",
"System.Net.Http": ""
}
},
"dnxcore50": {
"dependencies": {
"System.Runtime": "4.1.0",
"System.Linq": "4.0.1",
"System.Net.Http": "4.2.0"
}
}
},
"commands": {
"dribble": "Basketball.Dribble"
}
}
Project Metadata
The version, description and authors properties are exactly what you would expect if you were to see them in a NuGet .nuspec file. They simply describe the version number of the resulting project and package, the description of the package, and any authors that may have contributed. Pretty standard stuff.
Dependencies
The project.json file takes a multi-layered approach to dependencies, allowing you to specify both framework agnostic dependencies (i.e., I want to take this dependency for all frameworks) and framework specific dependencies (i.e., I want to take this dependency for only this framework). This first dependencies property is the former.
In this example, I'm taking Json.Net as a dependency, but I'm also doing something a little more interesting. If you have set up your folder structure in the way that was recommended before, you are also able to take other projects as dependencies as you could previously with .csproj files. In this case, I am taking Basketball.Dribble.Core as a dependency, with no version specified. This will take whatever best available version I have in my folder structure, and will also instruct the tooling to build those dependencies if they need to be built.
Frameworks
The frameworks property of the project.json file allows you to specify what framework monikers you are targeting (as seen in Part One) as well as what dependencies that you require as a result of targeting a specific framework.
There are two different ways of indicating dependencies in this area. The first is with a frameworkAssemblies property. This property is like the References you are used to in .csproj style projects, and allows you to specify an assembly that will be needed to be referenced from the framework itself. The second method is to use the dependencies property, which instructs the build system to look for a NuGet package with the specified package name and version. When compiling against that framework, the package will be downloaded and used to reference to.
My recommendation is to use frameworkAssemblies for full .Net framework targets (net20 through net46), and dependencies for CoreCLR targets (dnx451, dnx46, and dnxcore50). This gives the same references as you would have had before for normal .Net framework builds, and the new NuGet packages for CoreCLR builds.
Commands
Things get a little strange in the commands property. At current, this instructs dnx (soon to be retired) what things to run when executing. So, in this case, you could run the Basketball.Dribble command line program by running dnx dribble. However, this doesn't seem to work with the latest CoreCLR tooling, dotnet. Running dotnet dribble only throws up an error. However, VS2015 is still using dnx, so you'll need at least one command here to run your project in VS.
Wait, What?
DNX is really something specific to ASP.Net. It bootstraps a number of services that ASP.Net needs and injects them into the application, then runs your ASP.Net app. This includes things like the dependency injection framework, assembly location services, and the like. It isn't really general purpose, so the ASP.Net team is removing the bootstrapping bits from DNX in RC2 to a different layer, which will allow the dotnet core CLR program to be the single entry point for any .Net application.
However, until the tooling catches up, VS2015 uses DNX for any application, not just ASP.Net apps.
A Word of Caution
The tooling is not yet perfect. I ran into a particularly nasty bug that I will forewarn all my readers about.
Always specify every dependency and version in every project. Currently, in VS2015, if Project A requires Json.Net, and project B requires Project A and Json.Net, because Json.Net comes with Project A, it will not bother you to add a dependency for Json.Net to Project B. However, the tooling mangles the versions for these automatically picked-up dependencies of dependencies. In my case, I was referencing System.Net.Http version 4.0.1-beta-23516 from Project A, and also using System.Net.Http from Project B.
However, when Project B was build using Project A, the dependency version that the tooling picked for System.Net.Http in Project B was actually version 4.0.0, which doesn't include any of the non-Windows bits of the HttpClient implementation, which for Linux and Unix is built on top of Curl. It took a couple days and many frustrated hours to figure it out.
So, my recommendation is to always specify every dependency you need for a particular project, even if that dependency is going to be supplied by another dependency.