Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sentry launches MCP monitoring tool

      August 14, 2025

      10 Benefits of Hiring a React.js Development Company (2025–2026 Edition)

      August 13, 2025

      From Line To Layout: How Past Experiences Shape Your Design Career

      August 13, 2025

      Hire React.js Developers in the US: How to Choose the Right Team for Your Needs

      August 13, 2025

      I’ve tested every Samsung Galaxy phone in 2025 – here’s the model I’d recommend on sale

      August 14, 2025

      Google Photos just put all its best editing tools a tap away – here’s the shortcut

      August 14, 2025

      Claude can teach you how to code now, and more – how to try it

      August 14, 2025

      One of the best work laptops I’ve tested has MacBook written all over it (but it’s even better)

      August 14, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Controlling Execution Flow with Laravel’s Sleep Helper

      August 14, 2025
      Recent

      Controlling Execution Flow with Laravel’s Sleep Helper

      August 14, 2025

      Generate Secure Temporary Share Links for Files in Laravel

      August 14, 2025

      This Week in Laravel: Filament 4, Laravel Boost, and Junie Review

      August 14, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      KDE Plasma 6 on Wayland: the Payoff for Years of Plumbing

      August 14, 2025
      Recent

      KDE Plasma 6 on Wayland: the Payoff for Years of Plumbing

      August 14, 2025

      FOSS Weekly #25.33: Debian 13 Released, Torvalds vs RISC-V, Arch’s New Tool, GNOME Perfection and More Linux Stuff

      August 14, 2025

      Ultimate ChatGPT-5 Prompt Guide: 52 Ideas for Any Task

      August 14, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Get Started with ASP.NET Core and gRPC: A Handbook for Developers

    How to Get Started with ASP.NET Core and gRPC: A Handbook for Developers

    August 13, 2025

    In today’s distributed computing landscape, efficient service-to-service communication is crucial for building scalable, high-performance applications. gRPC (Google Remote Procedure Call) has emerged as one of the most powerful frameworks for creating robust, type-safe APIs that can handle thousands of requests per second with minimal latency.

    gRPC is a modern, open-source RPC framework that leverages HTTP/2, Protocol Buffers, and advanced streaming capabilities to deliver exceptional performance. Unlike traditional REST APIs, gRPC offers strongly-typed contracts, automatic code generation, and built-in support for multiple programming languages. This makes it an ideal choice for microservices architectures and cross-platform development.

    In this handbook, I’ll take you on a journey from absolute beginner to building production-ready gRPC services with ASP.NET Core. Whether you’re migrating from REST APIs or starting fresh with gRPC, this guide will provide you with practical, hands-on experience and real-world examples.

    What you’ll learn:

    • How to set up your first gRPC service in .NET

    • How to define service contracts with Protocol Buffers

    • How to implement unary, server streaming, and client streaming operations

    • How to build CRUD (Create, Read, Update, Delete) operations

    Let’s dive in and discover how gRPC can revolutionize your API development experience!

    You can find all the code in this GitHub Repository.

    Table of Contents

    1. gRPC Overview and How It Works with .NET

    2. How to Set Up gRPC with .NET

    3. How to Create the Product Model

    4. How to Set Up the SQLite Database

    5. How to Create Product Protocol Buffers

    6. How to Implement CRUD Operations Services with gRPC

    7. How to Implement gRPC CRUD Database Operations With SQLite

    8. How to Test gRPC Services with Postman

    9. How to Test Product Creation

    10. How to Test All Product Operations

    11. Conclusion

    Perquisites

    Before we start, make sure you have the following installed:

    • .NET SDK

    • Visual Studio Code

    • Postman

    gRPC Overview and How It Works with .NET

    gRPC is a high-performance, cross-platform framework that works seamlessly with many technologies, including .NET Core.

    Why choose gRPC with .NET?

    There are many reasons why this is a good combination. First, of all, this combo is up to 8x faster than using REST APIs with JSON. Its strongly-typed contracts also help prevent runtime errors.

    It also has built-in support for client, server, and bidirectional streaming, as well as seamless integration across different languages and platforms. Finally, it leverages HTTP/2 for multiplexing and header compression – so as you can see, these two tools are a super effective pair.

    To understand in more detail why gRPC is so valuable, let’s explore a common real-world scenario.

    The Challenge: Microservices Communication

    Imagine you’re building a large e-commerce application. For better maintainability and scalability, you decide to split your monolithic application into smaller, focused services:

    • Product Service – Handles product catalog, inventory, and product management

    • Authentication Service – Manages user authentication, authorization, and user profiles

    These services need to communicate with each other frequently. For example, before a user can add a product to their cart, the Product Service must verify with the Authentication Service that the user is logged in and has the proper permissions.

    Traditional Approach: HTTP REST APIs

    Traditionally, in .NET applications, we solve this inter-service communication using HttpClient to make REST API calls between services. While this works, it comes with several challenges:

    • Network failures: API calls can fail unexpectedly, even when everything appears correct

    • Performance bottlenecks: JSON serialization/deserialization adds overhead

    • Slow response times: HTTP/1.1 limitations affect performance under high load

    • Type safety: No compile-time contract validation between services

    • Verbose payloads: JSON can be bulky compared to binary formats

    The gRPC Solution

    This is where gRPC shines. It addresses these challenges by providing some really helpful features in addition to the ones we’ve already discussed above like protocol buffers, code generation for client and server, and more.

    When to Use gRPC in .NET

    gRPC is particularly beneficial in certain scenarios, but it’s not a great choice in others. Here are some example use cases, as well as some to avoid:

    ✅ Perfect for:

    • Microservices architecture: High-frequency service-to-service communication

    • Real-time applications: Chat applications, live updates, gaming

    • High-performance APIs: When speed and efficiency are critical

    • Polyglot environments: Services written in different programming languages

    • Internal APIs: Backend services that don’t need browser compatibility

    ❌ Consider Alternatives When:

    • Browser-based applications: Limited browser support (use gRPC-Web instead)

    • Public APIs: REST might be more familiar to external developers

    • Simple CRUD operations: Where REST’s simplicity is sufficient

    • Legacy system integration: When existing systems only support HTTP/1.1

    gRPC vs REST: A Quick Comparison

    Here’s a quick side-by-side comparison of their main features:

    FeaturegRPCREST
    ProtocolHTTP/2HTTP/1.1
    Data FormatProtocol Buffers (Binary)JSON (Text)
    PerformanceHighModerate
    Browser SupportLimited (needs gRPC-Web)Full
    StreamingBuilt-inLimited
    Code GenerationAutomaticManual

    In this handbook, we’ll build a complete Product Management system using gRPC with .NET, demonstrating how to implement efficient service-to-service communication with full CRUD operations.

    How to Set Up gRPC with .NET

    In this tutorial, we’ll use Visual Studio Code to build our complete gRPC application. Let’s start by creating a new gRPC project using the .NET CLI.

    Creating Your First gRPC Project

    Start by opening your terminal (you can use VS Code’s integrated terminal or your system terminal) and navigate to your desired directory where you want to create the project.

    Run the following command to create a new gRPC project:

    dotnet new grpc -o ProductGrpc
    

    What this command does:

    • dotnet new grpc creates a new project using the gRPC template

    • -o ProductGrpc specifies the output directory name for our project

    Next, navigate into the project directory:

    cd ProductGrpc
    

    Then open the project in Visual Studio Code:

    code .
    

    Understanding the Project Structure

    After running the command, you should see output similar to the following in your terminal, confirming that the project was created successfully:

    Vs Code  Initial  Project Structure

    Let’s explore what the .NET gRPC template has generated for us:

    ProductGrpc/
    ├── Protos/
    │   └── greet.proto          # Protocol Buffer definition file
    ├── Services/
    │   └── GreeterService.cs    # Sample gRPC service implementation
    ├── Program.cs               # Application entry point
    ├── ProductGrpc.csproj       # Project file
    └── appsettings.json         # Configuration file
    

    Key files:

    • Protos/greet.proto: Defines the service contract using Protocol Buffers

    • Services/GreeterService.cs: Contains the actual service implementation

    • Program.cs: Configures and starts the gRPC server

    • ProductGrpc.csproj: Contains project dependencies and build settings

    Verifying the Setup

    Let’s make sure everything is working correctly by running the default application:

    dotnet run
    

    You should see output indicating that the gRPC server is running:

    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: https://localhost:7042
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    

    🎉 Congratulations! You’ve successfully created your first gRPC application using the .NET CLI. The server is now running and ready to accept gRPC requests.

    Let’s move on to the next section, where we’ll start building our Product Management system.

    How to Create the Product Model

    Now that we have our gRPC project set up, let’s create our Product model. In .NET applications, models represent the data structure and business entities that our application will work with. Think of models as blueprints that define what properties our data objects should have.

    Understanding Models in gRPC Applications

    Models serve several important purposes:

    • Data structure: They define the shape and properties of our business entities.

    • Type safety: They ensure compile-time validation of our data.

    • Business logic: They represent real-world objects in our application.

    • Database mapping: They serve as entities for database operations.

    Creating the Models Folder

    Let’s organize our code by creating a dedicated folder for our models called Models in your project root directory.

    Inside the Models folder, create a new file called Product.cs.

    Your project structure should now look like this:

    ProductGrpc/
    ├── Models/
    │   └── Product.cs           # Our new Product model
    ├── Protos/
    ├── Services/
    └── ...
    

    Implementing the Product Model

    Add the following code to your Product.cs file:

    
    // Models/Product.cs
    using System.ComponentModel.DataAnnotations;
    
    namespace ProductGrpc.Models
    {
        public class Product
        {
    
            public Guid Id { get; set; }
            public required string Name { get; set; }
            public required string Description { get; set; }
            public decimal Price { get; set; }
    
            public DateTime Created { get; set; } = DateTime.UtcNow;
    
            public DateTime Updated { get; set; } = DateTime.UtcNow;
            public string? Tags { get; set; }
        }
    }
    

    Modern C# features:

    • required keyword: Ensures properties must be initialized when creating an object

    • string?: Nullable reference type for optional properties

    • Default values: Created and Updated automatically set to the current UTC

    Why Use Guid for ID?

    We’re using Guid instead of int for our primary key for a few reasons:

    • Uniqueness: Guaranteed to be unique across different systems

    • Security: Harder to guess than sequential integers

    • Distributed systems: No need for centralized ID generation

    • Scalability: Perfect for microservices architecture

    Namespace Considerations

    Important Note: If you changed your project name when creating it, make sure your namespace matches your project name. For example:

    • If your project is named MyProductService, use namespace MyProductService.Models

    • If your project is named ProductGrpc, use namespace ProductGrpc.Models

    🎉 Excellent work! You’ve successfully created your first business model that will serve as the foundation for our entire gRPC application.

    Next Steps

    Now that we have our Product model ready, let’s move on to setting up SQLite as our database and configuring Entity Framework Core to handle our data persistence. This will allow us to store and retrieve our Product data efficiently.

    How to Set Up the SQLite Database

    To persist our product data, we need a database that can handle our CRUD (Create, Read, Update, Delete) operations efficiently. We’ll use SQLite for this tutorial because it’s lightweight, requires no separate server installation, and works perfectly for developing small-to-medium applications.

    Installing the Required Packages

    Before we create our database context, we need to install the necessary Entity Framework Core packages. Open your terminal and make sure you’re in the root directory of your project, then run these commands:

    dotnet add package Microsoft.EntityFrameworkCore.Design
    
    dotnet add package Microsoft.EntityFrameworkCore.Sqlite
    

    What these packages do:

    • Microsoft.EntityFrameworkCore.Design provides design-time tools for EF Core (migrations, scaffolding)

    • Microsoft.EntityFrameworkCore.SQLite is a SQLite database provider for Entity Framework Core

    You should see output confirming the packages were added successfully:

    info : PackageReference for 'Microsoft.EntityFrameworkCore.Design' version 'x.x.x' added to file 'ProductGrpc.csproj'.
    info : PackageReference for 'Microsoft.EntityFrameworkCore.Sqlite' version 'x.x.x' added to file 'ProductGrpc.csproj'.
    

    Creating the Database Context

    Now let’s create our database context, which acts as a bridge between our .NET objects and the database.

    First, create a new folder called Data In your project root. Inside the Data folder, create a file called AppDbContext.cs.

    Your project structure should now look like this:

    ProductGrpc/
    ├── Data/
    │   └── AppDbContext.cs      # Our new database context
    ├── Models/
    │   └── Product.cs
    ├── Protos/
    ├── Services/
    └── ...
    

    Add the following code to your AppDbContext.cs file:

    
    // Data/AppDbContext.cs
    using Microsoft.EntityFrameworkCore;
    using ProductGrpc.Models;
    
    namespace ProductGrpc.Data
    {
        public class AppDbContext : DbContext
        {
            public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
            {
            }
    
            public DbSet<Product> Products { get; set; }
        }
    }
    

    Let’s understand the key components of DbContext:

    • Constructor: Accepts DbContextOptions for configuration (connection string, provider, and so on)

    • DbSet Products: Represents the Products table in our database

    Registering the Database Context

    Now we need to register our AppDbContext With the dependency injection container so our application can use it.

    Open your Program.cs file and add the database configuration:

    // Program.cs
    
    using ProductGrpc.Data;
    using ProductGrpc.Services;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddDbContext<AppDbContext>(opt=> 
        opt.UseSqlite("Data Source=ProductGrpc.db "));
    // Add services to the container.
    builder.Services.AddGrpc();
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    app.MapGrpcService<GreeterService>();
    app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
    
    app.Run();
    
    
    app.Run();
    

    Data Source=ProductGrpc.db creates a SQLite database file named ProductGrpc.db in your project directory.

    Creating and Running Migrations

    Now we need to create a migration to generate the database schema based on our Product model.

    Start by creating the initial migration:

    dotnet ef migrations add InitialCreate
    

    This command will:

    • Analyze your models and DbContext

    • Generate migration files in a Migrations folder

    • Create SQL commands needed to build your database schema

    You should see output like this:

    Build succeeded.
    Done. To undo this action, use 'dotnet ef migrations remove'
    

    Apply the migration to create the database:

    dotnet ef database update
    

    This command will:

    • Execute the migration SQL commands

    • Create the ProductGrpc.db file in your project directory

    • Set up the Products table with all the correct columns

    You should see output confirming the database was created:

    Build succeeded.
    Applying migration '20240101000000_InitialCreate'.
    Done.
    

    Verifying the Setup

    After running the migration, you should see:

    1. A new Migrations folder in your project with migration files

    2. A ProductGrpc.db file in your project root (this is your SQLite database)

    3. No errors in the terminal output

    Your project structure should now look like this:

    ProductGrpc/
    ├── Data/
    │   └── AppDbContext.cs
    ├── Migrations/
    │   ├── 20240101000000_InitialCreate.cs
    │   └── AppDbContextModelSnapshot.cs
    ├── Models/
    │   └── Product.cs
    ├── ProductGrpc.db            # Your SQLite database file
    └── ...
    

    Congratulations! You’ve successfully installed Entity Framework Core packages, created a database context, registered the context with dependency injection, generated and applied your first migration, and created a working SQLite database. Whew!

    What’s Next?

    Now that our database is set up and ready, we can move on to creating our Protocol Buffer definitions (.proto files) and implementing our gRPC services for CRUD operations.

    How to Create Product Protocol Buffers

    Protocol Buffers (protobuf) are the heart of gRPC communication. They define the structure of your data and services in a language-neutral way, which then gets compiled into native C# code. Protocol Buffers use the efficient HTTP/2 protocol, making service-to-service communication fast and reliable.

    Understanding Protocol Buffers vs REST APIs

    To better understand Protocol Buffers, let’s compare them to what you might already know from REST API development.

    In REST API development, you typically define your API endpoints using controllers and action methods. The contract between client and server is often documented separately (like with OpenAPI/Swagger), and there’s no compile-time guarantee that your documentation matches your actual implementation.

    [ApiController]
    [Route("api/[controller]")]
    public class ProductsController : ControllerBase
    {
        [HttpGet("{id}")]
        public async Task<ActionResult<ProductDto>> GetProduct(int id) { ... }
    

    With gRPC, the service contract is defined first in the .proto file using the service keyword. This contract becomes the single source of truth, and both client and server code are generated from it, ensuring they’re always in sync.

    service ProductService {
      rpc GetProduct(GetProductRequest) returns (GetProductResponse);
    }
    

    Data Transfer and Serialization

    REST APIs typically use JSON for data transfer, which is human-readable and widely supported. But JSON is text-based, which has a few negatives. First, it has larger payload sizes due to text encoding. It also comes with some runtime parsing overhead. It doesn’t have any built-in schema validation, and there’s a high potential for typos in field names

    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "name": "Wireless Headphones",
      "price": 99.99
    }
    

    gRPC instead uses Protocol Buffers, which serialize data into a compact binary format. This provides significantly smaller payloads (up to 6x smaller than JSON), faster serialization/deserialization, strong typing with compile-time validation, and schema evolution without breaking changes.

    Transport Protocol Differences

    REST APIs run over HTTP/1.1, which has some limitations:

    • One request-response cycle per connection

    • Text-based headers (larger overhead)

    • No built-in multiplexing

    • Limited streaming capabilities

    gRPC leverages HTTP/2, which offers some advantages:

    • Multiplexing: Multiple requests over a single connection

    • Header compression: Reduced overhead with HPACK

    • Server push: The Server can initiate streams to clients

    • Flow control: Better handling of slow consumers

    Data Structure Definitions

    In REST APIs, you define DTOs (Data Transfer Objects) as regular classes:

    public class ProductDto
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
    

    These DTOs exist only in your specific language and need manual synchronization across different services or languages.

    In gRPC, you define Messages in the proto file:

    message ProductModel {
      string id = 1;
      string name = 2;
      double price = 3;
    }
    

    These message definitions are language-agnostic and automatically generate equivalent classes in any supported programming language.

    Here’s a quick comparison table to summarize these differences:

    Service Contracts and Interfaces Service Contracts and Interfaces REST API ConceptgRPC EquivalentPurpose
    InterfaceServiceDefines available operations
    DTO (Data Transfer Object)MessageDefines a data structure
    JSON Request/ResponseBinary Protocol BufferData serialization format
    HTTP/1.1HTTP/2Transport protocol

    Creating the Product Proto File

    Navigate to the Protos folder in your project and create a new file called product.proto. Make sure the file extension is .proto.

    Your project structure should look like this:

    ProductGrpc/
    ├── Protos/
    │   ├── greet.proto          # Default template file
    │   └── product.proto        # Our new proto file
    └── ...
    

    Setting Up the Proto File Header

    Add the following header to your product.proto file:

    
    //  Protos/product.proto
    syntax = "proto3";
    
    option csharp_namespace = "ProductGrpc";
    
    package product;
    

    Here’s what’s going on:

    • syntax = "proto3": Specifies that we’re using Protocol Buffers version 3

    • option csharp_namespace = "ProductGrpc": Sets the C# namespace for generated code

    • package product: Defines the protobuf package name

    Note: If you named your project differently, make sure the csharp_namespace matches your project name.

    Defining the Product Service

    In gRPC, services define the available operations (similar to interfaces in REST APIs). Add the following service definition:

    // :Protos/product.proto
    service  ProductsServiceProto {
      rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse);
      rpc GetProduct(GetProductRequest) returns (GetProductResponse);
      rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
      rpc UpdateProduct(UpdateProductRequest) returns (UpdateProductResponse);
      rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse);
    }
    

    Service methods explained:

    • rpc: Defines a remote procedure call

    • CreateProduct: Method name

    • (CreateProductRequest): Input message type

    • returns (CreateProductResponse): Output message type

    Defining Protocol Buffer Messages

    Messages in gRPC are equivalent to DTOs in REST APIs. They define the structure of data being exchanged. Let’s create all the messages we need:

    Product Model Message:

    // product.proto
    message ProductModel {
      string id = 1;
      string name = 2;
      string description = 3;
      double price = 4;
      string created_at = 5;
      string updated_at = 6;
      string tags = 7;
    }
    

    Create Operation Messages:

    // Protos/product.proto
    message CreateProductRequest {
      string name = 1;
      string description = 2;
      double price = 3;
      string tags = 4;
    }
    
    message CreateProductResponse {
      bool success = 1;
      string message = 2;
      ProductModel product = 3;
    }
    

    Read Operation Messages:

    // Protos/product.proto
    message GetProductRequest {
      string id = 1;
    }
    
    message GetProductResponse {
      bool success = 1;
      string message = 2;
      ProductModel product = 3;
    }
    
    message ListProductsRequest {
      int32 page = 1;
      int32 page_size = 2;
    }
    
    message ListProductsResponse {
      bool success = 1;
      string message = 2;
      repeated ProductModel products = 3;
      int32 total_count = 4;
    }
    

    Update Operation Messages:

     // Protos/product.proto
    message UpdateProductRequest {
      string id = 1;
      string name = 2;
      string description = 3;
      double price = 4;
      string tags = 5;
    }
    
    message UpdateProductResponse {
      bool success = 1;
      string message = 2;
      ProductModel product = 3;
    }
    

    Delete Operation Messages:

    // Protos/product.proto
    message DeleteProductRequest {
      string id = 1;
    }
    
    message DeleteProductResponse {
      bool success = 1;
      string message = 2;
    }
    

    Understanding Protocol Buffer Syntax

    There are a few key concepts you should understand about how protocol buffers work:

    • Field Numbers: Each field has a unique number (for example, = 1, = 2) used for binary encoding

    • Field Types: string, int32, double, bool are common scalar types

    • repeated: Indicates an array/list (for example, repeated ProductModel products)

    • Message Nesting: Messages can contain other messages (for example, ProductModel product)

    Keep in mind that field numbers must be unique within a message, field numbers 1-15 use 1 byte encoding (more efficient), and you should never reuse field numbers (for backward compatibility).

    Complete Product Proto File

    Here’s your complete product.proto file:

    // Protos/product.proto
    syntax = "proto3";
    
    option csharp_namespace = "ProductGrpc";
    
    package product;
    
    // Product service definition
    service  ProductsServiceProto {
      rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse);
      rpc GetProduct(GetProductRequest) returns (GetProductResponse);
      rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
      rpc UpdateProduct(UpdateProductRequest) returns (UpdateProductResponse);
      rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse);
    }
    
    // Product model message
    message ProductModel {
      string id = 1;
      string name = 2;
      string description = 3;
      double price = 4;
      string created_at = 5;
      string updated_at = 6;
      string tags = 7;
    }
    
    // Create operation messages
    message CreateProductRequest {
      string name = 1;
      string description = 2;
      double price = 3;
      string tags = 4;
    }
    
    message CreateProductResponse {
      bool success = 1;
      string message = 2;
      ProductModel product = 3;
    }
    
    // Read operation messages
    message GetProductRequest {
      string id = 1;
    }
    
    message GetProductResponse {
      bool success = 1;
      string message = 2;
      ProductModel product = 3;
    }
    
    message ListProductsRequest {
      int32 page = 1;
      int32 page_size = 2;
    }
    
    message ListProductsResponse {
      bool success = 1;
      string message = 2;
      repeated ProductModel products = 3;
      int32 total_count = 4;
    }
    
    // Update operation messages
    message UpdateProductRequest {
      string id = 1;
      string name = 2;
      string description = 3;
      double price = 4;
      string tags = 5;
    }
    
    message UpdateProductResponse {
      bool success = 1;
      string message = 2;
      ProductModel product = 3;
    }
    
    // Delete operation messages
    message DeleteProductRequest {
      string id = 1;
    }
    
    message DeleteProductResponse {
      bool success = 1;
      string message = 2;
    }
    

    Building the Project to Generate C# Code

    Now that we’ve defined our Protocol Buffer contract, we need to build the project to generate the corresponding C# code:

    dotnet build
    

    This command will compile your .proto files into C# classes, generate client and server code, and create strongly-typed request/response classes.

    You should see output confirming the build was successful:

    Restore complete (0.6s)
      ProductGrpc succeeded (9.5s) → binDebugnet9.0ProductGrpc.dll
    
    Build succeeded in 11.1s
    

    What Gets Generated?

    After building, the Protocol Buffer compiler generates several C# files (you won’t see them directly, but they’re available in your code):

    • ProductModel: C# class representing your product data

    • CreateProductRequest/Response: Request and response classes for create operations

    • ProductService.ProductServiceBase: Base class for implementing your service

    • ProductService.ProductServiceClient: Client class for calling the service

    Congratulations! You’ve successfully created a comprehensive Protocol Buffer definition, defined a complete CRUD service contract, set up a strongly-typed message structure, and generated C# code from your proto file.

    What’s Next?

    Now that we have our Protocol Buffer contract defined, we can start implementing the actual gRPC service methods. In the next section, we’ll create the ProductService Class and implement each CRUD operation.

    Remember: Protocol Buffers are language-agnostic, so this same .proto file could be used to generate client code in Python, Java, Go, or any other supported language.

    How to Implement CRUD Operations Services with gRPC

    Now that we have our database set up and Protocol Buffer contracts defined, it’s time to implement the actual CRUD (Create, Read, Update, Delete) functionality. We’ll create a gRPC service that brings together our database models and Protocol Buffer definitions.

    Understanding the Implementation Architecture

    Before we start coding, let’s understand how the pieces fit together:

    Protocol Buffer (.proto) → Generated C# Code → Our Service Implementation → Database
    

    Key concepts in this code:

    • Proto Service: Interface (defines what methods are available)

    • Proto Messages: DTOs (define data structure)

    • Service Implementation: Business logic (what happens)

    • Database Context: Data persistence layer

    Configuring Proto File Build

    First, we need to ensure our product.proto file gets compiled into C# code during the build process.

    Open your ProductGrpc.csproj file and locate the <ItemGroup> section that references proto files:

      <!-- ProductGrpc.csproj -->
    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    
      <ItemGroup>
        <Protobuf Include="Protosgreet.proto" GrpcServices="Server" />
        <!-- Add this line to include our product.proto file -->
        <Protobuf Include="Protosproduct.proto" GrpcServices="Server" />
      </ItemGroup>
    
      <ItemGroup>
        <PackageReference Include="Grpc.AspNetCore" Version="2.64.0" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
          <PrivateAssets>all</PrivateAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
      </ItemGroup>
    </Project>
    

    What this configuration does:

    • Include="Protosproduct.proto" tells .NET to process our proto file

    • GrpcServices="Server" generates server-side code (service base classes)

    Building the Project

    Now let’s build the project to generate the C# code from our proto file:

    dotnet build
    

    This command will compile your .proto files into C# classes, generate the ProductsServiceProto. ProductsServiceProtoBase class we’ll inherit from, create all the request/response message classes, and validate that everything compiles correctly.

    You should see output like:

    Build succeeded.
        0 Warning(s)
        0 Error(s)
    

    Creating the ProductService Class

    Navigate to the Services folder and create a new file called ProductService.cs. This will contain our gRPC service implementation.

    Your project structure should now look like this:

    ProductGrpc/
    ├── Services/
    │   ├── GreeterService.cs    # Default template service
    │   └── ProductService.cs    # Our new product service
    └── ...
    

    Setting Up the Service Foundation

    Start by creating the basic service class structure:

    
     // Services/ProductService.cs
    using Grpc.Core;
    using Microsoft.EntityFrameworkCore;
    using ProductGrpc.Data;
    using ProductGrpc.Models;
    
    namespace ProductGrpc.Services
    {
        public class ProductService :  ProductsServiceProto .ProductsServiceProtoBase
        {
            private readonly AppDbContext _dbContext;
            private readonly ILogger<ProductService> _logger;
    
            public ProductService(AppDbContext dbContext, ILogger<ProductService> logger)
            {
                _dbContext = dbContext;
                _logger = logger;
            }
    
            // CRUD methods will be implemented here
        }
    }
    

    Key components of this code:

    • Inheritance: ProductsServiceProto .ProductsServiceProtoBase is generated from our proto file

    • Dependency injection: We inject AppDbContext for database operations and ILogger for logging

    • Constructor: Initializes our dependencies

    Implementing Method Signatures

    Now let’s add all the method signatures that we defined in our proto file. These methods override the virtual methods from the base class:

     //Services/ProductService.cs
    using Grpc.Core;
    using Microsoft.EntityFrameworkCore;
    using ProductGrpc.Data;
    using ProductGrpc.Models;
    
    namespace ProductGrpc.Services
    {
        public class ProductService :  ProductsServiceProto .ProductsServiceProtoBase
        {
            private readonly AppDbContext _dbContext;
            private readonly ILogger<ProductService> _logger;
    
            public ProductService(AppDbContext dbContext, ILogger<ProductService> logger)
            {
                _dbContext = dbContext;
                _logger = logger;
            }
    
            public override async Task<CreateProductResponse> CreateProduct(
                CreateProductRequest request, 
                ServerCallContext context)
            {
                // Implementation will go here
                throw new NotImplementedException();
            }
    
            public override async Task<GetProductResponse> GetProduct(
                GetProductRequest request, 
                ServerCallContext context)
            {
                // Implementation will go here
                throw new NotImplementedException();
            }
    
            public override async Task<ListProductsResponse> ListProducts(
                ListProductsRequest request, 
                ServerCallContext context)
            {
                // Implementation will go here
                throw new NotImplementedException();
            }
    
            public override async Task<UpdateProductResponse> UpdateProduct(
                UpdateProductRequest request, 
                ServerCallContext context)
            {
                // Implementation will go here
                throw new NotImplementedException();
            }
    
            public override async Task<DeleteProductResponse> DeleteProduct(
                DeleteProductRequest request, 
                ServerCallContext context)
            {
                // Implementation will go here
                throw new NotImplementedException();
            }
        }
    }
    

    Understanding Method Parameters

    Each gRPC method receives two parameters:

    1. Request parameter: Contains the data sent by the client (for example, CreateProductRequest)

    2. ServerCallContext: Provides access to request metadata, cancellation tokens, and response headers

    Method Signature Pattern:

    public override async Task<ResponseType> MethodName(
        RequestType request, 
        ServerCallContext context)
    

    Registering the Service

    Before we implement the methods, we need to register our service with the application. Open Program.cs and add the service:

    
     // Program.cs
    using Microsoft.EntityFrameworkCore;
    using ProductGrpc.Data;
    using ProductGrpc.Services; // Add this using statement
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddGrpc();
    
    // Register our database context
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlite("Data Source=ProductGrpc.db"));
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    app.MapGrpcService<GreeterService>();
    app.MapGrpcService<ProductService>(); // Add this line
    
    app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
    
    app.Run();
    

    Handling Compiler Warnings

    You might see warnings like:

    This async method lacks 'await' operators and will run synchronously.
    

    This is expected since we haven’t implemented the actual logic yet. These warnings will disappear once we add the implementation in the following sections.

    Creating Helper Methods

    Before implementing CRUD operations, let’s add some helper methods for converting between our database models and Protocol Buffer messages:

    :Services/ProductService.cs
    // Add these helper methods to your ProductService class
    
    
     private static ProductModel MapToProductModel(Product product)
    {
        return new ProductModel
        {
            Id = product.Id.ToString(),
            Name = product.Name,
            Description = product.Description,
            Price = (double)product.Price,
            CreatedAt = product.Created.ToString("yyyy-MM-ddTHH:mm:ssZ"),
            UpdatedAt = product.Updated.ToString("yyyy-MM-ddTHH:mm:ssZ"),
            Tag = product.Tags ?? string.Empty
        };
    }
    

    Excellent work! You’ve successfully:

    • Configured proto file compilation

    • Created the ProductService class structure

    • Set up dependency injection

    • Defined all CRUD method signatures

    • Registered the service with the application

    • Created helper methods for data mapping

    What’s Next?

    Now that we have our service foundation ready, we’ll implement each CRUD operation one by one:

    1. CreateProduct: Add new products to the database

    2. GetProduct: Retrieve a single product by ID

    3. ListProducts: Get a paginated list of products

    4. UpdateProduct: Modify existing products

    5. DeleteProduct: Remove products from the database

    In the next sections, we’ll dive deep into each implementation, handling error cases, validation, and best practices.

    💡 Pro Tip: The ServerCallContext parameter provides useful information like request cancellation tokens, client metadata, and response headers. We’ll use these in our implementations for better error handling and logging.

    Note: The override keyword is crucial – it tells C# that we’re implementing the virtual methods defined in the generated base class from our proto file.

    How to Implement gRPC CRUD Database Operations With SQLite

    Now that we have our service foundation ready, let’s implement each CRUD operation. Each method will handle database operations, error handling, and return appropriate responses using our Protocol Buffer messages.

    Understanding the Implementation Pattern

    Each CRUD operation follows a consistent pattern:

    1. Input Validation: Validate request parameters

    2. Database Operation: Perform the actual database work

    3. Response Mapping: Convert database models to Protocol Buffer messages

    4. Error Handling: Catch and handle exceptions gracefully

    Creating the CreateProduct Service

    The CreateProduct method handles adding new products to our database. This is the “C” in CRUD (Create).

    //Services/ProductService.cs
    public override async Task<CreateProductResponse> CreateProduct(
        CreateProductRequest request, 
        ServerCallContext context)
    {
        try
        {
            // Input validation
            if (string.IsNullOrWhiteSpace(request.Name))
            {
                return new CreateProductResponse
                {
                    Success = false,
                    Message = "Product name is required"
                };
            }
    
            if (request.Price <= 0)
            {
                return new CreateProductResponse
                {
                    Success = false,
                    Message = "Product price must be greater than zero"
                };
            }
    
            // Create new product entity
            var productItem = new Product
            {
                Id = Guid.NewGuid(),
                Name = request.Name,
                Description = request.Description,
                Price = Convert.ToDecimal(request.Price),
                Created = DateTime.UtcNow,
                Updated = DateTime.UtcNow,
                Tags = request.Tags
            };
    
            // Add to database
            _dbContext.Products.Add(productItem);
            await _dbContext.SaveChangesAsync();
    
            _logger.LogInformation("Product created successfully with ID: {ProductId}", productItem.Id);
    
            // Return success response
            return new CreateProductResponse
            {
                Success = true,
                Message = "Product created successfully",
                Product = MapToProductModel(productItem)
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error creating product");
    
            return new CreateProductResponse
            {
                Success = false,
                Message = $"Error creating product: {ex.Message}"
            };
        }
    }
    

    These are the important implementation details:

    • Unique ID generation: Guid.NewGuid() Creates a unique identifier

    • Timestamp management: DateTime.UtcNow Ensures consistent timezone handling

    • Type conversion: Convert.ToDecimal() converts double to decimal for database storage

    • Input validation: Checks for required fields and valid values

    • Logging: Records successful operations and errors for debugging

    Creating the GetProduct Service

    The GetProduct method retrieves a single product by its ID. This is the “R” in CRUD (Read).

    //Services/ProductService.cs
    public override async Task<GetProductResponse> GetProduct(
        GetProductRequest request, 
        ServerCallContext context)
    {
        try
        {
            // Validate and parse the product ID
            if (!Guid.TryParse(request.Id, out var productId))
            {
                return new GetProductResponse
                {
                    Success = false,
                    Message = "Invalid product ID format. Please provide a valid GUID."
                };
            }
    
            // Find product in database
            var product = await _dbContext.Products.FindAsync(productId);
    
            if (product == null)
            {
                _logger.LogWarning("Product not found with ID: {ProductId}", productId);
    
                return new GetProductResponse
                {
                    Success = false,
                    Message = "Product not found"
                };
            }
    
            _logger.LogInformation("Product retrieved successfully with ID: {ProductId}", productId);
    
            // Return success response with product data
            return new GetProductResponse
            {
                Success = true,
                Message = "Product retrieved successfully",
                Product = MapToProductModel(product)
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving product with ID: {ProductId}", request.Id);
    
            return new GetProductResponse
            {
                Success = false,
                Message = $"Error retrieving product: {ex.Message}"
            };
        }
    }
    

    Important implementation details:

    • ID validation: Guid.TryParse() safely validates the ID format

    • Database query: FindAsync() efficiently finds records by primary key

    • Null checking: Handles cases where the product doesn’t exist

    • Detailed logging: Tracks both successful retrievals and missing products

    Creating the ListProducts Service

    The ListProducts method retrieves multiple products with pagination support. This is also part of the “R” in CRUD (Read).

    // Services/ProductService.cs
    public override async Task<ListProductsResponse> ListProducts(
        ListProductsRequest request, 
        ServerCallContext context)
    {
        try
        {
            // Set default pagination values
            var pageSize = request.PageSize <= 0 ? 10 : Math.Min(request.PageSize, 100); // Max 100 items per page
            var page = request.Page <= 0 ? 1 : request.Page;
    
            // Calculate skip amount for pagination
            var skip = (page - 1) * pageSize;
    
            // Get total count for pagination metadata
            var totalCount = await _dbContext.Products.CountAsync();
    
            // Retrieve paginated products
            var products = await _dbContext.Products
                .OrderBy(p => p.Created) // Consistent ordering
                .Skip(skip)
                .Take(pageSize)
                .ToListAsync();
    
            // Create response
            var response = new ListProductsResponse
            {
                Success = true,
                Message = products.Any() 
                    ? $"Retrieved {products.Count} products (Page {page} of {Math.Ceiling((double)totalCount / pageSize)})"
                    : "No products found",
                TotalCount = totalCount
            };
    
            // Add products to response
            response.Products.AddRange(products.Select(MapToProductModel));
    
            _logger.LogInformation("Listed {ProductCount} products for page {Page}", products.Count, page);
    
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving products list");
    
            return new ListProductsResponse
            {
                Success = false,
                Message = $"Error retrieving products: {ex.Message}",
                TotalCount = 0
            };
        }
    }
    

    Here are the key implementation details:

    • Pagination logic: Calculates skip and take values for efficient data retrieval

    • Default values: Sets sensible defaults for page size and page number

    • Performance optimization: Uses Skip() and Take() for database-level pagination

    • Consistent ordering: OrderBy() ensures predictable results across requests

    • Metadata: Returns total count for client-side pagination UI

    Creating the UpdateProduct Service

    The UpdateProduct method modifies existing products. This is the “U” in CRUD (Update).

    // Services/ProductService.cs
    public override async Task<UpdateProductResponse> UpdateProduct(
        UpdateProductRequest request, 
        ServerCallContext context)
    {
        try
        {
            // Validate product ID
            if (!Guid.TryParse(request.Id, out var productId))
            {
                return new UpdateProductResponse
                {
                    Success = false,
                    Message = "Invalid product ID format. Please provide a valid GUID."
                };
            }
    
            // Input validation
            if (string.IsNullOrWhiteSpace(request.Name))
            {
                return new UpdateProductResponse
                {
                    Success = false,
                    Message = "Product name is required"
                };
            }
    
            if (request.Price <= 0)
            {
                return new UpdateProductResponse
                {
                    Success = false,
                    Message = "Product price must be greater than zero"
                };
            }
    
            // Find existing product
            var existingProduct = await _dbContext.Products.FindAsync(productId);
    
            if (existingProduct == null)
            {
                return new UpdateProductResponse
                {
                    Success = false,
                    Message = "Product not found"
                };
            }
    
            // Update product properties
            existingProduct.Name = request.Name;
            existingProduct.Description = request.Description;
            existingProduct.Price = Convert.ToDecimal(request.Price);
            existingProduct.Tags = request.Tags;
            existingProduct.Updated = DateTime.UtcNow; // Track when updated
    
            // Save changes to database
            await _dbContext.SaveChangesAsync();
    
            _logger.LogInformation("Product updated successfully with ID: {ProductId}", productId);
    
            // Return success response with updated product
            return new UpdateProductResponse
            {
                Success = true,
                Message = "Product updated successfully",
                Product = MapToProductModel(existingProduct)
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error updating product with ID: {ProductId}", request.Id);
    
            return new UpdateProductResponse
            {
                Success = false,
                Message = $"Error updating product: {ex.Message}"
            };
        }
    }
    

    Key details:

    • Existence check: Verifies the product exists before attempting updates

    • Selective updates: Only updates the fields provided in the request

    • Timestamp tracking: Updates the Updated field to track modification time

    • Input validation: Ensures data integrity before saving

    • Atomic operation: All changes are saved together or not at all

    Creating the DeleteProduct Service

    The DeleteProduct method removes products from the database. This is the “D” in CRUD (Delete).

     // Services/ProductService.cs
    public override async Task<DeleteProductResponse> DeleteProduct(
        DeleteProductRequest request, 
        ServerCallContext context)
    {
        try
        {
            // Validate product ID
            if (!Guid.TryParse(request.Id, out var productId))
            {
                return new DeleteProductResponse
                {
                    Success = false,
                    Message = "Invalid product ID format. Please provide a valid GUID."
                };
            }
    
            // Find the product to delete
            var product = await _dbContext.Products.FindAsync(productId);
    
            if (product == null)
            {
                return new DeleteProductResponse
                {
                    Success = false,
                    Message = "Product not found"
                };
            }
    
            // Remove product from database
            _dbContext.Products.Remove(product);
            await _dbContext.SaveChangesAsync();
    
            _logger.LogInformation("Product deleted successfully with ID: {ProductId}", productId);
    
            // Return success response
            return new DeleteProductResponse
            {
                Success = true,
                Message = "Product deleted successfully"
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error deleting product with ID: {ProductId}", request.Id);
    
            return new DeleteProductResponse
            {
                Success = false,
                Message = $"Error deleting product: {ex.Message}"
            };
        }
    }
    

    Key details:

    • Soft vs hard delete: This implements hard delete (permanent removal)

    • Existence verification: Checks if the product exists before deletion

    • Clean removal: Uses Entity Framework’s Remove() method

    • Confirmation: Returns a success message confirming deletion

    Complete ProductService Implementation

    Here’s your complete ProductService.cs file with all CRUD operations:

    // Services/ProductService.cs
    using Grpc.Core;
    using Microsoft.EntityFrameworkCore;
    using ProductGrpc.Data;
    using ProductGrpc.Models;
    
    namespace ProductGrpc.Services
    {
        public class ProductService : Product.ProductServiceBase
        {
            private readonly AppDbContext _dbContext;
            private readonly ILogger<ProductService> _logger;
    
            public ProductService(AppDbContext dbContext, ILogger<ProductService> logger)
            {
                _dbContext = dbContext;
                _logger = logger;
            }
    
            // Helper method to map Product entity to ProductModel message
            private static ProductModel MapToProductModel(Product product)
            {
                return new ProductModel
                {
                    Id = product.Id.ToString(),
                    Name = product.Name,
                    Description = product.Description,
                    Price = (double)product.Price,
                    CreatedAt = product.Created.ToString("yyyy-MM-ddTHH:mm:ssZ"),
                    UpdatedAt = product.Updated.ToString("yyyy-MM-ddTHH:mm:ssZ"),
                    Tags = product.Tags ?? string.Empty
                };
            }
    
            // All CRUD methods go here (as implemented above)
            // CreateProduct, GetProduct, ListProducts, UpdateProduct, DeleteProduct
        }
    }
    

    Excellent work! You’ve successfully implemented all CRUD operations:

    • Create: Add new products with validation

    • Read: Retrieve single products and paginated lists

    • Update: Modify existing products with validation

    • Delete: Remove products safely

    What’s Next?

    Now that our gRPC service is fully implemented, we need to test it! In the next section, we’ll learn how to test our gRPC endpoints using Postman.

    How to Test gRPC Services with Postman

    Testing gRPC services requires different tools and approaches compared to traditional REST APIs. While REST APIs use HTTP/1.1 with JSON payloads, gRPC uses HTTP/2 with binary Protocol Buffer messages. Fortunately, Postman provides excellent support for gRPC testing, making it easy to test our service without writing client code.

    Why gRPC Testing is Different

    Here are the important differences between gRPC and REST API testing summarized:

    AspectREST APIgRPC
    ProtocolHTTP/1.1HTTP/2
    Data FormatJSON/XMLProtocol Buffers (Binary)
    SchemaOptional (OpenAPI/Swagger)Required (.proto files)
    Content-Typeapplication/jsonapplication/grpc
    Testing ToolsAny HTTP clientSpecialized gRPC clients

    Why we need the proto file:

    • gRPC requires the service contract (.proto file) to understand available methods

    • Protocol Buffers need schema definition for serialization/deserialization

    • Postman uses the proto file to generate the correct request/response structure

    Setting Up Postman for gRPC Testing

    Step 1: Launch Postman

    Open Postman on your machine. You should see the main dashboard similar to this:

    Postman main dashboard showing the workspace interface

    Step 2: Create a New gRPC Request

    Click on “New” in the top-left corner or use the “+” button. Then select “gRPC Request” from the available options.

    You should see a modal dialog with different request types:

    Postman's new request modal showing gRPC option

    Click on “gRPC Request” to create a new gRPC request.

    Step 3: Configure the gRPC Request Interface

    After creating a gRPC request, you’ll see the gRPC request interface:

    Postman gRPC request interface with service definition section

    These are the notable components of the gRPC interface:

    • Server URL: Where your gRPC service is running

    • Service definition: Where you import your .proto file

    • Method selection: Choose which RPC method to call

    • Message body: Request payload based on proto definitions

    Importing the Proto File

    Step 4: Access Service Definition

    Locate the “Service definition” section in the gRPC request interface. Then click on “Import .proto file” or a similar option.

    Service definition section where proto files are imported

    Step 5: Import Your Proto File

    1. Click “Select Files” or “Import” button

    2. Navigate to your project directory

    3. Go to the Protos folder

    4. Select product.proto file

    5. Click “Open” to import

    File Path Structure:

    YourProject/
    ├── Protos/
    │   ├── greet.proto
    │   └── product.proto    ← Select this file
    └── ...
    

    Step 6: Configure Import Settings

    When importing, you’ll see options like:

    Proto file import configuration in Postman

    Import Configuration:

    • Import as: Select “API”

    • API name: Choose a descriptive name (for example, “ProductGrpc API”)

    • Import location: Select your workspace

    You want to import as an API because it creates a reusable API definition, allows multiple team members to use the same proto definitions, and provides better organization for multiple services.

    Step 7: Verify Successful Import

    After successful import, you should see:

    1. API Collection: Your named API appears in the left sidebar under “APIs.”

    2. Available Methods: All RPC methods from your proto file are listed

    3. Request/Response Schemas: Postman understands your message structures

    Successfully imported proto file showing available methods

    Understanding the gRPC Request Interface

    Once connected, you’ll see a method selection dropdown with the following:

    • CreateProduct

    • GetProduct

    • ListProducts

    • UpdateProduct

    • DeleteProduct

    Excellent! You’ve successfully created a gRPC request in Postman, imported your proto file, configured the server connection, and set up the API collection for reuse.

    What’s Next?

    Now that Postman is configured with your proto file, you’re ready to test each CRUD operation:

    1. CreateProduct: Test adding new products

    2. GetProduct: Test retrieving single products

    3. ListProducts: Test pagination and listing

    4. UpdateProduct: Test modifying existing products

    5. DeleteProduct: Test removing products

    In the next section, we’ll walk through testing each operation with sample data and expected responses.

    How to Test Product Creation

    Now that we have Postman configured with our proto file, it’s time to test our gRPC service! We’ll start by testing the CreateProduct Method to add a new product to our database.

    Request Structure

    Before sending your first request in Postman, select the RPC method from the dropdown. The request body’s shape comes directly from the Protocol Buffer definitions in your .proto file. Postman renders those proto messages as JSON for easier editing, but the proto types still apply: each field must match the type defined in the schema (including nested messages, enums, and repeated fields).

    Step 1: Select the CreateProduct Method

    Open your gRPC request in Postman and click the method dropdown (should show available methods). Select “CreateProduct” from the list.

    You should see all the methods we defined in our proto file:

    • CreateProduct

    • GetProduct

    • ListProducts

    • UpdateProduct

    • DeleteProduct

    Step 2: Request Schema

    When you select CreateProduct, Postman automatically generates the request structure based on our CreateProductRequest message from the proto file:

    Proto Definition Reminder:

    message CreateProductRequest {
      string name = 1;
      string description = 2;
      double price = 3;
      string tags = 4;
    }
    

    Postman JSON Representation:

    {
      "name": "",
      "description": "",
      "price": 0,
      "tags": ""
    }
    

    Step 3: Prepare Test Data

    Let’s create our first product with meaningful test data. In the request body, enter:

    {
      "name": "MacBook Pro 16-inch",
      "description": "Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD",
      "price": 2499.99,
      "tags": "laptop, apple, professional"
    }
    

    So what are these fields?

    • name: Product title (required, string)

    • description: Detailed product information (string)

    • price: Product cost (double/number, must be > 0)

    • tags: Comma-separated keywords (string)

    Step 4: Send the Request

    Click the “Invoke” button (or “Send” depending on Postman version) and wait for the response (should be very fast for local testing). Then check the response status (should show success).

    Step 5: Analyze the Response

    If everything works correctly, you should receive a response like this:

    {
      "success": true,
      "message": "Product created successfully",
      "product": {
        "id": "920b98d2-4feb-4705-8303-ce6e28bd3694",
        "name": "MacBook Pro 16-inch",
        "description": "Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD",
        "price": 2499.99,
        "created_at": "2024-01-15T16:11:38Z",
        "updated_at": "2024-01-15T16:11:38Z",
        "tags": "laptop, apple, professional"
      }
    }
    

    Response fields:

    • success: Boolean indicating operation success

    • message: Human-readable status message

    • product: The created product with generated fields

      • id: Auto-generated GUID

      • created_at/updated_at: UTC timestamps

      • Other fields: Echo of the input data

    Visual Confirmation in Postman

    Here’s how a successful request looks in Postman:

    Postman interface showing successful product creation with request and response

    Congratulations! You’ve successfully made your first gRPC request using Postman! You’ve also created a product in the database, received a properly formatted response, and verified the auto-generated ID and timestamps.

    What’s Next?

    Now that we’ve successfully tested product creation, let’s test the other CRUD operations:

    1. GetProduct: Retrieve the product we just created

    2. ListProducts: See all products with pagination

    3. UpdateProduct: Modify the existing product

    4. DeleteProduct: Remove the product from the database

    Each operation will help us verify that our complete gRPC service is working correctly.

    How to Test All Product Operations

    Now that we’ve successfully created a product, let’s test all the remaining CRUD operations to ensure our complete gRPC service works correctly.

    Get All Products (ListProducts)

    The ListProducts method retrieves all products from our database with pagination support. Since we’ve created some products, we should be able to see them in the response.

    Step 1: Select ListProducts Method

    Click the method dropdown in your Postman gRPC request. Then select “ListProducts” from the available methods. Notice the request structure – it includes pagination parameters.

    Step 2: Configure the Request

    The ListProductsRequest supports pagination parameters:

    {
      "page": 1,
      "pageSize": 10
    }
    

    Here’s what’s going on with these parameters:

    • page: Which page of results to retrieve (default: 1)

    • pageSize: Number of products per page (default: 10, max: 100)

    Step 3: Send the Request

    Click “Invoke” to send the request and wait for the response containing all your products.

    Step 4: Response

    You should receive a response like this:

    {
      "success": true,
      "message": "Retrieved 2 products (Page 1 of 1)",
      "totalCount": 2,
      "products": [
        {
          "id": "920b98d2-4feb-4705-8303-ce6e28bd3694",
          "name": "MacBook Pro 16-inch",
          "description": "Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD",
          "price": 2499.99,
          "created_at": "2024-01-15T16:11:38Z",
          "updated_at": "2024-01-15T16:11:38Z",
          "tags": "laptop, apple, professional"
        },
        {
          "id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
          "name": "iPhone 15 Pro",
          "description": "Latest iPhone with titanium design",
          "price": 999.99,
          "created_at": "2024-01-15T16:15:22Z",
          "updated_at": "2024-01-15T16:15:22Z",
          "tags": "smartphone, apple, premium"
        }
      ]
    }
    

    Response structure:

    • success: Operation status

    • message: Descriptive message with pagination info

    • totalCount: Total number of products in the database

    • products: Array of product objects

    Postman showing successful retrieval of all products with pagination

    Testing Pagination

    Let’s now test some different pagination scenarios:

    Get the first 5 products:

    {
      "page": 1,
      "pageSize": 5
    }
    

    Get the second page:

    {
      "page": 2,
      "pageSize": 5
    }
    

    Get Product By ID (GetProduct)

    The GetProduct method retrieves a single product using its unique ID. Unlike REST APIs, where the ID is part of the URL path, gRPC passes the ID in the message body.

    Step 1: Select the GetProduct Method

    Select “GetProduct” from the method dropdown. Notice that the request structure requires an ID field.

    Step 2: Prepare the Request

    Copy a product ID from your previous ListProducts response:

    {
      "id": "920b98d2-4feb-4705-8303-ce6e28bd3694"
    }
    

    Important notes:

    • ID Format: Must be a valid GUID string

    • Case Sensitivity: GUIDs are case-insensitive

    • Validation: Invalid GUIDs will return an error

    Step 3: Send the Request

    Paste a valid product ID from your ListProducts response. Click “Invoke” to send the request.

    Step 4: Analyze the Response

    Successful response:

    {
      "success": true,
      "message": "Product retrieved successfully",
      "product": {
        "id": "920b98d2-4feb-4705-8303-ce6e28bd3694",
        "name": "MacBook Pro 16-inch",
        "description": "Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD",
        "price": 2499.99,
        "created_at": "2024-01-15T16:11:38Z",
        "updated_at": "2024-01-15T16:11:38Z",
        "tags": "laptop, apple, professional"
      }
    }
    

    Postman showing successful retrieval of a single product by ID

    Update Product (UpdateProduct)

    The UpdateProduct method modifies an existing product. You need to provide the product ID and the fields you want to update.

    Step 1: Select the UpdateProduct Method

    Select “UpdateProduct” from the method dropdown. Review the request structure which includes ID and all updatable fields.

    Step 2: Prepare the Update Request

    {
      "id": "920b98d2-4feb-4705-8303-ce6e28bd3694",
      "name": "MacBook Pro 16-inch (Updated)",
      "description": "Apple MacBook Pro with M2 Pro chip, 16GB RAM, 1TB SSD - Updated Storage",
      "price": 2799.99,
      "tags": "laptop, apple, professional, updated"
    }
    

    Update guidelines:

    • ID: Must match an existing product

    • All Fields: Currently required (not partial updates)

    • Price: Must be greater than 0

    • Name: Cannot be empty

    Step 3: Send the Update Request

    Make sure the ID exists (use one from your ListProducts response). Then click “Invoke” to send the update.

    Step 4: Verify the Update

    Successful response:

    {
      "success": true,
      "message": "Product updated successfully",
      "product": {
        "id": "920b98d2-4feb-4705-8303-ce6e28bd3694",
        "name": "MacBook Pro 16-inch (Updated)",
        "description": "Apple MacBook Pro with M2 Pro chip, 16GB RAM, 1TB SSD - Updated Storage",
        "price": 2799.99,
        "created_at": "2024-01-15T16:11:38Z",
        "updated_at": "2024-01-15T16:25:14Z",
        "tags": "laptop, apple, professional, updated"
      }
    }
    

    Notice the changes:

    • updated_at: Timestamp changed to reflect the update

    • Modified Fields: All updated fields reflect new values

    • created_at: Remains unchanged (original creation time)

    Postman showing successful product update with modified fields

    Delete Product By ID (DeleteProduct)

    The DeleteProduct method permanently removes a product from the database using its ID.

    Step 1: Select DeleteProduct Method

    Select “DeleteProduct” from the method dropdown. Note the simple request structure – it only requires an ID.

    Step 2: Prepare the Delete Request

    {
      "id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
    }
    

    ⚠️ Warning: This operation permanently deletes the product. Make sure you’re using the correct ID.

    Step 3: Send the Delete Request

    Double-check the product ID you want to delete. Click “Invoke” to send the delete request.

    Step 4: Confirm Deletion

    Successful response:

    {
      "success": true,
      "message": "Product deleted successfully"
    }
    

    Verification steps:

    1. Try GetProduct with the same ID – it should return “Product not found”

    2. Run ListProducts – the product should no longer appear in the list

    3. Check totalCount – should be reduced by 1

    Postman showing successful product deletion

    Conclusion

    Congratulations! 🎉 You’ve successfully completed this comprehensive journey into building gRPC services with ASP.NET Core. Throughout this handbook, you’ve gained hands-on experience with one of the most powerful and efficient communication frameworks available for modern distributed applications.

    What You’ve Accomplished

    Let’s recap the impressive skills and knowledge you’ve acquired:

    Foundation building

    • Set up a complete gRPC project from scratch using .NET CLI

    • Configured SQLite database with Entity Framework Core

    • Created robust data models with proper validation

    • Implemented database migrations and seeding

    Learning protocol buffers

    • Designed comprehensive .proto files with service definitions

    • Created strongly-typed message contracts for all CRUD operations

    • Understood the advantages of binary serialization over JSON

    • Implemented efficient data transfer objects (DTOs)

    Service implementation

    • Built a complete ProductService with all CRUD operations

    • Implemented proper error handling and validation

    • Added comprehensive logging for debugging and monitoring

    • Created efficient pagination for large datasets

    • Handled data mapping between entities and Protocol Buffer messages

    Testing and validation

    • Configured Postman for gRPC testing

    • Tested all CRUD operations with real data

    • Verified data integrity and proper response formatting

    Key Technical Skills Gained

    gRPC Expertise:

    • Understanding of the HTTP/2 protocol advantages

    • Protocol Buffer schema design and evolution

    • Service-to-service communication patterns

    • Performance optimization techniques

    🔗 You can access the code in this GitHub Repository.

    Thank You!

    Thank you for following along with this comprehensive tutorial. Your dedication to learning these advanced concepts will serve you well in building the next generation of distributed applications.

    Happy coding, and may your services be fast, reliable, and scalable!

    If you want to learn more about .NET Core, you can subscribe to my YouTube channel here.

    🔗 Connect with the author:

    • GitHub: @CliffTech123

    • Twitter: @Clifftech_Dev

    • LinkedIn: Isaiah Clifford Opoku

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleLearn Next.js 15 Caching & Rendering
    Next Article Tailwind CSS vs. Bootstrap: Which Framework Is Right for Your Next Project?

    Related Posts

    Development

    Controlling Execution Flow with Laravel’s Sleep Helper

    August 14, 2025
    Development

    Generate Secure Temporary Share Links for Files in Laravel

    August 14, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    CVE-2025-1907 – Instantel Micromate Unauthenticated Command Execution

    Common Vulnerabilities and Exposures (CVEs)

    yeTTY views logs from serial ports

    Linux

    Linux drops support for 486 and early Pentium processors – 20 years after Microsoft

    News & Updates

    Zuckerberg’s latest AI pitch isn’t about replacing you — it’s making superhumans with “personal superintelligence” instead

    News & Updates

    Highlights

    Linux Schools – Ubuntu-based server based distribution

    May 1, 2025

    Linux Schools is a server operating system designed for schools, providing a simple GUI for…

    UNC6148 Backdoors Fully-Patched SonicWall SMA 100 Series Devices with OVERSTEP Rootkit

    July 24, 2025

    Yomikiru – offline manga, manhwa, comic, and novel reader

    July 29, 2025

    Microsoft urges users to ditch Windows 10 for Windows 11 because it’s better in 7 ways

    April 10, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.