Integração de fábrica do cliente gRPC no .NET

Por James Newton-King

A integração do gRPC com o HttpClientFactory oferece uma maneira centralizada de criar clientes gRPC. Ele pode ser usado como uma alternativa para configurar instâncias de cliente gRPC autônomas. A integração de fábrica está disponível no pacote NuGet Grpc.Net.ClientFactory.

O alocador oferece os seguintes benefícios:

  • Fornece um local central para configurar instâncias de cliente gRPC lógicas.
  • Gerencia o tempo de vida do HttpClientMessageHandlersubjacente.
  • Propagação automática de prazo e cancelamento em um serviço gRPC do ASP.NET Core.

Registrar clientes gRPC

Para registrar um cliente gRPC, o método de extensão genérica AddGrpcClient pode ser usado em uma instância do WebApplicationBuilder no ponto de entrada do aplicativo em Program.cs, especificando a classe de cliente e o endereço de serviço tipado do gRPC:

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

O tipo de cliente gRPC é registrado como transitório com DI (injeção de dependência). O cliente agora pode ser injetado e consumido diretamente em tipos criados pela DI. Os controladores MVC do ASP.NET Core, os hubs do SignalR e os serviços gRPC são locais em que os clientes gRPC podem ser injetados automaticamente:

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

Configurar o HttpHandler

O HttpClientFactory cria o HttpMessageHandler usado pelo cliente gRPC. Os métodos padrão do HttpClientFactory podem ser usados para adicionar o middleware de solicitação de saída ou para configurar o HttpClientHandler subjacente do HttpClient:

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

Para obter mais informações, confira Fazer solicitações HTTP usando IHttpClientFactory.

Configurar interceptadores

Os interceptadores gRPC podem ser adicionados aos clientes usando o método AddInterceptor.

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

O código anterior:

  • Registra o tipo do GreeterClient.
  • Configura um LoggingInterceptor para esse cliente. O LoggingInterceptor é criado uma vez e compartilhado entre as instâncias do GreeterClient.

Por padrão, um interceptador é criado uma vez e compartilhado entre clientes. Esse comportamento pode ser substituído especificando um escopo ao registrar um interceptador. A fábrica de clientes pode ser configurada para criar um novo interceptador para cada cliente especificando o InterceptorScope.Client.

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

A criação de interceptadores no escopo do cliente é útil quando um interceptador requer serviços com escopo ou escopos transitórios da DI.

Um interceptador gRPC ou credenciais de canal podem ser usados para enviar metadados de Authorization com cada solicitação. Para obter mais informações sobre como configurar a autenticação, confira Enviar um token de portador com o alocador de clientes gRPC.

Configurar canal

É possível aplicar configuração adicional a um canal usando o método ConfigureChannel:

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

O ConfigureChannel é passado por uma instância GrpcChannelOptions. Para obter mais informações, confira Configurar opções de cliente.

Observação

Algumas propriedades são definidas em GrpcChannelOptions antes que o retorno de chamada ConfigureChannel seja executado:

Esses valores podem ser substituídos por ConfigureChannel.

Credenciais de chamada

Um cabeçalho de autenticação pode ser adicionado a chamadas gRPC usando o método AddCallCredentials:

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

Para obter mais informações sobre como configurar credenciais de chamada, confira Token de portador com o alocador de clientes gRPC.

Propagação de prazo final e cancelamento

Os clientes gRPC criados pelo alocador em um serviço gRPC podem ser configurados com EnableCallContextPropagation() para propagar automaticamente o token de prazo final e cancelamento para chamadas filho. O método de extensão EnableCallContextPropagation() está disponível no pacote NuGet Grpc.AspNetCore.Server.ClientFactory.

A propagação de contexto de chamada funciona com a leitura do prazo final e o token de cancelamento do contexto de solicitação gRPC atual, propagando-os automaticamente para chamadas de saída feitas pelo cliente gRPC. A propagação de contexto de chamada é uma excelente maneira de garantir que cenários gRPC complexos e aninhados sempre propaguem o prazo final e o cancelamento.

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

Por padrão, EnableCallContextPropagation gerará um erro se o cliente for usado fora do contexto de uma chamada gRPC. O erro foi projetado para alertar você de que não há um contexto de chamada a ser propagado. Se você quiser usar o cliente fora de um contexto de chamada, suprima o erro quando o cliente estiver configurado com SuppressContextNotFoundErrors:

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

Para obter mais informações sobre prazos finais e cancelamento de RPC, confira Serviços gRPC confiáveis com prazos finais e cancelamento.

Clientes nomeados

Normalmente, um tipo de cliente gRPC é registrado uma vez e, em seguida, injetado diretamente no construtor de um tipo por DI. No entanto, há cenários em que é útil ter várias configurações para um cliente. Por exemplo, um cliente que faz chamadas gRPC com e sem autenticação.

Vários clientes com o mesmo tipo podem ser registrados dando um nome a cada cliente. Cada cliente nomeado pode ter sua própria configuração. O método de extensão genérico AddGrpcClient tem uma sobrecarga que inclui um parâmetro de nome:

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

O código anterior:

  • Registra o GreeterClient tipo duas vezes, especificando um nome exclusivo para cada um.
  • Define configurações diferentes para cada cliente nomeado. O registro GreeterAuthenticated adiciona credenciais ao canal para que as chamadas gRPC feitas com ele sejam autenticadas.

