Grpc web service (NET Core) with Oxygene

Hello,
I’m trying to test a NetCore Grpc web service with Oxygene and Water but Water doesn’t recognize the proto file.
What did I miss :slight_smile:

First i hear of grpc and “proto” files, so if those are files that Water or our build chain would have to do something special with, what you’re missing is probably that we don’t have support for this ;).

Do you have test-case project and/or any links so I could have a look at what is needed here?

thanx,
marc

Great!

What exactly do you want?

I have a working source test in c# and the one with Water de base not working.

My goal is to use Oxygene for an HTTP/3 server with Google Protobuf protocol ( https://grpc.io )

Norbert

1 Like

Getting both of this projects would be a great start.

Oops, sorry for the delay.
I quickly rewrote a server and a console client in Visual Studio 2022.
7z file attached
Thanks in advance for taking the time to look at it.

Sincerely,
Norbert
Grpc.7z (101.9 KB)

To start off, I’ve added .proto to list of known text file types, for Fire/Water.

That said, your project seems to build and run fine, and serves at http://localhost:5000. What errors/problems should I be looking for?

the class proto3 are not generated (Generated by the protocol buffer compiler cf: project VS2022) and therefore not accessible to work with Water :slight_smile:

Souce VS auto generated

type or paste code here// <auto-generated>
//     Generated by the protocol buffer compiler.  DO NOT EDIT!
//     source: Protos/greet.proto
// </auto-generated>
#pragma warning disable 0414, 1591, 8981
#region Designer generated code

using grpc = global::Grpc.Core;

namespace GrpcService1 {
  /// <summary>
  /// The greeting service definition.
  /// </summary>
  public static partial class Greeter
  {
    static readonly string __ServiceName = "greet.Greeter";

    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static void __Helper_SerializeMessage(global::Google.Protobuf.IMessage message, grpc::SerializationContext context)
    {
      #if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION
      if (message is global::Google.Protobuf.IBufferMessage)
      {
        context.SetPayloadLength(message.CalculateSize());
        global::Google.Protobuf.MessageExtensions.WriteTo(message, context.GetBufferWriter());
        context.Complete();
        return;
      }
      #endif
      context.Complete(global::Google.Protobuf.MessageExtensions.ToByteArray(message));
    }

    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static class __Helper_MessageCache<T>
    {
      public static readonly bool IsBufferMessage = global::System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(global::Google.Protobuf.IBufferMessage)).IsAssignableFrom(typeof(T));
    }

    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static T __Helper_DeserializeMessage<T>(grpc::DeserializationContext context, global::Google.Protobuf.MessageParser<T> parser) where T : global::Google.Protobuf.IMessage<T>
    {
      #if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION
      if (__Helper_MessageCache<T>.IsBufferMessage)
      {
        return parser.ParseFrom(context.PayloadAsReadOnlySequence());
      }
      #endif
      return parser.ParseFrom(context.PayloadAsNewBuffer());
    }

    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static readonly grpc::Marshaller<global::GrpcService1.HelloRequest> __Marshaller_greet_HelloRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::GrpcService1.HelloRequest.Parser));
    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static readonly grpc::Marshaller<global::GrpcService1.HelloReply> __Marshaller_greet_HelloReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::GrpcService1.HelloReply.Parser));
    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static readonly grpc::Marshaller<global::GrpcService1.Doc_Id> __Marshaller_greet_Doc_Id = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::GrpcService1.Doc_Id.Parser));
    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static readonly grpc::Marshaller<global::GrpcService1.Doc_result> __Marshaller_greet_Doc_result = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::GrpcService1.Doc_result.Parser));

    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static readonly grpc::Method<global::GrpcService1.HelloRequest, global::GrpcService1.HelloReply> __Method_SayHello = new grpc::Method<global::GrpcService1.HelloRequest, global::GrpcService1.HelloReply>(
        grpc::MethodType.Unary,
        __ServiceName,
        "SayHello",
        __Marshaller_greet_HelloRequest,
        __Marshaller_greet_HelloReply);

    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    static readonly grpc::Method<global::GrpcService1.Doc_Id, global::GrpcService1.Doc_result> __Method_GetDoc = new grpc::Method<global::GrpcService1.Doc_Id, global::GrpcService1.Doc_result>(
        grpc::MethodType.Unary,
        __ServiceName,
        "GetDoc",
        __Marshaller_greet_Doc_Id,
        __Marshaller_greet_Doc_result);

    /// <summary>Service descriptor</summary>
    public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
    {
      get { return global::GrpcService1.GreetReflection.Descriptor.Services[0]; }
    }

    /// <summary>Base class for server-side implementations of Greeter</summary>
    [grpc::BindServiceMethod(typeof(Greeter), "BindService")]
    public abstract partial class GreeterBase
    {
      /// <summary>
      /// Sends a greeting
      /// </summary>
      /// <param name="request">The request received from the client.</param>
      /// <param name="context">The context of the server-side call handler being invoked.</param>
      /// <returns>The response to send back to the client (wrapped by a task).</returns>
      [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
      public virtual global::System.Threading.Tasks.Task<global::GrpcService1.HelloReply> SayHello(global::GrpcService1.HelloRequest request, grpc::ServerCallContext context)
      {
        throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
      }

      [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
      public virtual global::System.Threading.Tasks.Task<global::GrpcService1.Doc_result> GetDoc(global::GrpcService1.Doc_Id request, grpc::ServerCallContext context)
      {
        throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
      }

    }

    /// <summary>Creates service definition that can be registered with a server</summary>
    /// <param name="serviceImpl">An object implementing the server-side handling logic.</param>
    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    public static grpc::ServerServiceDefinition BindService(GreeterBase serviceImpl)
    {
      return grpc::ServerServiceDefinition.CreateBuilder()
          .AddMethod(__Method_SayHello, serviceImpl.SayHello)
          .AddMethod(__Method_GetDoc, serviceImpl.GetDoc).Build();
    }

    /// <summary>Register service method with a service binder with or without implementation. Useful when customizing the service binding logic.
    /// Note: this method is part of an experimental API that can change or be removed without any prior notice.</summary>
    /// <param name="serviceBinder">Service methods will be bound by calling <c>AddMethod</c> on this object.</param>
    /// <param name="serviceImpl">An object implementing the server-side handling logic.</param>
    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    public static void BindService(grpc::ServiceBinderBase serviceBinder, GreeterBase serviceImpl)
    {
      serviceBinder.AddMethod(__Method_SayHello, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::GrpcService1.HelloRequest, global::GrpcService1.HelloReply>(serviceImpl.SayHello));
      serviceBinder.AddMethod(__Method_GetDoc, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::GrpcService1.Doc_Id, global::GrpcService1.Doc_result>(serviceImpl.GetDoc));
    }

  }
}
#endregion

