I am trying to generate the .proto of this structure:
-- MODELS --
base model
[DataContract]
public abstract class Base
{
[ProtoMember(1)]
public string Id { get; set; }
[ProtoMember(2, DataFormat = DataFormat.WellKnown)]
public DateTime CreatedDate { get; private set; } = DateTime.UtcNow;
[ProtoMember(3, DataFormat = DataFormat.WellKnown)]
public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
}
Todo model
[ProtoContract]
public class Todo : Base
{
[ProtoMember(1)]
public string Title { get; set; }
[ProtoMember(2)]
public string Content { get; set; }
[ProtoMember(3)]
public string Category { get; set; }
}
Plus this line:
RuntimeTypeModel.Default[typeof(Base)].AddSubType(42, typeof(Todo));
-- CONTRACTS --
Base contract
[ServiceContract]
public interface IBaseService<T>
{
// CREATE
[OperationContract]
Task<RStatus> CreateOneAsync(T request,CallContext context = default);
// FIND
[OperationContract]
ValueTask<T> GetById(UniqueIdentification request,CallContext context = default);
}
Todo contract
[ServiceContract]
public interface ITodoService : IBaseService<Todo>
{
// FIND
[OperationContract]
ValueTask<Todo> GetOneByQueryAsync(Query query, CallContext context = default);
}
With this generic approach, I am trying to prevent repeating code.
-- Startup.cs --
...
endpoints.MapGrpcService<TodoService>();
endpoints.MapCodeFirstGrpcReflectionService();
...
So, when I run this :
var schema = generator.GetSchema<ITodoService>();
I get this output in the .proto file:
syntax = "proto3";
package Nnet.Contracts;
import "google/protobuf/timestamp.proto";
message Base {
string Id = 1;
.google.protobuf.Timestamp CreatedDate = 2;
.google.protobuf.Timestamp UpdatedDate = 3;
oneof subtype {
Todo Todo = 42;
}
}
message IEnumerable_Todo {
repeated Base items = 1;
}
message Query {
string Filter = 1;
}
message Todo {
string Title = 1;
string Content = 2;
string Category = 3;
}
service TodoService {
rpc GetOneByQuery (Query) returns (Base);
}
In the .proto file section service Todoservice, I am missing the other two functions from the Base contract. Also, the return type of the function rpc GetOneByQuery (Query) returns (Base); is wrong, it should be Todo.
Any suggestions?
No, that's correct; protobuf itself has no concept of inheritance - protobuf-net has to shim it in, which it does using encapsulation, hence the
Basewith aoneof subtypethat has aTodo. In your case, we expect that the thing passed will always actually resolve as aTodo, but the .proto schema language has no syntax to express that. The absolute best we could do here would be to include an extra comment in the generated .proto saying// return type will always be a Todoor similar.service inheritance and generic services are not currently well supported here; again, these are concepts that have no matching metaphor in .proto or gRPC in general, and protobuf-net would need to invent something suitable; I have not - to date - sat down and thought through any such scheme or the implications there-of. Fundamentally, the problem here is that the service contract and name are used to construct a route/url; when talking about a single service contract and method, that's fine - but when talking about service inheritance and generics, it gets a lot more complicated to uniquely identify what service you're talking about, and how that should map between a route and an implementation (and, indeed, the .proto syntax). I'm entirely open to thoughts here - it just hasn't been a critical-path requirement to date.