Search code examples
asp.net-coreprotocol-buffersgrpcprotobuf-csharp-portgrpc-dotnet

How to support C# dynamic types in an gRPC proto file


We have a POST action in our asp.net core application that accepts a dynamic object.

[HttpPost]
public Task<ActionResult> SubmitAsync(dynamic unitOfWork)

We'd like to transform this POST action to a gRPC server and we'd like to continue receiving dynamic objects in the gRPC service. What is the equivalent of C# dynamic definition in gRPC protobuf file definition? Or if that cannot be achieved what's the best way to receive a dynamic object?


Solution

  • You can pass messages with fields whose type was not known in advance. You can also pass messages with fields that are not typed, such as dynamic objects that can take any scalar values, and collections null values are allowed. To do so, import the proto file "google/protobuf/struct.proto" and declare the dynamic type as google.protobuf.Value.

    So, first add bellow line at the top of your proto file:

    import "google/protobuf/struct.proto";
    

    Here my sample message with two dynamic fields:

    message BranchResponse {
     google.protobuf.Value BranchId = 1;
     google.protobuf.Value BranchLevel = 2;
    }
    

    Note that: the generated type in C# is Value and belongs to the Google.Protobuf.WellKnownTypes namespace, which belongs itself to the Google.Protobuf assembly. This type inherits from the IMessage, IMessage, IEquatable, IDeepCloneable, and IBufferMessage interfaces that all belong to the Google.Protobuf assembly, except for IEquatable, which comes from the .NET System.Runtime assembly. To write and read dynamic values, we have a set of methods available that shown bellow: (these are write static functions)

    enter image description here

    We can fill BranchResponse model like this:

    var branch = new BranchResponse();
    branch.BranchId = Value.ForNumber(1);
    branch.BranchLevel = Value.ForStruct(new Struct
    {
     Fields = {
     ["LevelId"] = Value.ForNumber(1),
     ["LevelName"] = Value.ForString("Gold"),
     ["IsProfessional"] = Value.ForBool(true)}
    });
    

    The read Value type is straightforward. The Value type has a set of properties that exposes its value in the wanted type. (these are read static functions)

    enter image description here

    At the end, you need to read data from your response model like this:

    Here my c# classes that my response model is supposed to bind to them.

    public class BranchModel
    {
     public int BranchId { get; set; }
     public LevelModel Level { get; set; }
    }
    public class LevelModel
    {
     public int LevelId{ get; set; }
     public string LevelName{ get; set; }
     public bool IsProfessional { get; set; }
    }
    

    Finally:

    var branch = new BranchResponse(); // Received filled from a gRPC call
    // Read
    var branchModel = new BranchModel
    {
     BranchId = Convert.ToInt32(branch.BranchId.NumberValue),
     Level= new LevelModel
       {
          LevelId = Convert.ToInt32(branchModel.Level.StructValue.
          Fields["LevelId"].NumberValue),
          LevelName = branchModel.Level.StructValue.
          Fields["LevelName"].StringValue,
          IsProfessional = branchModel.Level.StructValue.
          Fields["IsProfessional"].BoolValue,
       }
    };