Hmm. looks like they use some custom tool-chain tools that we don’t support. I’ll have a look, but no promises; this might just be tied to needing MSBuild.

It’s already great that you’re watching.
I have tried under all languages in Water and Grpc.tools (which generates the classes) blocks.
In VS, the configuration is:

<PackageReference Include="Grpc.Tools" Version="2.40.0">
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  <PrivateAssets>all</PrivateAssets>
</PackageReference>

The tricky part will be to find out what tool to run and how to run it, but more so, how to hook that discovery up in a more generic fashion that is, ideally, not hardcoded to this particular use case (Grpc) but applicable to any such similar tools.

start here :slight_smile:
grpc / (version 2.48.1 today)
I did the test and it does come out with a class in “imbitable” c#

1 Like

Thanx. i might just be bored enough today to hook this up explicitly (ie hardcoded Protobuffer support; not support for arbitrary custom MSBuild tasks).

One note before I forget later: for this to work (eventually) you’ll want to set the build action type for the file in your .elements to match VC#:

<Protobuf Include="Protos\greet.proto" />
2 Likes

/Users/mh/Downloads/Grpc/Water/TestGrpc/Protos/greet.proto: File does not reside within any path specified using --proto_path (or -I). You must specify a --proto_path which encompasses this file. Note that the proto_path must be an exact prefix of the .proto file names – protoc is too dumb to figure out when two paths (e.g. absolute and relative) are equivalent (it’s harder than you think).

LOL. :joy::joy:

