本文要点
.NET CLI 包含了一个模板引擎,它可以直接利用命令行创建新项目和项目项。这就是“dotnet new”命令。
默认模板集涵盖了默认控制台和基于 ASP.NET 的应用程序以及测试项目所需的基本项目和文件类型。
自定义模板可以创建更加有趣或定制化的项目和项目项,并且可以利用 NuGet 包或直接利用文件系统进行分发和安装。
自定义模板可以是非常简单的,也可以是比较复杂的,比较复杂的可以使用替换变量、命令行参数和文件的条件包含,甚至可以使用代码行。
通过确保项目模板始终是可运行的项目,维护和测试自定义模板是非常容易的,即使是使用条件代码也是如此。
本文是我们 .NET教育系列的一部分,该教育系列探讨了 .NET 技术的好处,以及它是如何不仅可以帮助传统的 .NET 开发人员,还可以帮助所有想要为市场提供可靠、高效且经济的解决方案的技术人员的。
随着 .NET Core 3.0 的发布,微软拥有了通用、模块化、跨平台和开源平台的下一个主要版本,该版本最初是在 2016 年发布的。.NET Core 最初是为了支持下一代 ASP.NET 解决方案而创建的,但现在它驱动了许多其他场景,包括物联网、云和下一代移动解决方案,并且是这些场景的基础。3.0 版本增加了许多常用的特性,比如对 WinForms、WPF 和 Entity Framework 6 的支持。
由于 .NET Core 对命令行的重视,导致它的工具发生了巨大的变化。这非常适用于 .NET Core 跨平台、工具无关的镜像。dotnet CLI 是实现这些优势功能的入口点,它包含了许多用于创建、编辑、构建和打包 .NET Core 项目的不同命令。在本文中,我们仅关注 dotnet CLI 的一个方面:dotnet new 命令。
该命令主要用于创建项目,学习过程中,我们经常会创建一个简单的样板项目,然后就把它忘掉了。本文中,我们将学习如何充分利用这个命令,通过传递参数来修改已生成的项目,并将学习如何使用该命令来创建文件和项目。我们还将看到这个工具是一个成熟的模板引擎,它不仅可以用来安装自定义模板,还可以用来制作个人模板。
dotnet new 实践
要怎样使用 dotnet new 呢?让我们从头开始,直到找出最有趣的部分。要创建一个简单的控制台应用程序,那么先启动命令行,将目录切换为一个新的空文件夹(这是一个重要步骤,后面将会对此进行说明),然后调用 dotnet new console:
> dotnet new console
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on /Users/matt/demo/MyNewApp/MyNewApp.csproj...
Restoring packages for /Users/matt/demo/MyNewApp/MyNewApp.csproj...
Generating MSBuild file /Users/matt/demo/MyNewApp/obj/MyNewApp.csproj.nuget.g.props.
Generating MSBuild file /Users/matt/demo/MyNewApp/obj/MyNewApp.csproj.nuget.g.targets.
Restore completed in 234.92 ms for /Users/matt/demo/MyNewApp/MyNewApp.csproj.
Restore succeeded.
正如前面提到的,首先确保我们在一个新的空文件夹中。默认情况下,dotnet new 将会在当前文件夹中创建文件,并且不会删除已经存在的文件。我们可以使用 --output 选项创建一个新文件夹。例如,可以通过键入如下命令在名为 ConsoleApp42 的新文件夹中创建项目:
> dotnet new console --output ConsoleApp42
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on ConsoleApp42/ConsoleApp42.csproj...
Restoring packages for /Users/matt/demo/ConsoleApp42/ConsoleApp42.csproj...
Generating MSBuild file /Users/matt/demo/ConsoleApp42/obj/ConsoleApp42.csproj.nuget.g.props.
Generating MSBuild file /Users/matt/demo/ConsoleApp42/obj/ConsoleApp42.csproj.nuget.g.targets.
Restore completed in 309.99 ms for /Users/matt/demo/ConsoleApp42/ConsoleApp42.csproj.
Restore succeeded.
看看创建了什么
此时,dotnet new 已经创建了一个新的控制台项目,并且还原了 NuGet 包(它已经准备好运行了)。但是,让我们先看下我们到底创建了什么:
> ls ConsoleApp42/
ConsoleApp42.csproj Program.cs obj/
可以看到,现在有了一个基于输出文件夹名称的项目文件。如果我们需要,也可以使用 --name 参数来指定一个不同的名称:
dotnet new console --output ConsoleApp42 --name MyNewApp
它将在名为 ConsoleApp42 的文件夹中创建项目文件,并使用 MyNewApp 作为正在创建的控制台应用程序的名称,我们将得到一个 MyNewApp.csproj。如果我们查看 Program.cs,还将看到该 name 参数也被用来更新命名空间了:
using System;
namespace ConsoleApp42
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
为另一个项目做准备
但是,如果我们查看一下我们刚才创建的项目的文件夹目录结构,可能会发现它丢失了一些内容,它没有解决方案文件。我们只有一个项目,虽然它可以在 dotnet run 时正常运行,但当我们添加另一个项目时,它将会引起问题。我们可以轻松创建一个项目:
该命令将创建一个新的空解决方案文件。然后,向其中添加项目是另外的步骤。
如果我们要在演示示例的根文件夹目录中创建解决方案,命令如下所示:
dotnet sln add ConsoleApp42/MyApp.sln
也可以使用 dotnet sln 命令删除或列出解决方案中的项目。如果要添加或删除对项目的引用,则需要使用 dotnet add 命令。我建议大家阅读下 Jeremy Miller 的关于可扩展的 dotnet CLI 的文章以获取更多详细细节,或者通过键入 dotnet help sln 或 dotnet help add 获取。
添加另一个项目也非常简单,但是我们必须以两步的方式来完成:创建,然后添加。例如,可以将一个测试项目添加到解决方案中:
dotnet new nunit
dotnet sln add Tests/MyAppTests.csproj
向项目添加新文件
向项目中添加新文件甚至更加容易,这主要归功于.NET Core 对 MSBuild 文件的改进。我们不需要再在.csproj 文件中显式地列出 C# 文件了,因为它们是通过通配符自动获取的。我们只需要在文件夹中创建一个文件,它将自动成为项目的一部分。我们既可以手动也可以使用 dotnet new 提供模板文件来创建文件。例如,可以使用 nunit-test 项模板将一个测试文件添加到我们的测试项目中:
说到模板,我们怎么知道有哪些可用的模板呢?如何区分项目模板( project template)和项模板(item template)呢?这是 dotnet new --list 的工作,它可以输出所有可用的模板列表:
模板 | 简称 | 语言 | 标签 |
---|---|---|---|
Console Application | console | [C#], F#, VB | Common/Console |
Class library | classlib | [C#], F#, VB | Common/Library |
Unit Test Project | mstest | [C#], F#, VB | Test/MSTest |
NUnit 3 Test Project | nunit | [C#], F#, VB | Test/NUnit |
NUnit 3 Test Item | nunit-test | [C#], F#, VB | Test/NUnit |
xUnit Test Project | xunit | [C#], F#, VB | Test/xUnit |
Razor Page | page | [C#] | Web/ASP.NET |
MVC ViewImports | viewimports | [C#] | Web/ASP.NET |
MVC ViewStart | viewstart | [C#] | Web/ASP.NET |
ASP.NET Core Empty | web | [C#], F# | Web/Empty |
ASP.NET Core Web App (Model-View-Controller) | mvc | [C#], F# | Web/MVC |
ASP.NET Core Web App | razor | [C#] | Web/MVC/Razor Pages |
ASP.NET Core with Angular | angular | [C#] | Web/MVC/SPA |
ASP.NET Core with React.js | react | [C#] | Web/MVC/SPA |
ASP.NET Core with React.js and Redux | reactredux | [C#] | Web/MVC/SPA |
Razor Class Library | razorclasslib | [C#] | Web/Razor/Library/Razor Class Library |
ASP.NET Core Web API | webapi | [C#], F# | Web/WebAPI |
global.json file | globaljson | Config | |
NuGet Config | nugetconfig | Config | |
Web Config | webconfig | Config | |
Solution File | sln | Solution |
上述列表列出了所有的模板。使用 --type 参数可以进行进一步的过滤,如使用 --type project、–type item 或–type other。项目模板将会创建一个项目,项模板将会创建一个文件,而其他模板仅对 sln 模板创建的解决方案文件有用。
这个列表中的简称(上表第 2 列)是调用 dotnet new 时使用的名称(例如 dotnet new console、dotnet new classlib、dotnet new mvc 等)。有些模板支持多种语言,默认语言为方括号中的(剧透下,它们都是 C#)。我们可以使用 --language 选项来选择不同的语言,但是要注意#符号!一些命令行 shell 将其视为注释字符,使用–language F# 解析可能会失败。这时可以通过引用值 - “–language F#” 来处理。
最后,每个模板都有一个或多个标签。这些标签是一种对模板进行分类的方法,但目前它们并不是作为命令行工具的一部分被使用。但是,它们可以被其他宿主用于分组或过滤。是的,没错,dotnet new 模板引擎可以用于其他宿主,比如 IDE。稍后再详细介绍。
自定义模板
到目前为止,我们只研究了一个非常简单的 Hello World 控制台应用程序,并添加了一些测试。让我们看些更有趣的东西吧。假设要创建一个新的 ASP.NET 项目。查看上面的模板列表,我们有几个选择。可以创建一个空的 web 项目、一个 MVC 项目、一个带有 Angular 的项目或者一个带有 React.js 的项目。但是这些都是相当严格的模板。我们能对这些模版进行定制吗?好消息是:可以的。
模板可以接受一些变更生成内容的参数。 – help 命令将会提供模板所理解的参数的详细信息。让我们从一个简单的例子开始吧:
> dotnet new classlib
Class library (C
Author: Microsoft
Description: A project for creating a class library that targets .NET Standard or .NET Core
Options:
-f|
netcoreapp2.1 - Target netcoreapp2.1
netstandard2.0 - Target netstandard2.0
Default: netstandard2.0
bool - Optional
Default: false / (*) true
* Indicates the value used if the switch is provided without a value.
在此,我们可以看到 classlib 模板有两个参数:一个是 --framework,它用于指定要将什么目标框架写入项目文件中;另一个是 --no-restore,它用于控制在创建项目时是否执行 NuGet 还原。
web 模板也有类似的参数,但是它们的数量比我们这里列出的要多得多。尝试 dotnet new mvc --help 可以了解更多详细信息。有些参数可以指定身份验证类型、是否禁用 HTTPS、是否是使用 LocalDB 替换 SQLite 等等。这些参数中的每一个都会变更模板代码的生成方式,它们有的是替换文件中的内容,有的是根据需要包含或排除文件。
当我们讨论帮助文档时,有两个非常有用的命令:dotnet help new,它将在 dotnet new 命令本身之上打开一个网页;dotnet new {template} –help,它将显示命名模板及其参数的帮助信息。
添加自定义模板
dotnet new 命令的真正强大之处在于添加新的自定义模板。更好的是,只需要将模版打包到一个 NuGet 包中并上传到 nuget.org 上,即可实现模板的分发和共享。这使得开始使用一个框架或自动化创建新项目样板或项目项样板非常容易。
要添加一个新的自定义模板,使用 dotnet new --install {template} 命令,传入一个 NuGet 包名或一个本地模板的文件目录。但如何找到新的模板呢?
一种方法是搜索我们正在使用的框架,并查看它的模板是否可用,但这样做容易出错。幸运的是,我们可以访问 dotnetnew.azurewebsites.net 并通过关键字搜索模板。网站上有超过 500 个模板,这使得它成为了一个很好的发现资源的地方。
例如,可以使用 dotnet new --install Amazon.Lambda.Templates 为 AWS Lambda 项目安装一组模板。通过 NuGet 包安装模板有一个非常好的特性,那就是每个包可以包含多个模板。这个 AWS Lambda 包包含了 28 个不同的模板,并且还包括了一个教程项目。
当然,如果我们不想再使用某个模板了,只需要使用 dotnet new --uninstall {package} 卸载它即可。这里传递的名称是已安装模板包的名称,如果我们不确定对应的名称,只需要运行 dotnet new --uninstall 即可获得一个列表。
创建自己的模板
我们还可以创建自己的自定义模板。它们不一定是针对流行框架的,但可能是针对内部或个人项目的。实际上,如果我们经常要自己创建一个特定的文件夹目录结构、引用集或样板文件,那么可以考虑创建项目或项模板。项目模板只是简单的纯文本文件,包括 .csproj files (它们不要求生成的模板是特定于 .NET Core 的,并且它们可以用于任何框架。)。
创建一个新的模板是非常容易的,而且维护起来也很容易。传统上,可以执行文本替换的模板会使用一种特殊的语法,比如 VARIABLEVARIABLE 标记,该标记将在计算模板时被替换。不幸的是,这对于文件类型来说通常是无效的语法,文件类型会使得它无法通过运行项目来测试模板的正确性。这会导致 bug 并减缓迭代时间,基本上也会带来一些维护上的麻烦。
幸运的是,模板引擎的设计者已经考虑到了这一点,并且想出了一个更好的工作方式:运行模板。
想法很简单:模板就是纯文本文件。没有特殊格式,也没有特殊标记。所以, C# 文件始终是有效的 C#文件。如果某个模板想要替换某些文本,比如用基于项目名称的命名空间替换 C# 命名空间,则可以使用简单的搜索和替换来处理。例如,假设我们有一个这样的模板:
namespace RootNamespace
{
public static class Main
{
}
}
模板的 JSON 配置定义了一个符号来替换命名空间。符号的值是基于项目名称的,它可能应用了内置转换来确保它只包含有效的字符。这个符号还将定义它要替换的文本 “RootNamespace”。当模板引擎处理每个文件时,如果它看到 "RootNamespace",会用符号值将其替换。
这种简单的搜索和替换通常是基于符号的,而符号又是基于参数的,例如模板名、输出名或其他实际的自定义参数。但是,也可以基于生成器来创建符号,如创建 GUID、随机数或当前时间戳等等。
但是,如果一个模版没有条件代码(根据参数添加或删除的内容),那么它是不完整的。dotnet new 是如何处理这个问题并将“运行模板”作为一个选项的呢?它实际上是在每个文件类型的基础上处理的,内置了一些默认配置,并且能够为未知的文件格式定义自己的样式。本质上,其思想是对那些支持它的文件类型使用特定于文件的预处理程序(例如,用于 C#或 C++ 的#if),对那些不支持它的文件类型使用特殊格式的注释,比如 JSON。
cs
public class HomeController : Controller
{
public IActionResult Index() => View();
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
#if (EnableContactPage)
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
#endif
public IActionResult Error() => View();
}
模板的所有元数据都位于 template.json 文件中。它包括模板的简称、描述、作者、标签和支持的语言。因为一个模板只能针对一种语言,所以它还包括一个“组标识”选项,当有多个模板时可以指定该选项,为每个模版指定一种语言。元数据文件还可以包括有关要复制或重命名文件的源和目标、条件文件副本、替换符号、命令行参数和创建后的操作(如包还原)等可选信息。但默认情况下,模板引擎将复制并处理模板文件结构中的所有文件。
{
"author": "Matt Ellis",
"classifications": [ "Hello world" ],
"name": "Hello world template",
"identity": "Matt.HelloWorldTemplate.CSharp",
"shortName": "helloworld",
"guids": [ "d23e3131-49a0-4930-9870-695e3569f8e6" ],
"sourceName": "MyTemplate"
}
template.json 文件必须放在模板文件夹结构的根目录下,即位于一个名为 .template.config 的文件夹中。文件夹目录结构的其余部分可以完全由我们来定:模板引擎在评估模板时将会保持相同的目录结构。换句话说,如果我们将 README.md 文件添加到模板的根目录中,那么当调用 dotnet new 时,模板引擎将会在输出文件夹的根目录中创建一个 README.md。所以,如果我们使用–output MyApp,将得到一个名为 MyApp/README.md 的文件。
> tree -a
.
├── .template.config
│ └── template.json
├── MyTemplate.csproj
├── Program.cs
└── Properties
└── AssemblyInfo.cs
2 个目录,4 个文件
要安装和测试模板,只需要调用 dotnet new --install {template},就像安装自定义模板一样,但这一次是将路径传递给模板文件夹结构的根目录。如果要卸载,使用 dotnet new --uninstall {template}。同样地,如果我们不确定要传递什么参数,可以使用 dotnet new --uninstall 来获取完整的列表。
分发模板
一旦我们的模板准备就绪,可以分发了,就可以把它打包成一个 NuGet 包并上传到 nuget.org 上。我们需要像往常一样创建一个 .nuspec 文件,但是需要做两个小的调整:添加一个 packageType 元素并将 name 属性设置为“Template”,然后确保模板文件夹结构被复制到一个名为“content”的文件夹中。
<package>
<metadata>
<id>MattDemo.HelloWorldTemplate</id>
<version>1.0</version>
<authors>Matt Ellis</authors>
<description>Hello World template</description>
<packageTypes>
<packageTypename="Template"/>
</packageTypes>
</metadata>
<files>
<filesrc=".template.config/template.json"target="content/.template.config"/>
<filesrc="MyTemplate.csproj"target="content/"/>
<filesrc="Program.cs"target="content/"/>
<filesrc="Properties/*"target="content/Properties/"/>
</files>
</package>
此外,可以在一个包中包含多个模板:只需在“content”下创建多个文件夹,并为每个模板添加一个 .tempate.config/template.json 即可。
在 template.json 文件中还有更多的选项和功能,但是涵盖所有选项和功能超出了本文的范围。不过,基于我们这里讨论的这些内容,也可以看出模板引擎是非常强大、灵活的,并且它使用起来相当简单。请查看微软文档网站和 dotnet/templating Github 上的 Wiki 了解更多内容。
模板引擎
dotnet new 最有趣的一点是,它被设计成可以在多个宿主上使用。dotnet new CLI 工具只是其中的一个宿主 :模板引擎本身也可以用作其他应用程序的 API。对于那些喜欢使用 IDE 而不是命令行的人来说,这是非常好的,但是他们仍然希望能够轻松地添加自定义项目模板,而这在 IDE 中并不总是那么容易。
我们可以在 JetBrains Rider 中看到这一点。“新建项目”对话框由模板引擎 API 来提供支持,其中列出了所有可用的模板,甚至包括自定义模板。当用户想要创建一个项目时,模板引擎用于生成文件。
如果仔细观察,我们会发现 Rider 的模版比 .NET CLI 的要多。这是因为 Rider 提供了额外的模板来支持 .NET 框架和 Xamarin 项目。模板引擎 API 允许宿主将模板安装到一个自定义的位置,并且可以从这两个位置拿到模板列表,这意味着 Rider 将显示由 dotnet new --install 安装的自定义模板,以及使用“新建项目”对话框的“更多模板”页中的安装按钮。重新加载后,新模板将与所有其他模板一样显示在列表中。
轻松创建自定义的新项目
dotnet new 命令使得创建新项目和项目项变得很容易。默认模板集将帮助我们快速构建基于命令行或 ASP.NET 的 .NET Core 应用程序,并且有助于创建测试项目和其他基于.NET 语言的目标项目。我们可以很容易地安装自定义模板来创建具有其他要求的项目,例如不同的文件夹结构或框架依赖关系。自定义模板的格式使我们可以轻松地创建自己的模板,它虽然利用替换变量和条件代码,但仍能保持模板项目的可编译性和可维护性。与 dotnet sln 命令以及其他可扩展的 dotnet CLI 命令一起使用,new 模板引擎使创建和管理项目、项目项和解决方案变得更加容易,并且它可以跨平台、直接利用命令行进行创建。
作者简介
Matt Ellis 是 JetBrains 公司的一名开发人员。他花了 20 多年的时间在各个行业开发软件,目前使用一些 IDE 和开发工具进行开发,他对抽象语法树和源代码分析很感兴趣。此外他还致力于 Rider 的 Unity 支持工作。
本文是我们 .NET教育系列的一部分,该教育系列探讨了 .NET 技术的好处,以及它是如何不仅可以帮助传统的 .NET 开发人员,还可以帮助所有想要为市场提供可靠、高效且经济的解决方案的技术人员的。
随着 .NET Core 3.0 的发布,微软拥有了通用、模块化、跨平台和开源平台的下一个主要版本,该版本最初是在 2016 年发布的。.NET Core 最初是为了支持下一代 ASP .NET 解决方案而创建的,但现在它驱动了许多其他场景,包括物联网、云和下一代移动解决方案,并且是这些场景的基础。3.0 版本增加了许多常用的特性,比如对 WinForms、WPF 和 Entity Framework 6 的支持。