Use Visual Studio to generate .cs from .proto and steamline your development process

Well, protobuf. Right? If you haven’t heard of it, please visit – https://developers.google.com/protocol-buffers/ and read it. It is pretty cool in terms network communication. Reduces size of your payload a lot.

Now, if you are like me trying to use .proto files with Visual Studio, you might find it painful, to re-generate .cs files after your .proto files are changed. And also make sure you could also right code logic around those .cs classes generated, without any hassle. I kind, came up with a solution to this for my cases. Sharing here, in case anyone else finds it useful.

The Solution

Lets create our solution first. My solution name is ProtobufDemo. I will add few projects (will describe later), and it looks like this –

This is a very simple solution –

  • src contains all the source code
    • ProtobufDemo.Message we will have .proto definition files here. I am a bit lazy and I really don’t going into command line and run a command each time I am modifying something in my definition file. I will use this project to auto generate the cs files from proto file.
    • ProtobufDemo.Message.Generated is the project we will have our generated POCO classes and also any additional logic that we might want to have for the message. For example, validation and adding some on demand fields, etc.

The Example Domain (.proto)

Our domain is a plain and simple blog entry. With 3 fields –

  • Title
  • Content
  • Author

So, it looks like this –

//Blog.proto

syntax = "proto3";
option csharp_namespace = "ProtobufDemo.Message"; // the generated namespace for cs classes

message Blog {
	string title = 1;
	string content = 2;
	string author = 3;
}

The Generated Domain (.cs)

Before we include the tools to generate the .cs classes, we need to fix few things. I have listed them bellow –

  1. I am going to use official grpc.tools to generate the .cs files. More about the grpc tools can be found here https://grpc.io/docs/quickstart/csharp/
  2. By default grpc.tools generates the files inside obj folder in the same project. This causes sometimes troubles for MSbuild system as something the build remnants stays there and causes double reference error.
  3. I am going to use the second project (ProtobufDemo.Message.Generated) to include those generated .cs files. This keeps both the projects and build system clean.

Add Required Tools

Lets add required nuget packages to our project, that will generate the .cs files from .proto files.

Go to project ProfibufDemo.Messages

Add packages –

<package id="Google.Protobuf" version="3.7.0" targetFramework="net47" />
  <package id="Grpc" version="1.20.1" targetFramework="net47" />
  <package id="Grpc.Core" version="1.20.1" targetFramework="net47" />
  <package id="Grpc.Core.Api" version="1.20.1" targetFramework="net47" />
  <package id="Grpc.Tools" version="1.20.1" targetFramework="net47" developmentDependency="true" />
  <package id="protobuf-net" version="2.4.0" targetFramework="net47" />
  <package id="System.Interactive.Async" version="3.2.0" targetFramework="net47" />

That will make sure every time we build project, it will convert any .proto file in this project to .cs .

Now, we need to control how that .cs file is being generated. As I mentioned earlier the default location is obj folder, which is not quite I want.

First, make sure the Build Action for your file is Protobuf. This option only appears after the nuget packages are installed.

You might need to reload the solution to have it enabled after installing the nuget package.

Modify Project file

Unfortunately, there is no shortcut for this. You have to modify the project file for ProtobufDemo.Message project to make sure the files are generated into correct locations. Since I am using the other project (ProfotbufDemo.Message.Generated) for including the .cs file, I will use the location for that project. Please use location that best suites your need.

<?xml version="1.0" encoding="utf-8"?>

 ....
  <ItemGroup>
    <None Include="packages.config" />
    <Protobuf Include="Protos\Blog.proto" CompilesOutput="False" GrpcServices="None" OutputDir="..\ProtobufDemo.Message.Generated\Generated" />
.....

That is right, just the item for the .proto file in this case –

<Protobuf Include="Protos\Blog.proto">

With –

<Protobuf Include="Protos\Blog.proto" CompilesOutput="False" GrpcServices="None" OutputDir="..\ProtobufDemo.Message.Generated\Generated" />

The Protobuf tag supports few more attributes. You can check them all out at grpc tools. https://grpc.io/docs/quickstart/csharp/

This practically says to generate the file to that location. Unfortunately, there is no shortcut to this, but luckily you only have to it once per file.

Now, build and you will see files generated in proper location.

Enable Show All Files from solution explorer, you will be able to see the generated folder/files. Lets include the file into the project –

That is it. Now every time you modify your .proto file, you will have your .cs auto generated and updated. No more extra works or commands to run.

Adding Custom Property to Generated POCO

Well, that solves half the problem. The other half is what if I Want to add some custom logic for the class or some custom validation or some custom read-only property to keep a clean implementation.

You are lucky, cause the POCO classes generated by grpc tools are partial. All you have to do is match the namepace and classname and you can have a very simple interface implementation and logic to inject with. I will not go into details, but a typical web framework with command handlers and controllers look like this –

The proto

syntax = "proto3";
option csharp_namespace = "ProtobufDemo.Message"; //match it in partial class

message Blog {
	string title = 1;
	string content = 2;
	string author = 3;
}

The Interface

namespace ProtobufDemo.Message
{
	public interface IDomain
	{
		bool HasTitle();
	}
}

The Custom Class



namespace ProtobufDemo.Message
{
	public partial class Blog : IDomain
	{
		public bool HasTitle()
		{
			return string.IsNullOrWhiteSpace(Title); //perfect use of shared property in partial class
		}
	}
}

The structure might look like this-

I hope this helps to keep things simple.

Advertisements

I would like to say something ...

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.