ASP.NET Core 与 .Net Core, .Net Platform Standard, 以及 Shared Runtime 之间的关系

原本打算这周末撰写基础建设中下一个主题,也就是 Logging 的内容,但由于 RC2 已经进入最后阶段,有些东西比 Logging 还更重要需要先介绍,所以这次的文章内容就切到与 .net core 有关的内容.这是 RC2 里改变相当大的东西,要了解它才对你撰写 ASP.NET Core 程序有帮助. 

[2017.07.23] .Net Platform Standard 已更名为 .Net Standard, 我有另一篇文章做更多的介绍.


这星期可精彩地忙碌了,ASP.NET Core 进入了 RC2 的 ask mode,每个工程师都在修正与检视要在 RC2 释出的所有功能.所谓的 ask mode 是指把 source control 上的开发用的 branch 合并到释出用的 branch,所以除非是紧急或等级高的问题,否则其他的问题修正或新功能将不会进入到释出用的 branch,也就是说 RC2 该有的功能都已经确定了.这次 RC2 最大的改变莫过于采用了 .net core cli.

历史

在 ASP.NET 要进入新一个版本时,内部已经讨论过这个版本需要有重大的改变,其中跨平台便是一个相当大的项目.为了要达成这一个目标,那表示 ASP.NET 的 runtime 不能再依靠 Windows,因此 ASP.NET team 自行设计了一套新的跨平台用的工具命令,它的名字叫 .net execution environment,执行的引擎就称为 .net execution engine.也就是大家简称的 DNX.从 beta1 一直到 RC1 都一直是采用 DNX 的方式在运行 .net core,相关的工具链也都是依靠它来运行.一直到去年年底时,公司组织内的高层有异动,所有制作 ASP.NET 产品线相关的团队被并入到开发者工具部门,从那时开始,事情有了转变.在开发者工具部门中有 .net core cli 的团队,他们做的工作其实原理上跟 DNX 是很类似的.后来的结果就如大家在 RC2 版本将会看到的,DNX没有了,取而代之的是 .net core cli,所以整个 ASP.NET 相关的工具链也都要重新检视然后更改.这是一个蛮大的工程,因此 RC2 才会一直延后到现在.所以,若你以前曾玩过 RC1 和 beta 版本的话,请把跟 DNX 忘了吧.你该学的是如何使用 .net core cli 的命令.

.NET Core

