gRPC 遇见 .NET SDK 和 Visual Studio:构建时自动代码生成
作为 Microsoft 迈向其跨平台 .NET 产品的一部分,他们极大地简化了项目文件格式,并允许第三方代码生成器与 .NET 项目紧密集成。我们一直在倾听,现在自豪地推出从 Grpc.Tools NuGet 包的 1.17 版本(现已在 Nuget.org 上提供)开始,在 .NET C# 项目中集成了 Protocol Buffer 和 gRPC 服务 .proto
文件的编译功能。
你不再需要使用手动编写的脚本从 .proto
文件生成代码:.NET 构建的魔法会为你处理。集成的工具会定位 proto 编译器和 gRPC 插件、标准 Protocol Buffer 导入,并在调用代码生成器之前跟踪依赖项,从而确保生成的 C# 源文件永远不会过时,同时将重新生成的工作量降至最低。本质上,.proto
文件在 .NET C# 项目中被视为一流的源文件。
操作指南
在这篇博文中,我们将通过最简单、可能也是最常见的场景,使用跨平台 dotnet
命令从 .proto
文件创建库。我们将主要实现 C# Helloworld
示例目录 中客户端和服务器项目共享的 Greeter
库的克隆版本。
创建新项目
让我们首先创建一个新的库项目。
~/work$ dotnet new classlib -o MyGreeter
The template "Class library" was created successfully.
~/work$ cd MyGreeter
~/work/MyGreeter$ ls -lF
total 12
-rw-rw-r-- 1 kkm kkm 86 Nov 9 16:10 Class1.cs
-rw-rw-r-- 1 kkm kkm 145 Nov 9 16:10 MyGreeter.csproj
drwxrwxr-x 2 kkm kkm 4096 Nov 9 16:10 obj/
请注意,dotnet new
命令已创建我们不需要的 Class1.cs
文件,因此请将其删除。另外,我们需要一些 .proto
文件来编译。在此练习中,我们将从 gRPC 发布版本中复制一个示例文件 examples/protos/helloworld.proto
。
~/work/MyGreeter$ rm Class1.cs
~/work/MyGreeter$ wget -q https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/helloworld.proto
(在 Windows 上,使用 del Class1.cs
,如果你没有 wget 命令,只需打开上面的 URL ,然后使用 Web 浏览器中的“另存为…”命令)。
接下来,向项目添加所需的 NuGet 包。
~/work/MyGreeter$ dotnet add package Grpc
info : PackageReference for package 'Grpc' version '1.17.0' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'.
~/work/MyGreeter$ dotnet add package Grpc.Tools
info : PackageReference for package 'Grpc.Tools' version '1.17.0' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'.
~/work/MyGreeter$ dotnet add package Google.Protobuf
info : PackageReference for package 'Google.Protobuf' version '3.6.1' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'.
将 .proto
文件添加到项目
接下来是重要部分。首先,默认情况下,.csproj
项目文件会自动在其目录中查找所有 .cs
文件,尽管 Microsoft 现在建议抑制此 globbing 行为,因此我们也决定不对 .proto
文件进行 globbing。因此,.proto
文件必须显式添加到项目中。
其次,重要的是要为 Grpc.Tools 包引用添加属性 PrivateAssets="All"
,这样你的新库的消费者就不必不必要地获取它。这很有意义,因为该包只包含编译器、代码生成器和导入文件,这些在编译 .proto
文件的项目外部是不需要的。虽然在此简单操作指南中并非严格要求,但这必须是你始终遵循的标准做法。
因此,编辑 MyGreeter.csproj
文件,添加 helloworld.proto
以便其能够被编译,并为 Grpc.Tools 包引用添加 PrivateAssets
属性。你的最终项目文件现在应如下所示:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
<PackageReference Include="Grpc" Version="1.17.0" />
<!-- The Grpc.Tools package generates C# sources from .proto files during
project build, but is not needed by projects using the built library.
It's IMPORTANT to add the 'PrivateAssets="All"' to this reference: -->
<PackageReference Include="Grpc.Tools" Version="1.17.0" PrivateAssets="All" />
<!-- Explicitly include our helloworld.proto file by adding this line: -->
<Protobuf Include="helloworld.proto" />
</ItemGroup>
</Project>
构建它!
此时,你可以使用 dotnet build
命令构建项目,以编译 .proto
文件和库程序集。在此操作指南中,我们将在命令中添加日志开关 -v:n
,这样我们就可以看到编译 helloworld.proto
文件的命令确实已运行。你可能会发现在第一次编译项目时始终这样做是一个好主意!
请注意,以下省略了许多输出行,因为构建输出非常详细。
~/work/MyGreeter$ dotnet build -v:n
Build started 11/9/18 5:33:44 PM.
1:7>Project "/home/kkm/work/MyGreeter/MyGreeter.csproj" on node 1 (Build target(s)).
1>_Protobuf_CoreCompile:
/home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/protoc
--csharp_out=obj/Debug/netstandard2.0
--plugin=protoc-gen-grpc=/home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/grpc_csharp_plugin
--grpc_out=obj/Debug/netstandard2.0 --proto_path=/home/kkm/.nuget/packages/grpc.tools/1.17.0/build/native/include
--proto_path=. --dependency_out=obj/Debug/netstandard2.0/da39a3ee5e6b4b0d_helloworld.protodep helloworld.proto
CoreCompile:
[ ... skipping long output ... ]
MyGreeter -> /home/kkm/work/MyGreeter/bin/Debug/netstandard2.0/MyGreeter.dll
Build succeeded.
如果此时你再次调用 dotnet build -v:n
命令,protoc
将不会被调用,也不会编译 C# 源文件。但如果你更改 helloworld.proto
源文件,则其输出将在构建期间重新生成,然后由 C# 编译器重新编译。这是一种常规的依赖项跟踪行为,与修改任何源文件时的预期相同。
当然,你也可以将 .cs
文件添加到同一个项目:毕竟,它是一个构建 .NET 库的常规 C# 项目。这在我们的 RouteGuide 示例中完成。
生成的文件在哪里?
你可能想知道 proto 编译器和 gRPC 插件输出的 C# 文件在哪里。默认情况下,它们与其它生成的文件(如对象,在 .NET 构建术语中称为“中间输出”目录)一起放置在同一目录下,即 obj/
目录下。这是 .NET 构建的常规做法,这样自动生成的文件就不会杂乱地堆积在工作目录中或意外地被置于源代码控制之下。否则,它们可供调试器等工具访问。你也可以在该目录中看到其他自动生成的源文件:
~/work/MyGreeter$ find obj -name '*.cs'
obj/Debug/netstandard2.0/MyGreeter.AssemblyInfo.cs
obj/Debug/netstandard2.0/Helloworld.cs
obj/Debug/netstandard2.0/HelloworldGrpc.cs
(如果你正在从 Windows 命令提示符执行此操作指南,请使用 dir /s obj\*.cs
)。
还有更多内容
虽然在许多情况下,最简单的默认行为已经足够,但在大型项目中,有许多方法可以微调你的 .proto
编译过程。如果你发现默认设置不适合你的工作流程,我们鼓励你仔细阅读文档文件 BUILD-INTEGRATION.md 以了解可用选项。该包还扩展了 Visual Studio 的“属性”窗口,因此你可以在 Visual Studio 界面中按文件设置一些选项。
也支持“经典”.csproj
项目和 Mono。
分享你的经验
与任何复杂功能的初次发布一样,我们很高兴收到你的反馈。是否有未按预期工作的地方?你是否有新工具难以覆盖的场景?你是否有关于如何普遍改进工作流程的想法?请仔细阅读文档,然后前往 GitHub 上的 gRPC 代码仓库提出问题。你的反馈对于确定我们构建集成工作的未来方向至关重要!