Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

gRPC in ASP.NET Core

Overview Questions

  • gRPC là gì và khác REST API như thế nào?
  • Protocol Buffers (protobuf) là gì?
  • Làm sao để tạo gRPC service trong ASP.NET Core?
  • Unary, Server Streaming, Client Streaming, Bidirectional Streaming là gì?
  • Khi nào nên dùng gRPC thay vì REST?

gRPC Basics

What is gRPC?

┌─────────────────────────────────────────────────────────────────┐
│                    gRPC vs REST                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  REST:                                                          │
│  ┌──────────┐    HTTP/JSON    ┌──────────┐                     │
│  │  Client  │◀───────────────▶│  Server  │                     │
│  └──────────┘                 └──────────┘                     │
│  - Text-based (JSON)                                              │
│  - Human readable                                                 │
│  - Larger payload size                                            │
│  - Request/Response only                                          │
│                                                                 │
│  gRPC:                                                          │
│  ┌──────────┐   HTTP/2/Proto  ┌──────────┐                     │
│  │  Client  │◀───────────────▶│  Server  │                     │
│  └──────────┘   buf           └──────────┘                     │
│  - Binary (Protocol Buffers)                                    │
│  - Not human readable                                           │
│  - Smaller payload size                                         │
│  - Streaming support                                            │
│  - Strongly typed contracts                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

When to Use gRPC

┌─────────────────────────────────────────────────────────────────┐
│                    WHEN TO USE gRPC                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Use gRPC for:                                                  │
│  - Microservices communication                                  │
│  - Real-time streaming                                          │
│  - Low latency requirements                                     │
│  - Polyglot environments (multiple languages)                   │
│  - Internal service-to-service calls                            │
│                                                                 │
│  Use REST for:                                                  │
│  - Public APIs                                                  │
│  - Browser clients                                              │
│  - Simple CRUD operations                                       │
│  - When human readability matters                               │
│  - When caching is important                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Protocol Buffers

.proto File

// Protos/greet.proto
syntax = "proto3";

option csharp_namespace = "MyGrpcService";

package greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
  
  // Streaming: server sends multiple responses
  rpc SayHelloStream (HelloRequest) returns (stream HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greeting.
message HelloReply {
  string message = 1;
}

Project Configuration

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.57.0" />
  </ItemGroup>
</Project>

gRPC Service

Basic Service

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)
    {
        _logger.LogInformation("Saying hello to {Name}", request.Name);
        
        return Task.FromResult(new HelloReply
        {
            Message = $"Hello {request.Name}"
        });
    }
}

Server Configuration

// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();

var app = builder.Build();

app.MapGrpcService<GreeterService>();

app.Run();

Streaming

Server Streaming

service ChatService {
  // Server sends multiple messages
  rpc Subscribe (SubscribeRequest) returns (stream ChatMessage);
}

message SubscribeRequest {
  string channel = 1;
}

message ChatMessage {
  string user = 1;
  string message = 2;
  int64 timestamp = 3;
}
public class ChatService : ChatService.ChatServiceBase
{
    private readonly IChannelReader<ChatMessage> _channel;

    public ChatService(IChannelReader<ChatMessage> channel)
    {
        _channel = channel;
    }

    public override async Task Subscribe(
        SubscribeRequest request,
        IServerStreamWriter<ChatMessage> responseStream,
        ServerCallContext context)
    {
        await foreach (var message in _channel.ReadAllAsync(context.CancellationToken))
        {
            if (message.Channel == request.Channel)
            {
                await responseStream.WriteAsync(message);
            }
        }
    }
}

Client Streaming

service UploadService {
  // Client sends multiple messages
  rpc Upload (stream FileChunk) returns (UploadResponse);
}

message FileChunk {
  bytes data = 1;
  int32 chunk_number = 2;
}

message UploadResponse {
  string file_id = 1;
  int64 total_size = 2;
}
public class UploadService : UploadService.UploadServiceBase
{
    public override async Task<UploadResponse> Upload(
        IAsyncStreamReader<FileChunk> requestStream,
        ServerCallContext context)
    {
        var totalSize = 0L;
        var fileId = Guid.NewGuid().ToString();
        
        await foreach (var chunk in requestStream.ReadAllAsync())
        {
            totalSize += chunk.Data.Length;
            // Process chunk
        }
        
        return new UploadResponse
        {
            FileId = fileId,
            TotalSize = totalSize
        };
    }
}

Bidirectional Streaming

service ChatService {
  // Both client and server stream
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
public class ChatService : ChatService.ChatServiceBase
{
    public override async Task Chat(
        IAsyncStreamReader<ChatMessage> requestStream,
        IServerStreamWriter<ChatMessage> responseStream,
        ServerCallContext context)
    {
        // Read from client and broadcast to all
        await foreach (var message in requestStream.ReadAllAsync())
        {
            // Process and broadcast
            await responseStream.WriteAsync(new ChatMessage
            {
                User = "Server",
                Message = $"Echo: {message.Message}"
            });
        }
    }
}

gRPC Client

Setup Client

dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

Client Code

// Program.cs
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);

// Unary call
var response = await client.SayHelloAsync(
    new HelloRequest { Name = "World" });

Console.WriteLine(response.Message);

gRPC Client Factory

builder.Services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
    options.Address = new Uri("https://localhost:5001");
});

// Usage
public class MyService
{
    private readonly Greeter.GreeterClient _client;

    public MyService(Greeter.GreeterClient client)
    {
        _client = client;
    }

    public async Task<string> Greet(string name)
    {
        var response = await _client.SayHelloAsync(
            new HelloRequest { Name = name });
        return response.Message;
    }
}

Interceptors

Server Interceptor

public class LoggingInterceptor : Interceptor
{
    private readonly ILogger<LoggingInterceptor> _logger;

    public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
    {
        _logger = logger;
    }

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        _logger.LogInformation("Starting call {Method}", context.Method);
        
        try
        {
            return await continuation(request, context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in call {Method}", context.Method);
            throw;
        }
    }
}

// Register
builder.Services.AddGrpc(options =>
{
    options.Interceptors.Add<LoggingInterceptor>();
});

Best Practices

1. Use Appropriate Call Types

// ✅ Unary for simple request/response
var response = await client.SayHelloAsync(request);

// ✅ Server streaming for real-time updates
await foreach (var message in client.SubscribeAsync(request))
{
    Console.WriteLine(message.Message);
}

// ✅ Client streaming for uploads
var uploadResponse = await client.UploadAsync(requestStream);

// ✅ Bidirectional for chat
await foreach (var msg in client.ChatAsync(requestStream))
{
    // Process
}

2. Handle Errors Properly

try
{
    var response = await client.SayHelloAsync(request);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
{
    Console.WriteLine("Resource not found");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
{
    Console.WriteLine("Service unavailable");
}
catch (RpcException ex)
{
    Console.WriteLine($"gRPC error: {ex.StatusCode}");
}

3. Use Deadlines

// Set deadline for call
var deadline = DateTime.UtcNow.AddSeconds(5);
var response = await client.SayHelloAsync(
    request,
    deadline: deadline);