Joshlo
Joshlo

Reputation: 794

Importing .proto files from another project

I have several contract projects that contains different protobuf files, but some of the message types have the same message type like

message user
{
  Address address = 1
}

message Address 
{
  ....
}

I have now created a shared project and added an Address.proto file to it only containing

syntax = "proto3"
option csharp_namespace = "shared.protos"
package AddressPackage
message Address {....}

My problem is to figure out how to import it into the protos in my different contract projects. I have added the shared project as a reference, but everything else that I have tried from there has resultet in errors.

I know that I need to use import just haven't figured out how to write the string.

Update

I'm using gRPC.tools nuget and all .proto files is set to protobuf compiler both

The files structure is as following

User.Contracts project

both projects is in it's own folder and those folders are placed next to each other.

in the shared project it says

<ItemGroup>
  <None Remove="Protos\Address.proto" />
</ItemGroup>

<ItemGroup>
  <Protobuf Include="Protos\Address.proto">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Protobuf>
</ItemGroup>

and in the user.contract is says

<ItemGroup>
  <None Remove="Protos\User.proto" />
</ItemGroup>

<ItemGroup>
  <Protobuf Include="Protos\User.proto" />
</ItemGroup>

Thanks in advance.

Upvotes: 10

Views: 26536

Answers (6)

TechnicalKeera
TechnicalKeera

Reputation: 1043

I was using MicroServices Architecture and I wanted my message ProtoFiles should in SharedProto Project and Used them in different projects

Let's Say I have message Proto files in SharedProto

SharedProto
--Protos
----company_dto.proto
----branch_dto.proto
---- ...

Content of company_dto.proto

syntax = "proto3";

option csharp_namespace = "SharedProto.Protos";

package sharedproto.protos;
// *** DTO
message CompanyDto {
  int32 Id = 1;
  string Code = 2;
  string Name = 3;
  string Logo = 4;
  string Website = 5;
}

Content of SharedProto.csproj

....
<ItemGroup>
    <Protobuf Include="Protos\company_dto.proto" GrpcServices="None">
    <Protobuf Include="Protos\branch_dto.proto" GrpcServices="None">
</ItemGroup>
....

Project Organization where I have Services Protos

Organization
...
--Protos
----company_rpc.proto
----branch_rpc.proto
...

I want to import comapany_dto.proto (which is a part of SharedProto Project) into comapany_rpc.proto (Which is part of Organization Project). Let's have look at .csproj file because all trick lies here:

Content of Organization.csproj

  <ItemGroup>
    <ProjectReference Include="..\SharedProto\SharedProto.csproj">
      <GlobalPropertiesToRemove></GlobalPropertiesToRemove>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <!-- DTOs -->
    <Protobuf Include="..\SharedProto\Protos\company_dto.proto" ProtoRoot=".." GrpcServices="None" Link="Protos\company_dto.proto" />
    
    <Protobuf Include="Protos\company_rpc.proto" AdditionalImportDirs="../SharedProto" GrpcServices="Server" />
  </ItemGroup>

If you see above Organzation.csproj file, there are some really important points you need to understand to avoid file not found error in case of import proto files from other projects.

ProtoRoot=".." and Link="Protos\company_dto.proto" after this, add this AdditionalImportDirs="../SharedProto" on that <Protobuf Include=.... where you want to import your file. However after these setting you are able to import SharedProto file in your other projects in my case it was Organization project. Have a look

Content of company_rpc.proto

syntax = "proto3";

import "google/protobuf/empty.proto";
import "Protos/company_dto.proto";

package organization.companyrpc;

// *** Rpc-Service

service CompanyRpcService {
  // Retrieve All
  rpc GetAllCompanies (google.protobuf.Empty) returns (GetCompanyListResponse){
    
  }
  ....

}
// Response: Retrieve All 
message GetCompanyListResponse {
  repeated sharedproto.protos.CompanyDto DtoRows = 1;
  string Message = 2;
}

If your see my company_rpc.proto file you will understand how I am importing it(import "Protos/company_dto.proto";) and how I am utilizing it (sharedproto.protos.CompanyDto DtoRows)

remember this sharedproto.protos came from a package name into company_dto.proto file

This above mention settings works for me in .net core grpc 2.60.0 and .net core 8

Upvotes: 1

user1638737
user1638737

Reputation: 91

I've resolved this issue using the documentation available at the following url Protocol Buffers/gRPC Codegen Integration Into .NET Build.

Long story shorts: the Xml attribute AdditionalImportDirs="A_DIR_WITH_PROTOS" for xml item Project\ItemGroup\Protobuf did the trick.

Car/engine example

Referring to car engine example above in ProjA you should have

<ItemGroup>
   <ProjectReference Include="../ProjB/ProjB.csproj" />
</ItemGroup>

<ItemGroup> 
   <Protobuf Include="../ProjB/Protos/engine.proto" GrpcServices="None" ProtoCompile="False" Link="ProjB\Protos\engine.proto"/>  
   <Protobuf Include="../ProjA/Protos/car.proto" AdditionalImportDirs="../ProjB" />
</ItemGroup>

