Intégration de la fabrique de clients gRPC dans .NET

Par James Newton-King

L’intégration de gRPC à HttpClientFactory offre un moyen centralisé de créer des clients gRPC. Elle peut être utilisée comme alternative à la configuration d’instances clientes gRPC autonomes. L’intégration aux fabriques est disponible dans le package NuGet Grpc.Net.ClientFactory.

La fabrique offre les avantages suivants :

  • Fournit un emplacement central pour la configuration d’instances de client gRPC logiques.
  • Gère la durée de vie du HttpClientMessageHandler sous-jacent.
  • Propagation automatique de l’échéance et de l’annulation dans un service gRPC ASP.NET Core.

Inscrire des clients gRPC

Pour inscrire un client gRPC, il est possible d’utiliser la méthode d’extension AddGrpcClient générique dans une instance de WebApplicationBuilderau point d’entrée de l’application dans Program.cs, en spécifiant la classe de client typé gRPC et l’adresse de service :

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

Le type de client gRPC est inscrit comme temporaire avec l’injection de dépendances (DI). Le client peut désormais être injecté et consommé directement dans les types créés par injection de dépendances (DI). Les contrôleurs MVC ASP.NET Core, les hubs SignalR et les services gRPC sont des emplacements où les clients gRPC peuvent être injectés automatiquement :

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

Configurer HttpHandler

HttpClientFactory crée le HttpMessageHandler utilisé par le client gRPC. Il est possible d’utiliser les méthodes HttpClientFactory standard pour ajouter un intergiciel de requête sortant ou pour configurer le HttpClientHandler sous-jacent du 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;
    });

Pour plus d’informations, consultez Effectuer des requêtes HTTP en utilisant IHttpClientFactory.

Configurer des intercepteurs

Il est possible d’ajouter des intercepteurs gRPC aux clients à l’aide de la méthode AddInterceptor.

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

Le code précédent :

  • Inscrit le type GreeterClient.
  • Configure un LoggingInterceptor pour ce client. LoggingInterceptor est créé une fois et partagé entre les instances GreeterClient.

Par défaut, un intercepteur est créé une seule fois et partagé entre les clients. Ce comportement peut être remplacé en spécifiant une étendue lors de l’inscription d’un intercepteur. La fabrique de clients peut être configurée pour créer un intercepteur pour chaque client en spécifiant InterceptorScope.Client.

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

La création d’intercepteurs étendus au client est utile lorsqu’un intercepteur nécessite des services étendus ou temporaires depuis l’injection de dépendances.

Un intercepteur gRPC ou des informations d’identification de canal peuvent servir à envoyer des métadonnées Authorization avec chaque requête. Pour plus d’informations sur la configuration de l’authentification, consultez Envoyer un jeton de porteur avec la fabrique de clients gRPC.

Configurer un canal

Une configuration supplémentaire peut être appliquée à un canal à l’aide de la méthode ConfigureChannel :

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

ConfigureChannelest passé une instance GrpcChannelOptions. Pour plus d’informations, consultez Configurer les options client.

Notes

Certaines propriétés sont définies sur GrpcChannelOptions avant l’exécution du rappel ConfigureChannel :

Ces valeurs peuvent être remplacées par ConfigureChannel.

Informations d’identification de l’appel

Il est possible d’ajouter un en-tête d’authentification aux appels gRPC à l’aide de la méthode 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;
    });

Pour plus d’informations sur la configuration des informations d’identification d’appel, consultez Jeton du porteur avec la fabrique de clients gRPC.

Propagation de l’échéance et de l’annulation

Il est possible de configurer les clients gRPC créés par la fabrique dans un service gRPC avec EnableCallContextPropagation() pour propager automatiquement le jeton d’échéance et d’annulation aux appels enfants. La méthode d’extension EnableCallContextPropagation() est disponible dans le package NuGet Grpc.AspNetCore.Server.ClientFactory.

La propagation du contexte d’appel fonctionne en lisant le jeton d’échéance et d’annulation du contexte de requête gRPC actuel et en le propageant automatiquement aux appels sortants effectués par le client gRPC. La propagation du contexte d’appel est un excellent moyen de s’assurer que les scénarios gRPC complexes et imbriqués propagent toujours l’échéance et l’annulation.

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

Par défaut, EnableCallContextPropagation génère une erreur si le client est utilisé en dehors du contexte d’un appel gRPC. L’erreur est conçue pour vous avertir qu’il n’y a pas de contexte d’appel à propager. Si vous souhaitez utiliser le client en dehors d’un contexte d’appel, supprimez l’erreur lorsque le client est configuré avec SuppressContextNotFoundErrors :

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

Pour plus d’informations sur les échéances et l’annulation RPC, consultez Services gRPC fiables avec échéances et annulation.

Clients nommés

En règle générale, un type de client gRPC est inscrit une seule fois, puis injecté directement dans le constructeur d’un type par injection de dépendances. Toutefois, il existe des scénarios dans lesquels il est utile d’avoir plusieurs configurations pour un client. Par exemple, un client qui effectue des appels gRPC avec et sans authentification.

Plusieurs clients avec le même type peuvent être inscrits en donnant un nom à chaque client. Chaque client nommé peut avoir sa propre configuration. La méthode d’extension AddGrpcClient générique a une surcharge qui inclut un paramètre de nom :

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

Le code précédent :

  • Inscrit le type GreeterClient deux fois, en spécifiant un nom unique pour chacun d’eux.
  • Configure différents paramètres pour chaque client nommé. L’inscription GreeterAuthenticated ajoute des informations d’identification au canal afin que les appels gRPC effectués avec celui-ci soient authentifiés.

Un client gRPC nommé est créé dans le code de l’application à l’aide de GrpcClientFactory. Le type et le nom du client souhaité sont spécifiés à l’aide de la méthode GrpcClientFactory.CreateClient générique :

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

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

Ressources supplémentaires

L’intégration de gRPC à HttpClientFactory offre un moyen centralisé de créer des clients gRPC. Elle peut être utilisée comme alternative à la configuration d’instances clientes gRPC autonomes. L’intégration aux fabriques est disponible dans le package NuGet Grpc.Net.ClientFactory.

La fabrique offre les avantages suivants :

  • Offre un emplacement central pour la configuration d’instances de client gRPC logiques
  • Gère la durée de vie du HttpClientMessageHandler sous-jacent
  • Propagation automatique de l’échéance et de l’annulation dans un service gRPC ASP.NET Core

Inscrire des clients gRPC

Pour inscrire un client gRPC, il est possible d’utiliser la méthode d’extension AddGrpcClient générique dans Startup.ConfigureServices, en spécifiant la classe de client typé gRPC et l’adresse de service :

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

Le type de client gRPC est inscrit comme temporaire avec l’injection de dépendances (DI). Le client peut désormais être injecté et consommé directement dans les types créés par injection de dépendances (DI). Les contrôleurs MVC ASP.NET Core, les hubs SignalR et les services gRPC sont des emplacements où les clients gRPC peuvent être injectés automatiquement :

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

Configurer HttpHandler

HttpClientFactory crée le HttpMessageHandler utilisé par le client gRPC. Il est possible d’utiliser les méthodes HttpClientFactory standard pour ajouter un intergiciel de requête sortant ou pour configurer le HttpClientHandler sous-jacent du HttpClient :

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

Pour plus d’informations, consultez Effectuer des requêtes HTTP en utilisant IHttpClientFactory.

Configurer des intercepteurs

Il est possible d’ajouter des intercepteurs gRPC aux clients à l’aide de la méthode AddInterceptor.

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

Le code précédent :

  • Inscrit le type GreeterClient.
  • Configure un LoggingInterceptor pour ce client. LoggingInterceptor est créé une fois et partagé entre les instances GreeterClient.

Par défaut, un intercepteur est créé une seule fois et partagé entre les clients. Ce comportement peut être remplacé en spécifiant une étendue lors de l’inscription d’un intercepteur. La fabrique de clients peut être configurée pour créer un intercepteur pour chaque client en spécifiant InterceptorScope.Client.

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

La création d’intercepteurs étendus au client est utile lorsqu’un intercepteur nécessite des services étendus ou temporaires depuis l’injection de dépendances.

Un intercepteur gRPC ou des informations d’identification de canal peuvent servir à envoyer des métadonnées Authorization avec chaque requête. Pour plus d’informations sur la configuration de l’authentification, consultez Envoyer un jeton de porteur avec la fabrique de clients gRPC.

Configurer un canal

Une configuration supplémentaire peut être appliquée à un canal à l’aide de la méthode ConfigureChannel :

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

ConfigureChannelest passé une instance GrpcChannelOptions. Pour plus d’informations, consultez Configurer les options client.

Notes

Certaines propriétés sont définies sur GrpcChannelOptions avant l’exécution du rappel ConfigureChannel :

Ces valeurs peuvent être remplacées par ConfigureChannel.

Propagation de l’échéance et de l’annulation

Il est possible de configurer les clients gRPC créés par la fabrique dans un service gRPC avec EnableCallContextPropagation() pour propager automatiquement le jeton d’échéance et d’annulation aux appels enfants. La méthode d’extension EnableCallContextPropagation() est disponible dans le package NuGet Grpc.AspNetCore.Server.ClientFactory.

La propagation du contexte d’appel fonctionne en lisant le jeton d’échéance et d’annulation du contexte de requête gRPC actuel et en le propageant automatiquement aux appels sortants effectués par le client gRPC. La propagation du contexte d’appel est un excellent moyen de s’assurer que les scénarios gRPC complexes et imbriqués propagent toujours l’échéance et l’annulation.

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

Par défaut, EnableCallContextPropagation génère une erreur si le client est utilisé en dehors du contexte d’un appel gRPC. L’erreur est conçue pour vous avertir qu’il n’y a pas de contexte d’appel à propager. Si vous souhaitez utiliser le client en dehors d’un contexte d’appel, supprimez l’erreur lorsque le client est configuré avec SuppressContextNotFoundErrors :

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

Pour plus d’informations sur les échéances et l’annulation RPC, consultez Services gRPC fiables avec échéances et annulation.

Clients nommés

En règle générale, un type de client gRPC est inscrit une seule fois, puis injecté directement dans le constructeur d’un type par injection de dépendances. Toutefois, il existe des scénarios dans lesquels il est utile d’avoir plusieurs configurations pour un client. Par exemple, un client qui effectue des appels gRPC avec et sans authentification.

Plusieurs clients avec le même type peuvent être inscrits en donnant un nom à chaque client. Chaque client nommé peut avoir sa propre configuration. La méthode d’extension AddGrpcClient générique a une surcharge qui inclut un paramètre de nom :

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

Le code précédent :

  • Inscrit le type GreeterClient deux fois, en spécifiant un nom unique pour chacun d’eux.
  • Configure différents paramètres pour chaque client nommé. L’inscription GreeterAuthenticated ajoute des informations d’identification au canal afin que les appels gRPC effectués avec celui-ci soient authentifiés.

Un client gRPC nommé est créé dans le code de l’application à l’aide de GrpcClientFactory. Le type et le nom du client souhaité sont spécifiés à l’aide de la méthode GrpcClientFactory.CreateClient générique :

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

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

Ressources supplémentaires