简体   繁体   English

如何在 protobuf-net 代码中首先使用(打包)Google.Protobuf.WellknownTypes.Any gRPC

[英]How to use (pack) Google.Protobuf.WellknownTypes.Any in protobuf-net code first gRPC

I am creating an gRPC service and we decided to choose the code first approach with protobuf-net.我正在创建一个 gRPC 服务,我们决定使用 protobuf-net 选择代码优先方法。 Now I am running into a scenario where we have a couple of classes that need to be wrapped.现在我遇到了一个场景,我们有几个需要包装的类。 We do not want to define KnownTypes in the MyMessage class (just a sample name to illustrate the problem).我们不想在 MyMessage class 中定义 KnownTypes(只是一个示例名称来说明问题)。 So I am trying to use the Any type which currently gives me some struggle with packing.所以我正在尝试使用目前让我在打包方面遇到一些困难的 Any 类型。

The sample code has the MyMessage which defines some header values and has to possiblity to deliver any type as payload.示例代码具有 MyMessage,它定义了一些 header 值,并且必须可以将任何类型作为有效负载传递。

[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; }  
}

Somewhere in the code I construct my message with a payload...在代码中的某处,我用有效负载构造了我的消息......

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

Which doesn't work because Payload1 and Payload2 need to implement Google.Protobuf.IMessage .这不起作用,因为Payload1Payload2需要实现Google.Protobuf.IMessage Since I can't figure out how and do not find a lot information how to do it at all I am wondering if I am going a wrong path.由于我无法弄清楚如何并且根本找不到很多信息如何做到这一点,我想知道我是否走错了路。

  • How is it intedend to use Any in protobuf-net?如何在 protobuf-net 中使用 Any?
  • Is there a simple (yet compatible) way to pack a C# code first class into Google.Protobuf.WellknownTypes.Any?是否有一种简单(但兼容)的方法将 C# 代码首先 class 打包到 Google.Protobuf.WellknownTypes.Any 中?
  • Do I really need to implement Google.Protobuf.IMessage?我真的需要实现 Google.Protobuf.IMessage 吗?

Firstly, since you say "where we have a couple of classes that need to be wrapped" (emphasis mine), I wonder if what you actually want here is oneof rather than Any .首先,既然您说“我们有几个需要包装的类”(强调我的),我想知道您在这里真正想要的是oneof而不是Any protobuf-net has support for the oneof concept, although it isn't obvious from a code-first perspective. protobuf-net 支持oneof概念,尽管从代码优先的角度来看并不明显。 But imagine we had (in a contract-first sense):但是想象一下我们有(在合同优先的意义上):

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

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

This would be implemented (via the protobuf-net schema tools) as:这将(通过 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);

optionally with an enum to help:可选用枚举来帮助:

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

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

This approach may be easier and preferable to Any .这种方法可能比Any更容易且更可取


On Any :Any

Short version: protobuf-net has not, to date, had any particular request to implement Any .简短版本:迄今为止,protobuf-net 还没有任何特定的要求来实现Any It probably isn't a huge amount of work - simply: it hasn't yet happened .这可能不是大量的工作——简单地说:它还没有发生 It looks like you're referencing both protobuf-net and the Google libs here, and using the Google implementation of Any .看起来您在这里同时引用protobuf-netGoogle 库,并使用了 Google 的Any实现。 That's fine, but protobuf-net isn't going to use it at all - it doesn't know about the Google APIs in this context, so: implementing IMessage won't actually help you.这很好,但是 protobuf-net 根本不会使用它——它不知道在这种情况下的 Google API,所以:实现IMessage实际上不会帮助你。

I'd be more than happy to look at Any with you, from the protobuf-net side.我很乐意从 protobuf-net 方面与您一起研究Any Ultimately, time/availability is always the limiting factor, so I prioritise features that are seeing demand.最终,时间/可用性始终是限制因素,因此我优先考虑看到需求的功能。 I think you may actually be the first person asking me about Any in protobuf-net.我想你实际上可能是第一个问我关于 protobuf-net 中的Any的人。

I wrote this hacky Any implementation.我写了这个 hacky 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' can be used as a field or property of another record with following gimmick: '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;
}

Serialization序列化

    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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM