gRPC services can be surprisingly slow if you don’t configure HTTP/2 correctly, even though it’s the protocol they rely on.

Let’s see gRPC in action. Imagine a simple Greeter service.

syntax = "proto3";

package greet;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

In C#, this translates to a server implementation:

using Grpc.Core;
using System.Threading.Tasks;

public class GreeterService : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" });
    }
}

And a client:

using Grpc.Net.Client;

// ... inside a method ...
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine($"Greeting: {response.Message}");

To run this, you’d set up the server with app.MapGrpcService<GreeterService>(); in your Program.cs and ensure your Kestrel server is configured for HTTPS. The client then connects to that HTTPS endpoint.

The core problem gRPC solves is efficient, high-performance inter-service communication, a stark contrast to the overhead of traditional REST/JSON over HTTP/1.1. It uses Protocol Buffers (protobuf) for efficient serialization and HTTP/2 for multiplexing, header compression, and server push. This means fewer connections, less bandwidth, and faster request/response cycles, especially in microservice architectures.

The mental model for gRPC is a strongly-typed contract defined in a .proto file. This contract acts as the single source of truth for both the client and server, generating C# classes for messages and service interfaces. The server implements the service interface, and the client uses the generated client stubs to call methods on the server. Under the hood, gRPC handles the serialization, deserialization, and network transport using HTTP/2.

When you configure Kestrel for gRPC, you’re essentially telling it to listen for HTTP/2 connections. The default ASP.NET Core configuration often prioritizes HTTP/1.1 for compatibility. For gRPC to perform optimally, you must ensure HTTP/2 is enabled and preferred. This involves setting up TLS certificates (even self-signed for development) because HTTP/2 over TLS is mandatory for most gRPC implementations in .NET.

The most surprising thing is how often the Grpc.Net.Client library can fall back to HTTP/1.1 if the server isn’t configured for HTTP/2, even if you’ve explicitly told it to use https://. The client library is quite smart and will attempt to negotiate the best protocol. If the server doesn’t advertise HTTP/2 support (e.g., by not having app.UseHttpsRedirection() or a proper Kestrel HTTPS endpoint), the client will happily downgrade to HTTP/1.1, and your gRPC calls will feel sluggish and inefficient, defeating the purpose of using gRPC in the first place.

The next conceptual hurdle is understanding how to handle streaming scenarios, both client-side and server-side.

Want structured learning?

Take the full Csharp course →