RSS

gRPC 遇见 .NET SDK 和 Visual Studio:构建时自动代码生成

作为微软向其跨平台 .NET 产品迈进的一部分,他们极大地简化了项目文件格式,并允许第三方代码生成器与 .NET 项目紧密集成。我们一直关注此趋势,现在很高兴地宣布,从 Grpc.Tools NuGet 包的 1.17 版本开始(现已在 Nuget.org 上提供),我们在 .NET C# 项目中集成了 Protocol Buffer 和 gRPC 服务 .proto 文件的编译。

您不再需要使用手动编写的脚本从 .proto 文件生成代码:.NET 构建的“魔力”会为您处理这一切。集成工具会定位 proto 编译器和 gRPC 插件,标准的 Protocol Buffer imports,并在调用代码生成器之前跟踪依赖关系,确保生成的 C# 源文件始终是最新的,同时将重新生成(代码)的工作量降至最低所需。本质上,在 .NET C# 项目中,.proto 文件被视为一等源文件。

逐步指南

在这篇博文中,我们将逐步介绍使用跨平台 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 文件,尽管微软现在建议抑制此 globbing 行为,所以我们也决定不对 .proto 文件使用 globbing。因此,必须显式地将 .proto 文件添加到项目中。

其次,重要的是要为 Grpc.Tools 包引用添加属性 PrivateAssets="All",这样使用您新库的项目就不会不必要地获取它。这是有道理的,因为该包仅包含编译器、代码生成器和 import 文件,这些在编译了 .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 代码仓库中提出问题。您的反馈对于确定我们构建集成工作的未来方向至关重要!