Protocol Buffers/gRPC Codegen Integration Into .NET Build

The Grpc.Tools NuGet package provides C# tooling support for generating C# code from .proto files in .csproj projects:

  • It contains protocol buffers compiler and gRPC plugin to generate C# code.
  • It can be used in building both grpc-dotnet projects and legacy c-core C# projects.

Using Grpc.Tools in .csproj files is described below. Other packages providing the runtime libraries for gRPC are described elsewhere.

Getting Started

The package Grpc.Tools is used automatically to generate the C# code for protocol buffer messages and gRPC service stubs from .proto files. These files:

  • are generated on an as-needed basis each time the project is built.
  • aren't added to the project or checked into source control.
  • are a build artifact usually contained in the obj directory.

This package is optional. You may instead choose to generate the C# source files from .proto files by running the protoc compiler manually or from a script. However this package helps to simplify generating the C# source files by integrating the code generation into the build process. It can be used when building both the server and client projects, and by both c-core C# projects and grpc-dotnet projects:

  • The Grpc.AspNetCore metapackage already includes a reference to Grpc.Tools.
  • gRPC for .NET client projects and projects using Grpc.Core need to reference Grpc.Tools explicity if you want code generation for those projects

Grpc.Tools is only used at build-time and has no runtime components. It should be marked with PrivateAssets="All" to prevent it from being included at runtime, e.g.

<PackageReference Include="Grpc.Tools" Version="2.50.0">
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  <PrivateAssets>all</PrivateAssets>
</PackageReference>

Support is provided for the following platforms:

  • Windows (x86, x64, and arm64 via using the x86 binaries)
  • MacOS (x64 and arm64 via using the x64 binaries)
  • Linux (x86, x64, and arm64)

You may still use the MSBuild integration provided by Grpc.Tools for other architectures provided you can supply the codegen binaries for that platform/architecture. See Using Grpc.Tools with unsupported architectures below.

Adding .proto files to a project

To add .proto files to a project edit the project’s .csproj file and add an item group with a <Protobuf> element that refers to the .proto file, e.g.

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

Wildcards can be used to select several .proto files, e.g.

<ItemGroup>
    <Protobuf Include="**\*.proto" />
</ItemGroup>

By default, a <Protobuf> reference generates gRPC client and a service base class from the service definitions in the .proto files. The GrpcServices attribute can be used to limit C# asset generation. See the reference section below for all options. E.g. to only generate client code:

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

For .proto files that are outside of the project directory a link can be added so that the files are visible in Visual Studio. E.g.

<ItemGroup>
   <Protobuf Include="..\Proto\aggregate.proto" GrpcServices="Client" Link="Protos\aggregate.proto"/>
   <Protobuf Include="..\Proto\greet.proto" GrpcServices="None" Link="Protos\greet.proto"/>
   <Protobuf Include="..\Proto\count.proto" GrpcServices="None" Link="Protos\count.proto"/>
</ItemGroup>

For more examples see the example project files in GitHub: https://github.com/grpc/grpc-dotnet/tree/master/examples

Sharing .proto files between multiple projects (in the same VS solution)

It's common to want to share .proto files between projects. For example, a gRPC client and gRPC server share the same contract. It is preferable to share contracts without copying .proto files because copies can go out of sync over time.

There are a couple of ways to use .proto files in multiple projects without duplication:

  • Sharing .proto files between projects with MSBuild links.
  • Generating code in a class library and sharing the library.

Sharing .proto with MSBuild links

.proto files can be placed in a shared location and referenced by multiple projects using MSBuild's Link or LinkBase settings.

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

In the example above, greet.proto is in a shared Protos directory outside the project directory. Multiple projects can reference the proto file.

Generating code in a class library

Create a class library that references .proto files and contains generated code. The other projects in the solution can then reference this shared class library instead of each project having to compile the same .proto files.

The advantages of this are:

  • The .proto only need to be compiled once.
  • It prevents some projects getting multiple definitions of the same generated code, which can in turn break the build.

There are a couple of examples in GitHub:

  • The Liber example demonstrates how common protocol buffers messages can be compiled once and used in other projects:

    • The Common project creates a class library that includes the generates messages contained in common.proto
    • The Client and Server projects reference the Common project.
    • They do not need to recompile common.proto as those .NET types are already in the Common class library.
    • They do however each generate their own gRPC client or server code as both have a <Protobuf> reference for greet.proto. The Client and Server projects each having their own version of greet.proto is OK since they don't reference each other - they only reference the shared Common class library.
  • The RouteGuide example demonstrates how the gRPC client and server code can be generated once and used in other projects:

    • Note: this example uses the legacy c-core C# packages, but the principles are the same for gRPC for .NET projects.
    • The RouteGuide project has a <Protobuf> reference to route_guide.proto and generates both the gRPC client and server code.
    • The RouteGuideClient and RouteGuideServer projects reference the RouteGuide project.
    • They do not need any <Protobuf> references since the code has already been generated in the RouteGuide project.

Reference

Protobuf item metadata reference

The following metadata are recognized on the <Protobuf> items.

NameDefaultValueSynopsis
Accesspublicpublic, internalGenerated class access
AdditionalProtocArgumentsarbitrary cmdline argumentsExtra command line flags passed to protoc command. To specify multiple arguments use semi-colons (;) to separate them. See example below
ProtoCompiletruetrue, falseIf false, don't invoke protoc to generate code.
ProtoRootSee notesA directoryCommon root for set of files
CompileOutputstruetrue, falseIf false, C# code will be generated, but it won't be included in the C# build.
OutputDirSee notesA directoryDirectory for generated C# files with protobuf messages
OutputOptionsarbitrary optionsExtra options passed to C# codegen as --csharp_opt=opt1,opt2
GrpcOutputDirSee notesA directoryDirectory for generated gRPC stubs
GrpcOutputOptionsarbitrary optionsExtra options passed to gRPC codegen as --grpc_opt=opt1,opt2
GrpcServicesBothNone, Client, Server, BothGenerated gRPC stubs
AdditionalImportDirsSee notesDirectoriesSpecify additional directories in which to search for imports .proto files

Notes

  • ProtoRoot
    For files inside the project directory or its subdirectories, ProtoRoot is set by default to the project directory.

    For files outside of the project directory, the value is set to the file's containing directory name, individually per file. If you include a subtree of .proto files that lies outside of the project directory, you need to set ProtoRoot. There is an example of this below. The path in this variable is relative to the project directory.

  • OutputDir
    The default value is the value of the property Protobuf_OutputPath. This property, in turn, unless you set it in your project, will be set to the value of the standard MSBuild property IntermediateOutputPath, which points to the location of compilation object outputs, such as "obj/Release/netstandard1.5/". The path in this property is considered relative to the project directory.

  • GrpcOutputDir
    Unless explicitly set, will follow OutputDir for any given file.

  • Access
    Sets generated class access on both generated message and gRPC stub classes.

  • AdditionalProtocArguments Pass additional commandline arguments to the protoc command being invoked. Normally this option should not be used, but it exists for scenarios when you need to pass otherwise unsupported (e.g. experimental) flags to protocol buffer compiler.

  • OutputOptions Pass additional C# code generation options to protoc in the form --csharp_opt=opt1,opt2. See C#-specific options for possible values.

  • GrpcOutputOptions Pass additional options to the grpc_csharp_plugin in form of the --grpc_opt flag. Normally this option should not be used as its values are already controlled by Access and GrpcServices metadata, but it might be useful in situations where you want to explicitly pass some otherwise unsupported (e.g. experimental) options to the grpc_csharp_plugin.

  • AdditionalImportDirs Specify additional directories in which to search for imports in .proto files. The directories are searched in the order given. You may specify directories outside of the project directory. The directories are passed to the protoc code generator via the -I/--proto_path option together with Protobuf_StandardImportsPath and ProtoRoot directories.

Specifying multiple values in properties

Some properties allow you to specify multiple values in a list. The items in a list need to be separated by semi-colons (;). This is the syntax that MsBuild uses for lists.

The properties that can have lists of items are: OutputOptions, AdditionalProtocArguments, GrpcOutputOptions, AdditionalImportDirs