1 Like
                  Starting compile for TestGrpc Echoes 
                  RemObjects Elements Compiler for .NET, Cocoa, Java and Island.
                  Copyright 2003-2022 RemObjects Software, LLC. All rights reserved. Created by Carlo Kok
                  Version 11.0.0.2772 (develop) built on bajor, 20220912-174036. Commit 7194c80.
                  Source file: /Users/mh/Downloads/Grpc/Water/TestGrpc/Controllers/WeatherForecastController.pas
                  Source file: /Users/mh/Downloads/Grpc/Water/TestGrpc/Program.pas
                  Source file: /Users/mh/Downloads/Grpc/Water/TestGrpc/Startup.pas
                  Source file: /Users/mh/Downloads/Grpc/Water/TestGrpc/WeatherForecast.pas
                  Source file: /Users/mh/Library/Application Support/RemObjects Software/EBuild/Obj/TestGrpc-ED4E41A26237CF6705ADDF2B93CF1461DFEFA2C2/Debug/Echoes/ProtoBuffers/Greet.cs

oddly it only genertates one file; thje VC# one has Greet.cs and GreetGrpc.cs in its objc folder…

Done and in (pending actual testing/feedback). Unfortunately I cant get a new build to you until later tonight.

1 Like

Wonderful.

As we don’t live on the same continent, late tonight is really not a problem, so I’ll try to wait :slight_smile:

Thanks in advance
Norbert

1 Like

Try if adding this .cs fine to your project manually makes it work; if so we should be good.

Greet.cs (42.3 KB)

Oddly, when i specify --grpc_out as mention in the doc you linked earlier, and which I assume is supposed to generate the second file:

--grpc_out=/Users/mh/Library/Application Support/RemObjects Software/EBuild/Obj/TestGrpc-ED4E41A26237CF6705ADDF2B93CF1461DFEFA2C2/Debug/Echoes/ProtoBuffers
--plugin=/Users/mh/Library/Application Support/RemObjects Software/EBuild/Packages/NuGet/grpc.tools/2.48.0/tools/macosx_x64/grpc_csharp_plugin

it fails with

protoc-gen-grpc: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
–grpc_out: protoc-gen-grpc: Plugin failed with status code 1.

Update nevermind, works: needs --plugin protoc-gen-grpc=/Users/mh/Library/Application Support/RemObjects Software/EBuild/Packages/NuGet/grpc.tools/2.48.0/tools/macosx_x64/grpc_csharp_plugin

However, I do get 4 errors with this generated code

                  -> Phase Resolving Bodies started.
E:                   Type mismatch, cannot assign "Grpc.Core.Marshaller<Google.Protobuf.IMessage>" to "Grpc.Core.Marshaller<HelloRequest>" [/Users/mh/Library/Application Support/RemObjects Software/EBuild/Obj/TestGrpc-ED4E41A26237CF6705ADDF2B93CF1461DFEFA2C2/Debug/Echoes/ProtoBuffers/GreetGrpc.cs (52)]
E:                   Type mismatch, cannot assign "Grpc.Core.Marshaller<Google.Protobuf.IMessage>" to "Grpc.Core.Marshaller<HelloReply>" [/Users/mh/Library/Application Support/RemObjects Software/EBuild/Obj/TestGrpc-ED4E41A26237CF6705ADDF2B93CF1461DFEFA2C2/Debug/Echoes/ProtoBuffers/GreetGrpc.cs (54)]
E:                   Type mismatch, cannot assign "Grpc.Core.Marshaller<Google.Protobuf.IMessage>" to "Grpc.Core.Marshaller<Doc_Id>" [/Users/mh/Library/Application Support/RemObjects Software/EBuild/Obj/TestGrpc-ED4E41A26237CF6705ADDF2B93CF1461DFEFA2C2/Debug/Echoes/ProtoBuffers/GreetGrpc.cs (56)]
E:                   Type mismatch, cannot assign "Grpc.Core.Marshaller<Google.Protobuf.IMessage>" to "Grpc.Core.Marshaller<Doc_result>" [/Users/mh/Library/Application Support/RemObjects Software/EBuild/Obj/TestGrpc-ED4E41A26237CF6705ADDF2B93CF1461DFEFA2C2/Debug/Echoes/ProtoBuffers/GreetGrpc.cs (58)]
                  <- Phase Resolving Bodies finished, took 3.974s.

GreetGrpc.cs (13.5 KB)

1 Like