Search code examples
c#grpcprotobuf-net

How to use IAsyncEnumerable within a message in protobuf-net.grpc?


So, recently I learned about how to do streaming with protobuf-net.grpc with the help of IAsyncEnumerable<T>. This all works fine and dandy, but I'm facing a bit of an issue right now.

For some of my calls, I'd like to call with both some metadata as well as the stream as parameter.

For example:

[OperationContract]
Task<bool> UploadPicture(ProfilePictureQuery query);

With the following signature:

[ProtoContract]
public class ProfilePictureQuery
{
    [ProtoMember(1)]
    public IAsyncEnumerable<byte[]> RawDataStream { get; set; }

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

However, when trying to call this, I'm getting an exception like 'Status(StatusCode=Unimplemented, Detail="Method is unimplemented."). I know that status responses are a bit cryptic, so I figured out that this is actually just an issue with the parameter.

How do I do this then?

I also tried to do it with the following signature:

[OperationContract]
Task<bool> UploadPicture(IAsyncEnumerable<byte[]> rawDataStream, string fileExtension);

With the same outcome, as I'm apparently only allowed to provide a single parameter (True to the grpc definition of one message parameter and one response output).

Sooo, how do I do this then?


Solution

  • There are two separate concepts here:

    • gRPC allows a stream of messages via IAsyncEnumerable<T> (instead of a Task<T> for a single message)
    • however, each individual message must be complete and self contained; the marshaller (per-message serializer) is synchronous only

    So: you can use IAsyncEnunerable<T> as the parameter or return of a service method, but not as a field on a message.

    If you need to provide a stream and additional metadata a few options exist:

    • use http headers (via CallContext as a second parameter)
    • have two service calls - one that initiates things and gets the metadata, one that returns the stream
    • have a stream with some optional fields in the message, and only populate the optional fields in the first or last message