dotnet cli 是 .Net Core 功能中最有用的特性之一。在這篇文章裡,我們將介紹幾個.Net OSS 工具是如何使用 dotnet cli,並介紹如何在日常開發中使用新的 cli 工具。
正文
關鍵要點
- dotnet cli 使得基於. Net 專案的自動化和指令碼編寫變得非常簡單,尤其是與十多年前的. Net 技術相比。
- dotnet cli 可擴充套件性模型創造了條件,使得透過 Nuget 將外部.NET 編寫的命令列程式整合到你的自動化構建中成為可能。
- dotnet cli 允許在你的構建指令碼中針對解決方案進行測試。
- dotnet cli 的測試輸出有助於更好地使用持續整合 (CI)。
- 使用 Docker 之類的容器技術比使用 dotnet cli 要容易得多。
隨著.NET Core 2.0 的釋出,微軟擁有了通用、模組化、跨平臺和開源平臺的下一個主要版本,該版本最初於 2016 年釋出。.NET Core 已經建立了許多 API,這些 API 在.NET 框架的當前版本中是可用的。它最初是為了下一代 ASP.NET 解決方案建立的,但現在是許多其他場景的驅動和基礎,包括物聯網、雲和下一代移動解決方案。在關於.NET Core 的第二個系列的文章中,我們將進一步探討.NET Core 的優點,以及它如何不僅有益於傳統的.NET 開發人員,也有益於所有需要為市場提供強健的、高效的和經濟的解決方案的技術人員。
InfoQ 的這篇文章是“.NET Core” 系列的一部分。您可以透過 RSS 訂閱接收通知。
最近總有人問我,和那些要麼遲疑,要麼不能退出舊版本、全功能的.NET 的人相比,選擇.NET Core 的優勢是什麼?我在回答中會提到.NET Core 有更好的效能、改進的 csproj 檔案格式、改進的 ASP 可測試性,並且它是跨平臺的。
作為幾個 OSS 工具 (Marten、StructureMap,以及在這個專案中作為例子被取用的Alba) 的作者,對我個人而言最大的優勢可能是dotnet cli的出現。我個人認為,結合新的.NET SDK csproj檔案格式一起使用時,dotnet cli 工具使我可以更容易建立專案和維護構建指令碼。我可以更容易在構建指令碼中執行測試,更容易使用和分發 Nuget 包,cli 可擴充套件性機制非常適合將透過 Nuget 包分發的自定義可執行檔案合併到自動構建中。
若要開始使用 dotnet cli,首先要在開發機器上安裝.NET SDK。安裝完成後,給你一些有用的提示:
- 將“dotnet”工具全域性安裝到你的 PATH 中,這樣在任何地方都可以透過命令列提示符使用它。
- dotnet cli 採用 Linux 風格的命令語法,用“–word [value]”這種普通寫法表示選擇的引數,或者直接用縮寫形式“-w [value]”。如果您習慣 Git 或 Node.js 命令列工具,就不會對 dotnet cli 感到陌生。
- “dotnet –help”將列出已安裝的命令和一些基本語法用法。
- “dotnet –info”將告訴你使用的是哪個版本的 dotnet cli。在持續整合構建中呼叫此命令可能是一個好主意,以便在本地工作併在構建伺服器失敗時排除故障,反之亦然。
- 儘管我在本文中討論的是.NET Core,但是請註意,你可以在完整.NET 框架的以前版本中使用新的 SDK 專案格式和 dotnet cli。
命令列中的 Hello World
為了簡單瞭解一下 dotnet cli 的一些亮點,讓我們假設想構建一個簡單的“Hello World”ASP.NET Core 應用程式。不過,為了好玩,我們來新增一些新花樣:
1. 我們的 web 服務將在一個單獨的專案中進行自動化測試。
2. 我們將透過 Docker 容器部署我們的服務,因為這是很酷的做法 (它展示了更多的 dotnet cli)。
3. 當然,我們將盡可能多地使用 dotnet cli。
如果您想看到這段程式碼的最終結果,請檢視this GitHub repository。
首先,讓我們從一個名為“DotNetCliArticle”的空目錄開始,並開啟您最喜歡的命令列工具到該目錄。我們將從使用“dotnet new”命令來生成解決方案檔案和新專案開始。.NET SDK 附帶了幾個用於建立常見專案型別或檔案的通用模板,以及其他可作為外接程式使用的模板 (稍後部分將對此進行詳細介紹)。要檢視在你的機器上可用的模板,可以使用以下命令 dotnet new -help,它應該會給出如下輸出:
你可能會留意到有一個 sln 模板,它針對的是空解決方案檔案。我們將使用該模板,鍵入 dotnet new sln 命令,該命令將生成以下輸出:
The template “Solution File” was created successfully. |
預設情況下,此命令將以包含的目錄命名解決方案檔案。因為我將根目錄命名為為“DotNetCliArticle”,所以生成的解決方案檔案是“DotNetCliArticle.sln”。
接下來,讓我們用以下命令新增“Hello,World”的實際專案:
dotnet new webapi –output HeyWorld |
上面的命令意思是將“webapi”模板用到透過“output”引數選擇的“HeyWorld”中。這個模板將生成一個精簡的 MVC Core 專案結構,適合於無頭 API。同樣,預設的做法是根據所在的目錄命名專案檔案,因此我們在目錄下得到一個名為“HeyWorld.csproj”的檔案,以及所有基本檔案,組成一個最小的 ASP.NET MVC Core API 專案。該模板還設定了所有必要的 Nuget 對 ASP.NET Core 的取用,我們在新專案啟動時會用到它們。
由於我剛好在一個小型 Git 儲存庫中構建了它,在使用 Git add 添加了任何新檔案之後,我使用 Git status 檢視新建立的檔案:
new file: HeyWorld/Controllers/ValuesController.cs |
new file: HeyWorld/HeyWorld.csproj |
new file: HeyWorld/Program.cs |
new file: HeyWorld/Startup.cs |
new file: HeyWorld/appsettings.Development.json |
new file: HeyWorld/appsettings.json |
現在,要將新專案新增到我們的空解決方案檔案中,您可以像這樣使用“dotnet sln”命令:
dotnet sln DotNetCliArticle.sln add HeyWorld/HeyWorld.csproj |
現在我們有了一個新的 ASP.NET Core API 服務作為外殼,無需開啟 Visual Studio.NET(或者是 JetBrains Rider)。為了更進一步,在編寫任何實際程式碼之前啟動我們的測試專案,我發出以下命令:
dotnet new xunit –output HeyWorld.Tests |
dotnet sln DotNetCliArticle.sln add HeyWorld.Tests/HeyWorld.Tests.csproj |
上面的命令使用 xUnit.NET 建立一個新專案,並將該新專案新增到我們的解決方案檔案中。測試工程需要對“HeyWorld”的工程取用,幸運的是,我們可以使用很棒的“dotnet add”工具新增工程取用,如下所示:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj reference HeyWorld/HeyWorld.csproj |
在開啟解決方案之前,我知道還有一些 Nuget 參考資料,我想在測試專案中使用它們。我選擇的斷言工具是Shoully,因此我將透過對命令列發出另一個呼叫來新增對最新版本的 shoully 的取用:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj package Shouldly |
命令列的輸出如下:
info : Adding PackageReference for package ‘Shouldly’ into project ‘HeyWorld.Tests/HeyWorld.Tests.csproj’. |
log : Restoring packages for /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/HeyWorld.Tests.csproj… |
info : GET https://api.nuget.org/v3-flatcontainer/shouldly/index.json |
info : OK https://api.nuget.org/v3-flatcontainer/shouldly/index.json 109ms |
info : Package ‘Shouldly’ is compatible with all the specified frameworks in project ‘HeyWorld.Tests/HeyWorld.Tests.csproj’. |
info : PackageReference for package ‘Shouldly’ version ‘3.0.0’ added to file ‘/Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/HeyWorld.Tests.csproj’. |
接下來,我想向名為 Alba 的測試專案新增至少一個 Nuget 取用。我將使用AspNetCore2來編寫針對新的 web 應用程式的 HTTP 契約測試:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj package Alba.AspNetCore2 |
現在,在使用程式碼之前先檢查一下,我將在命令列發出以下命令構建解決方案中的所有專案,確保它們都可以正常編譯:
dotnet build DotNetCliArticle.sln |
由於 Alba.AspNetCore2 和 ASP.NET Core Nuget 在 HeyWorld 專案中的取用之間的菱形依賴版本的衝突,所以沒有編譯。不過不用擔心,因為這個問題很容易解決,只需修複 Microsoft.AspNetCore 的版本依賴關係即可。測試專案中的所有 Nuget 都是這樣的:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj package Microsoft.AspNetCore.All –version 2.1.2 |
在上面的示例中,使用值為“2.1.2”的“–version”標誌將修複對該版本的取用,而不僅僅是使用從 Nuget 提要中找到的最新版本。
為了再次檢查我們的 Nuget 依賴問題是否已經解決,我們可以使用下麵的命令進行檢查,它比重新編譯所有東西要更快:
dotnet clean && dotnet restore DotNetCliArticle.sln |
作為一個有經驗的.NET 開發人員,我非常擔心臨時 /obj 和 /bin 檔案夾中殘留的檔案。因此,我在 Visual Studio 中使用“Clean Solution”命令,以防我試圖改變取用的時候落下些什麼。從命令列執行“dotnet clean”命令是完全相同的操作。
同樣,針對眾所周知的 Nuget 依賴問題,“dotnet restore”命令在解決方案中都試著去解決了。在這種情況下,使用“dotnet restore”可以讓我們快速發現任何潛在的衝突或丟失的 Nuget 取用,而無需進行完整的編譯,我在自己的工作中主要就採用該命令。在最新版本的 dotnet cli 中,在呼叫“dotnet build/test/pack/etc”時,會自動為您完成 Nuget 解析 (該行為可以用標記改寫),這將首先需要 Nuget。
我們呼叫的“dotnet restore DotNetCliArticle.sln”乾凈利落地執行完畢,沒有錯誤,所以我們終於可以準備編寫一些程式碼了。讓我們開啟您選擇的 C# 編輯器,向 HeyWorld 新增一個程式碼檔案。測試專案包含一個非常簡單的 HTTP 協議測試,它將指定我們希望從新的 HeyWorld 應用程式中的“GET: /”路由獲得的行為:
using System.Threading.Tasks; |
using Alba; |
using Xunit; |
namespace HeyWorld.Tests |
{ |
public class verify_the_endpoint |
{ |
[ | ]
public async Task check_it_out() |
{ |
using (var system = SystemUnderTest.ForStartup()) |
{ |
await system.Scenario(s => |
{ |
s.Get.Url(“/”); |
s.ContentShouldBe(“Hey, world.”); |
s.ContentTypeShouldBe(“text/plain; charset=utf-8”); |
}); |
} |
} |
} |
} |
結果檔案應該儲存在具有適當名稱 (如 verify_the_endpoints.cs) 的 HeyWorld.Tests 目錄。
在沒有深入瞭解 Alba 機制前,我們的新 HeyWorld 應用的首頁路由應該寫出“Hey, world”。雖然我們還沒有在 HeyWorld 應用中編寫任何實際的程式碼,但是我們仍然可以執行這個測試,看看它能夠連線正確,還是因為某些“理所當然的理由”而失敗。
回到命令列,我可以使用以下命令執行測試專案中的所有測試:
dotnet test HeyWorld.Tests/HeyWorld.Tests.csproj |
我們的一個測試會失敗,因為還沒有實現任何東西,它給了我們這樣的輸出:
Build started, please wait… |
Build completed. |
Test run for /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/bin/Debug/netcoreapp2.1/HeyWorld.Tests.dll(.NETCoreApp,Version=v2.1) |
Microsoft (R) Test Execution Command Line Tool Version 15.7.0 |
Copyright (c) Microsoft Corporation. All rights reserved. |
Starting test execution, please wait… |
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0. |
Test Run Successful. |
Test execution time: 2.4565 Seconds |
為了把輸出求和,執行了一個測試,但是失敗了。我們還可以看到標準的 xUnit 輸出,它提供了一些關於測試失敗原因的資訊。這裡需要註意的是,“dotnet test”命令將傳回一個退出程式碼,如果所有測試都透過,則傳回 0,表示成功;如果任何測試失敗,則傳回一個非零退出程式碼,表示失敗。這對於持續整合 (CI) 指令碼非常重要,大多數 CI 工具使用任何命令的退出程式碼來確定構建何時失敗。
我認為上面的測試之所以失敗是因為“理所當然的原因”,這意味著測試工具似乎能夠引導真正的應用程式,我希望得到 404 響應,因為還沒有編寫任何程式碼。接下來,讓我們為預期的行為實現一個 MVC Core 端點:
public class HomeController : Controller |
{ |
[ | ]
public string SayHey() |
{ |
return “Hey, world!”; |
} |
} |
(註意,前面的程式碼應該作為 HeyWorld\startup.cs 檔案中的附加類新增)
再次回到命令列,讓我們執行前面的“dotnet test HeyWorld.Tests/HeyWorld.Tests.csproj”命令,希望看到這樣的結果:
Build started, please wait… |
Build completed. |
Test run for /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/bin/Debug/netcoreapp2.1/HeyWorld.Tests.dll(.NETCoreApp,Version=v2.1) |
Microsoft (R) Test Execution Command Line Tool Version 15.7.0 |
Copyright (c) Microsoft Corporation. All rights reserved. |
Starting test execution, please wait… |
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0. |
Test Run Successful. |
Test execution time: 2.4565 Seconds |
好了,現在測試透過了,讓我們執行實際的應用程式。由於“dotnet new webapi”模板使用行程內的 in-process Kestrel web server 來處理 HTTP 請求,所以要執行新的 HeyWorld 應用程式,我們唯一需要做的一件事就是從命令列使用以下命令啟動它:
dotnet run –project HeyWorld/HeyWorld.csproj |
執行上面的命令應該會得到如下輸出:
Using launch settings from HeyWorld/Properties/launchSettings.json… |
: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0] |
User profile is available. Using ‘/Users/jeremydmiller/.aspnet/DataProtection-Keys’ as key repository; keys will not be encrypted at rest. |
Hosting environment: Development |
Content root path: /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld |
Now listening on: https://localhost:5001 |
Now listening on: http://localhost:5000 |
Application started. Press Ctrl+C to shut down. |
要測試我們現在正在執行的新應用程式,只需在瀏覽器中導航如下:
處理 HTTPS 設定超出了本文的範圍。
請再次註意,我假設所有命令都是在將當前目錄設定為解決方案根檔案夾的情況下執行的。如果當前目錄是一個專案目錄,並且只有一個 *.csproj。那麼,您只需在該目錄下鍵入“dotnet run”即可。現在我們已經有了一個經過測試的 web api 應用程式,接下來讓我們將 HeyWorld 放到 Docker 映象中。使用 the standard template for dockerizing a .NET Core application,我們將向 HeyWorld 專案新增一個 Dockerfile,內容如下:
FROM microsoft/dotnet:sdk AS build-env |
WORKDIR /app |
Copy csproj and restore as distinct layers |
COPY *.csproj ./ |
RUN dotnet restore |
Copy everything else and build |
COPY . ./ |
RUN dotnet publish -c Release -o out |
Build runtime image |
FROM microsoft/dotnet:aspnetcore-runtime |
WORKDIR /app |
COPY –from=build-env /app/out . |
ENTRYPOINT [“dotnet”, “HeyWorld.dll”] |
(註意,前面的文字應該儲存到專案目錄中名為 Dockerfile 的文字檔案中——在本例中是 HeyWorld\Dockerfile)。
因為這篇文章僅僅是關於 dotnet cli 的,我只想關註 Dockerfile 中它的兩種用法:
1.“dotnet restore”–正如我們在上面學到的,這個命令將解決應用程式的任何 Nuget 依賴關係。
2.“dotnet publish -c Release -o out”–“dotnet publish”命令將構建指定的專案,並將組成應用程式的所有檔案複製到給定位置。在我們的例子中,“dotnet publish”將為 HeyWorld 本身複製已編譯的程式集、從 Nuget 依賴項取用的所有程式集、配置檔案以及 csproj 檔案中取用的任何檔案。
請註意,在上面的用法中,我們必須透過使用“-c Release”標誌明確地告知“dotnet publish”用“Release”配置編譯。那些用於編碼的 dotnet cli 命令 (例如“build”、“publish”、“pack”)如果沒有指定,將以 “Debug”為預設值。註意這種行為,如果要釋出用於生產的 Nuget 或應用程式,請記住指定“-c Release”或“-configuration Release”。別怪我沒提醒你。
為了完成整個週期,我們現在可以使用以下命令透過 Docker 構建和部署我們的小 HeyWorld 應用程式:
docker build -t heyworld . |
docker run -d -p 8080:80 –name myapp heyworld |
第一個命令為我們的應用程式“heyworld”構建並本地釋出 Docker 映象。第二個命令實際上作為一個名為“myapp”的 Docker 容器執行我們的應用程式。您可以開啟瀏覽器訪問“http://localhost:8080”予以驗證。
總結
dotnet cli 使得基於. NET 專案的自動化和指令碼編寫變得非常簡單,尤其是與十多年前的.NET 技術相比。在許多情況下,您甚至可能會避開任何基於任務的構建指令碼工具 (Cake、Fake、Rake、Psake 等),而選擇只委託給 dotnet cli 的簡單 shell 指令碼。此外,dotnet cli 可擴充套件性模型可以很容易地將外部.NET 授權的命令列應用程式透過 Nuget 分佈到自動構建程式中。
關於作者
傑裡米·米勒 (Jeremy Miller)在密蘇裡州一個農場社群長大,那裡有一群“特別”的人,名叫“樹蔭技工”。通常,他們不是世界上最有名望的人,但他們有解決機械問題的訣竅,而且做事魯莽無畏。如果你發現從一輛停在街區的通勤車下伸出來兩條腿,那他想必就是名修理工了,他的周圍是一些骨架車,堆擠在他那長滿灌木、堆滿垃圾的院子裡。你看到的被遺棄在他周圍的打漿機並不是沒用的,他們是素材。他會零零碎碎地進行些小調整,然後根據你的需要想出一個創造性的解決方案。儘管名聲一般,但一個樹蔭技工知道如何讓東西執行。雖然米勒沒有任何特殊的機械能力 (儘管他擁有機械工程學位),但他喜歡把自己當成一個像樹蔭技工似的開發人員。他的硬碟上肯定到處都是廢棄的開源專案碎片。
隨著.NET Core 2.0 的釋出,微軟擁有了通用、模組化、跨平臺和開源平臺的下一個主要版本,該版本最初於 2016 年釋出。.NET Core 已經建立了許多 API,這些 API 在.NET 框架的當前版本中是可用的。它最初是為了下一代 ASP.NET 解決方案建立的,但現在是許多其他場景的驅動和基礎,包括物聯網、雲和下一代移動解決方案。在關於.NET Core 的第二個系列的文章中,我們將進一步探討.NET Core 的優點,以及它如何不僅有益於傳統的.NET 開發人員,也有益於所有需要為市場提供強健的、高效的和經濟的解決方案的技術人員。