深入 .NET Core 基礎:deps.json, runtimeconfig.json 以及 dll

2021-02-16 DotNET技術圈

原文地址:https://natemcmaster.com/blog/2017/12/21/netcore-primitives/

1. .NET Core 應用程式基礎

我學習過使用 gcc,C++ 和 vim 編程。當我開始使用 C# 和 .NET 的時候,點擊 Visual Studio 中的 運行 按鈕就是魔法,也帶者失望。失望 - 不是因為我希望編寫 Makefile - 而是因為我不知道 運行 都做了什麼。所以,我開始探索。在本博文中,我將展示在 .NET Core 中使用的多數基礎工具,並手工創建 .NET Core 應用程式而不藉助於 Visual Studio。如果你是 .NET Core 的新手,並且希望揭開內幕,本文就是為您而來。如果您已經是一個 .NET Core 的開發者,並且好奇 *.deps.json 或者 *.runtimeconfig.json 文件是做什麼的,我也會涵蓋這些內容。

我將會終止 Visual Studio 的魔法,而一直使用命令行工具。為了你能夠進行,您需要

.NET Core 2.1 SDK ( 實際上,.NET Core 3.1 SDK 已經發布,我想你更應該下載這個最新版)。下面的這些步驟是在 macOS 上完成的,但是它們也同樣在 Linux 和 Windows 上一樣工作,如果您將路徑更改為 c:\Program Files\dotnet\ 和 dotnet.exe 的話。

2. C# 編譯器

C# 編譯器將 *.cs 文件編譯為 *.dll 文件,也被稱為程序集文件。程序集文件具有便攜可執行文件格式,.NET Core 可以在 Windows、macOS 和 Linux 上執行它。.NET Core app 是一系列 *.dll 文件的集合 (包括少量的配置文件)。它可以通過多種程序設計語言,例如 VB 或者 F# 等所生成,但是,C# 是最常用的一種。

C# 編譯器可以直接調用來生成程序集文件。C# 編譯器可以在 .NET Core SDK 中發現,並像下面這樣被調用。

dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll -help

讓我們為它提供一個輸入內容。首先,創建名為 Program.cs 的文件,並編寫如下 C# 代碼:

1

2

3

4

5

6

class Program

{

    static void Main(string[] args)

        => System.Console.WriteLine("Hello World!");

}

然後,在命令行,執行如下命令:

1

2

3

4

5

6

> dotnet \

  /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \

  -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \

  -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Console.dll \

  -out:Program.dll \

  Program.cs

在 .NET Core 3.1 中,NuGetFallbackFoler 已經從 sdk 文件夾中移除了。這些程序集已經轉移到 C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1 文件夾中。

如果在 Windows 下,注意空格的處理:

1

dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -out:Program.dll Program.cs

參數的含義如下:

dotnet - C# 編譯器本身也是一個 .NET Core 應用程式,所以,我們需要通過 dotnet 命令來啟動它

/usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll - C# 編譯器的路徑。在 Windows 上,路徑就是:C:\Program Files\dotnet\

-reference 參數指向了 System.Runtime.dll 和 System.Console.dll,這些類似於 C++ 中的頭文件,它們為編譯器提供關於 System.Object 和 System.Console 的信息。

-out:Program.dll,輸出文件名。.dll 的擴展名是 .NET Core 的約定,並不是必需的。如果沒有指定,編譯器將生成名為 Program.exe 的文件。在 Windows 系統上,這會導致一點誤解,因為你並不能通過雙擊 Program.exe 來啟動它,所以,在 .NET Core 中,我們總是使用 .dll 擴展名。

Reference 引用允許我們使用代碼中涉及的在其它 .NET Core 代碼中定義的成千上萬的類型,例如 List、Integer 以及 HttpClient 類型等等。但是,你不得不告訴編譯器到哪裡去找到它們。如果你刪除掉 -reference:*** 部分,編譯器將會失敗,並返回如下錯誤:

1

2

3

Program.cs(1,11): error CS0518: Predefined type 'System.Object' is not defined or imported

Program.cs(3,26): error CS0518: Predefined type 'System.String' is not defined or imported

Program.cs(3,16): error CS0518: Predefined type 'System.Void' is not defined or imported

示例中使用的路徑是 /usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app。這些來自與 Microsoft.NETCore.App 這個 NugGet 包,後面我們會討論它。

3. runtimeconfig.json

對於 .NET Core 應用程式來說,runtime.config.json 文件是必需的。術語 runtime 、shared framework、或者 platform 經常互換,但是,在談論 .NET Core 的時候,它們是一回事。該 JSON 配置文件用於運行時。

如果您擁有了上一步所得到的程序集,您可以試著在命令行運行它,通過 dotnet 工具。沒有這個 runtime.config.json,該嘗試將會失敗:

1

2

>dotnet Program.dll

A fatal error was encountered. The library 'libhostpolicy.dylib' required to execute the application was not found in '/Users/nmcmaster/code/'.

在 Windows 環境的 .NET Core 3.1 環境下,我得到是:

1

2

3

>dotnet Program.dll

A fatal error was encountered. The library 'hostpolicy.dll' required to execute the application was not found in 'C:\temp\dotnet\'.

Failed to run as a self-contained app. If this should be a framework-dependent app, add the C:\temp\dotnet\Program.runtimeconfig.json file specifying the appropriate framework.

該段說明的意思是,.NET Core 不能找到用於執行 Program.dll 文件所必需的某些文件。為了解決這個問題,創建名為 Program.runtimeconfig.json 的文件,並使用如下內容:

1

2

3

4

5

6

7

8

{

  "runtimeOptions": {

    "framework": {

      "name": "Microsoft.NETCore.App",

      "version": "2.0.0"

    }

  }

}

注意,在 .NET Core .3.1 下,文件內容如下:

1

2

3

4

5

6

7

8

{

  "runtimeOptions": {

    "framework": {

      "name": "Microsoft.NETCore.App",

      "version": "3.1.0"

    }

  }

}

這些設置指示 dotnet 使用 Microsoft.NETCore.App 3.1.0 共享框架。該框架也是最常使用的框架,但是,還有其它的框架,例如 Microsoft.AspNetCore.App。不像 .NET Framework 是裝個機器範圍生效,可以有多個 .NET Core 共享框架安裝在同一臺機器上。dotnet 將讀取該 JSON 文件,並在 /usr/local/share/dotnet/shared/$FrameworkName/$Version/ 中查找需要的文件並運行應用程式。

說明:如果有更高版本的 Microsoft.NeTCore.App 補丁安裝,例如 shared/Microsoft.NETCore.App/2.0.4/,dotnet 將自動使用更高版本。

現在,執行 dotnet Program.dll。

1

2

>dotnet Program.dll

Hello world!

4. Package 包

包提供了在不同項目之間、項目組之間以及組織之間共享代碼的方式,.NET 程序集被打包到 *.nupkg 文件中,這僅僅是一個 ZIP 壓縮格式文件,並含有一個 XML 文件 (.nuspec) ,包含有關於該包的元數據。

最流行的一個 .NET 包稱為 JSON.NET,也被稱為 Newtonsoft.Json。它提供了解析和序列化 JSON 的 API。我們可以從 NuGet.org 得到它並提取到磁碟上。

1

2

3

mkdir -p ./packages/Newtonsoft.Json/10.0.3/

curl -L https://www.nuget.org/api/v2/package/Newtonsoft.Json/10.0.3 | tar -xf - -C ./packages/Newtonsoft.Json/10.0.3/

Windows 環境下

1

2

3

4

mkdir ./packages/Newtonsoft.Json/10.0.3/

Invoke-WebRequest https://www.nuget.org/api/v2/package/Newtonsoft.Json/10.0.3 -OutFile Newtonsoft.Json.10.0.3.zip

Expand-Archive Newtonsoft.Json.10.0.3.zip -D ./packages/Newtonsoft.Json/10.0.3/

為了演示它的使用,我們將更新前一步的示例代碼,以 JSON 對象格式輸出信息。

1

2

3

4

5

6

class Program

{

    static void Main(string[] args)

      => System.Console.WriteLine(

          Newtonsoft.Json.JsonConvert.SerializeObject(new { greeting = "Hello World!" }));

}

這需要添加更多的編譯參數到編譯器的參數列表中,以便使用 Newtonsoft.Json 的 API。

1

2

3

4

5

6

7

> dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \

    -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \

    -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Console.dll \

    -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Collections.dll \

    -reference:./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll \

    -out:Program.dll \

    Program.cs

使用 .NET Core 3.1 的命令如下:

1

dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Collections.dll' -reference:'./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll' -out:Program.dll Program.cs

注意:顯然我們需要 reference:Newtonsoft.Json.dll,但是,為什麼需要 System.Collections.dll?這是因為我們還使用了匿名類型,new { greeting }。在背後,C# 編譯器在匿名類型上生成了一個 .Equals() 方法,該方法調用了System.Collections.Generic.EqualityComparer,它定義在 System.Collections.dll 中。

編譯應當成功,雖然帶有一些警告信息。

1

Program.cs(4,35): warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, <br>Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches <br>identity 'System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' <br>of 'System.Runtime', you may need to supply runtime policy

在 .NET Core 3.1 下,實際上,我得到的輸出信息如下:

