gRPC ASP.NET Core — Silent Leak from Missing Deadline
- gRPC is a high-performance RPC framework with strong contracts and binary serialization.
- ASP.NET Core offers first-class support for gRPC including DI, interceptors, and health checks.
- Always set deadlines and use cancellation tokens to prevent resource leaks.
- gRPC uses Protocol Buffers for binary, strongly-typed contracts
- ASP.NET Core adds first-class gRPC support via Grpc.AspNetCore
- Unary, server-streaming, client-streaming, and bidirectional streaming are the four RPC types
- Interceptors act like middleware — they inspect and modify calls at the server or client
- Production traps: deadlines not set, load balancers that don't support HTTP/2, and browser limitations
- Performance: Protobuf serialization is 3–10x faster and 3–10x smaller than JSON
gRPC Debugging Commands
Deadline exceeded errors
dotnet-counters monitor --process-id $(pid) Grpc.AspNetCore.Server.TotalCallskubectl logs -l app=my-grpc-service --tail=100 | grep -i deadlineConnection refused / UNAVAILABLE
curl -v --http2-prior-knowledge http://localhost:5001/grpc.health.v1.Health/Check 2>&1 | grep -i 'HTTP/2|error'kubectl exec -it pod-name -- wget -qO- http://localhost:5001/grpc.health.v1.Health/CheckStreaming call hangs indefinitely
dotnet-dump collect --process-id $(pid) && dotnet-dump analyze dump.dmp > threads.txtgrep -c 'Grpc.Core.AsyncClientStreamingCall\|WriteAsync\|MoveNext' threads.txtProduction Incident
var call = client.SomeMethod(request); without a cancellation token or deadline.var call = client.SomeMethod(request, deadline: DateTime.UtcNow.AddSeconds(5)); Use CancellationToken for cancellation. On the server side, enforce a maximum deadline via CallContext.Deadline or middleware.Production Debug GuideCommon symptoms and their root causes when gRPC calls fail in ASP.NET Core
grpc.invoke.latency metrics.app.UseGrpcWeb(); or set GrpcChannelOptions.ThrowOperationCanceledOnCancellation = true. Check server-side exception logs.GrpcChannel.MaxRetryBufferSize and ensure deadlines are propagated. Use CallContext.Deadline to enforce server-side timeout.Microservices are eating the enterprise, and with that comes a brutal problem: how do dozens of services talk to each other efficiently, reliably, and without turning into a web of hand-rolled HTTP clients? REST and JSON got us far, but they carry hidden costs — verbose payloads, no enforced contracts, no built-in streaming, and type information that evaporates at the boundary. At scale, these costs compound. A single inter-service call that serializes a 50-field object as JSON a million times a day quietly burns CPU, bandwidth, and developer sanity.
gRPC was built by Google to solve exactly this. It puts a strongly-typed contract — the .proto file — at the center of every conversation, uses Protocol Buffers for binary serialization that's 3–10x smaller than equivalent JSON, and rides HTTP/2 so you get multiplexing, header compression, and real bidirectional streaming for free. ASP.NET Core embraced gRPC as a first-class citizen starting with .NET 3.0, meaning you get Kestrel's raw performance, the full DI ecosystem, middleware, and health checks all wired up without ceremony.
By the end of this article you'll know how Protobuf encodes data on the wire, how to build all four gRPC communication patterns in ASP.NET Core (unary, server-streaming, client-streaming, and bidirectional), how to write interceptors that behave like middleware, and exactly which production traps — deadlines, load balancing, browser compatibility — will catch you off-guard if you don't know they're there.
What is gRPC and Why ASP.NET Core?
gRPC is a high-performance RPC framework by Google that uses Protocol Buffers for serialization and HTTP/2 for transport. ASP.NET Core provides built-in support via the Grpc.AspNetCore package, which integrates with Kestrel's HTTP/2 pipeline. Unlike REST, where you craft endpoints manually, gRPC generates client and server stubs from a shared .proto contract. This means both sides speak the same typed language — no guessing field names, no parsing JSON strings for every call. ASP.NET Core adds dependency injection, logging, and middleware (interceptors) out of the box, making it a natural home for gRPC in the .NET ecosystem.
syntax = "proto3"; option csharp_namespace = "io.thecodeforge.Greet"; package greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
- REST: each request carries its own schema (JSON). The server parses and validates every time.
- gRPC: the schema (proto) is agreed before the first call. Serialization is a binary protocol — fast and deterministic.
- This difference matters when you have 1000+ services exchanging billions of messages per day.
Protobuf Wire Format and Code Generation
Protocol Buffers (Protobuf) are the serialization engine of gRPC. Messages are defined in .proto files, then compiled into C# classes by the protoc compiler or the Grpc.Tools NuGet package. The on-the-wire format is a compact binary representation: field tags, wire types, and values. Varint encoding keeps small integers small, and fields can be omitted entirely if not set. This results in payloads often 3–10x smaller than equivalent JSON.
The code generator produces both the message classes (with Equals/GetHashCode overrides, ToString, and Clone) and the service stubs (client proxy and server base). In ASP.NET Core, you reference the generated files from your .csproj and implement the server base class. The generated client handles serialization, HTTP/2 framing, and connection pooling.
using Grpc.Core; using io.thecodeforge.Greet; namespace io.thecodeforge.Services; public class GreeterService : Greeter.GreeterBase { private readonly ILogger<GreeterService> _logger; public GreeterService(ILogger<GreeterService> logger) => _logger = logger; public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) {\n _logger.LogInformation(\"Saying hello to {Name}\", request.Name);\n return Task.FromResult(new HelloReply\n {\n Message = $\"Hello {request.Name}\"\n });\n }\n}" }
Building All Four gRPC Streaming Patterns in ASP.NET Core
gRPC defines four RPC types: unary (one request, one response), server-streaming (one request, multiple responses), client-streaming (multiple requests, one response), and bidirectional streaming (multiple requests, multiple responses). ASP.NET Core supports all four with simple async method signatures.
Unary is the most common; streaming is where gRPC shines for real-time data. Server-streaming is ideal for watch operations (e.g., monitoring stream). Client-streaming is useful for uploads or batch processing. Bidirectional streaming enables chat, live collaboration, or game state sync.
Here's a server-streaming example: a service that returns stock price updates.
using Grpc.Core; using io.thecodeforge.Stock; namespace io.thecodeforge.Services; public class StockService : Stock.StockBase { public override async Task StreamStockPrices( StockRequest request, IServerStreamWriter<StockPrice> responseStream, ServerCallContext context) {\n while (!context.CancellationToken.IsCancellationRequested)\n {\n var price = await GetCurrentPriceAsync(request.Symbol);\n await responseStream.WriteAsync(price);\n await Task.Delay(1000, context.CancellationToken);\n } } private async Task<StockPrice> GetCurrentPriceAsync(string symbol) { // Simulate price fetch await Task.Delay(100); return new StockPrice { Symbol = symbol, Price = Random.Shared.NextDouble() * 1000, Timestamp = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow) }; } }
throw an RpcException with StatusCode.Cancelled when cancelled. Not respecting cancellation leads to resource leaks.ObjectDisposedException. Always WriteAsync with CancellationToken and check it inside loops.WriteAsync and loop conditions.Interceptors — gRPC Middleware in ASP.NET Core
Interceptors are the gRPC analogue of ASP.NET Core middleware. They run before and after each RPC call on both the server and client side. Use them for cross-cutting concerns like logging, authentication, rate limiting, and metrics.
Server-side interceptors implement Interceptor and override UnaryServerHandler, ServerStreamingServerHandler, etc. Client-side interceptors work similarly. They are registered via DI and can be ordered.
A common pattern is a server interceptor that validates the JWT token from the metadata and sets a user identity for the handler.
using Grpc.Core; using Grpc.Core.Interceptors; namespace io.thecodeforge.Interceptors; public class AuthInterceptor : Interceptor { private readonly ILogger<AuthInterceptor> _logger; public AuthInterceptor(ILogger<AuthInterceptor> logger) => _logger = logger; public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(\n TRequest request,\n ServerCallContext context,\n UnaryServerMethod<TRequest, TResponse> continuation) { var authHeader = context.RequestHeaders.GetValue("authorization"); if (string.IsNullOrEmpty(authHeader)) { _logger.LogWarning("Missing authorization header"); throw new RpcException(new Status(StatusCode.Unauthenticated, "Missing auth token")); } // Validate token — omitted for brevity // var user = await ValidateTokenAsync(authHeader); // context.UserState["User"] = user; return await continuation(request, context); } }
- Middleware: can modify raw HTTP/2 frames, authentication, compression. Use for global HTTP concerns.
- Interceptors: have access to the deserialized request/response objects and metadata headers. Use for gRPC-specific logic.
- Recommendation: use middleware for authentication and rate limiting; use interceptors for business context enrichment and logging.
Production Gotchas: Deadlines, Load Balancing, and Browser Compatibility
gRPC relies on HTTP/2, which brings several production pitfalls.
- Deadlines: gRPC calls can hang forever if no deadline is set. Always set a client-side deadline (
Deadline) and respectCancellationTokenon the server. - Load Balancing: L7 load balancers need to support HTTP/2 connection multiplexing. Many legacy balancers (e.g., AWS Classic LB) don't — they break gRPC. Use Envoy, Nginx with HTTP/2, or a gRPC-aware balancer. Also, gRPC connections are typically long-lived; round-robin load balancing across connections may not evenly distribute requests. Use client-side load balancing or a service mesh.
- Browser Compatibility: gRPC-Web or gRPC JSON transcoding needed for browser clients. ASP.NET Core supports gRPC-Web via
app..UseGrpcWeb() - Health Checks: Built-in gRPC health check protocol (
grpc.health.v1.Health) enables liveness and readiness probes without custom endpoints.
var builder = WebApplication.CreateBuilder(args); builder.Services.AddGrpc(); builder.Services.AddGrpcHealthChecks(); var app = builder.Build(); app.MapGrpcHealthChecksService(); // Enable gRPC-Web for browser clients app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); app.MapGrpcService<io.thecodeforge.Services.GreeterService>(); app.Run();
| Feature | gRPC | REST/JSON | SignalR |
|---|---|---|---|
| Contract enforcement | Strong (via .proto file) | Weak (openAPI optional) | Weak (hub method signatures) |
| Payload size | Binary, 3-10x smaller than JSON | Verbose JSON | JSON (configurable) |
| Streaming | Native bidirectional | Not built-in | Bidirectional via WebSocket |
| Performance | Highest (binary + HTTP/2) | Moderate (HTTP/1.1 or 2) | High (WebSocket persistent) |
| Browser support | gRPC-Web required | Native | Native via JS client |
| Tooling & ecosystem | .NET, Go, Java, Python, etc. | Universal | .NET first-class |
🎯 Key Takeaways
- gRPC is a high-performance RPC framework with strong contracts and binary serialization.
- ASP.NET Core offers first-class support for gRPC including DI, interceptors, and health checks.
- Always set deadlines and use cancellation tokens to prevent resource leaks.
- Streaming requires careful cancellation handling to avoid disposal exceptions.
- Production deployment must account for HTTP/2 load balancing and browser gRPC-Web.
- Interceptors are powerful but can become blocking bottlenecks — keep them lightweight for streaming.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QExplain the difference between gRPC unary and streaming calls. When would you use each?Mid-levelReveal
- QHow does gRPC handle load balancing? Why is it different from REST load balancing?SeniorReveal
- QWhat are interceptors in gRPC and how do you implement them in ASP.NET Core?Mid-levelReveal
- QHow do you handle deadlines and cancellation in gRPC?JuniorReveal
Frequently Asked Questions
What is gRPC with ASP.NET Core in simple terms?
gRPC with ASP.NET Core is a way to build fast, typed APIs between services using Protocol Buffers instead of JSON. ASP.NET Core provides the server and client libraries to make it easy.
Do I need a separate proto compiler?
No. The Grpc.Tools NuGet package automatically compiles .proto files during build. Add <Protobuf Include="..." /> in your .csproj.
Can I call gRPC from a browser?
Yes, with gRPC-Web. ASP.NET Core has built-in gRPC-Web support. Alternatively, use JSON transcoding to expose REST endpoints.
Why is my gRPC call failing with UNAVAILABLE?
Likely the load balancer does not support HTTP/2, or the server is not listening on an HTTP/2 port. Check Kestrel configuration and the reverse proxy.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.