Как использовать (упаковать) Google.Protobuf.WellknownTypes.Any в коде protobuf-net сначала gRPC

Я создаю службу gRPC, и мы решили выбрать сначала код с помощью protobuf-net. Теперь я столкнулся со сценарием, в котором у нас есть пара классов, которые нужно обернуть. Мы не хотим определять KnownTypes в классе MyMessage (просто пример имени, чтобы проиллюстрировать проблему). Поэтому я пытаюсь использовать тип Any, который в настоящее время вызывает у меня проблемы с упаковкой.

В образце кода есть MyMessage, который определяет некоторые значения заголовков и может доставлять любой тип в качестве полезной нагрузки.

      [ProtoContract]
public class MyMessage 
{
  [ProtoMember(1)] public int HeaderValue1 { get; set; }
  [ProtoMember(2)] public string HeaderValue2 { get; set; }
  [ProtoMember(3)] public Google.Protobuf.WellknownTypes.Any Payload { get; set; }
}

[ProtoContract]
public class Payload1 
{
  [ProtoMember(1)] public bool Data1   { get; set; }
  [ProtoMember(2)] public string Data2 { get; set; }
}

[ProtoContract]
public class Payload2 
{
  [ProtoMember(1)] public string Data1 { get; set; }
  [ProtoMember(2)] public string Data2 { get; set; }  
}

Где-то в коде я создаю свое сообщение с полезной нагрузкой ...

        Payload2 payload = new Payload2 {
    Data1 = "abc",
    Data2 = "def"
  };
  
  MyMessage msg = new MyMessage 
  {
    HeaderValue1 = 123,
    HeaderValue2 = "iAmHeaderValue2",
    Payload = Google.Protobuf.WellknownTypes.Any.Pack(payload)
  };

Что не работает, потому что Payload1 а также Payload2 необходимо реализовать Google.Protobuf.IMessage. Поскольку я не могу понять, как это сделать, и не могу найти много информации, как это сделать, мне интересно, не ошибаюсь ли я.

  • Как предполагается использовать Any в protobuf-net?
  • Есть ли простой (но совместимый) способ упаковать первый класс кода C # в Google.Protobuf.WellknownTypes.Any?
  • Мне действительно нужно внедрять Google.Protobuf.IMessage?

2 ответа

Я написал эту хакерскую реализацию Any.

      [ProtoContract(Name = "type.googleapis.com/google.protobuf.Any")]
public class Any
{
    /// <summary>Pack <paramref name="value"/></summary>
    public static Any Pack(object? value)
    {
        // Handle null
        if (value == null) return new Any { TypeUrl = null!, Value = Array.Empty<byte>() };
        // Get type
        System.Type type = value.GetType();
        // Write here
        MemoryStream ms = new MemoryStream();
        // Serialize
        RuntimeTypeModel.Default.Serialize(ms, value);
        // Create any
        Any any = new Any
        {
            TypeUrl = $"{type.Assembly.GetName().Name}/{type.FullName}",
            Value = ms.ToArray()
        };
        // Return
        return any;
    }

    /// <summary>Unpack any record</summary>
    public object? Unpack()
    {
        // Handle null
        if (TypeUrl == null || Value == null || Value.Length == 0) return null;
        // Find '/'
        int slashIx = TypeUrl.IndexOf('/');
        // Convert to C# type name
        string typename = slashIx >= 0 ? $"{TypeUrl.Substring(slashIx + 1)}, {TypeUrl.Substring(0, slashIx)}" : TypeUrl;
        // Get type (Note security issue here!)
        System.Type type = System.Type.GetType(typename, true)!;
        // Deserialize
        object value = RuntimeTypeModel.Default.Deserialize(type, Value.AsMemory());
        // Return
        return value;
    }

    /// <summary>Test type</summary>
    public bool Is(System.Type type) => $"{type.Assembly.GetName().Name}/{type.FullName}" == TypeUrl;
      
    /// <summary>Type url (using C# type names)</summary>
    [ProtoMember(1)]
    public string TypeUrl = null!;
    /// <summary>Data serialization</summary>
    [ProtoMember(2)]
    public byte[] Value = null!;

    /// <summary></summary>
    public static implicit operator Container(Any value) => new Container(value.Unpack()! );
    /// <summary></summary>
    public static implicit operator Any(Container value) => Any.Pack(value.Value);

    /// <summary></summary>
    public struct Container
    {
        /// <summary></summary>
        public object? Value;
        /// <summary></summary>
        public Container()
        {
            this.Value = null;
        }

        /// <summary></summary>
        public Container(object? value)
        {
            this.Value = value;
        }
    }
}

«System.Object» можно использовать как поле или свойство другой записи со следующим трюком:

      [DataContract]
public class Dummy
{
    /// <summary></summary>
    [DataMember(Order = 1, Name = nameof(Value))]
    public Any.Container Any { get => new Any.Container(Value); set => Value = value.Value; }
    /// <summary>Object</summary>
    public object? Value;
}

Сериализация

          RuntimeTypeModel.Default.Add(typeof(Any.Container), false).SetSurrogate(typeof(Any));
    var ms = new MemoryStream();
    RuntimeTypeModel.Default.Serialize(ms, new Dummy { Value = "Hello world" });
    Dummy dummy = RuntimeTypeModel.Default.Deserialize(typeof(Dummy), ms.ToArray().AsMemory()) as Dummy;

Во- первых, поскольку вы говорите «где у нас есть пара классов , которые нужно обернуть» (выделено мной), мне интересно, действительно ли вы хотите здесь, а не . protobuf-net поддерживает концепция, хотя она и не очевидна с точки зрения кода. Но представьте, что у нас было (в смысле контракта):

      syntax = "proto3";
message SomeType {
    oneof Content {
       Foo foo = 1;
       Bar bar = 2;
       Blap blap = 3;
    }
}

message Foo {}
message Bar {}
message Blap {}

Это будет реализовано (с помощью инструментов схемы protobuf-net) как:

      private global::ProtoBuf.DiscriminatedUnionObject __pbn__Content;

[global::ProtoBuf.ProtoMember(1, Name = @"foo")]
public Foo Foo
{
    get => __pbn__Content.Is(1) ? ((Foo)__pbn__Content.Object) : default;
    set => __pbn__Content = new global::ProtoBuf.DiscriminatedUnionObject(1, value);
}
public bool ShouldSerializeFoo() => __pbn__Content.Is(1);
public void ResetFoo() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__Content, 1);

[global::ProtoBuf.ProtoMember(2, Name = @"bar")]
public Bar Bar
{
    get => __pbn__Content.Is(2) ? ((Bar)__pbn__Content.Object) : default;
    set => __pbn__Content = new global::ProtoBuf.DiscriminatedUnionObject(2, value);
}
public bool ShouldSerializeBar() => __pbn__Content.Is(2);
public void ResetBar() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__Content, 2);

[global::ProtoBuf.ProtoMember(3, Name = @"blap")]
public Blap Blap
{
    get => __pbn__Content.Is(3) ? ((Blap)__pbn__Content.Object) : default;
    set => __pbn__Content = new global::ProtoBuf.DiscriminatedUnionObject(3, value);
}
public bool ShouldSerializeBlap() => __pbn__Content.Is(3);
public void ResetBlap() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__Content, 3);

необязательно с перечислением, чтобы помочь:

      public ContentOneofCase ContentCase => (ContentOneofCase)__pbn__Content.Discriminator;

public enum ContentOneofCase
{
    None = 0,
    Foo = 1,
    Bar = 2,
    Blap = 3,
}

Этот подход может быть проще и предпочтительнее , чем .


На :

Краткая версия: на сегодняшний день у protobuf-net не было конкретного запроса на реализацию . Вероятно, это не такой уж большой объем работы — просто: этого еще не было . Похоже, вы ссылаетесь здесь как на protobuf-net , так и на библиотеки Google, и используете реализацию Google . Это хорошо, но protobuf-net вообще не собирается его использовать — он не знает об API Google в этом контексте, поэтому: на самом деле вам не поможет.

Я был бы более чем счастлив посмотреть на это вместе с вами со стороны protobuf-net. В конечном счете, время/доступность всегда являются ограничивающим фактором, поэтому я отдаю предпочтение функциям, которые пользуются спросом. Я думаю, вы можете быть первым, кто спрашивает меня о в protobuf-net.

Другие вопросы по тегам