
Learn gRPC in .NET with Protocol Buffers to build fast, type-safe APIs. Step-by-step C# tutorial with code examples and best practices. Start building today!
If you're building microservices or high-throughput APIs in 2026, gRPC .NET is one of the most powerful tools in the .NET ecosystem. Built on HTTP/2 and Protocol Buffers, gRPC delivers smaller payloads, faster serialization, and strongly-typed contracts that REST APIs simply can't match. In this gRPC C# tutorial, you'll learn how to build a high-performance gRPC service in ASP.NET Core from scratch, understand Protocol Buffers, and apply production-ready best practices.
What Is gRPC in .NET and Why Should You Use It?
gRPC (gRPC Remote Procedure Calls) is an open-source, contract-first RPC framework originally developed by Google. In the .NET world, Grpc.AspNetCore provides first-class, fully managed support. Instead of sending verbose JSON over HTTP/1.1, gRPC .NET serializes data using Protocol Buffers (protobuf) — a compact binary format — and streams it over HTTP/2.
The result is dramatic: benchmarks consistently show gRPC payloads that are 30–60% smaller than equivalent JSON and serialization that's significantly faster. For service-to-service communication inside a microservices architecture, this matters at scale.
- Strongly-typed contracts: Your
.protofile is the single source of truth for client and server. - HTTP/2 by default: Multiplexing, header compression, and bidirectional streaming.
- Cross-platform & polyglot: Generate clients for C#, Go, Java, Python, and more from the same contract.
- Built-in code generation: No hand-written DTOs or serialization logic.
gRPC vs REST: When to Choose Each
A common search among developers is gRPC vs REST — and the honest answer is that they solve different problems. REST with JSON remains the right choice for public-facing APIs, browser clients, and anything that benefits from human-readable payloads. gRPC shines for internal, high-performance scenarios.
- Choose gRPC for: microservice-to-microservice calls, low-latency real-time systems, streaming data, and polyglot back ends.
- Choose REST for: public APIs, browser-based apps without a proxy, and cases where caching and broad tooling matter.
Why the difference? Browsers can't speak raw gRPC over HTTP/2 trailers without gRPC-Web, so for front-end clients you'll need an extra layer. Internally, where you control both ends, gRPC's efficiency wins.
Understanding Protocol Buffers in C#
Protocol Buffers are the contract language of gRPC. You define your service and message types in a .proto file, and the tooling generates the C# classes for you. This contract-first approach is what makes Protocol Buffers C# development so robust — breaking changes are caught at compile time.
syntax = "proto3";
option csharp_namespace = "ProductApi";
package product;
// The product service definition.
service ProductService {
rpc GetProduct (ProductRequest) returns (ProductReply);
rpc ListProducts (ProductFilter) returns (stream ProductReply);
}
message ProductRequest {
int32 id = 1;
}
message ProductFilter {
string category = 1;
}
message ProductReply {
int32 id = 1;
string name = 2;
double price = 3;
bool in_stock = 4;
}
Notice the numbered field tags (= 1, = 2). These tags — not the field names — are what protobuf uses on the wire. Never reuse or change an existing tag number, because doing so breaks backward compatibility with deployed clients.
How to Build a gRPC Service in .NET (Step by Step)
Let's walk through building a working gRPC service in ASP.NET Core. This is the core of any how to build gRPC service in .NET search, so we'll keep it practical and runnable.
Step 1: Create the Project
// Create a new gRPC service from the built-in template
dotnet new grpc -o ProductApi
cd ProductApi
The template wires up the Grpc.AspNetCore package and a sample .proto file. Replace the sample proto with the product.proto above, placed in a Protos folder.
Step 2: Register the Proto in the Project File
<ItemGroup>
<Protobuf Include="Protos\product.proto" GrpcServices="Server" />
</ItemGroup>
When you build, the tooling generates a ProductService.ProductServiceBase class you inherit from. Set GrpcServices="Client" for a client project, or "Both" when a project acts as both.
Step 3: Implement the Service
using Grpc.Core;
using ProductApi;
namespace ProductApi.Services;
public class ProductServiceImpl : ProductService.ProductServiceBase
{
private readonly ILogger<ProductServiceImpl> _logger;
public ProductServiceImpl(ILogger<ProductServiceImpl> logger)
{
_logger = logger;
}
// Unary call: one request, one response
public override Task<ProductReply> GetProduct(
ProductRequest request, ServerCallContext context)
{
_logger.LogInformation("Fetching product {Id}", request.Id);
var reply = new ProductReply
{
Id = request.Id,
Name = "Mechanical Keyboard",
Price = 129.99,
InStock = true
};
return Task.FromResult(reply);
}
// Server streaming: one request, many responses
public override async Task ListProducts(
ProductFilter request,
IServerStreamWriter<ProductReply> responseStream,
ServerCallContext context)
{
for (int i = 1; i <= 5; i++)
{
if (context.CancellationToken.IsCancellationRequested)
break;
await responseStream.WriteAsync(new ProductReply
{
Id = i,
Name = $"{request.Category} item {i}",
Price = 19.99 * i,
InStock = i % 2 == 0
});
}
}
}
Step 4: Map the Service in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc(options =>
{
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
options.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4 MB
});
var app = builder.Build();
app.MapGrpcService<ProductServiceImpl>();
app.MapGet("/", () => "Use a gRPC client to talk to this service.");
app.Run();
That's a complete, production-shaped ASP.NET Core gRPC server. Note we enable detailed errors only in development — leaking stack traces to clients in production is a security risk.
Building a gRPC Client in C#
Consuming the service is just as type-safe. Reference the same .proto (with GrpcServices="Client") and call it like a local method.
using Grpc.Net.Client;
using ProductApi;
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new ProductService.ProductServiceClient(channel);
// Unary call
var product = await client.GetProductAsync(new ProductRequest { Id = 1 });
Console.WriteLine($"{product.Name}: ${product.Price}");
// Server streaming call
using var call = client.ListProducts(new ProductFilter { Category = "Gadgets" });
await foreach (var item in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"Streamed: {item.Name} (in stock: {item.InStock})");
}
In real apps, don't create a new GrpcChannel per call — channels are expensive and meant to be reused. Register a gRPC client factory instead, which handles connection pooling and resilience.
builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(o =>
{
o.Address = new Uri("https://localhost:7042");
});
gRPC .NET Best Practices for Production
Intermediate and senior developers searching for gRPC best practices should pay close attention here — these are the lessons that separate a demo from a resilient service.
- Reuse channels and use the client factory. Creating channels repeatedly causes connection churn and degrades performance.
- Always propagate deadlines. Set a deadline on every call so hung servers don't pile up requests:
client.GetProductAsync(request, deadline: DateTime.UtcNow.AddSeconds(5)). - Honor cancellation tokens in streaming methods via
context.CancellationTokento free resources when clients disconnect. - Never change or reuse field tags in
.protofiles. Add new fields with new tag numbers and mark removed onesreserved. - Return rich status codes. Throw
RpcExceptionwith the correctStatusCode(e.g.NotFound,InvalidArgument) rather than generic failures. - Enable TLS. gRPC over HTTP/2 expects transport security; use HTTPS in production.
- Use interceptors for cross-cutting concerns like logging, authentication, and metrics instead of repeating code in every method.
// Throwing a meaningful gRPC error
if (product is null)
{
throw new RpcException(
new Status(StatusCode.NotFound, $"Product {request.Id} not found"));
}
Common Pitfalls to Avoid
Even experienced teams trip over these when adopting gRPC .NET:
- Trying to call gRPC directly from a browser. Browsers can't use the raw protocol — you need gRPC-Web and a configured middleware.
- Forgetting HTTP/2 support. Some older proxies and load balancers default to HTTP/1.1 and silently break gRPC. Verify end-to-end HTTP/2.
- Sending huge messages. gRPC is optimized for many small messages, not multi-megabyte blobs. Use streaming for large data sets.
- Ignoring versioning. Treat your
.protoas a public API contract and evolve it additively.
Conclusion: Key Takeaways
gRPC .NET gives you a fast, strongly-typed, contract-first way to build high-performance APIs with Protocol Buffers. By defining your service in a .proto file, ASP.NET Core generates the boilerplate for both server and client, letting you focus on business logic instead of serialization.
Here's what to remember from this gRPC C# tutorial:
- Use gRPC for internal, high-throughput microservices and REST for public, browser-facing APIs.
- Protocol Buffers deliver smaller, faster payloads than JSON — but never reuse field tags.
- Reuse channels, set deadlines, honor cancellation, and use the gRPC client factory in production.
- Return proper
RpcExceptionstatus codes and secure everything with TLS.
Start small: convert one chatty internal REST endpoint to gRPC, measure the latency and payload improvements, and expand from there. Once you experience the type safety and performance of ASP.NET Core gRPC, you'll reach for it on every microservices project.
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment