Understanding how proto files generate C# code
Normally, you wouldn't need to worry about how C# classes are generated from proto files. The compiler does it all for you. But occasionally, there may be a problem with the process. Therefore, it would be useful to know how to find the generated code and what the expected output should be.
Where is auto-generated code stored?
At this point, you know that the .NET compiler generates code from the proto files. This can then be referenced from inside your application code. And you can also get it to share the same namespace as your application. But despite the ability of this code to inter-operate with your application code, it's not part of your application.
The auto-generated code is placed in the obj
folder inside your project folder. The purpose of this folder is to store intermediate resources that are required to compile your application. Since auto-generated classes aren't part of your main application, but your application cannot be compiled without them, they are placed alongside other intermediate files in this folder.
More precisely, the location of those auto-generated files is as follows. This represents the path on the Windows system. For a Unix-based system, such as macOS or Linux, replace back-slashes (\
) with forward-slashes (/
):
{your project folder}\obj\{build configuration}\{framework name}\Ptotos
So, for our BasicGrpcService
project, which is based on .NET 5's built-in Debug
mode, the path would be as follows:
BasicGrpcService\obj\Debug\net5.0\Ptotos
For each proto file that you reference in your project, a pair of files containing C# code will be generated:
{PascalCase proto file name}.cs
{PascalCase proto file name}Grpc.cs
The {PascalCase proto file name}.cs
file contains a C# representation of the proto messages that your services use, while {PascalCased proto file name}Grpc.cs
contains a C# representation of the services themselves, whether it's overridable base classes for the server or ready-made classes for the client.
In our example, which uses the greeter.proto
file, we would end up with two files with the following names:
Greeter.cs
GreeterGrpc.cs
The content of those auto-generated files would be similar to the following:
You can examine the structure of these files if you like. Now, let's learn how making changes to the namespaces in Protobuf will affect the auto-generated code.
Modifying Protobuf namespaces
So far, we have been using the csharp_namespace
option inside our proto files to set the namespaces of auto-generated code classes to the same root namespace that our application uses. But it doesn't have to be this way. You can set the namespaces in auto-generated code to absolutely anything.
You can also omit the csharp_namespace
option entirely. If you do so, the namespace that will be applied to your auto-generated code will be the PascalCase version of the package name that's specified in the package
element of the proto file.
In our case, since the package is called greeter
, the C# namespace that's generated from it will be Greeter
.
Now, go ahead and remove the csharp_namespace
element from both the client and server versions of the greeter.proto
file. Both copies of the files should now look as follows:
syntax = "proto3"; package greeter; // The greetings manager service definition. service GreetingsManager { // Request the service to generate a greeting message. rpc GenerateGreeting (GreetingRequest) returns (GreetingResponse); } // The request message definition containing the name to be addressed in the greeting message. message GreetingRequest { string name = 1; } // The response message definition containing the greeting text. message GreetingResponse { string greetingMessage = 1; }
Now, if you try to compile the projects, they will show errors. What you need to do is add a using
statement to both the client and the server code referencing this namespace.
The content of the GreetingsManagerService.cs
file inside the BasicGreeterService
project should now look as follows:
using System.Threading.Tasks; using Greeter; using Grpc.Core; namespace BasicGrpcService { public class GreetingsManagerService : GreetingsManager.GreetingsManagerBase { public override Task<GreetingResponse> GenerateGreeting(GreetingRequest request, ServerCallContext context) { return Task.FromResult(new GreetingResponse { GreetingMessage = "Hello " + request.Name }); } } }
The content of the Program.cs
file inside the BasicGreeterClient
project should now look as follows:
using System; using System.Threading.Tasks; using Greeter; using Grpc.Net.Client; namespace BasicGrpcClient { class Program { static async Task Main() { // The port number(5001) must match the port of the gRPC server. using var channel = GrpcChannel.ForAddress("https:// localhost:5001"); var client = new GreetingsManager. GreetingsManagerClient(channel); var reply = await client.GenerateGreetingAsync( new GreetingRequest { Name = "BasicGrpcClient" }); Console.WriteLine("Greeting: " + reply.GreetingMessage); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } }
Now, you know how easy it is to regenerate relevant code after making changes to the Protobuf definition. At this point, you have two copies of the greeter.proto
file that are identical.
At this stage, you may be wondering whether having separate copies of this file would violate the don't repeat yourself (DRY) principle, which is a commonly accepted best practice when writing software. Will any problems occur if you update one of these files while forgetting to update the other? Isn't it possible to keep a single shared copy of the file that both the client and the server use?
Fortunately, you can share the same file between multiple applications in .NET. Let's have a look at how.