原文地址:https://natemcmaster.com/blog/2018/08/29/netcore-primitives-2/
共享框架從 .NET Core 1.0 就成為基礎部分。ASP.NET Core 從 .NET Core 2.1 開始也作為共享框架發布。你可能沒有注意到該進展是否順利。但是,這里有一些關於該設計的顛簸和討論。本文將深入到共享框架,並探討它的一些常見陷阱。
1. 基礎
.NET Core 應用程序有兩種運行模型:基於框架或者自包含。在我的 MacBook 上,最小的自包含 ASP.NET Core 應用程序的尺寸是 83MB 和 350 個文件。另一方面,最小的框架依賴應用的尺寸是 239KB 和 5 個文件。
可以通過下面的命令來生成兩種應用程序
dotnet new web dotnet publish --runtime osx-x64 --output bin/self_contained_app/ dotnet publish --output bin/framework_dependent_app/
在應用程序運行的時候,兩種模式的功能是等效的。所以為什么存在不同類型的模型?如微軟的文檔所述:
框架依賴的發布基於共享的系統范圍的 .NET Core 版本......
而自包含的發布不依賴與目標系統上的共享組件。所有的組件......都包含在應用程序中。
該文檔非常好地解釋了每種模式的優點。
2. 共享框架
長短短說,.NET Core 共享框架是一個包含程序集 (*.dll 文件) 的,不在應用程序文件夾中的文件夾。這些程序集一起版本化和發布。該文件夾是 "共享的系統范圍的 .NET Core 版本" 的一部分,通常在 C:/Program Files/dotnet/shared
文件夾中。
當你執行 dotnet.exe WebApp.dll
的時候,.NET Core 宿主 必須:
-
發現你的應用所依賴的名稱和版本
-
在公共位置找到這些依賴內容
這些依賴可以在多個位置發現,包括,但是不限於,這些共享框架。在上一篇文章中,我已經總結了 deps.json
和 runtimeconfig.json
文件是如何配置宿主的行為。請查看它來得到更詳細的說明。
.NET Core 宿主讀取 *.runtimeconfig.json
文件來得到需要加載哪個共享框架。其內容可能類似於如下:
{ "runtimeOptions": { "framework": { "name": "Microsoft.AspNetCore.App", "version": "2.1.1" } } }
共享框架名稱 只是一個名稱。根據約定,該名稱以 App
結束,但可以是任何名稱,比如 "FooBananaShark"。
共享框架版本
2.1 我已經安裝的共享框架是哪些?
執行 dotnet --list-runtimes
。它將會顯示名稱、版本和共享框架的位置。對於 .NET Core 3.1,共享框架的列表如下所示。
dotnet --list-runtimes Microsoft.AspNetCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
2.2 比較 Microsoft.NETCore.App,AspNetCore.App 和 AspNetCore.All
在 .NET Core 2.2 中,有如下三種共享框架:
框架名稱 | 說明 |
---|---|
Microsoft.NETCore.App | 基礎運行時. 它支持類似 System.Object , List , string , 內存管理,文件和網絡 I/O,線程等等 |
Microsoft.AspNetCore.App | 默認的 Web 運行時. 它導入了 Microsoft.NETCore.App, 並添加了使用 Kestrel Mvc、SingalR、Razor 和部分 EF Core 構建 HTTP 服務的 API |
Microsoft.AspNetCore.All | 集成了第三方內容。它導入了 Microsoft.AspNetCore.App. 加入了對 EF Core + SQLite 支持, 使用 Redis 的擴展, 從 Azure Key Vault 進行配置, 以及更多內容. (在 .NET Core 3.0 中將被退役 deprecated in 3.0.) |
.NET Core 3.0 增加了 Microsoft.WindowsDesktop.App,並刪除了
Microsoft.AspNetCore.All
。
2.3 與 NuGet package 的關系
.NET Core SDK 生成 runtimeconfig.json
文件。在 .NET Core 1 和 2 中,它使用項目文件中的兩個片段來決定該文件中框架部分的內容:
-
MicrosoftNETPlatformLibrary
屬性。默認對於所有的 .NET Core 項目設置為Microsoft.NETCore.App
-
NuGet 恢復的結果,它必須包含一個同名的包
對於所有的項目,.NET Core SDK 對 Microsoft.NETCore.App
添加隱式的包引用。ASP.NET Core 默認設置 MicrosoftNETPlatformLibrary
為 Microsoft.AspNetCore.App
此 NuGet 包,實際上,並不提供共享框架。重復一遍,這個 NuGet 包 不提供共享框架(后面我還會再次重復)。該 NuGet 包僅僅為編譯器提供 API 集和很少的其它 SDK 部分。共享框架文件來自於安裝的運行時,或者在 Visual Studio 中打包,Docker 映像,以及一些 Azure 服務。
2.4 版本前滾
如前所述,runtimeconfig.json 是最小版本號。實際使用的版本基於版本前滾策略。常見的方式:
-
如果某個應用程序的最小版本是 2.1.0,那么 2.1.* 的最高版本將會被應用
我將會在下一篇詳細說明。
2.5 層化的共享框架
此特性在 .NET Core 2.1 被加入。
共享框架可以依賴於其它的共享框架。它被引入來支持 ASP.NET Core,它被從包的運行時存儲轉換成了共享框架。
例如,如果你進入並查看 $DOTNET_ROOT/shared/Microsoft.AspNetCore.All/$version/
文件夾,你將會看到一個 Microsoft.AspNetCore.All.runtimeconfig.json
文件。
{ "runtimeOptions": { "tfm": "netcoreapp2.1", "framework": { "name": "Microsoft.AspNetCore.App", "version": "2.1.2" } } }
在 .NET Core 3.1 中,內容如下:
{ "runtimeOptions": { "tfm": "netcoreapp3.1", "framework": { "name": "Microsoft.NETCore.App", "version": "3.1.0" }, "rollForward": "LatestPatch" } }
2.6 多層查找
此特性在 .NET Core 2.0 加入。
宿主會探測多個位置來尋找合適的共享框架。查找從 dotnet root 開始,這是包含 dotnet
可執行程序的文件夾。它可以被環境變量 DOTNET_ROOT
所指定的文件夾覆蓋。第一個探測的位置是
$DOTNET_ROOT/shared/$name/$version
如果沒有合適的文件夾存在,將會使用 多層查找 試圖查看預定義的全局位置。此特性可以通過環境變量 DOTNET_MULTILEVEL_LOOKUP=0
來關閉。默認的全局位置是:
OS | Location |
---|---|
Windows | C:\Program Files\dotnet (64-bit processes) C:\Program Files (x86)\dotnet (32-bit processes) (See in the source code) |
macOS | /usr/local/share/dotnet (source code) |
Unix | /usr/share/dotnet (source code) |
宿主將檢測的目錄位於:
$GLOBAL_DOTNET_ROOT/shared/$name/$version
2.7 ReadyToRun
共享框架中的程序集語境使用名為 crossgen
的工具進行了預優化。該過程生成了 ReadyToRun
版本的程序集,其對特定版本的操作系統和 CPU 架構進行了優化。主要的性能收益在於縮短了 JIT 花費在啟動階段的代碼准備時間。
3. 陷阱
我想每個 .NET Core 開發者都可能在某個時候落入某個陷阱中。我將試圖說明它是如何發生的。
3.1 HTTP Error 502.5 Process Failure
當在 IIS 上寄宿 ASP.NET Core 或者在 Azure 的 Web Services 上寄宿的時候,這是最常見的問題。典型發生在開發者升級項目之后,或者部署到一台最近沒有更新的機器上。實際的錯誤來自於共享框架沒有被找到,導致 .NET Core 應用程序不能啟動。當 .NET Core 不能運行應用程序,IIS 輸出 502.5
錯誤,但是並沒有暴露內部的錯誤信息。
3.2 “The specified framework was not found”
It was not possible to find any compatible framework version The specified framework 'Microsoft.AspNetCore.App', version '2.1.3' was not found. - Check application dependencies and target a framework version installed at: /usr/local/share/dotnet/ - Installing .NET Core prerequisites might help resolve this problem: http://go.microsoft.com/fwlink/?LinkID=798306&clcid=0x409 - The .NET Core framework and SDK can be installed from: https://aka.ms/dotnet-download - The following versions are installed: 2.1.1 at [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] 2.1.2 at [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
此錯誤通常潛伏在 HTTP 502.5 之后,或者 Visual Studio 測試管理器的錯誤中。
它發生在 runtimeconfig.json 文件中指定了特定的框架名稱和版本,而宿主不能使用多級查找和前向錯略找到對應的版本。如前所述。
3.3 為 Microsoft.AspNetCore.App 更新 NuGet 包
NuGet 中的 Microsoft.AspNetCore.App
包不提供共享框架。僅僅提供用於 C#/ F# 編譯器的 API 和一些 SDK 支持。你必須單獨下載並安裝共享框架。
另外,基於前滾策略,你也不必升級 NuGet 包的版本來使得你的應用程序運行在更新的共享框架版本上。
將共享框架表現為項目文件中的一個 NuGet 包可能是 ASP.NET Core 團隊的一個設計錯誤。表現共享框架的包並不是一個正常的包。不像多數的其它包,它不是自滿足的。我們有理由期待在項目使用 <PackageReference>
引用某個包的時候,NuGet 能夠安裝任何所需要的內容,令人沮喪的是,這里的包偏離了該模式。有多個提案建議修復該問題。我期望某個提案很快落地。
3.4 <PackageReference Include="Microsoft.AspNetCore.App" />
所有其它的 <PackageReference> 都必須包含 Version
屬性。缺失版本的包引用僅僅工作於項目開始部分使用 <Project Sdk="Microsoft.NET.Sdk.Web">。且僅僅工作於 Microsoft.AspNetCore.{App, All}
包。Web SDK 將基於項目中的其它值自動提取這些包的版本,例如 <TargetFramework> 和 <RuntimeIdentifier>
如果你為包引用元素指定了版本的話,或者你沒有使用 Web SDK,該魔法將不會工作。很難建議一個好的解決方案,因為最佳的方式依賴於你理解的水平和項目的類型。
3.5 發布修剪
當你使用 dotnet publish
創建一個框架依賴的應用程序時,SDK 使用 NuGet 恢復結果來決定哪個程序集將會包含到發布文件夾中。有些從 NuGet 包中復制過來,有些不會,因為它們被期望存在於共享框架中。
這很容易導致錯誤,因為 ASP.NET Core 作為共享框架存在,也作為 NuGet 包存在。修剪使用進行某些圖匹配來決定傳遞依賴、升級等等,來選取正確的文件。
例如,對於下面的項目
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.9" />
MVC 實際上是 Microsoft.AspNetCore.App 的一部分,但是,當執行 dotnet publish
的時候,它發現你的項目決定 升級 Microsoft.AspNetCore.Mvc.dll
到比 Microsoft.AspNetCore.App
的版本 2.1.1 更高的版本,所以,它將會把 Mvc.dll 放到發布目錄中。
這樣是不理想的,因為你的應用程序尺寸變得更大了,並且你沒有得到 ReadyToRun
優化之后的 Microsoft.AspNetCore.Mvc.dll
。如果你通過 ProjectReference 傳遞升級或者通過第三方依賴升級,就會無意中發生。
3.6 困惑於共享框架的目標框架名稱
很容易認為:"netcoreapp2.0" == "Microsoft.NETCore.App, v2.0.0"
。但這不是真的。目標框架名稱 (也稱為 TFM) 在項目文件中使用 <TargetFramework> 指定。"netcoreapp2.0" 是一個友好的,你所使用的 .NET Core 版本名稱。
這個 TFM 的缺陷在於它太短了。它不能說明像多個共享框架這樣的問題,特定版本的補丁,版本前滾,輸出類型,以及自包含和框架依賴的發布等等。SDK 將試圖從 TFM 來推斷這些設置,但它不能推斷所有的事情。
所以,精確地說,“netcoreapp2.0” 表示至少 V2.0.0 的 "Microsoft.NETCore.App“
3.7 困惑的項目設置
最后一個提醒的陷阱是項目設置。許多術語和設置的名稱並不確切。使用令人困惑的術語,所以,如果你搞混了它們,這並不是你的過錯。
下面,我列出常見的項目設置,以及實際的含義。
<PropertyGroup> <TargetFramework>netcoreapp2.1</TargetFramework> <!-- Actual meaning: * The API set version to use when resolving compilation references from NuGet packages. --> <TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks> <!-- Actual meaning: * Compile for two different API version sets. This does not represent multi-layered shared frameworks. --> <MicrosoftNETPlatformLibrary>Microsoft.AspNetCore.App</MicrosoftNETPlatformLibrary> <!-- Actual meaning: * The name of the top-most shared framework --> <RuntimeFrameworkVersion>2.1.2</RuntimeFrameworkVersion> <!-- Actual meaning: * version of the implicit package reference to Microsoft.NETCore.App which then becomes the _minimum_ shared framework version. --> <RuntimeIdentifier>win-x64</RuntimeIdentifier> <!-- Actual meaning: * Operating system kind + CPU architecture --> <RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers> <!-- Actual meaning: * A list of operating systems and CPU architectures which this project _might_ run on. You still have to select one by setting RuntimeIdentifier. --> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.2" /> <!-- Actual meaning: * Use the Microsoft.AspNetCore.App shared framework. * Minimum version = 2.1.2 --> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.2" /> <!-- Actual meaning: * Use the Microsoft.AspNetCore.Mvc package. * Exact version = 2.1.2 --> <FrameworkReference Include="Microsoft.AspNetCore.App" /> <!-- Actual meaning: * Use the Microsoft.AspNetCore.App shared framework. (This is new and unreleased...see https://github.com/dotnet/sdk/pull/2486) --> </ItemGroup>
4. 總結
共享框架是 .NET Core 一個可選特性,我可以合理地認為多數的用戶憎恨陷阱。我仍然認為對於 .NET Core 開發者來說,理解背后發生了什么是有用的。並期望這是關於共享框架的有用的總結。我還給出了官方的鏈接和指南,以便幫助你找到更多信息。如果有任何問題,歡迎留言。
More
-
Packages, metapackages and frameworks: https://docs.microsoft.com/en-us/dotnet/core/packages
-
.NET Core application deployment: https://docs.microsoft.com/en-us/dotnet/core/deploying/. Especially read the part about “Framework-dependent deployments (FDD)”
-
Specs on runtimeconfig.json and deps.json:
https://github.com/dotnet/cli/blob/v2.1.400/Documentation/specs/runtime-configuration-file.md
-
The implementation of the shared framework lookup: https://github.com/dotnet/core-setup/blob/v2.1.3/src/corehost/cli/fxr/fx_muxer.cpp#L464