简体   繁体   English

使用 protobuf-net 库调用 grpc 服务时不支持指定的方法错误

[英]specified method is not supported error on calling grpc service using protobuf-net library

My team uses Google grpc communication for micro service communication.我的团队使用 Google grpc 通信进行微服务通信。 I came across protobuf-net that is fast, reduces code complexity and no.proto file to be defined.我遇到了 protobuf-net,它速度快,降低了代码复杂性,并且没有要定义的 proto 文件。 I wanted to give a try using protobuf-net to see if we gain considerable performance improvement.我想尝试使用 protobuf-net 看看我们是否获得了相当大的性能提升。 However, I am getting error "specified method is not supported".但是,我收到错误“不支持指定的方法”。 I think I am not able to mark the entity correctly.我认为我无法正确标记实体。 I can use @marc-gravel help to understand the problem.我可以使用@marc-gravel 帮助来理解问题。 Here are the details of my dotnet code这是我的 dotnet 代码的详细信息

[ProtoContract]
public class ProtoBufInput
{
    [ProtoMember(1)]
    public string Id { get; set; }

    [ProtoMember(2)]
    public Building BuildingObj { get; set; }

    [ProtoMember(3)]
    public byte[] Payload { get; set; }

    public ProtoBufInput(string id, Building buildingObj, byte[] payload)
    {
        BuildingObj = buildingObj;
        Id = id;
        Payload = payload;
    }
}

[ProtoContract]
public class ProtoBufResult
{
    [ProtoMember(1)]
    public int RandomNumber { get; set; }

    [ProtoMember(2)]
    public bool RandomBool { get; set; }

    [ProtoMember(3)]
    public IList<string> ErrorMessages { get; set; }

    [ProtoMember(5)]
    public Building BuildingObj { get; set; }

    [ProtoMember(6)]
    public string RandomString { get; set; }

    public ProtoBufResult()
    {
        RandomNumber = 0;
        RandomBool = false;
    }
}

[ProtoContract]
public class Building : Component<BuildingMetadata>
{
    [ProtoMember(1)]
    public string Id { get; set; }

    [ProtoMember(2)]
    public string tag { get; set; }
} 

[ProtoContract]
public class BuildingMetadata : ComponentMetadata
{
    [ProtoMember(1)]
    public BuildingType Type { get; set; }

    [ProtoMember(2)]
    public bool IsAttached { get; set; }

    public override object Clone()
    {
        var baseClone = base.Clone() as ComponentMetadata;
        return new BuildingMetadata()
        {
            Model = baseClone.Model,
            PropertyMetadata = baseClone.PropertyMetadata,
        };
    }
}

[ProtoContract]
public enum BuildingType
{
}

[ProtoContract]
public class ComponentMetadata : ICloneable
{
    [ProtoMember(1)]
    public string Model { get; set; }

    [ProtoMember(2)]
    public IDictionary<string, PropertyMetadata> PropertyMetadata { get; set; } = new Dictionary<string, PropertyMetadata>();

    public virtual object Clone()
    {
        return new ComponentMetadata()
        {
            Model = Model,
            PropertyMetadata = PropertyMetadata.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone() as PropertyMetadata),
        };
    }
}

[ProtoContract]
public class PropertyMetadata : ICloneable
{
    [ProtoMember(1)]
    [JsonProperty("Value")]
    public JToken Value { get; set; }

    [ProtoMember(2)]
    [JsonProperty("Version")]
    public int Version { get; set; }

    [ProtoMember(3)]
    [JsonProperty("backVersion")]
    public int BackVersion { get; set; }

    [ProtoMember(4)]
    [JsonProperty("backCode")]
    public int BackCode { get; set; }

    [ProtoMember(5)]
    [JsonProperty("Description")]
    public string Description { get; set; }

    [ProtoMember(6)]
    [JsonProperty("createTime")]
    public string CreateTime { get; set; }

    public object Clone()
    {
        return new PropertyMetadata()
        {
            CreateTime = CreateTime ?? DateTime.UtcNow.ToString("o"),
        };
    }
}

[ProtoContract]
[ProtoInclude(1, typeof(Component<ComponentMetadata>))]
public class Component<TMetadataType> : ComponentBase, IComponent where TMetadataType : ComponentMetadata, new()
{
    [ProtoMember(1)]
    public TMetadataType Metadata { get; set; } = new TMetadataType();

    public string Model => Metadata.Model;

    public IEnumerable<(string, IComponent)> ListComponents() => Components.Select(x => (x.Key, x.Value as IComponent));

    public IEnumerable<(string, JToken)> ListProperties() => Properties.Select(x => (x.Key, x.Value));

    public ComponentMetadata GetMetadata() => Metadata;

    public bool TryGetComponent(string name, out IComponent component)
    {
        component = null;
        if (!Components.TryGetValue(name, out var innerComponent))
        {
            return false;
        }

        component = innerComponent as IComponent;
        return true;
    }

