Isaac.

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

  1. Proto versioning: Plan for evolution
  2. Error handling: Use gRPC status codes
  3. Timeouts: Set deadlines for calls
  4. Compression: Enable for large messages
  5. 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.