protobuf-net / protobuf-net.Grpc

GRPC bindings for protobuf-net and grpc-dotnet
Other
846 stars 106 forks source link

Can't use `GrpcClientFactoryOptions` - what am I doing wrong? #294

Open Eagle3386 opened 1 year ago

Eagle3386 commented 1 year ago

Since my Blazor WASM-based app implements the BFF pattern, each & every gRPC client calls the very same base address, i. e. just the URL's path differs between clients:

Startup.cs:

// … Code left out for brevity …
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services
       // … Code left out for brevity …
       .AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) })
       .AddSingleton<GrpcWebHandler>(_ => new(GrpcWebMode.GrpcWeb, new HttpClientHandler()))
       .AddSingleton<GrpcClientFactoryOptions>(provider => new()
       {
         Address = new Uri(builder.HostEnvironment.BaseAddress), // NOTE: Ignored - why?
         ChannelOptionsActions =
         {
             options =>
             {
                 options.DisposeHttpClient = true;
                 options.HttpHandler = provider.GetRequiredService<GrpcWebHandler>();
                 options.ServiceConfig = new()
                 {
                   // … Code left out for brevity …
                 };
                 options.ServiceProvider = provider;
             }
         },
         // TODO: InterceptorRegistrations = { }
       })
       .AddCodeFirstGrpcClient<IMyFirstService>((provider, options) =>
         options = provider.GetRequiredService<GrpcClientFactoryOptions>())
// … Code left out for brevity …

Though, upon debugging, I can see that options.Address is still not set after the AddCodeFirstGrpcClient(…) line above. The only fix working so far is changing that line to this:

.AddCodeFirstGrpcClient<IMyFirstService>((provider, options) => options =
{
  var factory = provider.GetRequiredService<GrpcClientFactoryOptions>();
  options.Address = factory.Address;
  foreach (Action<CallOptionsContext> action in factory.CallOptionsActions)
  {
    options.CallOptionsActions.Add(action);
  }
}

However, that would require to repeat those lines for each gRPC client - which is obviously not desirable.

So, I'd like to ask 2 things:

  1. Why is the first line that configures the Address property of GrpcClientFactoryOptions (see // NOTE above) ignored, even if I'd hard-coded it to e. g. https://localhost/?
  2. Please, can you provide me with some advice and/or help towards centralizing the configuration of all my gRPC clients in a singleton GrpcClientFactoryOptions instance that each gRPC client just gets assign/injected?

Any help would be greatly appreciated - thanks in advance!

Eagle3386 commented 1 year ago

@mgravell, I've deliberately closed this issue in favor of protobuf-net/protobuf-net/issues/1071 as that repo's ReadMe sounds more like the right place for such a question.

Was that wrong or why did you reopen this issue?

mgravell commented 1 year ago

This is very definitely a protobuf-net.Grpc topic, not a protobuf-net topic. I reopened it because I didn't feel it was resolved; given the lack of explanation when closing, I presumed that it was an "actually I'll just work around it" closure, but: if we can fix the issue at the library level: that's a better solution. But if it was a "I'll file the issue over here" closure, then: this is still the right location, not there.

Eagle3386 commented 1 year ago

@mgravell thanks for the clarification - yes, I closed it because of "filing it over there", so reopening was absolutely correct.

Given your statement, "fix the issue at the library level", I suppose my scenario is currently not supported & I'll have to wait for a library fix/improvement, right? Honestly, I'm really more than eagerly waiting for your "official" statement regarding this. Because if I'm just doing it wrong, then please: tell me so right away - ideally with (a) hint(s) at required changes to get it working as soon as possible.

Right now, I'm using this workaround in my app client's Program.cs:

// … Code left out for brevity …
static void ConfigureGrpcClient<T>(IServiceProvider provider, GrpcClientFactoryOptions options)
{
    GrpcClientFactoryOptions factory = provider.GetRequiredService<GrpcClientFactoryOptions>();
    options.Address = factory.Address;
    foreach (Action<CallOptionsContext> action in factory.CallOptionsActions)
    {
        options.CallOptionsActions.Add(action);
    }

    foreach (Action<GrpcChannelOptions> action in factory.ChannelOptionsActions)
    {
        options.ChannelOptionsActions.Add(action);
    }

    options.Creator = provider.GetRequiredService<GrpcClientFactoryOptions>().Creator;
    foreach (InterceptorRegistration registration in factory.InterceptorRegistrations)
    {
        options.InterceptorRegistrations.Add(registration);
    }
}

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services
// … Code left out for brevity …
       .AddCodeFirstGrpcClient<IMyFirstService>(ConfigureGrpcClient<IMyFirstService>)
       .Services
// … Code left out for brevity …