首页 > 解决方案 > WPF .NET Core 中的 gRPC 错误

问题描述

我想创建一个简单的 WPF Core、gRPC 项目。此“代码”在我的 .NET Core 控制台应用程序中完美运行,但 WPF 似乎有些特别。

原始文件

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 服务器

在 Visual Studio 2019 中创建了一个默认模板(使用 .NET Core 3.1

控制台应用程序(完美运行)

创建了一个默认的 .NET Core 控制台应用程序 -> 将 Proto 文件从服务器添加到客户端,并将gRPC 存根类更改为仅客户端

用途:.NET Core 3.1

有以下 NuGet:

代码

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 核心

创建了一个默认的 WPF .NET Core 应用程序 -> 将 Proto 文件从服务器添加到客户端,并将gRPC 存根类更改为仅客户端

用途:.NET Core 3.1

有以下 NuGet:

代码

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" });
};

问题

我无法构建 WPF 应用程序

错误:

命名空间“MyWPFNameSpace”中不存在类型或命名空间名称“MyProtoNamespace”(您是否缺少程序集引用?)

标签: c#wpfgrpc

解决方案


此问题与Grpc.ToolsWPF 应用程序的包和特殊构建过程有关。该Grpc.Tools包负责编译 .proto 文件并生成服务类。

解决方案是将 gRPC 客户端实例化移动到单独的 .NET Core 类库程序集(项目)中。只需将此代码提取到一个类中,例如GrpcServiceProvider在这个新程序集中。让我们GrpcServiceProvider返回一个 gRPC 客户端的实例。现在从您的 WPF 程序集中引用 .NET Core 库程序集和GrpcServiceProvider类以获取客户端的实例。
这将修复构建错误并改进应用程序设计。

不要忘记GrpcChannel实现IDisposable。这意味着GrpcServiceProvider也应该实施它并正确处置其资源。

.NET Core 类库项目

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项目

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

推荐阅读