|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: gRPC Meets .NET SDK And Visual Studio: Automatic Codegen On Build |
| 4 | +published: true |
| 5 | +permalink: blog/grpc-dotnet-build |
| 6 | +author: Kirill 'kkm' Katsnelson |
| 7 | +author-link: https://github.com/kkm000 |
| 8 | +company: SmartAction |
| 9 | +company-link: https://www.smartaction.ai |
| 10 | +--- |
| 11 | + |
| 12 | +As part of Microsoft's move towards its cross-platform .NET offering, they have |
| 13 | +greatly simplified the project file format, and allowed a tight integration of |
| 14 | +third-party code generators with .NET projects. We are listening, and now proud |
| 15 | +to introduce integrated compilation of Protocol Buffer and gRPC service |
| 16 | +`.proto` files in .NET C# projects starting with the version 1.17 of the |
| 17 | +Grpc.Tools NuGet package, now available from Nuget.org. |
| 18 | + |
| 19 | +You no longer need to use hand-written scripts to generate code from `.proto` |
| 20 | +files: The .NET build magic handles this for you. The integrated tools locate |
| 21 | +the proto compiler and gRPC plugin, standard Protocol Buffer imports, and track |
| 22 | +dependencies before invoking the code generators, so that the generated C# |
| 23 | +source files are never out of date, at the same time keeping regeneration to |
| 24 | +the minimum required. In essence, `.proto` files are treated as first-class |
| 25 | +sources in a .NET C# project. |
| 26 | + |
| 27 | +<!--more--> |
| 28 | + |
| 29 | +## A Walkthrough |
| 30 | + |
| 31 | +In this blog post, we'll walk through the simplest and probably the most common |
| 32 | +scenario of creating a library from `.proto` files using the cross-platform |
| 33 | +`dotnet` command. We will implement essentially a clone of the `Greeter` |
| 34 | +library, shared by client and server projects in the [C# `Helloworld` example |
| 35 | +directory |
| 36 | +](https://github.com/grpc/grpc/tree/master/examples/csharp/Helloworld/Greeter). |
| 37 | + |
| 38 | +### Create a new project |
| 39 | + |
| 40 | +Let's start by creating a new library project. |
| 41 | + |
| 42 | +``` |
| 43 | +~/work$ dotnet new classlib -o MyGreeter |
| 44 | +The template "Class library" was created successfully. |
| 45 | +
|
| 46 | +~/work$ cd MyGreeter |
| 47 | +~/work/MyGreeter$ ls -lF |
| 48 | +total 12 |
| 49 | +-rw-rw-r-- 1 kkm kkm 86 Nov 9 16:10 Class1.cs |
| 50 | +-rw-rw-r-- 1 kkm kkm 145 Nov 9 16:10 MyGreeter.csproj |
| 51 | +drwxrwxr-x 2 kkm kkm 4096 Nov 9 16:10 obj/ |
| 52 | +``` |
| 53 | + |
| 54 | +Observe that the `dotnet new` command has created the file `Class1.cs` that |
| 55 | +we won't need, so remove it. Also, we need some `.proto` files to compile. For |
| 56 | +this exercise, we'll copy an example file [`examples/protos/helloworld.proto` |
| 57 | +](https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto) |
| 58 | +from the gRPC distribution. |
| 59 | + |
| 60 | +``` |
| 61 | +~/work/MyGreeter$ rm Class1.cs |
| 62 | +~/work/MyGreeter$ wget -q https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/helloworld.proto |
| 63 | +``` |
| 64 | + |
| 65 | +(on Windows, use `del Class1.cs`, and, if you do not have the wget command, |
| 66 | +just [open the above URL |
| 67 | +](https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/helloworld.proto) |
| 68 | +and use a *Save As...* command from your Web browser). |
| 69 | + |
| 70 | +Next, add required NuGet packages to the project: |
| 71 | + |
| 72 | +``` |
| 73 | +~/work/MyGreeter$ dotnet add package Grpc |
| 74 | +info : PackageReference for package 'Grpc' version '1.17.0' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'. |
| 75 | +~/work/MyGreeter$ dotnet add package Grpc.Tools |
| 76 | +info : PackageReference for package 'Grpc.Tools' version '1.17.0' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'. |
| 77 | +~/work/MyGreeter$ dotnet add package Google.Protobuf |
| 78 | +info : PackageReference for package 'Google.Protobuf' version '3.6.1' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'. |
| 79 | +``` |
| 80 | + |
| 81 | +### Add `.proto` files to the project |
| 82 | + |
| 83 | +**Next comes an important part.** First of all, by default, a `.csproj` project |
| 84 | +file automatically finds all `.cs` files in its directory, although |
| 85 | +[Microsoft now recommends suppressing this globbing |
| 86 | +behavior](https://docs.microsoft.com/dotnet/core/tools/csproj#recommendation), |
| 87 | +so we too decided against globbing `.proto` files. Thus the `.proto` |
| 88 | +files must be added to the project explicitly. |
| 89 | + |
| 90 | +Second of all, it is important to add a property `PrivateAssets="All"` to the |
| 91 | +Grpc.Tools package reference, so that it will not be needlessly fetched by the |
| 92 | +consumers of your new library. This makes sense, as the package only contains |
| 93 | +compilers, code generators and import files, which are not needed outside of |
| 94 | +the project where the `.proto` files have been compiled. While not strictly |
| 95 | +required in this simple walkthrough, it must be your standard practice to do |
| 96 | +that always. |
| 97 | + |
| 98 | +So edit the file `MyGreeter.csproj` to add the `helloworld.proto` so that it |
| 99 | +will be compiled, and the `PrivateAssets` property to the Grpc.Tools package |
| 100 | +reference. Your resulting project file should now look like this: |
| 101 | + |
| 102 | +```xml |
| 103 | +<Project Sdk="Microsoft.NET.Sdk"> |
| 104 | + |
| 105 | + <PropertyGroup> |
| 106 | + <TargetFramework>netstandard2.0</TargetFramework> |
| 107 | + </PropertyGroup> |
| 108 | + |
| 109 | + <ItemGroup> |
| 110 | + <PackageReference Include="Google.Protobuf" Version="3.6.1" /> |
| 111 | + <PackageReference Include="Grpc" Version="1.17.0" /> |
| 112 | + |
| 113 | + <!-- The Grpc.Tools package generates C# sources from .proto files during |
| 114 | + project build, but is not needed by projects using the built library. |
| 115 | + It's IMPORTANT to add the 'PrivateAssets="All"' to this reference: --> |
| 116 | + <PackageReference Include="Grpc.Tools" Version="1.17.0" PrivateAssets="All" /> |
| 117 | + |
| 118 | + <!-- Explicitly include our helloworld.proto file by adding this line: --> |
| 119 | + <Protobuf Include="helloworld.proto" /> |
| 120 | + </ItemGroup> |
| 121 | + |
| 122 | +</Project> |
| 123 | +``` |
| 124 | + |
| 125 | +### Build it! |
| 126 | + |
| 127 | +At this point you can build the project with the `dotnet build` command to |
| 128 | +compile the `.proto` file and the library assembly. For this walkthrough, we'll |
| 129 | +add a logging switch `-v:n` to the command, so we can see that the command to |
| 130 | +compile the `helloworld.proto` file was in fact run. You may find it a good |
| 131 | +idea to always do that the very first time you compile a project! |
| 132 | + |
| 133 | +Note that many output lines are omitted below, as the build output is quite |
| 134 | +verbose. |
| 135 | + |
| 136 | +``` |
| 137 | +~/work/MyGreeter$ dotnet build -v:n |
| 138 | +
|
| 139 | +Build started 11/9/18 5:33:44 PM. |
| 140 | + 1:7>Project "/home/kkm/work/MyGreeter/MyGreeter.csproj" on node 1 (Build target(s)). |
| 141 | + 1>_Protobuf_CoreCompile: |
| 142 | + /home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/protoc |
| 143 | + --csharp_out=obj/Debug/netstandard2.0 |
| 144 | + --plugin=protoc-gen-grpc=/home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/grpc_csharp_plugin |
| 145 | + --grpc_out=obj/Debug/netstandard2.0 --proto_path=/home/kkm/.nuget/packages/grpc.tools/1.17.0/build/native/include |
| 146 | + --proto_path=. --dependency_out=obj/Debug/netstandard2.0/da39a3ee5e6b4b0d_helloworld.protodep helloworld.proto |
| 147 | + CoreCompile: |
| 148 | +
|
| 149 | + [ ... skipping long output ... ] |
| 150 | +
|
| 151 | + MyGreeter -> /home/kkm/work/MyGreeter/bin/Debug/netstandard2.0/MyGreeter.dll |
| 152 | +
|
| 153 | +Build succeeded. |
| 154 | +``` |
| 155 | + |
| 156 | +If at this point you invoke the `dotnet build -v:n` command again, `protoc` |
| 157 | +would not be invoked, and no C# sources would be compiled. But if you change |
| 158 | +the `helloworld.proto` source, then its outputs will be regenerated and then |
| 159 | +recompiled by the C# compiler during the build. This is a regular dependency |
| 160 | +tracking behavior that you expect from modifying any source file. |
| 161 | + |
| 162 | +Of course, you can also add `.cs` files to the same project: It is a regular C# |
| 163 | +project building a .NET library, after all. This is done in our [RouteGuide |
| 164 | +](https://github.com/grpc/grpc/tree/master/examples/csharp/RouteGuide/RouteGuide) |
| 165 | +example. |
| 166 | + |
| 167 | +### Where are the generated files? |
| 168 | + |
| 169 | +You may wonder where the proto compiler and gRPC plugin output C# files are. By |
| 170 | +default, they are placed in the same directory as other generated files, such |
| 171 | +as objects (termed the "intermediate output" directory in the .NET build |
| 172 | +parlance), under the `obj/` directory. This is a regular practice of .NET |
| 173 | +builds, so that autogenerated files do not clutter the working directory or |
| 174 | +accidentally placed under source control. Otherwise, they are accessible to the |
| 175 | +tools like the debugger. You can see other autogenerated sources in that |
| 176 | +directory, too: |
| 177 | + |
| 178 | +``` |
| 179 | +~/work/MyGreeter$ find obj -name '*.cs' |
| 180 | +obj/Debug/netstandard2.0/MyGreeter.AssemblyInfo.cs |
| 181 | +obj/Debug/netstandard2.0/Helloworld.cs |
| 182 | +obj/Debug/netstandard2.0/HelloworldGrpc.cs |
| 183 | +``` |
| 184 | + |
| 185 | +(use `dir /s obj\*.cs` if you are following this walkthrough from a Windows |
| 186 | +command prompt). |
| 187 | + |
| 188 | +## There Is More To It |
| 189 | + |
| 190 | +While the simplest default behavior is adequate in many cases, there are many |
| 191 | +ways to fine-tune your `.proto` compilation process in a large project. We |
| 192 | +encourage you to read the [documentation file BUILD-INTEGRATION.md |
| 193 | +](https://github.com/grpc/grpc/blob/master/src/csharp/BUILD-INTEGRATION.md) |
| 194 | +for available options if you find that the default arrangement does not suit |
| 195 | +your workflow. The package also extends the Visual Studio's Properties window, |
| 196 | +so you may set some options per file in the Visual Studio interface. |
| 197 | + |
| 198 | +"Classic" `.csproj` projects and Mono are also supported. |
| 199 | + |
| 200 | +## Share Your Experience |
| 201 | + |
| 202 | +As with any initial release of a complex feature, we are thrilled to receive |
| 203 | +your feedback. Did something not work as expected? Do you have a scenario that |
| 204 | +is not easy to cover with the new tools? Do you have an idea how to improve the |
| 205 | +workflow in general? Please read the documentation carefully, and then [open an |
| 206 | +issue](https://github.com/grpc/grpc/issues) in the gRPC code repository on |
| 207 | +GitHub. Your feedback is important to determine the future direction for our |
| 208 | +build integration work! |
0 commit comments