You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AddCodeFirstGrpcClient与WebApplicationFactory测试代码优先gRPC服务失败求助

问题描述

我尝试用Protobuf-net的客户端工厂方法AddCodeFirstGrpcClient,结合WebApplicationFactory<TEntryPoint>在测试环境创建客户端,但第二个测试方法TestViaCodeFirst执行失败,抛出以下错误:

Message: 
Grpc.Core.RpcException : Status(StatusCode="Unavailable", Detail="Error connecting to subchannel.", DebugException="System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it.")
---- System.Net.Sockets.SocketException : No connection could be made because the target machine actively refused it.

Stack Trace: 
ConnectionManager.PickAsync(PickContext context, Boolean waitForReady, CancellationToken cancellationToken)
BalancerHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
GrpcCall2.RunCall(HttpRequestMessage request, Nullable1 timeout)
Reshape.UnaryTaskAsyncImpl[TRequest,TResponse](AsyncUnaryCall`1 call, MetadataContext metadata, CancellationToken cancellationToken) line 554
GreeterServiceTests.TestViaCodeFirst() line 50
--- End of stack trace from previous location ---
----- Inner Stack Trace -----
AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
IValueTaskSource.GetResult(Int16 token)
Socket.g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
SocketConnectivitySubchannelTransport.TryConnectAsync(ConnectContext context, Int32 attempt)

我的代码

共享代码

using ProtoBuf.Grpc;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace GrpcExperiments.Shared;

[DataContract]
public class HelloReply
{
    [DataMember(Order = 1)]
    public string? Message { get; set; }
}

[DataContract]
public class HelloRequest
{
    [DataMember(Order = 1)]
    public string? Name { get; set; }
}

[ServiceContract]
public interface IGreeterService
{
    [OperationContract]
    Task<HelloReply> SayHelloAsync(HelloRequest request);
}

服务端代码

using ProtoBuf.Grpc;
using GrpcExperiments.Shared;

public class GreeterService : IGreeterService
{
    public Task<HelloReply> SayHelloAsync(HelloRequest request)
    {
        return Task.FromResult(
            new HelloReply { Message = $"Hello {request.Name}" });
    }
}

客户端代码

using Grpc.Net.Client;
using GrpcExperiments.Shared;
using Microsoft.Extensions.DependencyInjection;
using ProtoBuf.Grpc.Client;
using ProtoBuf.Grpc.ClientFactory;
using Gncf = Grpc.Net.ClientFactory;

public class Program
{
    public static async Task Main()
    {
        await CreateClientOld();
        await CreateClientNew();

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }

    static async Task CreateClientNew()
    {
        var services = new ServiceCollection();
        services.AddCodeFirstGrpcClient<IGreeterService>(o =>
        {
            o.Address = new Uri("https://localhost:7158");
        });
        using var serviceProvider = services.BuildServiceProvider();
        var factory = serviceProvider.GetRequiredService<Gncf.GrpcClientFactory>();
        var client = factory.CreateClient<IGreeterService>(nameof(IGreeterService));

        var reply = await client.SayHelloAsync(
            new HelloRequest { Name = "GreeterClient" });

        Console.WriteLine($"Greeting (new): {reply.Message}");
    }

    static async Task CreateClientOld()
    {
        using var channel = GrpcChannel.ForAddress("https://localhost:7158");
        var client = channel.CreateGrpcService<IGreeterService>();

        var reply = await client.SayHelloAsync(
            new HelloRequest { Name = "GreeterClient" });

        Console.WriteLine($"Greeting (old): {reply.Message}");
    }
}

测试代码

using Grpc.Net.Client;
using GrpcExperiments.Shared;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using ProtoBuf.Grpc.Client;
using ProtoBuf.Grpc.ClientFactory;
using Gncf = Grpc.Net.ClientFactory;

namespace GrpcExperiements.Test;

public class GreeterServiceTests
{
    [Fact]
    public async Task TestViaGrpcChannel()
    {
        using var factory = new WebApplicationFactory<Program>();

        using var channel = GrpcChannel.ForAddress(factory.Server.BaseAddress, new GrpcChannelOptions
        {            
            HttpClient = factory.CreateClient()
        });

        var client = channel.CreateGrpcService<IGreeterService>();

        var reply = await client.SayHelloAsync(
            new HelloRequest { Name = "GreeterClient" });

        Assert.Equal("Hello GreeterClient", reply.Message);
    }

    [Fact]
    public async Task TestViaCodeFirst()
    {
        using var webApp = new WebApplicationFactory<Program>();

        using var services = new ServiceCollection();
        services
            .AddCodeFirstGrpcClient<IGreeterService>(nameof(IGreeterService), clientFactoryOptions =>
            {
                clientFactoryOptions.Address = webApp.Server.BaseAddress;
                //clientFactoryOptions.ChannelOptionsActions.Add(options => options.HttpClient = webApp.CreateClient());
            });
            //.ConfigureChannel(channel => 
            //    channel.HttpClient = webApp.CreateClient());

        using var serviceProvider = services.BuildServiceProvider();
        var factory = serviceProvider.GetRequiredService<Gncf.GrpcClientFactory>();
        var client = factory.CreateClient<IGreeterService>(nameof(IGreeterService));

        var reply = await client.SayHelloAsync(
            new HelloRequest { Name = "GreeterClient" });

        Assert.Equal("Hello GreeterClient", reply.Message);
    }
}

请问该如何解决这个连接被拒绝的问题?


解决方案

问题核心是你没有把WebApplicationFactory生成的测试HttpClient绑定到gRPC客户端工厂的通道配置里。WebApplicationFactory的测试服务器不会监听实际网络端口,而是通过内部管道处理请求,默认情况下gRPC客户端工厂会创建独立的HttpClient,无法访问这个内部管道,导致连接被拒绝。

修改TestViaCodeFirst方法,在配置AddCodeFirstGrpcClient时,通过ChannelOptionsActions注入测试用的HttpClient即可解决:

[Fact]
public async Task TestViaCodeFirst()
{
    using var webApp = new WebApplicationFactory<Program>();

    using var services = new ServiceCollection();
    services
        .AddCodeFirstGrpcClient<IGreeterService>(nameof(IGreeterService), clientFactoryOptions =>
        {
            clientFactoryOptions.Address = webApp.Server.BaseAddress;
            // 绑定测试HttpClient到通道配置
            clientFactoryOptions.ChannelOptionsActions.Add(options => 
                options.HttpClient = webApp.CreateClient());
        });

    using var serviceProvider = services.BuildServiceProvider();
    var factory = serviceProvider.GetRequiredService<Gncf.GrpcClientFactory>();
    var client = factory.CreateClient<IGreeterService>(nameof(IGreeterService));

    var reply = await client.SayHelloAsync(
        new HelloRequest { Name = "GreeterClient" });

    Assert.Equal("Hello GreeterClient", reply.Message);
}

原理说明

  • WebApplicationFactory.CreateClient()返回的HttpClient已经配置为直接和测试服务器的内部管道通信,不需要走实际网络端口,这也是第一个测试方法TestViaGrpcChannel能成功的原因。
  • 使用AddCodeFirstGrpcClient时必须显式关联这个测试HttpClient,否则客户端会尝试通过网络连接BaseAddress,但测试服务器并未监听该端口,自然会被拒绝。

另外,也可以用ConfigureChannel方法来配置通道的HttpClient,效果完全一致:

services
    .AddCodeFirstGrpcClient<IGreeterService>(nameof(IGreeterService), o =>
    {
        o.Address = webApp.Server.BaseAddress;
    })
    .ConfigureChannel(options => 
        options.HttpClient = webApp.CreateClient());

内容的提问来源于stack exchange,提问作者Shimmy Weitzhandler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:39:52