深刻 .NET Core 基礎 - 2:共享框架

 

深刻 .NET Core 基礎 - 2:共享框架

原文地址:https://natemcmaster.com/blog/2018/08/29/netcore-primitives-2/javascript

共享框架從 .NET Core 1.0 就成爲基礎部分。ASP.NET Core 從 .NET Core 2.1 開始也做爲共享框架發佈。你可能沒有注意到該進展是否順利。可是,這裏有一些關於該設計的顛簸和討論。本文將深刻到共享框架,並探討它的一些常見陷阱。java

1. 基礎

.NET Core 應用程序有兩種運行模型:基於框架或者自包含。在個人 MacBook 上,最小的自包含 ASP.NET Core 應用程序的尺寸是 83MB 和 350 個文件。另外一方面,最小的框架依賴應用的尺寸是 239KB 和 5 個文件。git

能夠經過下面的命令來生成兩種應用程序github

dotnet new web
dotnet publish --runtime osx-x64 --output bin/self_contained_app/
dotnet publish --output bin/framework_dependent_app/

在應用程序運行的時候,兩種模式的功能是等效的。因此爲何存在不一樣類型的模型?如微軟的文檔所述:web

框架依賴的發佈基於共享的系統範圍的 .NET Core 版本......json

而自包含的發佈不依賴與目標系統上的共享組件。全部的組件......都包含在應用程序中。windows

該文檔很是好地解釋了每種模式的優勢。bash

2. 共享框架

長短短說,.NET Core 共享框架是一個包含程序集 (*.dll 文件) 的,不在應用程序文件夾中的文件夾。這些程序集一塊兒版本化和發佈。該文件夾是 "共享的系統範圍的 .NET Core 版本" 的一部分,一般在 C:/Program Files/dotnet/shared 文件夾中。網絡

當你執行 dotnet.exe WebApp.dll 的時候,.NET Core 宿主 必須:架構

  1. 發現你的應用所依賴的名稱和版本

  2. 在公共位置找到這些依賴內容

這些依賴能夠在多個位置發現,包括,可是不限於,這些共享框架。在上一篇文章中,我已經總結了 deps.jsonruntimeconfig.json 文件是如何配置宿主的行爲。請查看它來獲得更詳細的說明。

.NET Core 宿主讀取 *.runtimeconfig.json 文件來獲得須要加載哪一個共享框架。其內容可能相似於以下:

{
  "runtimeOptions": {
    "framework": {
      "name": "Microsoft.AspNetCore.App",
      "version": "2.1.1"
    }
  }
}

共享框架名稱 只是一個名稱。根據約定,該名稱以 App 結束,但能夠是任何名稱,好比 "FooBananaShark"。

共享框架版本 表示最小版本。.NET Core 宿主從不運行在最小版本上,而是試圖運行在更高的版本上。

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 中,它使用項目文件中的兩個片斷來決定該文件中框架部分的內容:

  1. MicrosoftNETPlatformLibrary 屬性。默認對於全部的 .NET Core 項目設置爲 Microsoft.NETCore.App

  2. NuGet 恢復的結果,它必須包含一個同名的包

對於全部的項目,.NET Core SDK 對 Microsoft.NETCore.App添加隱式的包引用。ASP.NET Core 默認設置 MicrosoftNETPlatformLibraryMicrosoft.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

https://github.com/dotnet/cli/blob/v2.1.400/Documentation/specs/runtime-configuration-file.md

相關文章
相關標籤/搜索