Then, when protobuffer compiler process car.proto file wil look inside folder ../ProjB/Protos to find engine. This imply that, inside file ProjA/Protos/car.proto, you should import engine in the following way:

syntax = "proto3";

import "Protos/Engine.proto"; // Because of 'AdditionalImportDirs="../ProjB"' you should specify here the remaining path from 'ProjB' to 'Engine.proto' that is 'Protos/Engine.proto'.

option csharp_namespace = "ProjA";

package ProjA;

message Car{
    ProjB.Engine engine = 1;
    string licensePlate = 2;
}

I've tried above code on Visual Studio 2022, dotnet 7.0 and Grpc.AspNetCore 2.52.0.

Upvotes: 4

Mos&#232; Bottacini
Mos&#232; Bottacini

Reputation: 4206

This is quite more complicated than I've foreseen due the amounts of "moving parts" plus the documentation is somehow outdated and at the moment of writing some attributes appears to behave differently from the post above.

Basically in the car example above, using Visual Studio 2022 you should end up with this enter image description here

to obtain this, ProjA needs a quite of work, the interesting bits appers to be

<ItemGroup>
   <ProjectReference Include="../ProjB/ProjB.csproj" />
</ItemGroup>

<ItemGroup> 
   <Protobuf Include="..\ProjB\Protos\engine.proto" GrpcServices="None" ProtoCompile="False">
      <Link>ProjB\Protos\engine.proto</Link>      
   </Protobuf>  
   <Protobuf Include="../ProjA/Protos/car.proto" Link="Protos/car.Proto" GrpcServices="None" ProtoRoot=".." />
</ItemGroup>

Please note that the important part is, as Yury Yatskov and Ivan Ivković said, to set the ProtoRoot=".." but it is mandatory that the path specified in ProtoRoot is a prefix of the path in the Include attribute. Note:

  • ProtoRoot is relative to the .csproj file
  • Import is relative to the .csproj file BUT need to have ProtoRoot as prefix, so basically one level up and then again enter inside the ProjA again

this way the include statement in car.proto will be able to start from the path specified in ProtoRoot for searching for the .proto files to include

** ProjA/Protos/car.proto

syntax = "proto3";

import "ProjB/Protos/Engine.proto";

option csharp_namespace = "ProjA";

package ProjA;

message Car{
    ProjB.Engine engine = 1;
    string licensePlate = 2;
}

** ProjB/Protos/engine.proto

syntax = "proto3";
option csharp_namespace = "ProjB";

package ProjB;

message Engine{
    string name = 1;
}

Again note that of course you will need to reference Engine message using its package so ProjB.Engine

Finally, this allows to include the ProjB as a normal reference of the ProjA

enter image description here

but PAY ATTENTION to set Compile Protobuf option to No (as you can see in the .csproj reported above) otherwise there will be class name conflicts as both projects will redefine the same "Engine" class.

grpc stub classes attributes seems to not play a big role here, it is more interesting if the .proto contains a service definition, I think.

Upvotes: 8

Ivan Ivković
Ivan Ivković

Reputation: 466

You can do so by adding the ProtoRoot attribute to the <Protobuf /> section in your .csproj file.

Let's say you have a .proto file in project A:

syntax = "proto3";
option csharp_namespace = "Project.A";
import "ProjectB/<path>/Engine.proto"

message Car {
    Engine engine = 1;
    ...
}

In project B you have:

syntax = "proto3";
option csharp_namespace = "Project.B";

message Engine {
    ...
}

As you can see, in car.proto we used an import statement to a .proto file from another project. In order to successfully import this file, we need to add ProtoRoot to the <Protobuf /> section in the .csproj file of the project A:

<ItemGroup>
  <Protobuf Include="ProjectA/<path>/car.proto" Link="<path>/car.proto" ProtoRoot=".." />
</ItemGroup>

<path> is equivalent to your folder structure within your .NET project. ProtoRoot needs to be set to the directory where import declarations in the .proto files are looking for files. In this case, it's the parent folder which contains two subfolders with project A and project B.

More interesting stuff can be found here: https://chromium.googlesource.com/external/github.com/grpc/grpc/+/HEAD/src/csharp/BUILD-INTEGRATION.md

Upvotes: 12

Yury Yatskov
Yury Yatskov

Reputation: 195

Another option. It's easier not to use a shared project. The .proto file needs to be placed only in the service project and specified

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

and in the project the client only needs to specify the link like this

<ItemGroup>
    <Protobuf Include="..\NameServerProject\Protos\address.proto" GrpcServices="Client">
        <Link>Protos\address.proto</Link>
    </Protobuf>
</ItemGroup>

ProtoRoot can be missed.

Upvotes: 1

Yury Yatskov
Yury Yatskov

Reputation: 195

In your projects, specify Protobuf with Include and ProtoRoot which should contain the path to a shared project with proto-files like

  <ItemGroup>
    <Protobuf Include="..\NameSharedProject\Protos\address.proto" ProtoRoot="..\NameSharedProject" GrpcServices="Server" />
  </ItemGroup>

Then, after building in each project, you will have class files created in the folder "\obj\Debug\net5.0\Protos".

Upvotes: 7

Related Questions