在 ASP.NET Core gRPC 项目中链接项目目录外的 Protobuf 文件

发布时间 2023-11-27 18:27:41作者: Tim·Hant

  内容包含在 ASP.NET Core gRPC Service 项目中引用当前project之外目录下的 .proto 文件方法,可以实现多个 gRPC 项目中引用同一个位置的 .proto 文件,减少 .proto 文件在不同位置来回复制所带来的不必要麻烦。

 

  一直以来一直在使用基于 gRPC C-core 的类库搭建 gRPC 服务程序(基于 .NET 6 的控制台程序注册为 Windows 系统服务),最初为了能将所有的 .proto 文件汇聚在一个项目中,不让所有 .proto 文件散落在各个工程项目中,单独创建了一个 project 命名为 xxxx.protocol,将所有的 .proto 文件以及 Google.Protobuf、Grpc.Core、Grpc.Tools 库全部聚合在了这个项目中,实现了在其他服务中直接引用 xxxx.protocol 项目即可引用相关服务gRPC接口的目的。

  但基于 C-core 的 gRPC C-core 已经不在更新新的内容,虽然一再延长项目的维护截止时间,但被弃用是早晚的事情(详情可查看官方博客 https://grpc.io/blog/grpc-csharp-future/#the-plan)。而使用基于 .NET 原生实现的 gRPC for .NET 则具备诸多新的特性,通过使用 Grpc.AspNetCore 项目模板可以更方便的创建 gRPC 服务,因此准备尝试一下使用 Grpc.AspNetCore 来搭建一个新的业务服务。

  使用 ASP.NET Core gRPC Service 项目模板创建项目,名字路径按需给。

  创建完成后可以看到项目内的 .proto 文件是创建在我们当前这个项目内部的 .Protos 目录下的。

  

  此时客户端想要使用这个服务的接口一般都需要基于这个 .proto 文件做 Client 代理类的生成,此时最简单粗暴的方式就是将这个 .proto 文件直接复制粘贴到调用方自己项目的 .Protos 目录下。但一份协议文件多处拷贝使用,若经常修改变动会带来频繁的替换更新,只通过拷贝的方式很难进行管理。因此如果我们能将所有的 .proto 文件都放置在一个统一的位置,然后各个项目从这个位置引用自己需要的 .proto 文件去使用,就可以避免粘来贴去带来的麻烦,实现对协议文件的一次修改,多处生效,通过修改 .csproj 中的配置可以达到我们想要的效果。

  具体方法:

  编辑 .csproj,默认的项目文件中为我们配置了

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

  

  在这个基础上我们只要对 Protobuf 项增加 AdditionalImportDirs 和 Link 这两个属性的配置即可达成我们的目的。

  现在假设:

  协议文件 greet.proto 位于 D:\xxxx\src\service.protocol\proto 目录下,

  服务 service.s1 的项目文件(.csproj)位于 D:\xxxx\src\service.s1 目录下。

  此时 greet.proto 文件不在 service.s1 的项目目录内,而我们想在 service.s1 中引用 greet.proto 到自己的项目中,来生成相关接口的代理类,我们需要在 service.s1 项目的 .csproj 中编辑 Protobuf 项为如下形式即可。

<ItemGroup>
    <Protobuf
        AdditionalImportDirs="..\service.protocol"
        Include="proto\greet.proto"
        GrpcServices="Server"
        Link="Protos\greet.proto" />
</ItemGroup>    

  

  AdditionalImportDirs 属性标识了解析 protobuf 文件时要使用的其他目录。可以通过分号(;)来分隔多个待扫描的路径列表。

  Link 属性标识了链接到的文件在本项目中的位置,同时表示该文件不是当前项目中存在的文件,该文件位于别处,相当于为该文件在本项目内创建了一个快捷方式。

  Include 属性与之前含义一样,标识被引用的 .proto 文件。

  GrpcServices 属性标识了在本项目中生成的 Grpc 代理类的类型,包含了 None、Default、Both、Server、Client。

  配置完成后可以看到我们的项目中的 Protos 目录的图标发生了变化,通过一个小箭头标识该目录为 Link 链接生成的,其下也可以看到我们链接过来的 greet.proto 的快捷方式了,可以像正常的代码文件一样进行编辑。

    

  如果我们在 greet.proto 中 import 了其他的 .proto 文件(比如 enum.proto),此时编译代码应该会遇到问题,提示我们找不到 enum.proto 文件。那么我们应该同样按照上面的方式将 enum.proto 文件也添加至 .csproj 的配置中,即可解决这个问题。

  例如在上面的基础上,我们的 greet.proto 文件中写了如下的 import 语句:

import "proto/enum.proto";

  同时 enum.proto 也位于 D:\xxxx\src\service.protocol\proto 目录下,那么我们需要在 .csproj 中配置如下内容:

<ItemGroup>
  <Protobuf
    AdditionalImportDirs="..\service.protocol"
    Include="..\service.protocol\proto\enum.proto"
    GrpcServices="Server"
    Link="Protos\enum.proto" />
  <Protobuf
    AdditionalImportDirs="..\service.protocol"
    Include="..\service.protocol\proto\greet.proto"
    GrpcServices="Server"
    Link="Protos\greet.proto" />
</ItemGroup>

  此时即可编译通过,若包含了其他文件的引入,同样处理即可。

  

  此处主要注意的是 AdditionalImportDirs 配置的是当在当前目录下找不到对应的代码文件时去另行检索的目录的路径,此路径应当为包含 import 目标路径最根层目录的父级目录(有点儿绕,可以多读几遍慢慢体会,我也是想了好一阵才明白),这也是在上面的例子中 AdditionalImportDirs 配置为 proto 目录的上一级 service.protocol 目录的原因,因为我们在 greet.proto 中 import 的是 proto/enum.proto,所以当在和 greet.proto 文件相同的路径下找不到 enum.proto 文件时,我们需要在 service.protocol 目录下寻找 proto/enum.proto,如果我们按照平时的理解将 AdditionalImportDirs 配置为 ..\service.protocol\proto,则将会去 ..\service.protocol\proto\proto\enum.proto 查找导入的文件,此时便会报错,请各位同学一定注意这个小坑。

 

  OK,通过 .csproj 进行配置方法到这里就结束了。感兴趣的朋友还可以去阅读一下微软的说明文档,里面提到了另一种通过 dotnet-grpc .NET Core 全局工具的配置方法,最终的效果我理解也是通过命令实现对 .csproj 的配置来完成外部文件的引用(不光可以创建本地磁盘上的 Protobuf 文件引用,还可以创建远程位置的 Protobuf 文件引用)。

  链接如下: https://learn.microsoft.com/zh-cn/aspnet/core/grpc/dotnet-grpc?view=aspnetcore-5.0

   

  本人水平有限,文章内如果有错误,还请大家帮忙指出,非常感谢!

  

  本文内容参考了这位大神的文章 .Net gRPC项目中导入.proto文件 · 博客 - leisn 非常感谢!