我不是 .net core 团队的成员,也没研读过 .net core 的程序,所以我无法提供程序运行的细节.因此,我在这边分享的 .net core 内容是这几个月从 pm 那边听来的.整个 .net core 的内容都是 open source,包含 compiler, framework, clr 等等,如果你对源代码有兴趣,你可以到 GitHub 查看 (http://www.github.com/dotnet/).

数据来源 : 从某一个 pm 的演讲里偷来的投影片 :psource: 从某一个 pm 的演讲投影片偷来的

.net core 里面基本上分为四个东西:

1. CoreFx,也就是核心的 framework,基本上如果你要写跨平台的 .net 程序时,假设你选择采用 .net core,那么 core framework 就是你的开发 sdk.基本上,你可以把它视为 Windows 版上的 .net framework (在内部俗称为 full framework) 的缩小版.虽然说是缩小版,但也没那么小,毕竟你以前用的 .net API 在这边都还是可以找到.比如一些基本的 data type, class library,或像网络,数据库,IO等等方式的 API 都仍是有的.但有一些跟 Windows 有关的 api 就不会有,比如像 WPF, WCF 之类,因为这些东西是绑在 windows 上,无法跨平台.接着你可能会马上问,如果没用到这些 windows 依靠度很高的 API,那是不是以前写的 .net code 都可以直接用 core framework.这想法是对的,但并非完成正确.原因是并非所有的 api (包含 property, constructor, method) 都被移转到 core framework 上.比如说, StreamWriter 在 .net framework 上有提供一个 overload constructor 可以直接让你带文件的路径,但这一个 constructor 并没有出现在 core framework 里.你就得参考使用 FileStream 来写数据到文件里.现在 .net core 还是 1.0 版,因此我相信在未来更新的版本里它的 api 完整度一定会非常接近 .net full framework.有关 core framework api,可参考 http://dotnet.github.io/api/

2. CoreCLR,这也是核心版的 CLR.在 CLR 里面最重要的两个功能是 JIT 与 garbage collection,而在 CoreCLR 里有属于自己的 JIT 和 garbage collection.我不知道 CoreCLR 的 JIT 和 garbage collection 与 full framework 的有什么差别,所以在这就无法提供太多的细节.

3. CoreRT,这是 core runtime,这部分听起来好像有点怪怪的,也许你会觉得 coreclr 和 corert 有什么差别,很抱歉,这部分的细节我也不知道,但我知道的是 corert 里有 native language 转换器,所以透过它,你可以把你的 c#/vb 等的程序变成 native code,也就是 machine code,而不是 IL code.所以有了它,你的 .net 程序将有机会变成无敌.^_^ 因为 native code 在运行时并不需要 framework/CLR 的存在,因为所有相关的组件也都编译成 native code,所以你拿到的 native program 也会变的比较肥.

4. CLI,这是 command line interface,是 .net core 里面所提供的工具链让你可以透过命令的方式来操作 .net core.比如执行 dotnet new 来建立项目,执行 dotnet build 来进行编译,执行 dotnet publish 来进行布署程序,执行 dotnet test 来进行程序测试.甚至你可以为了某个目的来自行开发相关的工具链.比如,entity framework 将以前所有的命令移转过来,所以你可以透过执行 dotnet ef dbconext list 来列出程序里的 dbcontext,也可以执行 dotnet ef migrations 来做 ef migrations 的功能.

.Net Platform Standard

这对 ASP.NET Core 来说是个新东西,由于 ASP.NET Core 底层是 .net core 来执行,所以也就得遵守 .net core 所定义的平台标准.平台标准的意思就是让应用程序可以指定要用什么样子的 api 来使用,所以 .net platform standard 基本上就像是一个菜单那般,而这份菜单上说明了使用的是那一个版本的组件.需要这样做的原因是因为 .net core 要支持的平台其实很多,所以必须要制定出一个标准出来才能让应用程序知道有什么样的 api 可以使用.举例来说,如果你今天开发了一个应用程序的组件,你要让这个组件可以用在 windows 8 与 windows phone 的话,那么这个组件只能用一些 windows 8 和 windows phone 共同提供的 api 才行.其实这就是 .net portable class library 的概念.在 .net core 的世界里便把这样的概念定义的更明确以方便开发者知道在写跨平台组件时要怎么处理不同平台 api 之间的问题.所以,你可以把 .net platform standard 想成像面向对象里的 interface 一样,每个平台标准都定义好了 interface,所以你就知道你的应用程序在用这份 interface  时有什么样的 api 可以使用了.而目前定义了那些平台标准呢 ?  请参考这份文档 https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md

source: https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md

在上面的表格中,你可以看到 .net platform standard 一共定义了 1.0 到 1.5 ,一共六种标准.每一个标准代表了下面平台里所代表的版本.例如,如果你的应用程序定义了要使用 .net platform standard 1.5 的话,那就表示当你使用 .net core 平台时,采用的是 .net core 1.0 版,或当你使用 .net framework 时,采用的是 .net 4.6.2 版.因此,这样你在撰写应用程序的时候你就知道你可以用什么样的 api 了.

若使用同一个平台的话,则低版本应相容于高版本.比如你的应用程序定义了要使用 .net standard 1.5,那表示你可以使用 .net framework 4.6.2 版的 api ,这也代表你也可以使用 .net framework 4.6.1 版的 api (定义在 .net standard 1.4).所以同一个平台时,低版本应相容于高版本.

source: https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md

从上面的表格你也可以看到使用多个平台组合时,你所需要采用的 .net standard 版本.

每个平台在 .net standard 里面都有定义一个缩写名称,同样地也可以在相同那份文档里找到数据.比如 .net framework 4.6 的缩写是 net46,而 Xamarin iOS 的缩写是 xamarinios.缩写的名称会需要用在 project.json 的 framework 定义中.这也使得当我们执行 dotnet restore 时,nuget 才会知道当下载某一个组件时需要下载那个平台的版本.举个例子如下

"frameworks": {
    "netcoreapp1.0": {
         "imports":["portable-net45+win8"]
    }
}

这是在 project.json 里面有关 framework 的定义.你可以看到这份应用程序说明了它将使用的是 netcoreapp1.0 的这份平台标准.从上面的内容中,你可以找出表格看到 netcoreapp1.0 也就是 .net standard 1.5 下所定义的平台.而 imports 里面所写的定义是为了向以前版本相容,因为有些旧名称在 .net standard 的定义里并不存在,所以暂时留了一个向前版本相容的空间.这份文档 ( https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md) 也提到了那些缩写名称已不在使用.比如,以前 beta 到 RC1 几乎都是写 dnxcore50/aspnet50 在 project.json 的 framework 区块里,而这些名称将不再使用了.

透过这样的平台定义,你可以了解到当你为你的应用程序定义了越高版本的平台标准时,这代表了你可以使用越多的 api,同时也代表了跨平台的能力也相对地越低.这里指的跨平台不限定是跨操作系统,windows 8 到 windows phone 到 universal windows app 也是属于跨平台的意思.

Shared Runtime

前面的内容在 beta 版时已经有相似的东西,所以对使用过 beta 版的使用者来说并不是一个完全新的事情.而 shared runtime 这个应该就是新的了.

在 project.json 里面也可以定义 runtimes,而 runtimes 定义代表你的应用程序会在操作系统里面去找 runtime 来执行.这是什么意思呢 ? 举个例子来看比较快,

"runtimes": {
    "win7-x64": {}
}

在你的 project.json 中如果有以上这份定义,则代表你的程序执行时需要 win7-x64 版本的 runtime,你已经为你的应用程序指定了要这一份 runtime.所以,当你在做 dotnet publish 的时候,除了应用程序本身与相依的组件会产生出来以外,win7-x64 所有的 runtime 组件也会一并产生.因此,在你的 publish 输出目录里,你除了看到应用程序本身以及相关组件外,你还会看到 runtimes 目录,而里面装的就是 win7-x64 的 runtime 组件.

如果在你的 project.json 没有定义像上面那样的 runtimes 内容时,则当你在做 dotnet publish 时,.net core 就不知道你需要的 runtime 是那一个,因此它会在 publish 输出目录下独立建一个目录,然后把所有 runtimes 组件依操作系统名称的目录依序放好,因此在输出目录里,除了你的程序及相关组件以外,还会有这一堆 runtimes 组件,但这并不代表你得把所有的内容带走才能执行你的程序.放在 runtimes 目录的那些 runtimes 程序可以不用跟着你的程序带走,只要你的目地电脑上已安装了 .net core,在这台电脑上执行此程序时,此程序会自动去依照环境变量的内容来找到该操作系统所需的 runtimes 组件.所以,你只要带走你的程序及相关组件即可.文件数量与容量就会小很多,这就是 shared runtime 的精神.但有一点千万别误会了,如果你已经指定了 runtime,则产生出来的程序就会依该操作系统不同而建立.比如,你指定 runtime 是 win7-x64 时,在编译后你的程序将会是可可执行文件的形式存在,让你可以直接执行该文件.但若你没指定 runtime 时,在编译后你的程序将以组件的形式存在,你只能用 dotnet.exe 来启动你的程序.

希望以上的内容对于你在使用 ASP.NET Core RC2 版本与 .Net Core 时能有帮助.