Example: to specify two additional arguments: --plugin=protoc-gen-myplugin=D:\myplugin.exe --myplugin_out=.

  <ItemGroup>
    <Protobuf Include="proto_root/**/*.proto" ProtoRoot="proto_root"
              OutputDir="%(RelativeDir)" CompileOutputs="false"
              AdditionalProtocArguments="--plugin=protoc-gen-myplugin=D:\myplugin.exe;--myplugin_out=." />
  </ItemGroup>

grpc_csharp_plugin command line options

Under the hood, the Grpc.Tools build integration invokes the protoc and grpc_csharp_plugin binaries to perform code generation. Here is an overview of the available grpc_csharp_plugin options:

NameDefaultSynopsis
no_clientoffDon't generate the client stub
no_serveroffDon't generate the server-side stub
internal_accessoffGenerate classes with “internal” visibility
file_suffixGrpc.csThe suffix that will get appended to the name of the generated file. Can only be used on the command line.
base_namespacenoneExperimental - may change or be removed. Same as base_namespace for protoc C# options . Can only be used on the command line.

To use these options with Grpc.Tools specify them in the GrpcOutputOptions metadata in the <Protobuf> item.

Notes:

  • file_suffix and base_namespace should not be used with Grpc.Tools. Using them will break the build.

  • using base_namespace changes the algorithm for the generated file names to align it with the algorithm used by protoc.

    This only affects files with punctuation or numbers in the name. E.g. hello.world2d.proto now generates file HelloWorld2DGrpc.cs instead of Hello.world2dGrpc.cs

To use these options on the command line specify them with the --grpc_opt option.

Code generated by protoc is independent of the plugin and you may also need to specify C# options for this with --csharp_opt. These are documented here.

e.g.:

protoc --plugin=protoc-gen-grpc=grpc_csharp_plugin \
    --csharp_out=OUT_DIR \
    --csharp_opt=base_namespace=Example \
    --grpc_out=OUT_DIR \
    --grpc_opt=no_server,base_namespace=Example \
    -I INCLUDE_DIR foo.proto

Environment Variables

Environment variables can be set to change the behavior of Grpc.Tools - setting the CPU architecture or operating system, or using custom built protocol buffers compiler and gRPC plugin.

NameSynopsis
PROTOBUF_TOOLS_OSOperating system version of the tools to use: linux, macosx, or windows
PROTOBUF_TOOLS_CPUCPU architecture version of the tools to use: x86, x64, or arm64
PROTOBUF_PROTOCFull path to the protocol buffers compiler
GRPC_PROTOC_PLUGINFull path to the grpc_csharp_plugin

For example, to use a custom built protoc compiler and grpc_csharp_plugin:

export PROTOBUF_PROTOC=$my_custom_build/protoc
export GRPC_PROTOC_PLUGIN=$my_custom_build/grpc_csharp_plugin
dotnet build myproject.csproj

MSBuild Properties

You can set some Properties in your project file or on the MSBuild command line. The following properties change the behavior of Grpc.Tools:

NameSynopsis
Protobuf_ToolsOsSame as PROTOBUF_TOOLS_OS environment variable
Protobuf_ToolsCpuSame as PROTOBUF_TOOLS_CPU environment variable
Protobuf_ProtocFullPathSame as PROTOBUF_PROTOC environment variable
gRPC_PluginFullPathSame as GRPC_PROTOC_PLUGIN environment variable
Protobuf_NoWarnMissingExpectedDefault: false. If true then no warnings are given if expected files not generated. See example below for an explanation.
Protobuf_OutputPathDefault: IntermediateOutputPath - ususally the obj directory. Sets the default value for OutputDir on <Protobuf> items.
EnableDefaultProtobufItemsDefault: false. If true then .proto files under the project are automatically included without the need to specify any <Protobuf> items.
Protobuf_StandardImportsPathThe path for protobuf's well known types included in the NuGet package. It is automcatically passed to protoc via the -I/--proto_path option.

Scenarios and Examples

For other examples see also the .csproj files in the examples in GitHub:

Quick links to the examples below:


ProtoRoot - Common root for one or more .proto files

