Sharing message types between gRPC services in .NET

When we started our move from WCF to gRPC, among the first problems we had to solve was how we can share message types between contracts in different independent services living in separate repositories.

When we started our move from WCF to gRPC, among the first problems we had to solve was how we can share message types between contracts in different independent services living in separate repositories. We won't share all types, but some of the fundamental ones made sense to reuse across our services.

The solution we landed on, was having them in a separate repository, along with handy extension methods to help with mapping, null checking and similar, since the protobuf-protocol is quite strict in certain areas.

The project file ended up as a NuGet package looks pretty much like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Version>1.0.0</Version>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.14.0" />
    <PackageReference Include="Grpc.Tools" Version="2.34.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <-- Any other dependencies you might need -->
  </ItemGroup>

  <ItemGroup>
    <Protobuf Include="Protos\sharedtypes.proto" GrpcServices="None" />
    <Protobuf Include="Protos\someMoreSpecificTypes.proto" GrpcServices="None" />
    <Content Include="Protos\**\*.proto">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</Project>

dotnet pack this, and distribute however you'd like.

Then, as a one-time-job in the service you're installing (Since nugets no longer support install-scripts), you'll have to:

  1. Add GeneratePathProperty="true" to the <PackageReference Include="YourSystem.Grpc.SharedContracts" ... /> element in your csproj-file. The GeneratePathProperty-attribute is explained here.
  2. Remove all previous <Protobuf Include="..."> in your csproj-file.
  3. In their place, add <Protobuf Include="Protos/*.proto" AdditionalImportDirs="$(PkgYourSystem_Grpc_SharedContracts)\content\protos" GrpcServices="Server" ProtoRoot="Protos" />. Along with this, of course, ensure the project-local .proto-files live in this Protos/ directory.
  4. If you have any proto files with imports between them already: Remove Protos/ from all import statements to reference other "local" proto files.

From now, you should be able to import "sharedtypes.proto"; at the top of your proto files. You should also see the imported (but read only) files in Protos/ for available types.