My team uses Google grpc communication for micro service communication. I came across protobuf-net that is fast, reduces code complexity and no .proto file to be defined. I wanted to give a try using protobuf-net to see if we gain considerable performance improvement. 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. Here are the details of my dotnet code
[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
[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
serviceCollection.AddCodeFirstGrpc();
builder.MapGrpcService<Services.V2.ProtoBufService>();
You have three problems with your serialization code:
As noted by Marc Gravell, Protobuf-net does not know how to serialize Json.NET's
JTokenobjects.Since
JTokenobjects are intended to represent free-form JSON, the easiest way to serialize them with Protobuf-net is to serialize surrogatestringproperties instead that represent the raw JSON value:and
Note I am serializing the entire
IDictionary<string, JToken> Propertiesobject as a single JSON object.When serializing an inheritance hierarchy, Protobuf-net requires that every base class
TBasebe informed of the existence of all immediate derived classesTDerived. This can be done via attributes by addingto the base class. Note that the numbers
Nmust be unique and not overlap with anyProtoMemberAttribute.Tagvalues so it is wise to start them from a large number such as 1000:In your demo fiddle, your class
Component<TMetadataType>has a get-only propertyModelwhich you are serializing:With the other two problems fixed, for some reason this property causes the serializer to throw the following exception:
This can be resolved by either removing
Modelfrom serialization, or adding a private dummy setter like so:Complete modified classes here:
Fixed working fiddle here.