Skip to content

Machibuse/Porticle.Grpc.TypeMapper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Porticle.Grpc.TypeMapper

A Roslyn-based post-processor for protoc-generated files that adds automatic mappings for Guid, Guid?, string? or nullable enums. By simply adding this package and adding comments to your .proto file.

Build State

Build and Release

Nuget

NuGet Latest Version NuGet Downloads

Overview

This library adds automatic conversion for:

  • Protobuf string to C# Guid
  • Protobuf google.Protobuf.StringValue to C# Guid?
  • Protobuf google.Protobuf.StringValue to C# string?
  • Protobuf optional enum to C# nullable enum

This Library adds a Roslyn Postprocessing zu the c# files generated by the protoc compiler. Enabling seamless integration of Guid, Guid?, string? And nullable Enums in your gRPC services without manual conversion. Code.

Installation

Install the package via NuGet:

dotnet add package Porticle.Grpc.TypeMapper

After installing the Package, this Post build step ist dynamically added to your build.


<Project>
  <Target Name="RunProtoPostProcessing" AfterTargets="Protobuf_Compile">
    <ItemGroup>
      <_FilesToPostProcess Include="$(MSBuildProjectDirectory)\%(Protobuf_Compile.OutputDir)\%(Protobuf_Compile.Filename)"/>
    </ItemGroup>
    <Message Text="Proto Postprocessing" Importance="high"/>
    <Exec Command="dotnet &quot;$(MSBuildThisFileDirectory)..\tools\$(TargetFramework)\Porticle.Grpc.TypeMapper.dll&quot; -- %(_FilesToPostProcess.Identity)" Condition="'@(_FilesToPostProcess)' != ''"/>
  </Target>
</Project>

Don't wonder ist you cant se it in your csproj file. It is dynamically added when your build is processed.

Usage

There are three things you can do in your .proto files:

  • Add // [GrpcGuid] as comment to a string field - Converts the corresponding c# string property to Guid
  • Add // [GrpcGuid] as comment to a StringValue field - Converts the corresponding c# string property to Guid?
  • Add // [NullableString] as comment to a StringValue field - Converts the corresponding c# string property to string?
  • Add // [NullableEnum] as comment to a optional enum field - Converts the corresponding optional proto enum to a C# nullable Enum

First an Example of a default .proto file

Without TypeMapper

syntax = "proto3";

import "google/protobuf/wrappers.proto";

enum TestEnum {
  FOO = 0;
  BAR = 1;
}

message User {
  // Guid of the user object   
  string id = 1;

  // Optional parent UserId
  google.protobuf.StringValue optional_parent_user_id = 2;

  // Optional description
  google.protobuf.StringValue description = 3;

  // List of roles 
  repeated string role_ids = 4;

  // Simple Enum  
  optional TestEnum foo_bar = 5;
}

Will result in protoc generated code like this, everything is a string or a simple enum

/// <summary>Guid of the user object</summary>
public string Id {
  get { return id_; }
  set { id_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); }
}

/// <summary>Optional Guid of the parent UserId</summary>
public string OptionalParentUserId {
  get { return optionalParentUserId_; }
  set { optionalParentUserId_ = value; }
}

/// <summary>Optional description string</summary>
public string Description {
  get { return description_; }
  set { description_ = value; }
}

/// <summary>List of roles</summary>
public pbc::RepeatedField<string> RoleIds {
  get { return roleIds_; }
}

public global::Porticle.Grpc.UnitTests.TestEnum FooBar {
  get { 
	if ((_hasBits0 & 1) != 0) 
	  return fooBar_; 
	else 
	  return FooBarDefaultValue; 
  }
  set {
	_hasBits0 |= 1;
	fooBar_ = value;
  }
}

Now a sample with TypeMapper enabled

syntax = "proto3";

import "google/protobuf/wrappers.proto";

enum TestEnum {
  FOO = 0;
  BAR = 1;
}

message User {
  // [GrpcGuid] Guid of the user object   
  string id = 1;

  // [GrpcGuid] Optional Guid of the parent UserId
  google.protobuf.StringValue optional_parent_user_id = 2;

  // [NullableString] Optional description string
  google.protobuf.StringValue description = 3;

  // [GrpcGuid] List of roles 
  repeated string role_ids = 4;

  // [NullableEnum] Simple Enum  
  optional TestEnum foo_bar = 5;
}

Will result in generated code like this, using string? Guid and Guid? and a nullable Enum

/// <summary>[GrpcGuid] Guid of the user object</summary>
public global::System.Guid Id {
  get 
  {
      // Parse the internal string as Guid and return it 
      return global::System.Guid.Parse(id_);
  }
  set 
  {
      // Set the internal string fron the given guid
      id_ = (value).ToString("D");
  }
}

/// <summary>[GrpcGuid] Optional Guid of the parent UserId</summary>
public global::System.Guid? OptionalParentUserId {
  get { 
      // return null wehn corresponding string is null
      if(optionalParentUserId_==null) return default;
      // return a Guid instead of the string
      return global::System.Guid.Parse(optionalParentUserId_); 
  }
  set 
  {
      // sets the internal string from the given guid
      optionalParentUserId_ = (value)?.ToString("D");
  }
}


// enable nullable for this property and return string? instead of string 
#nullable enable
/// <summary>[NullableString] Optional description string</summary>
public string? Description {
  get { return description_; }
  set { description_ = value; }
}
#nullable disable

/// <summary>[GrpcGuid] List of roles</summary>
public IList<Guid> RoleIds {
  get 
  {
      // returns a wrapper that converts a list of strings to a list of guids 
      return new RepeatedFieldGuidWrapper(roleIds_);
  }
}

public global::Porticle.Grpc.UnitTests.TestEnum? FooBar {
  get { 
	if ((_hasBits0 & 1) != 0) 
	{ 
	  return fooBar_; 
	} 
	else 
	{ 
      // return null instead of default value when Has-Flag is false  
	  return null; 
    } 
  }
  set { 
	if(value==null) {
        // Set hasflag to false when null is assigend   
		_hasBits0 &=~1;
		fooBar_ = FooBarDefaultValue;
	}
	else
	{
		_hasBits0 |= 1;
		fooBar_ = value.Value;
	} 
  }
}

What is currently not Possible?

  • Mapping repeated google.protobuf.StringValue to List<Guid?> or List<string?> because grpc internally uses RepeatedField<string> instead of RepeatedField<StringValue>. This may be a bug in protoc compiler, because it is also not possible to add null to repeated google.protobuf.StringValue because there is a not null check in the Add function in RepeatedField<T>

  • This Tool actually don't works when protoc / Grpc.Tools is compiled with GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE

About

User nullable String and Guid properties with GRPC in c#

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages