Below is a complete, runnable .NET 8 demo where:
- One server exposes:
- A REST (JSON) endpoint
- A gRPC (Protobuf) endpoint
- One console client:
- Sends N requests to REST
- Sends N requests to gRPC
- Prints total time, average latency, and payload sizes
You’ll be able to feel the difference.
1. Solution structure
We’ll build this:
RpcPerfDemo/
RpcPerfDemo.sln
RpcPerfDemo.Server/ (ASP.NET Core – REST + gRPC)
Program.cs
Services/
PerfServiceImpl.cs
Models/
RestPerfResponse.cs
Protos/
perf.proto
RpcPerfDemo.Server.csproj
RpcPerfDemo.Client/ (Console – benchmark client)
Program.cs
RpcPerfDemo.Client.csproj
Target: .NET 8.0 (works with future .NET 10+ too).
2. Step-by-step setup
✅ Step 0 – Prerequisites
- Install .NET 8 SDK
dotnet --versionMake sure it shows8.x.x. - Ensure dev HTTPS cert is trusted (only needed once):
dotnet dev-certs https --trust
✅ Step 1 – Create solution + projects
mkdir RpcPerfDemo
cd RpcPerfDemo
# Create solution
dotnet new sln -n RpcPerfDemo
# Create server (web app)
dotnet new web -n RpcPerfDemo.Server
# Create client (console app)
dotnet new console -n RpcPerfDemo.Client
# Add to solution
dotnet sln add RpcPerfDemo.Server/RpcPerfDemo.Server.csproj
dotnet sln add RpcPerfDemo.Client/RpcPerfDemo.Client.csproj
Code language: PHP (php)
✅ Step 2 – Add packages
Server packages
cd RpcPerfDemo.Server
dotnet add package Grpc.AspNetCore
dotnet add package Grpc.Tools
dotnet add package Google.Protobuf
cd ..
Code language: CSS (css)
Client packages
cd RpcPerfDemo.Client
dotnet add package Grpc.Net.Client
dotnet add package Grpc.Tools
dotnet add package Google.Protobuf
cd ..
Code language: CSS (css)
✅ Step 3 – Define the Protobuf contract
Create folder and file:
mkdir -p RpcPerfDemo.Server/Protos
Create RpcPerfDemo.Server/Protos/perf.proto:
syntax = "proto3";
option csharp_namespace = "RpcPerfDemo.Grpc";
package perf;
// Request for performance test
message PerfRequest {
int32 id = 1;
string name = 2;
}
// Response from server
message PerfResponse {
int32 id = 1;
string name = 2;
repeated int32 values = 3;
int64 processedAtTicks = 4;
}
// gRPC service
service PerfService {
rpc GetData(PerfRequest) returns (PerfResponse);
}
Code language: JavaScript (javascript)
✅ Step 4 – Wire proto into both projects
4.1 Server .csproj
Open RpcPerfDemo.Server/RpcPerfDemo.Server.csproj and make it look like this (keep your PropertyGroup as generated, just ensure these ItemGroups exist):
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="*" />
<PackageReference Include="Grpc.Tools" Version="*">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Google.Protobuf" Version="*" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\perf.proto" GrpcServices="Server" />
</ItemGroup>
</Project>
Code language: HTML, XML (xml)
Version="*"is fine – the real versions were already resolved bydotnet add package.
4.2 Client .csproj
Open RpcPerfDemo.Client/RpcPerfDemo.Client.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.Net.Client" Version="*" />
<PackageReference Include="Grpc.Tools" Version="*">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Google.Protobuf" Version="*" />
</ItemGroup>
<!-- Reuse the same proto for client stubs -->
<ItemGroup>
<Protobuf Include="..\RpcPerfDemo.Server\Protos\perf.proto"
Link="Protos\perf.proto"
GrpcServices="Client" />
</ItemGroup>
</Project>
Code language: HTML, XML (xml)
✅ Step 5 – Implement the server (REST + gRPC)
5.1 REST model
Create folder:
mkdir -p RpcPerfDemo.Server/Models
Create RpcPerfDemo.Server/Models/RestPerfResponse.cs:
namespace RpcPerfDemo.Server.Models;
public class RestPerfResponse
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public List<int> Values { get; set; } = new();
public long ProcessedAtTicks { get; set; }
}
Code language: JavaScript (javascript)
5.2 gRPC service implementation
Create folder:
mkdir -p RpcPerfDemo.Server/Services
Create RpcPerfDemo.Server/Services/PerfServiceImpl.cs:
using Grpc.Core;
using RpcPerfDemo.Grpc;
namespace RpcPerfDemo.Server.Services;
public class PerfServiceImpl : PerfService.PerfServiceBase
{
public override Task<PerfResponse> GetData(PerfRequest request, ServerCallContext context)
{
// Simulate some CPU work
var values = Enumerable.Range(0, 200).ToList();
var response = new PerfResponse
{
Id = request.Id,
Name = request.Name,
ProcessedAtTicks = DateTime.UtcNow.Ticks
};
response.Values.AddRange(values);
return Task.FromResult(response);
}
}
Code language: HTML, XML (xml)
5.3 Minimal API + gRPC configuration
Replace RpcPerfDemo.Server/Program.cs with:
using RpcPerfDemo.Server.Models;
using RpcPerfDemo.Server.Services;
var builder = WebApplication.CreateBuilder(args);
// Add gRPC services
builder.Services.AddGrpc();
var app = builder.Build();
// Map REST endpoint
app.MapGet("/rest/perf", () =>
{
// Simulate some work, similar to gRPC
var values = Enumerable.Range(0, 200).ToList();
var response = new RestPerfResponse
{
Id = 1,
Name = "REST-JSON",
Values = values,
ProcessedAtTicks = DateTime.UtcNow.Ticks
};
return Results.Ok(response);
});
// Map gRPC endpoint
app.MapGrpcService<PerfServiceImpl>();
// Optional health check endpoint
app.MapGet("/", () => "RPC Performance Demo Server (REST + gRPC)");
app.Run();
Code language: PHP (php)
ASP.NET Core will host both REST and gRPC on the same app and ports.
✅ Step 6 – Implement the benchmark client
Replace RpcPerfDemo.Client/Program.cs with:
3. Build and run
✅ Step 7 – Build everything
From the solution root (RpcPerfDemo):
dotnet build
You should see Build succeeded.
✅ Step 8 – Run the server
From the solution root:
dotnet run --project RpcPerfDemo.Server
Code language: CSS (css)
You’ll see output similar to:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
Code language: JavaScript (javascript)
- Confirm
https://localhost:5001is there. - If it prints a different HTTPS port, either:
- Change
ServerAddressconstant in the client, or - Set
ASPNETCORE_URLS:set ASPNETCORE_URLS=https://localhost:5001;http://localhost:5000 # Windows export ASPNETCORE_URLS="https://localhost:5001;http://localhost:5000" # Linux/macOS
- Change
Leave the server running.
You can quickly test the REST endpoint in a browser:
- Visit:
https://localhost:5001/rest/perf
You should see JSON like:
{
"id": 1,
"name": "REST-JSON",
"values": [...],
"processedAtTicks": 638000000000000000
}
Code language: JSON / JSON with Comments (json)
✅ Step 9 – Run the client benchmark
In a new terminal, from solution root:
dotnet run --project RpcPerfDemo.Client
Code language: CSS (css)
You should see something like:
=== RPC Performance Demo: REST vs gRPC ===
Server address: https://localhost:5001
Total requests per style: 1000
Warming up REST endpoint...
Sample REST JSON payload size: 2350 bytes
Running REST benchmark...
REST total time: 950.32 ms for 1000 requests
REST avg per request: 0.9503 ms
Warming up gRPC endpoint...
Sample gRPC Protobuf payload size: 640 bytes
Running gRPC benchmark...
gRPC total time: 480.15 ms for 1000 requests
gRPC avg per request: 0.4802 ms
Done.
Code language: JavaScript (javascript)
(Exact numbers will vary by machine.)
4. How to interpret the results
You’ll get two kinds of insights:
1️⃣ Payload size
From log:
- REST JSON payload size – e.g.
2350 bytes - gRPC Protobuf payload size – e.g.
640 bytes
👉 This shows gRPC messages are much smaller, even for the same data (id, name, 200 ints, timestamp).
Smaller payloads → less bandwidth → better performance, especially over slow networks.
2️⃣ Latency per request
You’ll see:
REST total timeandREST avg per requestgRPC total timeandgRPC avg per request
Typically:
- gRPC average per request will be lower
- Total time for N requests will also be lower
On localhost the difference may be modest, but:
- Add more iterations (
Iterations = 10000) - Or run on separate machines
- Or add more data in
values(e.g.Enumerable.Range(0, 2000))
→ you’ll see gRPC scaling better.
5. What you just experienced
- Same server, same business logic size, same data.
- REST vs gRPC differ only in:
- Data format (JSON vs Protobuf)
- Protocol (HTTP/1.1 vs HTTP/2)
- Your measurements showed:
- gRPC = smaller payloads
- gRPC = lower average latency & total time
This is exactly the impact you care about for:
- High-throughput microservices
- Telemetry, streaming, real-time APIs
- Cloud Run → EKS migration with full gRPC support
I’m a DevOps/SRE/DevSecOps/Cloud Expert passionate about sharing knowledge and experiences. I have worked at Cotocus. I share tech blog at DevOps School, travel stories at Holiday Landmark, stock market tips at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow , and SEO strategies at Wizbrand.
Do you want to learn Quantum Computing?
Please find my social handles as below;
Rajesh Kumar Personal Website
Rajesh Kumar at YOUTUBE
Rajesh Kumar at INSTAGRAM
Rajesh Kumar at X
Rajesh Kumar at FACEBOOK
Rajesh Kumar at LINKEDIN
Rajesh Kumar at WIZBRAND