不支持指定的方法使用protobuf网络库调用grpc服务时出错



我的团队使用Google grpc通信进行微服务通信。我遇到了protobuf-net,它速度快,降低了代码的复杂性,而且没有需要定义.proto文件。我想尝试一下使用protobuf网络,看看我们是否能获得相当大的性能改进。然而,我犯了一个错误"不支持指定的方法";。我认为我无法正确标记实体。我可以使用@marc gravel帮助来理解这个问题。以下是我的网络代码的详细信息

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

现在来谈谈服务类及其实现,我有这样的

[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)
{
...
}
}

启动文件中的其余配置是正确的,就像添加一样

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

您的序列化代码有三个问题:

  1. 正如MarcGravell所指出的,Protobuf-net不知道如何序列化Json.net的JToken对象。

    由于JToken对象旨在表示自由格式的JSON,因此使用Protobuf-net序列化它们的最简单方法是序列化代表原始JSON值的代理string属性:

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

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

    注意,我正在将整个IDictionary<string, JToken> Properties对象序列化为单个JSON对象。

  2. 在序列化继承层次结构时,Protobuf-net要求每个基类TBase都被告知所有直接派生类TDerived的存在。这可以通过添加的属性来实现

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

    到基类。请注意,数字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. 在您的演示中,类Component<TMetadataType>有一个您正在序列化的get-only属性Model

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

    在修复了其他两个问题后,由于某种原因,此属性导致序列化程序引发以下异常:

    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])
    

    这可以通过从序列化中删除Model,或者添加一个私有的伪setter来解决,比如:

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

在此处完成修改后的类:

[ProtoContract]
public class Building : Component<BuildingMetadata>
{
[ProtoMember(1)]
public string Id { get; set; }
[ProtoMember(2)]
public string tag { get; set; }
}
[ProtoContract]
public class InnerComponent : Component<ComponentMetadata>
{
[ProtoMember(1)]
[JsonIgnore]
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]
[ProtoInclude(1001, typeof(BuildingMetadata))] 
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)]
string SerializedValue { get => Value?.ToString(Formatting.None); set => Value = (value == null ? null : JToken.Parse(value)); } // FIXED
[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]
public interface IComponent
{
ComponentMetadata GetMetadata();
IEnumerable<(string name, IComponent component)> ListComponents();
IEnumerable<(string name, JToken property)> ListProperties();
bool TryGetProperty(string name, out JToken property);
bool TryGetComponent(string name, out IComponent component);
}
[ProtoContract]
[ProtoInclude(1002, typeof(Building))] 
[ProtoInclude(1001, typeof(InnerComponent))] 
public class Component<TMetadataType> : ComponentBase, IComponent where TMetadataType : ComponentMetadata, new()
{
[ProtoMember(1)]
public TMetadataType Metadata { get; set; } = new TMetadataType();
[ProtoMember(2)]
public string Model { get => Metadata.Model; private set { } } // Private set required for serialization
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]
[ProtoInclude(1002, typeof(Component<BuildingMetadata>))] 
[ProtoInclude(1001, typeof(Component<ComponentMetadata>))] 
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)); }
public IDictionary<string, JToken> Properties { get; set; } = new Dictionary<string, JToken>();
[ProtoMember(2)]
public IDictionary<string, InnerComponent> Components { get; set; } = new Dictionary<string, InnerComponent>();
}

修复了这里的工作小提琴。

最新更新