    public bool TryGetProperty(string name, out JToken property) => Properties.TryGetValue(name, out property);
}

[ProtoContract]
public class ComponentBase
{
    [ProtoMember(1)]
    public IDictionary<string, JToken> Properties { get; set; } = new Dictionary<string, JToken>();

    [ProtoMember(2)]
    public IDictionary<string, InnerComponent> Components { get; set; } = new Dictionary<string, InnerComponent>();
}

[ProtoContract]
public class InnerComponent : Component<ComponentMetadata>
{
    [ProtoMember(1)]
    [JsonIgnore]
    public string tag { get; set; }
}

Now coming to the service class and its implementation, I have something like this现在来到服务 class 及其实现,我有这样的东西

[ServiceContract]
public interface IProtoBufService
{
    [OperationContract]
    public Task<ProtoBufResult> ProcessPb(ProtoBufInput input, CallContext context = default);
}

public class ProtoBufService : IProtoBufService
{
    public Task<ProtoBufResult> ProcessPb(ProtoBufInput protoBufInput, CallContext context)
    {
       ...
    }
}

Rest of the configuration in start up file is correct like adding启动文件中配置的 Rest 是正确的,如添加

serviceCollection.AddCodeFirstGrpc();
builder.MapGrpcService<Services.V2.ProtoBufService>();

You have three problems with your serialization code:您的序列化代码存在三个问题:

  1. As noted by Marc Gravell , Protobuf-net does not know how to serialize Json.NET's JToken objects.正如Marc Gravell指出的,Protobuf-net 不知道如何序列化 Json.NET 的JToken对象。

    Since JToken objects are intended to represent free-form JSON, the easiest way to serialize them with Protobuf-net is to serialize surrogate string properties instead that represent the raw JSON value:由于JToken对象旨在表示自由形式的 JSON,因此使用 Protobuf-net 序列化它们的最简单方法是序列化代理string属性,而不是表示原始 JSON 值:

     [ProtoContract] public class PropertyMetadata: ICloneable { [ProtoMember(1)] string SerializedValue { get => Value?.ToString(Formatting.None); set => Value = (value == null? null: JToken.Parse(value)); } // FIXED

    and

    public class ComponentBase { [ProtoMember(1)] string SerializedProperties { get => Properties == null? null: JsonConvert.SerializeObject(Properties); set => Properties = (value == null? null: JsonConvert.DeserializeObject<Dictionary<string, JToken>>(value)); }

    Note I am serializing the entire IDictionary<string, JToken> Properties object as a single JSON object.注意我将整个IDictionary<string, JToken> Properties object 序列化为单个 JSON object。

  2. When serializing an inheritance hierarchy, Protobuf-net requires that every base class TBase be informed of the existence of all immediate derived classes TDerived .在序列化 inheritance 层次结构时,Protobuf-net 要求每个基本 class TBase被告知所有直接派生类TDerived的存在。 This can be done via attributes by adding这可以通过添加属性来完成

    [ProtoContract] [ProtoInclude(N, typeof(TDerived))] public class TBase { }

    to the base class.到底座 class。 Note that the numbers N must be unique and not overlap with any ProtoMemberAttribute.Tag values so it is wise to start them from a large number such as 1000:请注意,数字N必须是唯一的,并且不能与任何ProtoMemberAttribute.Tag值重叠,因此明智的做法是从大数字(例如 1000)开始:

     [ProtoContract] [ProtoInclude(1001, typeof(BuildingMetadata))] public class ComponentMetadata: ICloneable
     [ProtoContract] [ProtoInclude(1002, typeof(Building))] [ProtoInclude(1001, typeof(InnerComponent))] public class Component<TMetadataType>: ComponentBase, IComponent where TMetadataType: ComponentMetadata, new()
     [ProtoContract] [ProtoInclude(1002, typeof(Component<BuildingMetadata>))] [ProtoInclude(1001, typeof(Component<ComponentMetadata>))] public class ComponentBase
  3. In your demo fiddle , your class Component<TMetadataType> has a get-only property Model which you are serializing:在您的演示小提琴中,您的 class Component<TMetadataType>有一个 get-only 属性Model您正在序列化:

     [ProtoMember(2)] public string Model => Metadata.Model;

    With the other two problems fixed, for some reason this property causes the serializer to throw the following exception:修复其他两个问题后,由于某种原因,此属性会导致序列化程序抛出以下异常:

     System.InvalidOperationException: Unable to wrap ComponentBase/ComponentBase: Unable to bind serializer: It was not possible to prepare a serializer for: ComponentBase (ProtoBuf.Internal.Serializers.InheritanceTypeSerializer`2[ComponentBase,ComponentBase])

    This can be resolved by either removing Model from serialization, or adding a private dummy setter like so:这可以通过从序列化中删除Model或添加一个私有虚拟设置器来解决,如下所示:

     [ProtoMember(2)] public string Model { get => Metadata.Model; private set { } } // Private set required for serialization

Fixed working fiddle here .固定工作小提琴在这里

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

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