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.
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.
dotnet add package Porticle.Grpc.TypeMapperAfter 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 "$(MSBuildThisFileDirectory)..\tools\$(TargetFramework)\Porticle.Grpc.TypeMapper.dll" -- %(_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.
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
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;
}
}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;
}
}
}-
Mapping
repeated google.protobuf.StringValuetoList<Guid?>orList<string?>because grpc internally usesRepeatedField<string>instead ofRepeatedField<StringValue>. This may be a bug in protoc compiler, because it is also not possible to addnulltorepeated google.protobuf.StringValuebecause there is a not null check in the Add function inRepeatedField<T> -
This Tool actually don't works when protoc / Grpc.Tools is compiled with GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE