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 开发人员,也有益于所有需要为市场提供强健的、高效的和经济的解决方案的技术人员。
最近总有人问我,和那些要么迟疑,要么不能退出旧版本、全功能的.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
{
[Fact]
public async Task check_it_out()
{
using (var system = SystemUnderTest.ForStartup<Startup>())
{
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
{
[HttpGet("/")]
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 开发人员,也有益于所有需要为市场提供强健的、高效的和经济的解决方案的技术人员。