gRPC with ASP.NET Core
Build high-performance services with gRPC.
By EMEPublished: February 20, 2025
grpcaspnet coreperformanceprotobufmicroservices
A Simple Analogy
gRPC is like FedEx for APIs. Instead of sending regular letters (REST), you send packages with a guaranteed structure (protobuf), compressed and fast.
Why gRPC?
- Performance: 10x faster than REST (binary protocol)
- Streaming: Bidirectional communication
- Type safety: Auto-generated strong typing
- Language agnostic: Use from any language
- Low latency: Multiplexing over HTTP/2
Protocol Buffer Definition
// service.proto
syntax = "proto3";
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (OrderResponse);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc ListOrders(Empty) returns (stream Order); // Server streaming
}
message CreateOrderRequest {
string customer_id = 1;
repeated LineItem items = 2;
}
message LineItem {
string product_id = 1;
int32 quantity = 2;
float price = 3;
}
message Order {
string id = 1;
string customer_id = 2;
repeated LineItem items = 3;
float total = 4;
}
message OrderResponse {
string order_id = 1;
bool success = 2;
}
message GetOrderRequest {
string order_id = 1;
}
message Empty {
}
Server Implementation
// Generated from proto file
public class OrderService : OrderService.OrderServiceBase
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
public override async Task<OrderResponse> CreateOrder(
CreateOrderRequest request,
ServerCallContext context)
{
try
{
var order = new Order
{
Id = Guid.NewGuid().ToString(),
CustomerId = request.CustomerId,
Items = request.Items.ToList()
};
await _repository.SaveAsync(order);
return new OrderResponse
{
OrderId = order.Id,
Success = true
};
}
catch (Exception ex)
{
throw new RpcException(new Status(
StatusCode.Internal,
ex.Message));
}
}
public override async Task ListOrders(
Empty request,
IServerStreamWriter<Order> responseStream,
ServerCallContext context)
{
var orders = await _repository.GetAllAsync();
foreach (var order in orders)
{
await responseStream.WriteAsync(order);
}
}
}
// Register in Program.cs
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<OrderService>();
Client Implementation
// Create channel
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new OrderService.OrderServiceClient(channel);
// Unary call
var response = await client.CreateOrderAsync(
new CreateOrderRequest
{
CustomerId = "cust-123",
Items =
{
new LineItem { ProductId = "p1", Quantity = 2, Price = 29.99f },
new LineItem { ProductId = "p2", Quantity = 1, Price = 49.99f }
}
});
Console.WriteLine($"Created order: {response.OrderId}");
// Server streaming
var call = client.ListOrders(new Empty());
await foreach (var order in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"Order {order.Id}: {order.Total}");
}
Practical Example
public override async Task<Order> GetOrder(
GetOrderRequest request,
ServerCallContext context)
{
// Cancellation support
if (context.CancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException();
}
var order = await _repository.GetAsync(request.OrderId);
if (order == null)
{
throw new RpcException(new Status(
StatusCode.NotFound,
$"Order {request.OrderId} not found"));
}
return order;
}
Best Practices
- Proto versioning: Plan for evolution
- Error handling: Use gRPC status codes
- Timeouts: Set deadlines for calls
- Compression: Enable for large messages
- Monitoring: Track performance metrics
Related Concepts
- GraphQL for flexible queries
- REST APIs for compatibility
- HTTP/2 multiplexing
- Load balancing gRPC
Summary
gRPC provides high-performance, type-safe communication using protocol buffers. Master it for microservices, real-time streaming, and language-agnostic APIs.