Um cliente gRPC nomeado é criado no código do aplicativo usando GrpcClientFactory. O tipo e o nome do cliente desejado são especificados usando o método genérico GrpcClientFactory.CreateClient:

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

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

Recursos adicionais

A integração do gRPC com o HttpClientFactory oferece uma maneira centralizada de criar clientes gRPC. Ele pode ser usado como uma alternativa para configurar instâncias de cliente gRPC autônomas. A integração de fábrica está disponível no pacote NuGet Grpc.Net.ClientFactory.

O alocador oferece os seguintes benefícios:

  • Fornece um local central para configurar instâncias de cliente gRPC lógicas
  • Gerencia o tempo de vida do HttpClientMessageHandlersubjacente
  • Propagação automática de prazo e cancelamento em um serviço gRPC do ASP.NET Core

Registrar clientes gRPC

Para registrar um cliente gRPC, o método de extensão AddGrpcClient genérico pode ser usado no Startup.ConfigureServices, especificando a classe de cliente e o endereço de serviço tipados do gRPC:

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

O tipo de cliente gRPC é registrado como transitório com DI (injeção de dependência). O cliente agora pode ser injetado e consumido diretamente em tipos criados pela DI. Os controladores MVC do ASP.NET Core, os hubs do SignalR e os serviços gRPC são locais em que os clientes gRPC podem ser injetados automaticamente:

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

Configurar o HttpHandler

O HttpClientFactory cria o HttpMessageHandler usado pelo cliente gRPC. Os métodos padrão do HttpClientFactory podem ser usados para adicionar o middleware de solicitação de saída ou para configurar o HttpClientHandler subjacente do HttpClient:

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

Para obter mais informações, confira Fazer solicitações HTTP usando IHttpClientFactory.

Configurar interceptadores

Os interceptadores gRPC podem ser adicionados aos clientes usando o método AddInterceptor.

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

O código anterior:

  • Registra o tipo do GreeterClient.
  • Configura um LoggingInterceptor para esse cliente. O LoggingInterceptor é criado uma vez e compartilhado entre as instâncias do GreeterClient.

Por padrão, um interceptador é criado uma vez e compartilhado entre clientes. Esse comportamento pode ser substituído especificando um escopo ao registrar um interceptador. A fábrica de clientes pode ser configurada para criar um novo interceptador para cada cliente especificando o InterceptorScope.Client.

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

A criação de interceptadores no escopo do cliente é útil quando um interceptador requer serviços com escopo ou escopos transitórios da DI.

Um interceptador gRPC ou credenciais de canal podem ser usados para enviar metadados de Authorization com cada solicitação. Para obter mais informações sobre como configurar a autenticação, confira Enviar um token de portador com o alocador de clientes gRPC.

Configurar canal

É possível aplicar configuração adicional a um canal usando o método ConfigureChannel:

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

O ConfigureChannel é passado por uma instância GrpcChannelOptions. Para obter mais informações, confira Configurar opções de cliente.

Observação

Algumas propriedades são definidas em GrpcChannelOptions antes que o retorno de chamada ConfigureChannel seja executado:

Esses valores podem ser substituídos por ConfigureChannel.

Propagação de prazo final e cancelamento

Os clientes gRPC criados pelo alocador em um serviço gRPC podem ser configurados com EnableCallContextPropagation() para propagar automaticamente o token de prazo final e cancelamento para chamadas filho. O método de extensão EnableCallContextPropagation() está disponível no pacote NuGet Grpc.AspNetCore.Server.ClientFactory.

A propagação de contexto de chamada funciona com a leitura do prazo final e o token de cancelamento do contexto de solicitação gRPC atual, propagando-os automaticamente para chamadas de saída feitas pelo cliente gRPC. A propagação de contexto de chamada é uma excelente maneira de garantir que cenários gRPC complexos e aninhados sempre propaguem o prazo final e o cancelamento.

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

Por padrão, EnableCallContextPropagation gerará um erro se o cliente for usado fora do contexto de uma chamada gRPC. O erro foi projetado para alertar você de que não há um contexto de chamada a ser propagado. Se você quiser usar o cliente fora de um contexto de chamada, suprima o erro quando o cliente estiver configurado com SuppressContextNotFoundErrors:

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

Para obter mais informações sobre prazos finais e cancelamento de RPC, confira Serviços gRPC confiáveis com prazos finais e cancelamento.

Clientes nomeados

Normalmente, um tipo de cliente gRPC é registrado uma vez e, em seguida, injetado diretamente no construtor de um tipo por DI. No entanto, há cenários em que é útil ter várias configurações para um cliente. Por exemplo, um cliente que faz chamadas gRPC com e sem autenticação.

Vários clientes com o mesmo tipo podem ser registrados dando um nome a cada cliente. Cada cliente nomeado pode ter sua própria configuração. O método de extensão genérico AddGrpcClient tem uma sobrecarga que inclui um parâmetro de nome:

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

O código anterior:

  • Registra o GreeterClient tipo duas vezes, especificando um nome exclusivo para cada um.
  • Define configurações diferentes para cada cliente nomeado. O registro GreeterAuthenticated adiciona credenciais ao canal para que as chamadas gRPC feitas com ele sejam autenticadas.

Um cliente gRPC nomeado é criado no código do aplicativo usando GrpcClientFactory. O tipo e o nome do cliente desejado são especificados usando o método genérico GrpcClientFactory.CreateClient:

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

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

Recursos adicionais