.NET Core系列 : 2 、project.json 这葫芦里卖的什么药

.NET Core系列 : 1、.NET Core 环境搭建和命令行CLI入门 介绍了.NET Core环境,本文介绍.NET Core中最重要的一个配置文件project.json的相关内容。我们可以使用.NET Core 的dotnet 命令行接口(CLI)dotnet new命令创建一个应用,也可以用Visual Studio 2015 update 3创建一个应用,他们都有一个project.json ,它是项目的配置文件,类似之前的*.csrpoj文件。Project.json 是一个新的项目文件,它的功能大部分与 *.*PROJ 文件重叠。它可标识项目引用、版本选项(如版本号)等事项,并可标识要编译的平台,例如,是 .NET Core 还是 .NET Framework。心细的你可能已经发现了他们所创建出来的project.json 文件的内容有很多都不一样。我们下面就来看下这个文件的内容,project.json 的官方文档 https://docs.microsoft.com/en-us/dotnet/articles/core/tools/project-json

project.json

首先,从我们 通过 Visual Studio 创建的项目 xproj  的 project.json︰

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

"dependencies": {
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.0.0"
    }
  },

"frameworks": {
    "netcoreapp1.0": {
      "imports": "dnxcore50"
    }
  }
}

dotnet new 命令创建项目的 project.json:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {},
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        }
      },
      "imports": "dnxcore50"
    }
  }
}

他们结构上看是相似的,但是具体内容上有很大的不同的地方,他们都有4个*的属性:version, buildOptions, dependencies 和 framework。.NET Core 项目结构中最重要的文件可能是 project.json。此文件旨在:

替换 NuGet 文件管理器 package.config 文件,它可标识项目的 NuGet 引用。
指定项目支持的框架,以及有关如何为特定框架构建项目的配置详细信息。
标识独立应用的目标平台,它含有其所有依赖项,包括对应平台所需的特定于平台的 .NET Core 运行时。或者,如果项目是可移植应用,project.json 可标识项目会在目标计算机(将在其上运行程序集)上安装的框架。
这三个任务分布在 project.json 中的四个主要部分(根据项目类型,我将Frameworks 和 dependencies合并为功能重叠):

Version

version 这个属性是你所要构建的组件的最小的元数据,其他元数据包括name,title,description ,copyright 等, name 选项默认情况下使用的是父文件夹名称,你可以通过name 这个属性给它取个你想要的名字。

buildOptions

buildOptions节点定义了如何编译和编译哪些文件等,也就是编译选项。编译选项部分包含一些有用的属性。首先是 emitEntryPoint,这用来确定是否生成可执行二进制文件或 exe 。默认情况下,调用 Program.Main() 方法将被调用来运行你的应用。

我发现一个有趣的属性是"debugType":"portable"。Visual Studio 代码调试器必须设置这个属性才能够工作的。但这也意味着您的应用程序将以不同的方式发布,具体哪个值取决于您在此处的设置。简要的可以看前一篇文章的dotnet publish 节,更多的介绍在后面发布应用程序的时候介绍。

Frameworks 和 dependencies

dependencies此部分列出了你的项目所依赖的各个 NuGet 包,包括所述依赖项的版本号。可以使用通配符指定版本号,从而你可以允许 NuGet 包管理器还原自动下载与通配符相匹配的“最新版本”。版本号的空引号对表示“使用最新可用项”。我们创建的项目可以针对一个或者多个Framework(比如我们希望创建的可以同时在.NET Framework和.NET Core上运行),支持的Framework定义在frameworks节点下。如果添加了多个Framework,并不是说最终生成的应用可以同时在这 些Framework中运行,而是说源文件在编译的时候会针对这些Framework生成对应的程序集。对于传统的.NET项目来说,如果我们需要调用某个API,需要添加所在程序集的引用。对于.NET Core来说,所有使用到的程序集都被打包成一个NuGet包,所以针对程序集的直接依赖转变成针对某个NuGet包的依赖。针对NuGet的依赖主要有 两种类型,一种是针对所有Framework的,它们会直接定义在dependencies节点下,另一种则是针对某个具体Framework的, 定义为当前Framework节点下的dependencies子节点。对于独立应用,运行时部分指定将支持的 OS,因此可指定要绑定到应用程序的运行时库。

