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
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services — all in one place.
Explore Hospitals
This blog provides a clear comparison of gRPC and REST in .NET applications, helping readers understand how communication choices can impact performance, scalability, and system design. The explanation of protocol behavior, serialization, and network efficiency offers valuable insight for architects and developers working on distributed systems. By focusing on practical performance considerations rather than theory alone, the article makes it easier to choose the right approach based on real use cases. Overall, it is a helpful resource for teams aiming to build efficient, reliable, and well-designed service-to-service communication in modern .NET environments.
This article provides a clear, insightful comparison between gRPC and REST in the .NET world, especially highlighting how gRPC’s binary protocol and strong typing can dramatically reduce latency and improve throughput compared to traditional REST/JSON calls. The discussion around serialization overhead, connection reuse, and how gRPC integrates with .NET’s async ecosystem makes it evident why many performance-sensitive applications — microservices, real-time systems, or high-volume APIs — benefit from gRPC’s efficiency. The examples and performance impact analysis help readers make realistic decisions about when to choose gRPC over REST, depending on their use-case. Overall — a very practical and informative guide for any .NET team deciding between RPC and REST approaches.