ProtoRoot specifies a common directory that is an ancestor for a set of .proto files.

It has two purposes:

  • working out relative directories to preserve the structure when generating .cs files
  • adding a directory to be searched for imported .proto files

These are explained in an example below.

For .proto files under the project directory ProtoRoot is by default set to .. It can also be explicitly set.

For .proto files outside of the project the value is set to the file's containing directory name. If you include a subtree of .proto files then you must set ProtoRoot to give the parent of the directory tree.

In either case if you are importing a .proto file from within another file then you should set ProtoRoot so that the import paths can be found. (See also AdditionalImportDirs below.)

Generated files in the output directory will have the same directory structure as the .proto files under ProtoRoot.

By default the output directory for generated files is obj\CONFIGURATION\FRAMEWORK\ (e.g. obj\Debug\net6.0\) unless OutputDir or GrpcOutputDir are specified.

Example use of ProtoRoot

Specifying:

<Protobuf Include="Protos\Services\**\*.proto"
          ProtoRoot="Protos" />
<Protobuf Include="Protos\Messages\**\*.proto"
          ProtoRoot="Protos"
          GrpcServices="None" />
<Protobuf Include="..\OutsideProjectProtos\**\*.proto"
          ProtoRoot="..\OutsideProjectProtos" />

for files:

	ProjectFolder\Protos\Services\v1\hello.proto
	ProjectFolder\Protos\Services\v2\hello.proto
	ProjectFolder\Protos\Messages\v1\message.proto
	..\OutsideProjectProtos\MyApi\alpha.proto
	..\OutsideProjectProtos\MyApi\beta.proto

will generate files:

  ProjectFolder\obj\Debug\net6.0\Services\v1\Hello.cs
  ProjectFolder\obj\Debug\net6.0\Services\v1\HelloGrpc.cs
  ProjectFolder\obj\Debug\net6.0\Services\v2\Hello.cs
  ProjectFolder\obj\Debug\net6.0\Services\v2\HelloGrpc.cs
  ProjectFolder\obj\Debug\net6.0\Messages\v1\Message.cs
  ProjectFolder\obj\Debug\net6.0\MyApi\Alpha.cs
  ProjectFolder\obj\Debug\net6.0\MyApi\AlphaGrpc.cs
  ProjectFolder\obj\Debug\net6.0\MyApi\Beta.cs
  ProjectFolder\obj\Debug\net6.0\MyApi\BetaGrpc.cs

Things to notes:

  • the directory structures under ProjectFolder\Protos\ and ..\OutsideProjectProtos\ are mirrored in the output directory.
  • the import search paths passed to protoc via -I/--proto_path option will include ProjectFolder\Protos and ..\OutsideProjectProtos

AdditionalImportDirs - Setting location of imported .proto files

In addition to specifying ProtoRoot other import directories can be specified for directories to search when importing .proto files by specifying AdditionalImportDirs and provide a list of directories. The directories are searched in the order given.

You would use this when you want to import .proto files that you don't need to separately compile as they are only used in import statements. E.g.:

  <Protobuf Include="protos/*.proto"
            ProtoRoot="protos"
            AdditionalImportDirs="/folder/protos/mytypes/;/another/folder/"
      ... />

Note: The path for protobuf's well known types is automatically included. E.g. the import below will work without having to explicity specifying the path in AdditionalImportDirs:

import "google/protobuf/wrappers.proto";

GrpcServices - Generating gRPC services and protocol buffers messages

The protocol buffers files (.proto files) define both the service interface and the structure of the payload messages.

Two .cs file can be generated from a .proto file. For example, if the .proto file is myfile.proto then the two possible files are:

  • Myfile.cs - contains the generated code for protocol buffers messages
  • MyfileGrpc.cs - contains the generated code for gRPC client and/or server

When a .proto file contains service definitions the protocol buffers compiler calls the gRPC plugin to generate gRPC client and/or server stub code. Whether or not the *Grpc.cs file is generated and what it contains is controlled by the GrpcServices metadata on the <Protobuf> item.

  • GrpcServices="Both" (the default) - Myfile.cs and MyfileGrpc.cs generated
  • GrpcServices="None" - just Myfile.cs generated
  • GrpcServices="Client" - Myfile.cs and MyfileGrpc.cs (just client code)
  • GrpcServices="Server" - Myfile.cs and MyfileGrpc.cs (just server code)