Microsoft (R) Visual C# Compiler version 3.4.0-beta4-19562-05 (ff930dec)
Copyright (C) Microsoft Corporation. All rights reserved.

warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
Program.cs(5,11): warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy

這意味著在 Newtonsoft.Json 的作者創建 Newtonsoft.Json.dll 的時候,他基於的 System.Runtime.dll 的版本是 4.0.20.0。但是,現在提供的 System.Runtime.dll 更新一些,版本是 4.2.0.0。如果在版本 4.0.20.0 到 4.2.0.0 之間有變化的化,會導致你運行的應用程式出現問題,所以,編譯器發出警告。幸運的是,這些變更是後向兼容的,所以 Newtonsoft.Json 將工作正常。我們可以通過添加參數 -nowarn:/CS1701 來抑制這些警告。

1

2

3

4

5

6

7

8

> dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \

    -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \

    -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Console.dll \

    -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Collections.dll \

    -reference:./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll \

    -nowarn:CS1701 \

    -out:Program.dll \

    Program.cs

  

對於 Windows 環境下的 .NET Core 3.1,命令如下:

dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Collections.dll' -reference:'./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll' -nowarn:CS1701 -out:Program.dll Program.cs

注意 CS1701 中的字母是大寫。

5. 動態連結

在上一步,我們編譯了一個引用 Newtonsoft.dll、System.Runtime.dll 和其它程序集的簡單應用程式。在添加 Newtonsoft.dll 之前,我們的應用程式工作良好。但是,在更新版本之後,該程序的運行將會失敗。

1

2

> dotnet Program.dll

Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified.

.NET 在運行時動態連結程序集。編譯器在程序集 Program.dll 中添加了到 Newtonsoft.Json.dll 的引用,但並不會將代碼複製進來。.NET Core 運行時期待能夠在應用程式執行的時候能夠加載名為 Newtonsoft.Json.dll 的程序集。對於 System.Runtime.dll 和System.Console.dll ,以及其它引用的 System.* 文件也是同樣的。

.NET Core 可以通過配置在一系列位置尋找 Newtonsoft.Json.dll,但是,為了簡單起見,我們可以將它複製到 Program.dll 的同一個文件夾中。

1

2

3

> cp ./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll ./

> dotnet Program.dll

{"greeting":"Hello World!"}

為什麼我們不需要將 System.Runtime.dll 和其它文件複製過來呢?這些文件通過 Microsoft.NETCore.App 共享框架動態連結過來,如前面所述。

6. deps.json

deps.json 文件是依賴說明文件。它可以用來配置來自包的動態連結到程序集。如前所述,.NET Core 可以配置為從多個位置來動態加載程序集。這些位置包括:

應用程式所在的目錄,與應用程式入口相同的文件夾,不需要配置。

包的緩存文件夾 (NuGet 恢復緩存或者 NuGet 回落文件夾)

優化之後的包緩存,或者運行時包存儲。

服務目錄 (servicing index),很少使用,用於 Windows Update 方式

共享框架 (通過 runtimeconfig.json 配置)

綜上所述,deps.json 定義可以動態連結的依賴列表,通常,該文件由機器生成,對於實際的應用程式,可能變得很大並且很複雜。但是,它是純文本形式的,所以我們可以使用編輯器來處理它。

添加名為 Program.deps.json 的文件到項目中,內容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

{

  "runtimeTarget": {

    "name": ".NETCoreApp,Version=v2.0"

  },

  "targets": {

    ".NETCoreApp,Version=v2.0": {

      "Newtonsoft.Json/10.0.3": {

        "runtime": {

          "lib/netstandard1.3/Newtonsoft.Json.dll": {

            "assemblyVersion": "10.0.0.0",

            "fileVersion": "10.0.3.21018"

          }

        }

      }

    }

  },

  "libraries": {

    "Newtonsoft.Json/10.0.3": {

      "type": "package",

      "serviceable": false,

      "sha512": ""

    }

  }

}

為了展示這是如何工作的,將現在的與 Program.dll 同一文件夾的 Newtonsoft.Json.dll 刪除。然後,執行 dotnet Program.dll 。

1

2

3

4

5

6

> rm Newtonsoft.Json.dll

> dotnet Program.dll

Error:

  An assembly specified in the application dependencies manifest (Program.deps.json) was not found:

    package: 'Newtonsoft.Json', version: '10.0.3'

    path: 'lib/netstandard1.3/Newtonsoft.Json.dll'

雖然提供了 Program.deps.json 文件,.NET Core 還需要一點關於到哪裡定位匹配 deps.json 文件中程序集的信息。該配置可以通過如下三種方式之一實現:

*.runtimeconfig.dev.json。這是配置的典型的最佳方式。添加文件 Program.runtimeconfig.dev.json 文件,其中設置了包文件夾的位置。它類似於 Program.runtimeconfig.json 文件,但它是可選的。典型地其中包含了文件完全路徑,所以不適於在不同機器上發布。

1

2

3

4

5

6

7

{

  "runtimeOptions": {

    "additionalProbingPaths": [

      "/Users/nmcmaster/code/packages/"

    ]

  }

}

命令行。你可以使用 exec 命令來手工指定 dotnet 命令中程序集的位置。使用 --additionalprobingppath 參數,可以指定多個值。

1

> dotnet exec --additionalprobingpath ./packages/ Program.dll

  

*.runtimeconfig.json。可以添加一個運行時設置來指定新的探測位置。它可以使用相對路徑。

1

2

3

4

5

6

7

8

9

10

11

{

   "runtimeOptions": {

     "framework": {

       "name": "Microsoft.NETCore.App",

       "version": "2.0.0"

     },

     "additionalProbingPaths": [

       "./packages/"

     ]

   }

 }

  

注意:在我的 .NET Core 3.1 環境下,這個 additionalProbingPaths 沒有工作。這裡是 StackOverflow 上的一個問題:https://stackoverflow.com/questions/56844233/additional-probing-paths-for-net-core-3-migration

7. 總結

對於大多數的開發工作,並不需要這些基礎。類似 NuGet、MSBuild 和 Visual Studio 自動處理獲取應用,C# 文件,調用編譯器,連結到調試器,以及其它任務。但是,我認為知道背後是如何工作的還是非常用用。當然了,你還可以更加深入。在 *.dll 文件中實際有什麼?什麼是 *.pdb 文件?什麼是 crossgen 和 libcoreclr?我會把這些留在其它部分中。

8. 參考信息

Specs on runtimeconfig.json and deps.json: https://github.com/dotnet/cli/blob/v2.0.0/Documentation/specs/runtime-configuration-file.md

Assembly resolution and dynamic linking: https://github.com/dotnet/cli/blob/v2.0.0/Documentation/specs/corehost.md

相關焦點

  • JSON解析:JSON對象還能這樣???
    於是刷刷刷,開始編碼,模擬如下:import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;/** * @ClassName Test * @Description json字符串處理
  • 只要一個json文件3分鐘搭建一個json伺服器
    json-server是一款json數據伺服器,可以對json文件、js腳本生成的json數據、遠程json數據進行RESTFUL風格的增刪改查操作,可以通過參數、分頁、排序、全文搜索、關聯查詢、範圍查詢等進行複雜查詢,對開發者特別是前端開發者是一款非常好用的開發工具。
  • asp.net4.0,dll後臺限制破解
    ShoveEIMS3.SiteDataManager" enableEventValidation="false" %> <%@ Register Src="Controls/Login_Head.ascx" TagName="Head" TagPrefix="uc" %> 看得根目錄bin目錄下有, ShoveEIMS3.SiteDataManager,說明他就是調用了dll
  • [英文版].NET5 Uses database as configuration central server
    The value of column 'Name' conform with 「the flattening of hierarchical data」(https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?
  • npm配置文件package.json裡面的欄位你知道多少
    創建一個前端項目目前都離不開npm包管理工具,所以根目錄必須有一個package.json文件如何創建呢?description: 項目的描述-- 對項目進行說明的欄位main:包入口文件,一般開發自己的npm包功能才用得到,項目就不會用到-- 引入你的包的時候的入口文件(比如你的包叫做hello)// src// | - test.js// package.json
  • JSON.stringify() 的 5 個秘密特性
    zoomdong連結:https://juejin.im/post/5e842da76fb9a03c854610c7原文作者:原文作者:Prateek Singh原文連結:https://medium.com/javascript-in-plain-english/5-secret-features-of-json-stringify-c699340f9f27
  • 大家一般用什麼工具測試HTTP和json接口?
    歷程以前webservice接口,用soapUI可視化界面測試;到了Restful興起的時候測試json接口我就開始用Python編寫。下面列舉幾個市面上會提到的工具:Postman。Apifox。是不是很簡單,就能模擬請求,以及得到響應的信息。然後你如果要變成自動化測試,那麼你自己加斷言,判斷預期結果和實際結果是否一致就可以了。測試帶有加密信息的接口延伸一下,如果你參數是加密,或者接口有籤名,怎麼搞?
  • 如何在 ASP.Net Core 中使用 Swagger - 51CTO.COM
    });            }); 接下來就要啟動 Swagger了,在 Startup.Configure 方法下添加如下代碼:app.UseSwagger();     app.UseSwaggerUI(c =>     {         c.SwaggerEndpoint("/swagger/v1/swagger.json