.NET 中的 gRPC 客户端工厂集成

作者:James Newton-King

gRPC 与 HttpClientFactory 的集成提供了一种创建 gRPC 客户端的集中方式。 它可用作配置独立 gRPC 客户端实例的替代方法。 Grpc.Net.ClientFactory NuGet 包中提供了工厂集成。

工厂具有以下优势:

  • 提供了用于配置逻辑 gRPC 客户端实例的中心位置。
  • 可管理基础 HttpClientMessageHandler 的生存期。
  • 在 ASP.NET Core gRPC 服务中自动传播截止时间和取消。

注册 gRPC 客户端

若要注册 gRPC 客户端,可在 Program.cs 中的应用入口点处的 WebApplicationBuilder 的实例中使用通用的 AddGrpcClient 扩展方法,并指定 gRPC 类型化客户端类和服务地址:

builder.Services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
});

gRPC 客户端类型通过依赖项注入 (DI) 注册为暂时性。 现在可以在由 DI 创建的类型中直接注入和使用客户端。 ASP.NET Core MVC 控制器、SignalR 中心和 gRPC 服务是可以自动注入 gRPC 客户端的位置:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(Greeter.GreeterClient client)
    {
        _client = client;
    }

    public override async Task SayHellos(HelloRequest request,
        IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
    {
        // Forward the call on to the greeter service
        using (var call = _client.SayHellos(request))
        {
            await foreach (var response in call.ResponseStream.ReadAllAsync())
            {
                await responseStream.WriteAsync(response);
            }
        }
    }
}

配置 HttpHandler

HttpClientFactory 创建 gRPC 客户端使用的 HttpMessageHandler。 标准 HttpClientFactory 方法可用于添加传出请求中间件或配置 HttpClient 的基础 HttpClientHandler

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(LoadCertificate());
        return handler;
    });

有关详细信息,请参阅使用 IHttpClientFactory 发出 HTTP 请求

配置侦听器

可以使用 AddInterceptor 方法将 gRPC 侦听器添加到客户端。

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>();

前面的代码:

  • 注册 GreeterClient 类型。
  • 为此客户端配置 LoggingInterceptorLoggingInterceptor 创建一次,并在 GreeterClient 实例之间共享。

默认情况下,侦听器创建一次并在客户端之间共享。 可以通过在注册侦听器时指定一个作用域来替代此行为。 可以通过指定 InterceptorScope.Client,将客户端工厂配置为给每个客户端创建一个新的侦听器。

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>(InterceptorScope.Client);

当侦听器需要来自 DI 的作用域服务或暂时性作用域服务时,创建客户端作用域内的侦听器非常有用。

gRPC 侦听器或通道凭据可用于随每个请求发送 Authorization 元数据。 有关配置身份验证的详细信息,请参阅发送具有 gRPC 客户端工厂的持有者令牌

配置通道

可以使用 ConfigureChannel 方法将其他配置应用于通道:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

ConfigureChannel 被传递了 GrpcChannelOptions 实例。 有关详细信息,请参阅配置客户端选项

注意

在运行 ConfigureChannel 回调之前,在 GrpcChannelOptions 上设置了一些属性:

这些值可以通过 ConfigureChannel 替代。

调用凭据

可以使用 AddCallCredentials 方法将身份验证标头添加到 gRPC 调用:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials((context, metadata) =>
    {
        if (!string.IsNullOrEmpty(_token))
        {
            metadata.Add("Authorization", $"Bearer {_token}");
        }
        return Task.CompletedTask;
    });

有关配置调用凭据的详细信息,请参阅具有 gRPC 客户端工厂的持有者令牌

截止时间和取消传播

可以使用 EnableCallContextPropagation() 对 gRPC 服务中工厂所创建的 gRPC 客户端进行配置,以自动将截止时间和取消令牌传播到子调用。 Grpc.AspNetCore.Server.ClientFactory NuGet 包中提供了 EnableCallContextPropagation() 扩展方法。

调用上下文传播的工作方式是:从当前 gRPC 请求上下文中读取截止时间和取消令牌,并自动将其传播到 gRPC 客户端所发出的传出调用。 调用上下文传播是确保复杂的嵌套 gRPC 场景始终传播截止时间和取消的一种极佳方式。

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

默认情况下,如果客户端在 gRPC 调用的上下文之外使用,EnableCallContextPropagation 将引发错误。 此错误旨在提醒你没有要传播的调用上下文。 如果要在调用上下文之外使用客户端,请使用 SuppressContextNotFoundErrors 在配置客户端时禁止显示该错误:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation(o => o.SuppressContextNotFoundErrors = true);

有关截止时间和 RPC 取消的详细信息,请参阅具有截止时间和取消功能的可靠的 gRPC 服务

命名客户端

通常情况下,gRPC 客户端类型被注册一次,然后通过 DI 直接注入到类型的构造函数。 但在某些情况下,为一个客户端提供多个配置是很有用的。 例如,一个客户端在进行身份验证和不进行身份验证的情况下进行 gRPC 调用。

可以通过为每个客户端提供一个名称来注册同一类型的多个客户端。 每个命名的客户端可以有自己的配置。 泛型 AddGrpcClient 扩展方法具有一个包含名称参数的重载:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>("Greeter", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    });

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>("GreeterAuthenticated", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

上述代码:

  • 注册 GreeterClient 类型两次,每次注册指定一个唯一的名称。
  • 为每个命名的客户端配置不同的设置。 GreeterAuthenticated 注册向通道添加了凭据,这样用它进行的 gRPC 调用就经过了身份验证。

使用 GrpcClientFactory 在应用代码中创建一个命名的 gRPC 客户端。 所需客户端的类型和名称是使用泛型 GrpcClientFactory.CreateClient 方法指定的:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(GrpcClientFactory grpcClientFactory)
    {
        _client = grpcClientFactory.CreateClient<Greeter.GreeterClient>("GreeterAuthenticated");
    }
}

其他资源

gRPC 与 HttpClientFactory 的集成提供了一种创建 gRPC 客户端的集中方式。 它可用作配置独立 gRPC 客户端实例的替代方法。 Grpc.Net.ClientFactory NuGet 包中提供了工厂集成。

工厂具有以下优势:

  • 提供了用于配置逻辑 gRPC 客户端实例的中心位置
  • 可管理基础 HttpClientMessageHandler 的生存期
  • 在 ASP.NET Core gRPC 服务中自动传播截止时间和取消

注册 gRPC 客户端

若要注册 gRPC 客户端,可在 Startup.ConfigureServices 中使用通用的 AddGrpcClient 扩展方法,并指定 gRPC 类型化客户端类和服务地址:

services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
});

gRPC 客户端类型通过依赖项注入 (DI) 注册为暂时性。 现在可以在由 DI 创建的类型中直接注入和使用客户端。 ASP.NET Core MVC 控制器、SignalR 中心和 gRPC 服务是可以自动注入 gRPC 客户端的位置:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(Greeter.GreeterClient client)
    {
        _client = client;
    }

    public override async Task SayHellos(HelloRequest request,
        IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
    {
        // Forward the call on to the greeter service
        using (var call = _client.SayHellos(request))
        {
            await foreach (var response in call.ResponseStream.ReadAllAsync())
            {
                await responseStream.WriteAsync(response);
            }
        }
    }
}

配置 HttpHandler

HttpClientFactory 创建 gRPC 客户端使用的 HttpMessageHandler。 标准 HttpClientFactory 方法可用于添加传出请求中间件或配置 HttpClient 的基础 HttpClientHandler

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(LoadCertificate());
        return handler;
    });

有关详细信息,请参阅使用 IHttpClientFactory 发出 HTTP 请求

配置侦听器

可以使用 AddInterceptor 方法将 gRPC 侦听器添加到客户端。

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>();

前面的代码:

  • 注册 GreeterClient 类型。
  • 为此客户端配置 LoggingInterceptorLoggingInterceptor 创建一次,并在 GreeterClient 实例之间共享。

默认情况下,侦听器创建一次并在客户端之间共享。 可以通过在注册侦听器时指定一个作用域来替代此行为。 可以通过指定 InterceptorScope.Client,将客户端工厂配置为给每个客户端创建一个新的侦听器。

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>(InterceptorScope.Client);

当侦听器需要来自 DI 的作用域服务或暂时性作用域服务时,创建客户端作用域内的侦听器非常有用。

gRPC 侦听器或通道凭据可用于随每个请求发送 Authorization 元数据。 有关配置身份验证的详细信息,请参阅发送具有 gRPC 客户端工厂的持有者令牌

配置通道

可以使用 ConfigureChannel 方法将其他配置应用于通道:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

ConfigureChannel 被传递了 GrpcChannelOptions 实例。 有关详细信息,请参阅配置客户端选项

注意

在运行 ConfigureChannel 回调之前,在 GrpcChannelOptions 上设置了一些属性:

这些值可以通过 ConfigureChannel 替代。

截止时间和取消传播

可以使用 EnableCallContextPropagation() 对 gRPC 服务中工厂所创建的 gRPC 客户端进行配置,以自动将截止时间和取消令牌传播到子调用。 Grpc.AspNetCore.Server.ClientFactory NuGet 包中提供了 EnableCallContextPropagation() 扩展方法。

调用上下文传播的工作方式是:从当前 gRPC 请求上下文中读取截止时间和取消令牌,并自动将其传播到 gRPC 客户端所发出的传出调用。 调用上下文传播是确保复杂的嵌套 gRPC 场景始终传播截止时间和取消的一种极佳方式。

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

默认情况下,如果客户端在 gRPC 调用的上下文之外使用,EnableCallContextPropagation 将引发错误。 此错误旨在提醒你没有要传播的调用上下文。 如果要在调用上下文之外使用客户端,请使用 SuppressContextNotFoundErrors 在配置客户端时禁止显示该错误:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation(o => o.SuppressContextNotFoundErrors = true);

有关截止时间和 RPC 取消的详细信息,请参阅具有截止时间和取消功能的可靠的 gRPC 服务

命名客户端

通常情况下,gRPC 客户端类型被注册一次,然后通过 DI 直接注入到类型的构造函数。 但在某些情况下,为一个客户端提供多个配置是很有用的。 例如,一个客户端在进行身份验证和不进行身份验证的情况下进行 gRPC 调用。

可以通过为每个客户端提供一个名称来注册同一类型的多个客户端。 每个命名的客户端可以有自己的配置。 泛型 AddGrpcClient 扩展方法具有一个包含名称参数的重载:

services
    .AddGrpcClient<Greeter.GreeterClient>("Greeter", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    });

services
    .AddGrpcClient<Greeter.GreeterClient>("GreeterAuthenticated", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

上述代码:

  • 注册 GreeterClient 类型两次,每次注册指定一个唯一的名称。
  • 为每个命名的客户端配置不同的设置。 GreeterAuthenticated 注册向通道添加了凭据,这样用它进行的 gRPC 调用就经过了身份验证。

使用 GrpcClientFactory 在应用代码中创建一个命名的 gRPC 客户端。 所需客户端的类型和名称是使用泛型 GrpcClientFactory.CreateClient 方法指定的:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(GrpcClientFactory grpcClientFactory)
    {
        _client = grpcClientFactory.CreateClient<Greeter.GreeterClient>("GreeterAuthenticated");
    }
}

其他资源