从上面2个project.json 文件可以看出Frameworks 和 dependencies 存在依赖关系。他们是可以嵌套的,在最高一级的依赖项,将是所有的Frameworks所依赖的,也可以针对一个具体的Framework 构建它的依赖关系,不同的Framework使用不同版本的依赖项。看上面的例子,我们看到Visual Studio和dotnet CLI版本定义的是相同的结果,只是两种不同的表达方式。

Microsoft.NETCore.App

我们来仔细看下 Microsoft.NETCore.App:

"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
}
这是一个依赖项,平台的依赖,它也是Nuget包,其中包含了一堆系统库的NuGet程序包,包含.netcore的基础运行时和基础类库。 文章 

Running .NET Core apps on multiple frameworks and What the Target Framework Monikers (TFMs) are about 详细的介绍了具体的内容。

netcoreapp1.0

我们再来仔细看下  netcoreapp1.0:

"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
框架netcoreapp1.0 是个多目标框架对象名字(TFM)值,除了有经典的net45,net46,现在又有了一些新的像netcoreapp1.0,文章 

Running .NET Core apps on multiple frameworks and What the Target Framework Monikers (TFMs) are about 有更详细的说明。

NETStandard.Library

上面我们创建的项目是个应用程序,当我们回到类库的时候,在依赖项里会发现一个NETStandard.Library:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable"
  },
  "dependencies": {},
  "frameworks": {
    "netstandard1.6": {
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    }
  }
}

这也是一个NuGet 程序包: https://www.nuget.org/packages/NETStandard.Library/,里面包含了多个目标版本,类似于老的PCL方法,以后就使用NETStandard.Library替代了PCL,我们有了一个更加统一的版本控制策略。

本质上来说,NETStandard.Library 是一个目标最低支持基础类库,这样就可以更好的向前兼容性,在现有的平台出现新的版本时(如.net core 1.1 甚至 2.0)而无需重新发布新的变化。具体参考 https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md ,目前最新的表格:

Target Platform Name Alias              
.NET Platform Standard netstandard 1.0 1.1 1.2 1.3 1.4 1.5 1.6
.NET Core netcoreapp 1.0
.NET Framework net 4.6.3
    4.6.2  
    4.6.1    
    4.6      
    4.5.2        
    4.5.1        
    4.5          
Universal Windows Platform uap 10.0    
Windows win 8.1        
    8.0          
Windows Phone wpa 8.1        
Windows Phone Silverlight wp 8.1            
    8.0            
Mono/Xamarin Platforms   *
Mono   *      

如何理解这个表格

  • 如果一个类库指定.NET平台标准1.3版本,那么它仅能够运行在.NET Framework 4.6或更新的框架、Universal Windows Platform 10(UWP)、DNX Core 5.0和Mono/Xamarin这些平台上。
  • 如果一个类库指定.NET平台标准1.3版本,那么它能够引用(原文:consume)所有来自之前的.NET平台标准的版本(1.2、1.1、1.0)。

关于project.json 的更多信息

.NET Core项目依赖全部使用NuGet,要求使用NuGet 3.0版本,默认使用nuget.org 作为源。在安装VS2015 Update3时,.NET Core所需的官方依赖包都已经安装在了(默认安装)C:\Program Files (x86)\Microsoft SDKs\NuGetPackages目录下,在nuget管理中也可以看到这是默认的离线包目录,我们需要什么样的包只要把它复制到这个目录,在nuget管理中的程序包源选择离线的源即可。执行dotnet restore命令后项目会根据project.json文件配置来恢复项目依赖包,同时就会生成新的project.json.lock文件。

project.json.lock

