Overview
gRPC is used for synchronous communication when services need immediate responses. The primary example is the Basket.API service calling Discount.Grpc to retrieve discount information for products.
Architecture Flow
Protocol Buffers Definition
The contract is defined in discount.proto:
syntax = "proto3" ;
option csharp_namespace = "Discount.Grpc" ;
package discount ;
// The discount service definition.
service DiscountProtoService {
// Discount CRUD Operations
rpc GetDiscount ( GetDiscountRequest ) returns ( CouponModel );
rpc CreateDiscount ( CreateDiscountRequest ) returns ( CouponModel );
rpc UpdateDiscount ( UpdateDiscountRequest ) returns ( CouponModel );
rpc DeleteDiscount ( DeleteDiscountRequest ) returns ( DeleteDiscountResponse );
}
message GetDiscountRequest {
string productName = 1 ;
}
message CouponModel {
int32 id = 1 ;
string productName = 2 ;
string description = 3 ;
int32 amount = 4 ;
}
message CreateDiscountRequest {
CouponModel coupon = 1 ;
}
message UpdateDiscountRequest {
CouponModel coupon = 1 ;
}
message DeleteDiscountRequest {
string productName = 1 ;
}
message DeleteDiscountResponse {
bool success = 1 ;
}
Server Implementation (Discount.Grpc)
Service Registration
In Discount.Grpc/Program.cs:
using Discount . Grpc . Data ;
using Discount . Grpc . Services ;
using Microsoft . EntityFrameworkCore ;
var builder = WebApplication . CreateBuilder ( args );
// Add services to the container.
builder . Services . AddGrpc ();
builder . Services . AddDbContext < DiscountContext >( opts =>
opts . UseSqlite ( builder . Configuration . GetConnectionString ( "Database" )));
var app = builder . Build ();
// Configure the HTTP request pipeline.
app . UseMigration ();
app . MapGrpcService < DiscountService >();
app . MapGet ( "/" , () => "Communication with gRPC endpoints must be made through a gRPC client." );
app . Run ();
Service Implementation
In Discount.Grpc/Services/DiscountService.cs:
using Discount . Grpc . Data ;
using Discount . Grpc . Models ;
using Grpc . Core ;
using Mapster ;
using Microsoft . EntityFrameworkCore ;
namespace Discount . Grpc . Services ;
public class DiscountService
( DiscountContext dbContext , ILogger < DiscountService > logger )
: DiscountProtoService . DiscountProtoServiceBase
{
public override async Task < CouponModel > GetDiscount ( GetDiscountRequest request , ServerCallContext context )
{
var coupon = await dbContext
. Coupons
. FirstOrDefaultAsync ( x => x . ProductName == request . ProductName );
if ( coupon is null )
coupon = new Coupon { ProductName = "No Discount" , Amount = 0 , Description = "No Discount Desc" };
logger . LogInformation ( "Discount is retrieved for ProductName : {productName}, Amount : {amount}" ,
coupon . ProductName , coupon . Amount );
var couponModel = coupon . Adapt < CouponModel >();
return couponModel ;
}
public override async Task < CouponModel > CreateDiscount ( CreateDiscountRequest request , ServerCallContext context )
{
var coupon = request . Coupon . Adapt < Coupon >();
if ( coupon is null )
throw new RpcException ( new Status ( StatusCode . InvalidArgument , "Invalid request object." ));
dbContext . Coupons . Add ( coupon );
await dbContext . SaveChangesAsync ();
logger . LogInformation ( "Discount is successfully created. ProductName : {ProductName}" , coupon . ProductName );
var couponModel = coupon . Adapt < CouponModel >();
return couponModel ;
}
// ... UpdateDiscount and DeleteDiscount methods
}
Client Implementation (Basket.API)
Client Registration
In Basket.API/Program.cs:
using Discount . Grpc ;
var builder = WebApplication . CreateBuilder ( args );
// Grpc Services
builder . Services . AddGrpcClient < DiscountProtoService . DiscountProtoServiceClient >( options =>
{
options . Address = new Uri ( builder . Configuration [ "GrpcSettings:DiscountUrl" ] ! );
})
. ConfigurePrimaryHttpMessageHandler (() =>
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler . DangerousAcceptAnyServerCertificateValidator
};
return handler ;
});
Configuration
In appsettings.json:
{
"GrpcSettings" : {
"DiscountUrl" : "https://localhost:5052"
}
}
Client Usage
In Basket.API/Basket/StoreBasket/StoreBasketHandler.cs:
using Discount . Grpc ;
namespace Basket . API . Basket . StoreBasket ;
public class StoreBasketCommandHandler
( IBasketRepository repository , DiscountProtoService . DiscountProtoServiceClient discountProto )
: ICommandHandler < StoreBasketCommand , StoreBasketResult >
{
public async Task < StoreBasketResult > Handle ( StoreBasketCommand command , CancellationToken cancellationToken )
{
await DeductDiscount ( command . Cart , cancellationToken );
await repository . StoreBasket ( command . Cart , cancellationToken );
return new StoreBasketResult ( command . Cart . UserName );
}
private async Task DeductDiscount ( ShoppingCart cart , CancellationToken cancellationToken )
{
// Communicate with Discount.Grpc and calculate latest prices of products
foreach ( var item in cart . Items )
{
var coupon = await discountProto . GetDiscountAsync (
new GetDiscountRequest { ProductName = item . ProductName },
cancellationToken : cancellationToken );
item . Price -= coupon . Amount ;
}
}
}
Project References
In Basket.API.csproj, the proto file is referenced:
< ItemGroup >
< Protobuf Include = "..\..\Discount\Discount.Grpc\Protos\discount.proto" GrpcServices = "Client" >
< Link > Protos\discount.proto </ Link >
</ Protobuf >
</ ItemGroup >
This generates:
Client stub classes
Message types
Serialization code
Key Benefits
gRPC uses HTTP/2 and Protocol Buffers for efficient binary serialization, making it significantly faster than REST/JSON.
The .proto contract ensures type safety across services. Compilation fails if contracts don’t match.
Both client and server code are generated from the .proto file, reducing boilerplate and potential errors.
gRPC includes deadlines, cancellation, authentication, and streaming out of the box.
Error Handling
The server throws RpcException for error cases:
if ( coupon is null )
throw new RpcException ( new Status ( StatusCode . NotFound ,
$"Discount with ProductName= { request . ProductName } is not found." ));
The client can catch these exceptions:
try
{
var coupon = await discountProto . GetDiscountAsync ( request );
}
catch ( RpcException ex ) when ( ex . StatusCode == StatusCode . NotFound )
{
// Handle not found
}
Best Practices
In production, use proper SSL certificate validation instead of DangerousAcceptAnyServerCertificateValidator.
Consider implementing retry policies using Polly for transient failures: builder . Services . AddGrpcClient < DiscountProtoService . DiscountProtoServiceClient >()
. AddPolicyHandler ( GetRetryPolicy ());
Next Steps
RabbitMQ Messaging Learn about asynchronous event-based communication
MassTransit Setup Configure MassTransit for the message broker