However when a .proto does not file contains any service definitions but only contains message definitions then an empty (zero length) MyfileGrpc.cs may still be created by Grpc.Tools unless the .proto file is specified with GrpcServices="None" in the project file.

This is because Grpc.Tools has no way of knowing in advanced of running the protocol buffers compiler whether a .proto file has a service clause. It creates the empty files as a marker for incremental builds so that the .proto files are not unnecessarily recompiled. Empty files are not a problem on a small project but you may wish to avoid them on a larger project.

Therefore it is better to explicitly mark files with the correct GrpcServices metadata if you can. For example:

<ItemGroup>
  <Protobuf Include="**/*.proto" GrpcServices="None" />
  <Protobuf Update="**/hello/*.proto;**/bye/*.proto" GrpcServices="Both" />
</ItemGroup>

In the above example all .proto files are compiled with GrpcServices="None", except for .proto files in subdirectories on any tree level named hello and bye, which will take GrpcServices="Both". Note the use of the Update attribute instead of Include - otherwise the files would be added twice.

Another example would be the use of globbing if your service .proto files are named according to a pattern, for example *_services.proto. In this case the Update attribute can be written as Update="**/*_service.proto" to set the attribute GrpcServices="Both" only on these files.

Seeing a warning about a missing expected file

You will see the warning message:

Some expected protoc outputs were not generated

if all these are true:

  • the location for the generated files is configured to a directory outside of the project, e.g. OutputDir="..\outside-project\"
  • *Grpc.cs files have not been created because the .proto file does not contain a service definintion
  • you have not specified GrpcServices="None"

This is because Grpc.Tools only creates empty *Grpc.cs files in directories within the project (such as the intermediate obj directory). Empty files are not created outside of the project directory so as not to pollute non-project directories.

This warning can be suppressed by setting the MSBuild property:

<PropertyGroup>
  <Protobuf_NoWarnMissingExpected>true</Protobuf_NoWarnMissingExpected>
</PropertyGroup>

however it is better to set GrpcServices="None" on the .proto files affected to avoid unnecessary rebuilds.


Automatically including .proto files

For SDK projects it is possible to automatically include .proto files found in the project directory or sub-directories, without having to specify them with a <Protobuf> item. To do this the property EnableDefaultProtobufItems has be set to true in the project file or on the MSBuild command line.

It is recommended that you do not rely on automatic inclusion of .proto files except for the simplest of projects since it does not allow you to control other settings such as GrpcServices.

By default EnableDefaultProtobufItems is not set and <Protobuf> items must be included in the project for the .proto files to be compiled.


Generate proto and gRPC C# sources from .proto files (no C# compile)

If you just want to generate the C# sources from .proto files without compiling the C# files (e.g. for use in other projects) then you can do something similar to this to a .csproj file:

<ItemGroup>
 <Protobuf Include="**/*.proto"
     OutputDir="%(RelativeDir)" CompileOutputs="false"  />
</ItemGroup>
  • Include tells the build system to recursively examine project directory and its subdirectories (**) include all files matching the wildcard *.proto.
  • OutputDir="%(RelativeDir)" makes the output directory for each .cs file to be same as the corresponding .proto directory.
  • CompileOutputs="false" prevents compiling the generated files into an assembly.

Note that an empty assembly is still generated which can be ignored.

NOTE: To start with an empty project to add your .proto files to you can do the following at a command prompt:

dotnet new classlib
rm *.cs              # remove .cs files - for Windows the command is: del *.cs /y
dotnet add package Grpc.Tools

Visual Studio: setting per-file .proto file options

In Visual Studio it is possible to set some frequently used per-file options on .proto files without editing the .csproj file directly. However editing the .csproj gives you more flexibilty.

“dotnet SDK” projects

For a “dotnet SDK” project, you have more control of some frequently used options. You may need to open and close Visual Studio for this form to appear in the properties window after adding a reference to Grpc.Tools package:

Properties in an SDK project

You can also change options of multiple files at once by selecting them in the Project Explorer together.

“classic” projects

For a “classic” project, you can only add .proto files with all options set to default. Click on the “show all files” button, add files to project, then change file type of the .proto files to “Protobuf” in the Properties window drop-down. This menu item will appear after you import the Grpc.Tools package:

Properties in a classic project


Bypassing Grpc.Tools to run the protocol buffers compiler explicitly

It is possible to bypass all the build logic in Grpc.Tools and run the protocol buffers compiler explicitly in your project file, and just use the Grpc.Tools as a means of getting the compiler. This is not recommended but there may be situations where you want to do this.

You can use the following Properties:

  • Protobuf_ProtocFullPath points to the full path and filename of protoc executable, e.g. "...\.nuget\packages\grpc.tools\2.51.0\build\native\bin\windows\protoc.exe"

  • gRPC_PluginFullPath points to the full path and filename of gRPC plugin, e.g. "...\.nuget\packages\grpc.tools\2.51.0\build\native\bin\windows\grpc_csharp_plugin.exe"

  • Protobuf_StandardImportsPath points to the standard proto import directory, e.g. "...\.nuget\packages\grpc.tools\2.51.0\build\native\include". This is the directory where a declaration such as import "google/protobuf/wrappers.proto"; in a proto file would find its target.

then in your project file:

  <Target Name="MyProtoCompile">
    <PropertyGroup>
      <ProtoCCommand>$(Protobuf_ProtocFullPath) --plugin=protoc-gen-grpc=$(gRPC_PluginFullPath)  -I $(Protobuf_StandardImportsPath) ....rest of your command.... </ProtoCCommand>
    </PropertyGroup>
    <Message Importance="high" Text="$(ProtoCCommand)" />
    <Exec Command="$(ProtoCCommand)" />
  </Target>

Do not include any <Protobuf> items in the project file as that will invoke the Grpc.Tools build and your files will be compiled twice.


Using Grpc.Tools with unsupported architectures

You may still use the MSBuild integration provided by Grpc.Tools for architectures where the binaries are not included in the Grpc.Tools NuGet package.

If you are able to build your own binaries for protoc and grpc_csharp_plugin, or find pre-built binaries provided by the community, then you can define a couple of environment variables to tell Grpc.Tools to use those binaries instead of the ones provided in the NuGet package:

  • PROTOBUF_PROTOC - Full path to the protocol buffers compiler
  • GRPC_PROTOC_PLUGIN - Full path to the grpc_csharp_plugin

Things to note:

  • You need Grpc.Tools version 2.50.0 or later for these environment variables to be recognised.

  • The binaries bundled in Grpc.Tools already ensure that the correct and mutually compatible version of protoc and grpc_csharp_plugin will be chosen, but when providing them yourself, you're in charge.

  • If the versions of protoc and grpc_csharp_plugin you provide are mutually incompatible then code generated may not work with your application (e.g. breaks the build).

  • Specifically, older version of plugins may generate incompatible code or may not contain patches/fixes.

An example for Alpine Linux

For Alpine Linux (which uses the musl C standard library) there are community provided packages for the protocol buffers compiler and gRPC plugins: https://pkgs.alpinelinux.org/packages?name=grpc-plugins

To use these:

# Build or install the binaries for your architecture.

# e.g. for Alpine Linux the grpc-plugins package can be used
#  See https://pkgs.alpinelinux.org/package/edge/community/x86_64/grpc-plugins
apk add grpc-plugins  # Alpine Linux specific package installer

# Set environment variables for the built/installed protoc
# and grpc_csharp_plugin binaries
export PROTOBUF_PROTOC=/usr/bin/protoc
export GRPC_PROTOC_PLUGIN=/usr/bin/grpc_csharp_plugin

# When the dotnet build runs the Grpc.Tools NuGet package will
# use the binaries pointed to by the environment variables
dotnet build

Including .proto files in NuGet packages

There might be occassions when you are given a NuGet package that contains .proto files that you wish to use in your own project, or you may wish to package your own .proto files in a NuGet package for others to use.