Project.json.lock 存储编译所需文件的列表(通常为 NuGet 引用)。与 project.json 文件不同,它包括特定的包版本号,可支持通配符。如果没有 project.json.lock,将完整还原包。Project.json.lock 包括包图片以及本地下载的其他与包相关的数据(已还原)。它的工作方式 和 npm以及 RubyGems非常相似,你可以把这个文件签入版本库,也可以不签入,但此文件不存在时,将运行 NuGet restore 还原以重新创建。此文件列为 Visual Studio 中 project.json 的子项。

HellodotnetCore.xproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
  </PropertyGroup>

<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
  <PropertyGroup Label="Globals">
    <ProjectGuid>34ee435f-9fda-4fb2-b4fb-a3203939f0c5</ProjectGuid>
    <RootNamespace>HellodotnetCore</RootNamespace>
    <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
    <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
  </PropertyGroup>

<PropertyGroup>
    <SchemaVersion>2.0</SchemaVersion>
  </PropertyGroup>
  <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

和以往的csproj 文件一样,这个文件架起Visual Studio 和MSBuild沟通的桥梁。HellodotnetCore.xproj 定义构建项目时将发生的事项。最新版本可导入 Microsoft.DotNet.targets,它定义了利用新 DotNet.exe 命令的构建任务。与过去的 MSBuild proj 文件不同,xproj 文件非常小,因为大部分信息已(暂时)移到 project.json。不过后续这个文件要被csproj 替代,也许就在不久的将来的Visual Studio 2016上面就变成了csproj。

global.json

global.json是一个有待探究的神奇配置文件,我最喜欢的一个功能是全新的支持调试和单步执行,甚至可以实时修改包的源代码。假设你有公司范围的“框架”程序集,可以在众多团队之间共享。但是,

但是,框架包实际上是开源的,因此公司内(或者,甚至更好,公司外部)的任何人员均可进行完善和更改。现在,想像你如果为此框架引用 NuGet 包,但有时怀疑可能存在需要修复的缺陷或可能存在一个批准的增强功能。通常,这需要独立于项目/解决方案处理组件中的源代码。相反,如果你能够下载源代码并随时开发将其更新为集成式体验 - 甚至单步调试,而不依赖于符号服务器或 PDB 文件是否可用,会怎么样? 幸运地是,Visual Studio 2015 支持此关键场景。

例如,想象你想要调试 GitHub 上可用的 Microsoft.Extensions.Logging 包。要在项目中对其进行添加和调试,你需要下载(可能使用 git clone 或 git submodule 命令)源代码。接下来,为了使 Visual Studio 知晓在何处查找源代码,你需要编辑 global.json 项目节点,如将“submodules\Logging”添加到查看的目录列表:

{
  "projects": [ "src", "test", "submodules\Logging" ],
  "sdk": {
    "version": "1.0.0-*"
  }
}
当然,你可以提供完整路径(例如,你未将代码克隆到子目录)。但是,请注意,目录分隔符是两个反斜杠 (\\) 或单个正斜线(如 c:/users/geffzhang/documents/visual studio2015/Projects/Microsoft.Extensions.Logging)。

更新并保存 global.json 后,一旦 Visual Studio 成功找到源代码,它会自动将项目添加到你的解决方案,使你可以调试到源代码。

这里使用了一种非常棒的算法来确定要加载的源代码目录:

  1. 如果 global.json 中指定的任何源代码位置包含的文件夹具有与包相同的名称(如 Microsoft.Extensions.Logging),且此文件夹包含名为 project.json 的文件,调试程序将使用此文件夹及其内部的源文件。
  2. 否则,会加载包文件夹中编译的二进制程序。

本文简要介绍了.NET Core项目中最为重要的一个配置文件project.json的内容和相关的工具,类库等基础信息,下篇文章我们来聊聊如何构建多个Project的解决方案的内容。

上一篇:ASP.NET Core project.json imports 是什么意思?


下一篇:CSUOJ 1021 组合数末尾的零 二进制