4
votes

I want to create a simple WPF Core, gRPC project. This "code" works perfectly in my .NET Core Console app, however WPF seems to be something special.

Proto File

syntax = "proto3";

option csharp_namespace = "MyProtoNamespace";

package greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

gRPC Server

created a default template in Visual Studio 2019 (uses .NET Core 3.1)

Console App (works perfectly)

created a default .NET Core Console App -> added the Proto file from Server to Client and changed the gRPC Stub Classes to Client Only

uses: .NET Core 3.1

has following NuGets:

  • Grpc.Tools
  • Grpc.Net.Client
  • Google.Protobuf

code

static async Task Main(string[] args) // works perfectly
{
    using var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greeter.GreeterClient(channel);
    var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });

    System.Console.WriteLine("Greeting: " + reply.Message);
    System.Console.WriteLine("Press any key to exit...");
    System.Console.ReadKey();
}

WPF .NET Core

created a default WPF .NET Core app -> added the Proto file from Server to Client and changed the gRPC Stub Classes to Client Only

uses: .NET Core 3.1

has following NuGets:

  • Grpc.Tools
  • Grpc.Net.Client
  • Google.Protobuf

code

Loaded += async delegate
{
    using var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greeter.GreeterClient(channel);
    var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
};

PROBLEM

I can NOT build the WPF app

errors:

The type or namespace name 'MyProtoNamespace' does not exist in the namespace 'MyWPFNameSpace' (are you missing an assembly reference?)

1
Where did you define the .proto file? What's the differences between the project file of the console app vs the project file of the WPF app?mm8
@mm8 I defined my proto files in each of my projects (e.g. WPF has his proto, Console has his own etc). About Your second question: nothing, they are both client only and my grpc server proto has also the same content but it's server only.user12722843

1 Answers

6
votes

This issue is related to the Grpc.Tools package and the special build process of WPF applications. The Grpc.Tools package is responsible to compile the .proto file and generate the service classes.

The solution is to move the gRPC client instantiation into a separate .NET Core class library assembly (project). Just extract this code to a class e.g. GrpcServiceProvider in this new assembly. Let GrpcServiceProvider return an instance of the gRPC client. Now from your WPF assembly reference the .NET Core library assembly and the GrpcServiceProvider class to obtain the instance of the client.
This will fix the build error and also improve the application design.

Don't forget that GrpcChannel implements IDisposable. This means GrpcServiceProvider should implement it too and dispose its resources properly.

.NET Core class library project

public class GrpcServiceProvider : IDisposable 
{
  public GrpcServiceProvider()
  {
    this.ServiceUrl = "https://localhost:5001";
    this.DefaultRpcChannel = new Lazy<GrpcChannel>(GrpcChannel.ForAddress(this.ServiceUrl));
  }

  public Greeter.GreeterClient GetGreeterClient() => this.GreeterClient ??= new Greeter.GreeterClient(this.DefaultRpcChannel.Value);

  #region IDisposable Support
  private bool disposedValue = false; // To detect redundant calls

  protected virtual void Dispose(bool disposing)
  {
    if (!disposedValue)
    {
      if (disposing)
      {
        if (this.DefaultRpcChannel.IsValueCreated)
        {
          this.DefaultRpcChannel.Value.Dispose();
        }
      }

      // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
      // TODO: set large fields to null.

      disposedValue = true;
    }
  }

  // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
  // ~GrpcServiceProvider()
  // {
  //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  //   Dispose(false);
  // }

  // This code added to correctly implement the disposable pattern.
  public void Dispose()
  {
    // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
    Dispose(true);
    // TODO: uncomment the following line if the finalizer is overridden above.
    // GC.SuppressFinalize(this);
  }

  #endregion IDisposable Support

  private Lazy<GrpcChannel> DefaultRpcChannel { get; set; }    
  private string ServiceUrl { get; set; }    
  private Greeter.GreeterClient GreeterClient { get; set; }
}

WPF project

Loaded += async delegate
{
    using var serviceProvider = new GrpcServiceProvider();
    var client = serviceProvider.GetGreeterClient();
    var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
};