There is no automatic way for Grpc.Tools to locate and include .proto files from other NuGet packages. Below is a suggested convention to use when creating NuGet packages that contain .proto files.

Note: This is not the same as a NuGet package providing a library built from the code generated from the .proto files. Below just describes how to provide the uncompiled .proto files in a NuGet package and have Grpc.Tools automatically use them.

Creating the NuGet package

The NuGet package should:

  • provide the .proto files in a content\protos subdirectory in the package
  • provide a packagename.targets file in the build subdirectory in the package that:
    • defines an MSBuild property giving the path to the .proto files in the installed package
    • conditionally updates the Protobuf_StandardImportsPath property with the above path so that the files can be found by the protocol buffers compiler
      • it should be made optional forcing users to opt in to including the .proto files

For example, for the package My.Example.Protos:

My.Example.Protos.nuspec:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>My.Example.Protos</id>
    <version>1.0.0</version>
    <title>Example package containing proto files</title>
    <authors>author</authors>
    <owners>owner</owners>
    <licenseUrl>license url</licenseUrl>
    <projectUrl>project url</projectUrl>
    <description>See project site for more info.</description>
    <summary>Example package containing proto files.</summary>
    <releaseNotes>Example package containing proto files</releaseNotes>
    <copyright>Copyright 2023, My Company.</copyright>
  </metadata>
  
  <files>
    <!-- copy the My.Example.Protos.targets file for MSBuild integration -->
    <file src="build\**" target="build" />
    <!-- copy the .proto files into the package -->
    <file src="proto\**" target="content\protos" />
  </files>
</package>

My.Example.Protos.targets:

<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- This targets file allows .proto files bundled in package, 
  to be included in Grpc.Tools compilation. -->
  
  <PropertyGroup>
    <!-- Define a property containing the path of the proto files.
         Content from the nupkg. -->
    <MyExampleProtos_ProtosPath>$( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../content/protos) )</MyExampleProtos_ProtosPath>
  </PropertyGroup>

  <!-- Run immediately before the Protobuf_BeforeCompile extension point. -->
  <!-- Only include protos if project has set <IncludeMyExampleProtosProtos> 
       property to true. -->
  <Target Name="MyExampleProtos_BeforeCompile"
          BeforeTargets="Protobuf_BeforeCompile"
          Condition=" '$(IncludeMyExampleProtosProtos)' == 'true' ">
    <PropertyGroup>
      <!-- Add proto files by including path in Protobuf_StandardImportsPath.
           This path is passed to protoc via the -I option -->
      <Protobuf_StandardImportsPath>$(Protobuf_StandardImportsPath);$(MyPackage_ProtosPath)</Protobuf_StandardImportsPath>
    </PropertyGroup>

    <!-- These message are not required but included here for diagnostics -->
    <Message Text="Included proto files at $(MyExampleProtos_ProtosPath) in import path." Importance="high" />
    <Message Text="Updated proto imports path: $(Protobuf_StandardImportsPath)" Importance="high" />
  </Target>
</Project>

Using the NuGet package

The project needs to add the package containing the .proto files:

<PackageReference Include="My.Example.Protos" Version="1.0.0" />

If the project only wants to compile the .proto files included in the package then all it needs to do is add the <Protobuf> items using the property defined in the package for the path to the files. For example, if the NuGet package contained the file greet.proto, then the project should add:

<Protobuf Include="$(MyExampleProtos_ProtosPath)/greet.proto" />

However if the provided .proto files are to be imported by the projects own .proto files then the Protobuf_StandardImportsPath needs updated to add the directory containing the package's files. This is done by setting to true the property used in the package. For example, if the project has the local .proto file my_services.proto and it imported a file from the package common_message.proto, then:

<PropertyGroup>
  <!-- Update the Protobuf_StandardImportsPath -->
  <IncludeMyExampleProtosProtos>true</IncludeMyExampleProtosProtos>
</PropertyGroup>

<ItemGroup>
  <!-- my_services.proto imports common_message.proto from the package
   My.Example.Protos -->
  <Protobuf Include="my_services.proto" />
</ItemGroup>

See also

gRPC project documentation:

Microsoft documentation: