Configuring gRPC clients in .NET

Configuring gRPC Clients by Irina Scurtu

Configuring gRPC clients in .NET is similar to what we would configure when we use HTTP Clients. The NuGet package that will allow us to use gRPC clients, is Grpc.NET.ClientFactory. This allows us to build on top of the IHttpClientFactory by providing us with the necessary GrpcClientFactoryOptions we use to customize our globally scoped clients.

Underneath it all, a gRPC client is a special kind of HTTPClient that knows how to handle messages at a lower level, in a construct called 'channel'.

  services
            .AddHttpClient(name)
            .ConfigurePrimaryHttpMessageHandler(() =>
            {
                // Set PrimaryHandler to null so we can track whether the user
                // set a value or not. If they didn't set their own handler then
                // one will be created by PostConfigure.
                return null!;
            });

We configure our gRPC clients globally in Program.cs, just as we configure any regular HTTP Client, by using the extension method, and pointing it to the right server address.


builder.Services
    .AddGrpcClient<SpeakerServiceDefinition.SpeakerServiceDefinitionClient>
    (
    o => { o.Address = new Uri("https://localhost:7226"); }
    )

By default, the AddGrpcClient, configures the gRPC clients as being transient.

Configuring gRPC client channels #

We can configure several aspects of the gRPC channels, which resemble the configurations available for Kestrel. Most of these configurations pertain to the size of the transported data in bytes.

For example, below, you can see that the MaxReceiveMessageSize - referring to gRPC data is configured to be 100 bytes. This means that if the server sends a message that exceeds this limit, it will cause a ResourceExhausted exception in the consumer app.RpcException: Status(StatusCode="ResourceExhausted", Detail="Received message exceeds the maximum configured message size.")

builder.Services 
    .AddGrpcClient<SpeakerServiceDefinition.SpeakerServiceDefinitionClient> 
    ( 
     o => { o.Address = new Uri("https://localhost:7226"); }
    ) 
     .ConfigureChannel(o => 
     {  
         o.MaxReceiveMessageSize = 100; 
         o.MaxRetryBufferSize = 100;  
         o.MaxSendMessageSize = 100; 
     })

In a similar way, we can configure the MaxSendMessageSize property. This will prevent us from sending payloads that exceed what is configured globally.

We will get an exception that has the ResourceExhausted status code. This time, the exception happens when we call the server, and not when we get a response from it.

Grpc.Core.RpcException: 'Status(StatusCode="ResourceExhausted", Detail="Sending message exceeds the maximum configured message size.")'

The MaxRetryBufferSize is used when we have a retry policy in place, as a safety measure.

When a request fails, it gets added to the retry buffer. Then, the requests in the buffer are attempted again during subsequent retries. If the specified MaxRetryBufferSize is exceeded by the buffer size, new requests are not added to the buffer, and retries are not attempted.

This way, excessive memory consumption is prevented, and the buffer it's not allowed to grow indefinitely.

We can also configure the CompressionProviders and channel-level Credentials

Specifying a retry or a hedging policy #

When configuring gRPC clients, we have the ability to specify whether a retry or a hedging policy is being used. All we need to do is create a policy and pass it as an option.

var retryPolicy = new MethodConfig
{
    Names = { MethodName.Default },
    RetryPolicy = new RetryPolicy
    {
        MaxAttempts = 5,
        InitialBackoff = TimeSpan.FromSeconds(1),
        MaxBackoff = TimeSpan.FromSeconds(0.5),
        BackoffMultiplier = 1,
        RetryableStatusCodes = { StatusCode.Internal }
    }
};


builder.Services
    .AddGrpcClient<SpeakerServiceDefinition.SpeakerServiceDefinitionClient>
    (
    o => { o.Address = new Uri("https://localhost:7226"); }
    )
     .ConfigureChannel(o =>
     {
         o.ServiceConfig = new ServiceConfig()
         {
             MethodConfigs = {
                 retryPolicy
             }
         };
      
     });

With everything configured we now can inject our client in controllers:

  private readonly SpeakerServiceDefinition.SpeakerServiceDefinitionClient speakerService;

        public HomeController(SpeakerServiceDefinition.SpeakerServiceDefinitionClient speakerService)
        {
            this.speakerService = speakerService;
        }

Now that you know how to configure gRPC clients, you can enable gRPC server reflection on your server.