<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Csharhp - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Csharhp - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 24 Aug 2026 19:19:50 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/csharhp/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Get Started with ASP.NET Core and gRPC: A Handbook for Developers ]]>
                </title>
                <description>
                    <![CDATA[ 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 creatin... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/get-started-with-aspnet-core-and-grpc-handbook/</link>
                <guid isPermaLink="false">689ca3344cac10c21b1f670c</guid>
                
                    <category>
                        <![CDATA[ .NET ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Csharhp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Isaiah Clifford Opoku ]]>
                </dc:creator>
                <pubDate>Wed, 13 Aug 2025 14:37:40 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755043329753/f5ff4a61-79b7-44f0-9871-9dfef9f8d08a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>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.</p>
<p>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.</p>
<p>In this handbook, I’ll take you on a journey from absolute beginner to building production-ready gRPC services with <a target="_blank" href="http://ASP.NET">ASP.NET</a> 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.</p>
<p><strong>What you'll learn:</strong></p>
<ul>
<li><p>How to set up your first gRPC service in .NET</p>
</li>
<li><p>How to define service contracts with Protocol Buffers</p>
</li>
<li><p>How to implement unary, server streaming, and client streaming operations</p>
</li>
<li><p>How to build CRUD (Create, Read, Update, Delete) operations</p>
</li>
</ul>
<p>Let's dive in and discover how gRPC can revolutionize your API development experience!</p>
<p>You can find all the code in this <a target="_blank" href="https://github.com/Clifftech123/IsaiahCliffordOpokuBlog"><strong>GitHub Repository</strong></a><strong>.</strong></p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-grpc-overview-and-how-it-works-with-net">gRPC Overview and How It Works with .NET</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-grpc-with-net">How to Set Up gRPC with .NET</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-product-model">How to Create the Product Model</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-sqlite-database">How to Set Up the SQLite Database</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-product-protocol-buffers">How to Create Product Protocol Buffers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-crud-operations-services-with-grpc">How to Implement CRUD Operations Services with gRPC</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-grpc-crud-database-operations-with-sqlite">How to Implement gRPC CRUD Database Operations With SQLite</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-grpc-services-with-postman">How to Test gRPC Services with Postman</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-product-creation">How to Test Product Creation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-all-product-operations">How to Test All Product Operations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h3 id="heading-perquisites">Perquisites</h3>
<p>Before we start, make sure you have the following installed:</p>
<ul>
<li><p><a target="_blank" href="https://dotnet.microsoft.com/download">.NET SDK</a></p>
</li>
<li><p><a target="_blank" href="https://code.visualstudio.com/download">Visual Studio Code</a></p>
</li>
<li><p><a target="_blank" href="https://www.postman.com/downloads/">Postman</a></p>
</li>
</ul>
<h2 id="heading-grpc-overview-and-how-it-works-with-net">gRPC Overview and How It Works with .NET</h2>
<p>gRPC is a high-performance, cross-platform framework that works seamlessly with many technologies, including .NET Core.</p>
<h3 id="heading-why-choose-grpc-with-net">Why choose gRPC with .NET?</h3>
<p>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.</p>
<p>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.</p>
<p>To understand in more detail why gRPC is so valuable, let's explore a common real-world scenario.</p>
<h3 id="heading-the-challenge-microservices-communication">The Challenge: Microservices Communication</h3>
<p>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:</p>
<ul>
<li><p><strong>Product Service</strong> – Handles product catalog, inventory, and product management</p>
</li>
<li><p><strong>Authentication Service</strong> – Manages user authentication, authorization, and user profiles</p>
</li>
</ul>
<p>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.</p>
<h3 id="heading-traditional-approach-http-rest-apis">Traditional Approach: HTTP REST APIs</h3>
<p>Traditionally, in .NET applications, we solve this inter-service communication using <code>HttpClient</code> to make REST API calls between services. While this works, it comes with several challenges:</p>
<ul>
<li><p>Network failures: API calls can fail unexpectedly, even when everything appears correct</p>
</li>
<li><p>Performance bottlenecks: JSON serialization/deserialization adds overhead</p>
</li>
<li><p>Slow response times: HTTP/1.1 limitations affect performance under high load</p>
</li>
<li><p>Type safety: No compile-time contract validation between services</p>
</li>
<li><p>Verbose payloads: JSON can be bulky compared to binary formats</p>
</li>
</ul>
<h3 id="heading-the-grpc-solution">The gRPC Solution</h3>
<p>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.</p>
<h3 id="heading-when-to-use-grpc-in-net">When to Use gRPC in .NET</h3>
<p>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:</p>
<p><strong>✅ Perfect for:</strong></p>
<ul>
<li><p><strong>Microservices architecture</strong>: High-frequency service-to-service communication</p>
</li>
<li><p><strong>Real-time applications</strong>: Chat applications, live updates, gaming</p>
</li>
<li><p><strong>High-performance APIs</strong>: When speed and efficiency are critical</p>
</li>
<li><p><strong>Polyglot environments</strong>: Services written in different programming languages</p>
</li>
<li><p><strong>Internal APIs</strong>: Backend services that don't need browser compatibility</p>
</li>
</ul>
<p><strong>❌ Consider Alternatives When:</strong></p>
<ul>
<li><p><strong>Browser-based applications</strong>: Limited browser support (use gRPC-Web instead)</p>
</li>
<li><p><strong>Public APIs</strong>: REST might be more familiar to external developers</p>
</li>
<li><p><strong>Simple CRUD operations</strong>: Where REST's simplicity is sufficient</p>
</li>
<li><p><strong>Legacy system integration</strong>: When existing systems only support HTTP/1.1</p>
</li>
</ul>
<h3 id="heading-grpc-vs-rest-a-quick-comparison">gRPC vs REST: A Quick Comparison</h3>
<p>Here’s a quick side-by-side comparison of their main features:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>gRPC</td><td>REST</td></tr>
</thead>
<tbody>
<tr>
<td>Protocol</td><td>HTTP/2</td><td>HTTP/1.1</td></tr>
<tr>
<td>Data Format</td><td>Protocol Buffers (Binary)</td><td>JSON (Text)</td></tr>
<tr>
<td>Performance</td><td>High</td><td>Moderate</td></tr>
<tr>
<td>Browser Support</td><td>Limited (needs gRPC-Web)</td><td>Full</td></tr>
<tr>
<td>Streaming</td><td>Built-in</td><td>Limited</td></tr>
<tr>
<td>Code Generation</td><td>Automatic</td><td>Manual</td></tr>
</tbody>
</table>
</div><p>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.</p>
<h2 id="heading-how-to-set-up-grpc-with-net">How to Set Up gRPC with .NET</h2>
<p>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.</p>
<h3 id="heading-creating-your-first-grpc-project">Creating Your First gRPC Project</h3>
<p>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.</p>
<p>Run the following command to create a new gRPC project:</p>
<pre><code class="lang-bash">dotnet new grpc -o ProductGrpc
</code></pre>
<p><strong>What this command does:</strong></p>
<ul>
<li><p><code>dotnet new grpc</code> creates a new project using the gRPC template</p>
</li>
<li><p><code>-o ProductGrpc</code> specifies the output directory name for our project</p>
</li>
</ul>
<p>Next, navigate into the project directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ProductGrpc
</code></pre>
<p>Then open the project in Visual Studio Code:</p>
<pre><code class="lang-bash">code .
</code></pre>
<h3 id="heading-understanding-the-project-structure">Understanding the Project Structure</h3>
<p>After running the command, you should see output similar to the following in your terminal, confirming that the project was created successfully:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753873861602/6d135358-2065-40eb-9fe9-9a58bd8dc2eb.png" alt="Vs Code  Initial  Project Structure" width="1971" height="987" loading="lazy"></p>
<p>Let's explore what the .NET gRPC template has generated for us:</p>
<pre><code class="lang-makefile">ProductGrpc/
├── Protos/
│   └── greet.proto          <span class="hljs-comment"># Protocol Buffer definition file</span>
├── Services/
│   └── GreeterService.cs    <span class="hljs-comment"># Sample gRPC service implementation</span>
├── Program.cs               <span class="hljs-comment"># Application entry point</span>
├── ProductGrpc.csproj       <span class="hljs-comment"># Project file</span>
└── appsettings.json         <span class="hljs-comment"># Configuration file</span>
</code></pre>
<p>Key files:</p>
<ul>
<li><p><code>Protos/greet.proto</code>: Defines the service contract using Protocol Buffers</p>
</li>
<li><p><code>Services/GreeterService.cs</code>: Contains the actual service implementation</p>
</li>
<li><p><code>Program.cs</code>: Configures and starts the gRPC server</p>
</li>
<li><p><code>ProductGrpc.csproj</code>: Contains project dependencies and build settings</p>
</li>
</ul>
<h3 id="heading-verifying-the-setup">Verifying the Setup</h3>
<p>Let's make sure everything is working correctly by running the default application:</p>
<pre><code class="lang-yaml"><span class="hljs-string">dotnet</span> <span class="hljs-string">run</span>
</code></pre>
<p>You should see output indicating that the gRPC server is running:</p>
<pre><code class="lang-json">info: Microsoft.Hosting.Lifetime[<span class="hljs-number">14</span>]
      Now listening on: https:<span class="hljs-comment">//localhost:7042</span>
info: Microsoft.Hosting.Lifetime[<span class="hljs-number">0</span>]
      Application started. Press Ctrl+C to shut down.
</code></pre>
<p><strong>🎉 Congratulations!</strong> You've successfully created your first gRPC application using the .NET CLI. The server is now running and ready to accept gRPC requests.</p>
<p>Let's move on to the next section, where we'll start building our Product Management system.</p>
<h2 id="heading-how-to-create-the-product-model">How to Create the Product Model</h2>
<p>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.</p>
<h3 id="heading-understanding-models-in-grpc-applications">Understanding Models in gRPC Applications</h3>
<p>Models serve several important purposes:</p>
<ul>
<li><p><strong>Data structure</strong>: They define the shape and properties of our business entities.</p>
</li>
<li><p><strong>Type safety</strong>: They ensure compile-time validation of our data.</p>
</li>
<li><p><strong>Business logic</strong>: They represent real-world objects in our application.</p>
</li>
<li><p><strong>Database mapping</strong>: They serve as entities for database operations.</p>
</li>
</ul>
<h3 id="heading-creating-the-models-folder">Creating the Models Folder</h3>
<p>Let’s organize our code by creating a dedicated folder for our models called <code>Models</code> in your project root directory.</p>
<p>Inside the Models folder, create a new file called <code>Product.cs</code>.</p>
<p>Your project structure should now look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">ProductGrpc/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Models/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">Product.cs</span>           <span class="hljs-comment"># Our new Product model</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Protos/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Services/</span>
<span class="hljs-string">└──</span> <span class="hljs-string">...</span>
</code></pre>
<h3 id="heading-implementing-the-product-model">Implementing the Product Model</h3>
<p>Add the following code to your <code>Product.cs</code> file:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Models/Product.cs</span>
<span class="hljs-keyword">using</span> System.ComponentModel.DataAnnotations;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ProductGrpc.Models</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>
    {

        <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-keyword">public</span> DateTime Created { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = DateTime.UtcNow;

        <span class="hljs-keyword">public</span> DateTime Updated { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = DateTime.UtcNow;
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>? Tags { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    }
}
</code></pre>
<p>Modern C# features:</p>
<ul>
<li><p><code>required</code> keyword: Ensures properties must be initialized when creating an object</p>
</li>
<li><p><code>string?</code>: Nullable reference type for optional properties</p>
</li>
<li><p>Default values: <code>Created</code> and <code>Updated</code> automatically set to the current UTC</p>
</li>
</ul>
<h3 id="heading-why-use-guid-for-id">Why Use Guid for ID?</h3>
<p>We're using <code>Guid</code> instead of <code>int</code> for our primary key for a few reasons:</p>
<ul>
<li><p><strong>Uniqueness</strong>: Guaranteed to be unique across different systems</p>
</li>
<li><p><strong>Security</strong>: Harder to guess than sequential integers</p>
</li>
<li><p><strong>Distributed systems</strong>: No need for centralized ID generation</p>
</li>
<li><p><strong>Scalability</strong>: Perfect for microservices architecture</p>
</li>
</ul>
<h3 id="heading-namespace-considerations">Namespace Considerations</h3>
<p><strong>Important Note:</strong> If you changed your project name when creating it, make sure your namespace matches your project name. For example:</p>
<ul>
<li><p>If your project is named <code>MyProductService</code>, use <code>namespace MyProductService.Models</code></p>
</li>
<li><p>If your project is named <code>ProductGrpc</code>, use <code>namespace ProductGrpc.Models</code></p>
</li>
</ul>
<p>🎉 <strong>Excellent work!</strong> You've successfully created your first business model that will serve as the foundation for our entire gRPC application.</p>
<h3 id="heading-next-steps">Next Steps</h3>
<p>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.</p>
<h2 id="heading-how-to-set-up-the-sqlite-database">How to Set Up the SQLite Database</h2>
<p>To persist our product data, we need a database that can handle our CRUD (Create, Read, Update, Delete) operations efficiently. We'll use <strong>SQLite</strong> for this tutorial because it's lightweight, requires no separate server installation, and works perfectly for developing small-to-medium applications.</p>
<h3 id="heading-installing-the-required-packages">Installing the Required Packages</h3>
<p>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:</p>
<pre><code class="lang-powershell">dotnet add package Microsoft.EntityFrameworkCore.Design
</code></pre>
<pre><code class="lang-powershell">dotnet add package Microsoft.EntityFrameworkCore.Sqlite
</code></pre>
<p>What these packages do:</p>
<ul>
<li><p><strong>Microsoft.EntityFrameworkCore.Design</strong> provides design-time tools for EF Core (migrations, scaffolding)</p>
</li>
<li><p><strong>Microsoft.EntityFrameworkCore.SQLite</strong> is a SQLite database provider for Entity Framework Core</p>
</li>
</ul>
<p>You should see output confirming the packages were added successfully:</p>
<pre><code class="lang-bash">info : PackageReference <span class="hljs-keyword">for</span> <span class="hljs-string">'Microsoft.EntityFrameworkCore.Design'</span> version <span class="hljs-string">'x.x.x'</span> added to file <span class="hljs-string">'ProductGrpc.csproj'</span>.
info : PackageReference <span class="hljs-keyword">for</span> <span class="hljs-string">'Microsoft.EntityFrameworkCore.Sqlite'</span> version <span class="hljs-string">'x.x.x'</span> added to file <span class="hljs-string">'ProductGrpc.csproj'</span>.
</code></pre>
<h3 id="heading-creating-the-database-context">Creating the Database Context</h3>
<p>Now let's create our database context, which acts as a bridge between our .NET objects and the database.</p>
<p>First, create a new folder called <code>Data</code> In your project root. Inside the Data folder, create a file called <code>AppDbContext.cs</code>.</p>
<p>Your project structure should now look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">ProductGrpc/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Data/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">AppDbContext.cs</span>      <span class="hljs-comment"># Our new database context</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Models/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">Product.cs</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Protos/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Services/</span>
<span class="hljs-string">└──</span> <span class="hljs-string">...</span>
</code></pre>
<p>Add the following code to your <code>AppDbContext.cs</code> file:</p>
<pre><code class="lang-cs">
<span class="hljs-comment">// Data/AppDbContext.cs</span>
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> ProductGrpc.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ProductGrpc.Data</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AppDbContext</span> : <span class="hljs-title">DbContext</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">AppDbContext</span>(<span class="hljs-params">DbContextOptions&lt;AppDbContext&gt; options</span>) : <span class="hljs-title">base</span>(<span class="hljs-params">options</span>)</span>
        {
        }

        <span class="hljs-keyword">public</span> DbSet&lt;Product&gt; Products { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    }
}
</code></pre>
<p>Let’s understand the key components of DbContext:</p>
<ul>
<li><p><strong>Constructor</strong>: Accepts <code>DbContextOptions</code> for configuration (connection string, provider, and so on)</p>
</li>
<li><p><strong>DbSet Products</strong>: Represents the Products table in our database</p>
</li>
</ul>
<h3 id="heading-registering-the-database-context">Registering the Database Context</h3>
<p>Now we need to register our <code>AppDbContext</code> With the dependency injection container so our application can use it.</p>
<p>Open your <code>Program.cs</code> file and add the database configuration:</p>
<pre><code class="lang-cs"><span class="hljs-comment">// Program.cs</span>

<span class="hljs-keyword">using</span> ProductGrpc.Data;
<span class="hljs-keyword">using</span> ProductGrpc.Services;

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext&lt;AppDbContext&gt;(opt=&gt; 
    opt.UseSqlite(<span class="hljs-string">"Data Source=ProductGrpc.db "</span>));
<span class="hljs-comment">// Add services to the container.</span>
builder.Services.AddGrpc();

<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Configure the HTTP request pipeline.</span>
app.MapGrpcService&lt;GreeterService&gt;();
app.MapGet(<span class="hljs-string">"/"</span>, () =&gt; <span class="hljs-string">"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"</span>);

app.Run();


app.Run();
</code></pre>
<p><code>Data Source=ProductGrpc.db</code> creates a SQLite database file named <code>ProductGrpc.db</code> in your project directory.</p>
<h3 id="heading-creating-and-running-migrations">Creating and Running Migrations</h3>
<p>Now we need to create a migration to generate the database schema based on our Product model.</p>
<p>Start by creating the initial migration:</p>
<pre><code class="lang-powershell">dotnet ef migrations add InitialCreate
</code></pre>
<p>This command will:</p>
<ul>
<li><p>Analyze your models and DbContext</p>
</li>
<li><p>Generate migration files in a <code>Migrations</code> folder</p>
</li>
<li><p>Create SQL commands needed to build your database schema</p>
</li>
</ul>
<p>You should see output like this:</p>
<pre><code class="lang-powershell">Build succeeded.
Done. To undo this action, use <span class="hljs-string">'dotnet ef migrations remove'</span>
</code></pre>
<p>Apply the migration to create the database:</p>
<pre><code class="lang-powershell">dotnet ef database update
</code></pre>
<p>This command will:</p>
<ul>
<li><p>Execute the migration SQL commands</p>
</li>
<li><p>Create the <code>ProductGrpc.db</code> file in your project directory</p>
</li>
<li><p>Set up the Products table with all the correct columns</p>
</li>
</ul>
<p>You should see output confirming the database was created:</p>
<pre><code class="lang-powershell">Build succeeded.
Applying migration <span class="hljs-string">'20240101000000_InitialCreate'</span>.
Done.
</code></pre>
<h3 id="heading-verifying-the-setup-1">Verifying the Setup</h3>
<p>After running the migration, you should see:</p>
<ol>
<li><p>A new <code>Migrations</code> folder in your project with migration files</p>
</li>
<li><p>A <code>ProductGrpc.db</code> file in your project root (this is your SQLite database)</p>
</li>
<li><p>No errors in the terminal output</p>
</li>
</ol>
<p>Your project structure should now look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">ProductGrpc/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Data/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">AppDbContext.cs</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Migrations/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">20240101000000_InitialCreate.cs</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">AppDbContextModelSnapshot.cs</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Models/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">Product.cs</span>
<span class="hljs-string">├──</span> <span class="hljs-string">ProductGrpc.db</span>            <span class="hljs-comment"># Your SQLite database file</span>
<span class="hljs-string">└──</span> <span class="hljs-string">...</span>
</code></pre>
<p>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!</p>
<h3 id="heading-whats-next">What's Next?</h3>
<p>Now that our database is set up and ready, we can move on to creating our Protocol Buffer definitions (<code>.proto</code> files) and implementing our gRPC services for CRUD operations.</p>
<h2 id="heading-how-to-create-product-protocol-buffers">How to Create Product Protocol Buffers</h2>
<p>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 <strong>HTTP/2</strong> protocol, making service-to-service communication fast and reliable.</p>
<h3 id="heading-understanding-protocol-buffers-vs-rest-apis">Understanding Protocol Buffers vs REST APIs</h3>
<p>To better understand Protocol Buffers, let's compare them to what you might already know from REST API development.</p>
<p>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.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ApiController</span>]
[<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProductsController</span> : <span class="hljs-title">ControllerBase</span>
{
    [<span class="hljs-meta">HttpGet(<span class="hljs-meta-string">"{id}"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&lt;ProductDto&gt;&gt; GetProduct(<span class="hljs-keyword">int</span> id) { ... }
</code></pre>
<p>With <strong>gRPC,</strong> the service contract is defined first in the <code>.proto</code> file using the <code>service</code> 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.</p>
<pre><code class="lang-csharp">service ProductService {
  <span class="hljs-function">rpc <span class="hljs-title">GetProduct</span>(<span class="hljs-params">GetProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">GetProductResponse</span>)</span>;
}
</code></pre>
<h3 id="heading-data-transfer-and-serialization">Data Transfer and Serialization</h3>
<p>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</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"123e4567-e89b-12d3-a456-426614174000"</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Wireless Headphones"</span>,
  <span class="hljs-attr">"price"</span>: <span class="hljs-number">99.99</span>
}
</code></pre>
<p>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.</p>
<h4 id="heading-transport-protocol-differences">Transport Protocol Differences</h4>
<p>REST APIs run over <strong>HTTP/1.1</strong>, which has some limitations:</p>
<ul>
<li><p>One request-response cycle per connection</p>
</li>
<li><p>Text-based headers (larger overhead)</p>
</li>
<li><p>No built-in multiplexing</p>
</li>
<li><p>Limited streaming capabilities</p>
</li>
</ul>
<p>gRPC leverages <strong>HTTP/2</strong>, which offers some advantages:</p>
<ul>
<li><p><strong>Multiplexing</strong>: Multiple requests over a single connection</p>
</li>
<li><p><strong>Header compression</strong>: Reduced overhead with HPACK</p>
</li>
<li><p><strong>Server push</strong>: The Server can initiate streams to clients</p>
</li>
<li><p><strong>Flow control</strong>: Better handling of slow consumers</p>
</li>
</ul>
<h4 id="heading-data-structure-definitions">Data Structure Definitions</h4>
<p>In REST APIs, you define <strong>DTOs (Data Transfer Objects)</strong> as regular classes:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProductDto</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>These DTOs exist only in your specific language and need manual synchronization across different services or languages.</p>
<p>In gRPC, you define <strong>Messages</strong> in the proto file:</p>
<pre><code class="lang-csharp">message ProductModel {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> name = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">double</span> price = <span class="hljs-number">3</span>;
}
</code></pre>
<p>These message definitions are language-agnostic and automatically generate equivalent classes in any supported programming language.</p>
<p>Here's a quick comparison table to summarize these differences:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Service Contracts and Interfaces Service Contracts and Interfaces REST API Concept</td><td>gRPC Equivalent</td><td>Purpose</td></tr>
</thead>
<tbody>
<tr>
<td>Interface</td><td>Service</td><td>Defines available operations</td></tr>
<tr>
<td>DTO (Data Transfer Object)</td><td>Message</td><td>Defines a data structure</td></tr>
<tr>
<td>JSON Request/Response</td><td>Binary Protocol Buffer</td><td>Data serialization format</td></tr>
<tr>
<td>HTTP/1.1</td><td>HTTP/2</td><td>Transport protocol</td></tr>
</tbody>
</table>
</div><h3 id="heading-creating-the-product-proto-file">Creating the Product Proto File</h3>
<p>Navigate to the <code>Protos</code> folder in your project and create a new file called <code>product.proto</code>. Make sure the file extension is <code>.proto</code>.</p>
<p>Your project structure should look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">ProductGrpc/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Protos/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">greet.proto</span>          <span class="hljs-comment"># Default template file</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">product.proto</span>        <span class="hljs-comment"># Our new proto file</span>
<span class="hljs-string">└──</span> <span class="hljs-string">...</span>
</code></pre>
<h3 id="heading-setting-up-the-proto-file-header">Setting Up the Proto File Header</h3>
<p>Add the following header to your <code>product.proto</code> file:</p>
<pre><code class="lang-cs">
<span class="hljs-comment">//  Protos/product.proto</span>
syntax = <span class="hljs-string">"proto3"</span>;

option csharp_namespace = <span class="hljs-string">"ProductGrpc"</span>;

package product;
</code></pre>
<p>Here’s what’s going on:</p>
<ul>
<li><p><code>syntax = "proto3"</code>: Specifies that we're using Protocol Buffers version 3</p>
</li>
<li><p><code>option csharp_namespace = "ProductGrpc"</code>: Sets the C# namespace for generated code</p>
</li>
<li><p><code>package product</code>: Defines the protobuf package name</p>
</li>
</ul>
<p><strong>Note:</strong> If you named your project differently, make sure the <code>csharp_namespace</code> matches your project name.</p>
<h3 id="heading-defining-the-product-service">Defining the Product Service</h3>
<p>In gRPC, services define the available operations (similar to interfaces in REST APIs). Add the following service definition:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// :Protos/product.proto</span>
service  ProductsServiceProto {
  <span class="hljs-function">rpc <span class="hljs-title">CreateProduct</span>(<span class="hljs-params">CreateProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">CreateProductResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">GetProduct</span>(<span class="hljs-params">GetProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">GetProductResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">ListProducts</span>(<span class="hljs-params">ListProductsRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">ListProductsResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">UpdateProduct</span>(<span class="hljs-params">UpdateProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">UpdateProductResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">DeleteProduct</span>(<span class="hljs-params">DeleteProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">DeleteProductResponse</span>)</span>;
}
</code></pre>
<p>Service methods explained:</p>
<ul>
<li><p><code>rpc</code>: Defines a remote procedure call</p>
</li>
<li><p><code>CreateProduct</code>: Method name</p>
</li>
<li><p><code>(CreateProductRequest)</code>: Input message type</p>
</li>
<li><p><code>returns (CreateProductResponse)</code>: Output message type</p>
</li>
</ul>
<h3 id="heading-defining-protocol-buffer-messages">Defining Protocol Buffer Messages</h3>
<p>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:</p>
<h4 id="heading-product-model-message">Product Model Message:</h4>
<pre><code class="lang-yaml"><span class="hljs-string">//</span> <span class="hljs-string">product.proto</span>
<span class="hljs-string">message</span> <span class="hljs-string">ProductModel</span> {
  <span class="hljs-string">string</span> <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">1</span><span class="hljs-string">;</span>
  <span class="hljs-string">string</span> <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-number">2</span><span class="hljs-string">;</span>
  <span class="hljs-string">string</span> <span class="hljs-string">description</span> <span class="hljs-string">=</span> <span class="hljs-number">3</span><span class="hljs-string">;</span>
  <span class="hljs-string">double</span> <span class="hljs-string">price</span> <span class="hljs-string">=</span> <span class="hljs-number">4</span><span class="hljs-string">;</span>
  <span class="hljs-string">string</span> <span class="hljs-string">created_at</span> <span class="hljs-string">=</span> <span class="hljs-number">5</span><span class="hljs-string">;</span>
  <span class="hljs-string">string</span> <span class="hljs-string">updated_at</span> <span class="hljs-string">=</span> <span class="hljs-number">6</span><span class="hljs-string">;</span>
  <span class="hljs-string">string</span> <span class="hljs-string">tags</span> <span class="hljs-string">=</span> <span class="hljs-number">7</span><span class="hljs-string">;</span>
}
</code></pre>
<h4 id="heading-create-operation-messages">Create Operation Messages:</h4>
<pre><code class="lang-cs"><span class="hljs-comment">// Protos/product.proto</span>
message CreateProductRequest {
  <span class="hljs-keyword">string</span> name = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> description = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">double</span> price = <span class="hljs-number">3</span>;
  <span class="hljs-keyword">string</span> tags = <span class="hljs-number">4</span>;
}

message CreateProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  ProductModel product = <span class="hljs-number">3</span>;
}
</code></pre>
<h4 id="heading-read-operation-messages">Read Operation Messages:</h4>
<pre><code class="lang-cs"><span class="hljs-comment">// Protos/product.proto</span>
message GetProductRequest {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
}

message GetProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  ProductModel product = <span class="hljs-number">3</span>;
}

message ListProductsRequest {
  int32 page = <span class="hljs-number">1</span>;
  int32 page_size = <span class="hljs-number">2</span>;
}

message ListProductsResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  repeated ProductModel products = <span class="hljs-number">3</span>;
  int32 total_count = <span class="hljs-number">4</span>;
}
</code></pre>
<h4 id="heading-update-operation-messages">Update Operation Messages:</h4>
<pre><code class="lang-cs"> <span class="hljs-comment">// Protos/product.proto</span>
message UpdateProductRequest {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> name = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">string</span> description = <span class="hljs-number">3</span>;
  <span class="hljs-keyword">double</span> price = <span class="hljs-number">4</span>;
  <span class="hljs-keyword">string</span> tags = <span class="hljs-number">5</span>;
}

message UpdateProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  ProductModel product = <span class="hljs-number">3</span>;
}
</code></pre>
<h4 id="heading-delete-operation-messages">Delete Operation Messages:</h4>
<pre><code class="lang-cs"><span class="hljs-comment">// Protos/product.proto</span>
message DeleteProductRequest {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
}

message DeleteProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
}
</code></pre>
<h3 id="heading-understanding-protocol-buffer-syntax">Understanding Protocol Buffer Syntax</h3>
<p>There are a few key concepts you should understand about how protocol buffers work:</p>
<ul>
<li><p><strong>Field Numbers</strong>: Each field has a unique number (for example, <code>= 1</code>, <code>= 2</code>) used for binary encoding</p>
</li>
<li><p><strong>Field Types</strong>: <code>string</code>, <code>int32</code>, <code>double</code>, <code>bool</code> are common scalar types</p>
</li>
<li><p><strong>repeated</strong>: Indicates an array/list (for example, <code>repeated ProductModel products</code>)</p>
</li>
<li><p><strong>Message Nesting</strong>: Messages can contain other messages (for example, <code>ProductModel product</code>)</p>
</li>
</ul>
<p>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).</p>
<h3 id="heading-complete-product-proto-file">Complete Product Proto File</h3>
<p>Here's your complete <code>product.proto</code> file:</p>
<pre><code class="lang-cs"><span class="hljs-comment">// Protos/product.proto</span>
syntax = <span class="hljs-string">"proto3"</span>;

option csharp_namespace = <span class="hljs-string">"ProductGrpc"</span>;

package product;

<span class="hljs-comment">// Product service definition</span>
service  ProductsServiceProto {
  <span class="hljs-function">rpc <span class="hljs-title">CreateProduct</span>(<span class="hljs-params">CreateProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">CreateProductResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">GetProduct</span>(<span class="hljs-params">GetProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">GetProductResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">ListProducts</span>(<span class="hljs-params">ListProductsRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">ListProductsResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">UpdateProduct</span>(<span class="hljs-params">UpdateProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">UpdateProductResponse</span>)</span>;
  <span class="hljs-function">rpc <span class="hljs-title">DeleteProduct</span>(<span class="hljs-params">DeleteProductRequest</span>) <span class="hljs-title">returns</span> (<span class="hljs-params">DeleteProductResponse</span>)</span>;
}

<span class="hljs-comment">// Product model message</span>
message ProductModel {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> name = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">string</span> description = <span class="hljs-number">3</span>;
  <span class="hljs-keyword">double</span> price = <span class="hljs-number">4</span>;
  <span class="hljs-keyword">string</span> created_at = <span class="hljs-number">5</span>;
  <span class="hljs-keyword">string</span> updated_at = <span class="hljs-number">6</span>;
  <span class="hljs-keyword">string</span> tags = <span class="hljs-number">7</span>;
}

<span class="hljs-comment">// Create operation messages</span>
message CreateProductRequest {
  <span class="hljs-keyword">string</span> name = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> description = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">double</span> price = <span class="hljs-number">3</span>;
  <span class="hljs-keyword">string</span> tags = <span class="hljs-number">4</span>;
}

message CreateProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  ProductModel product = <span class="hljs-number">3</span>;
}

<span class="hljs-comment">// Read operation messages</span>
message GetProductRequest {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
}

message GetProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  ProductModel product = <span class="hljs-number">3</span>;
}

message ListProductsRequest {
  int32 page = <span class="hljs-number">1</span>;
  int32 page_size = <span class="hljs-number">2</span>;
}

message ListProductsResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  repeated ProductModel products = <span class="hljs-number">3</span>;
  int32 total_count = <span class="hljs-number">4</span>;
}

<span class="hljs-comment">// Update operation messages</span>
message UpdateProductRequest {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> name = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">string</span> description = <span class="hljs-number">3</span>;
  <span class="hljs-keyword">double</span> price = <span class="hljs-number">4</span>;
  <span class="hljs-keyword">string</span> tags = <span class="hljs-number">5</span>;
}

message UpdateProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
  ProductModel product = <span class="hljs-number">3</span>;
}

<span class="hljs-comment">// Delete operation messages</span>
message DeleteProductRequest {
  <span class="hljs-keyword">string</span> id = <span class="hljs-number">1</span>;
}

message DeleteProductResponse {
  <span class="hljs-keyword">bool</span> success = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> message = <span class="hljs-number">2</span>;
}
</code></pre>
<h3 id="heading-building-the-project-to-generate-c-code">Building the Project to Generate C# Code</h3>
<p>Now that we've defined our Protocol Buffer contract, we need to build the project to generate the corresponding C# code:</p>
<pre><code class="lang-bash">dotnet build
</code></pre>
<p>This command will compile your <code>.proto</code> files into C# classes, generate client and server code, and create strongly-typed request/response classes.</p>
<p>You should see output confirming the build was successful:</p>
<pre><code class="lang-bash">Restore complete (0.6s)
  ProductGrpc succeeded (9.5s) → bin\Debug\net9.0\ProductGrpc.dll

Build succeeded <span class="hljs-keyword">in</span> 11.1s
</code></pre>
<h3 id="heading-what-gets-generated">What Gets Generated?</h3>
<p>After building, the Protocol Buffer compiler generates several C# files (you won't see them directly, but they're available in your code):</p>
<ul>
<li><p><strong>ProductModel</strong>: C# class representing your product data</p>
</li>
<li><p><strong>CreateProductRequest/Response</strong>: Request and response classes for create operations</p>
</li>
<li><p><strong>ProductService.ProductServiceBase</strong>: Base class for implementing your service</p>
</li>
<li><p><strong>ProductService.ProductServiceClient</strong>: Client class for calling the service</p>
</li>
</ul>
<p>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.</p>
<h3 id="heading-whats-next-1">What's Next?</h3>
<p>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 <code>ProductService</code> Class and implement each CRUD operation.</p>
<p><strong>Remember:</strong> Protocol Buffers are language-agnostic, so this same <code>.proto</code> file could be used to generate client code in Python, Java, Go, or any other supported language.</p>
<h2 id="heading-how-to-implement-crud-operations-services-with-grpc">How to Implement CRUD Operations Services with gRPC</h2>
<p>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.</p>
<h3 id="heading-understanding-the-implementation-architecture">Understanding the Implementation Architecture</h3>
<p>Before we start coding, let's understand how the pieces fit together:</p>
<pre><code class="lang-bash">Protocol Buffer (.proto) → Generated C<span class="hljs-comment"># Code → Our Service Implementation → Database</span>
</code></pre>
<p>Key concepts in this code:</p>
<ul>
<li><p><strong>Proto Service:</strong> Interface (defines what methods are available)</p>
</li>
<li><p><strong>Proto Messages</strong>: DTOs (define data structure)</p>
</li>
<li><p><strong>Service Implementation</strong>: Business logic (what happens)</p>
</li>
<li><p><strong>Database Context</strong>: Data persistence layer</p>
</li>
</ul>
<h3 id="heading-configuring-proto-file-build">Configuring Proto File Build</h3>
<p>First, we need to ensure our <code>product.proto</code> file gets compiled into C# code during the build process.</p>
<p>Open your <code>ProductGrpc.csproj</code> file and locate the <code>&lt;ItemGroup&gt;</code> section that references proto files:</p>
<pre><code class="lang-xml">  <span class="hljs-comment">&lt;!-- ProductGrpc.csproj --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk.Web"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net9.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Nullable</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">Nullable</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ImplicitUsings</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">ImplicitUsings</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Protobuf</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Protos\greet.proto"</span> <span class="hljs-attr">GrpcServices</span>=<span class="hljs-string">"Server"</span> /&gt;</span>
    <span class="hljs-comment">&lt;!-- Add this line to include our product.proto file --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Protobuf</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Protos\product.proto"</span> <span class="hljs-attr">GrpcServices</span>=<span class="hljs-string">"Server"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Grpc.AspNetCore"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"2.64.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.Design"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"9.0.6"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">IncludeAssets</span>&gt;</span>runtime; build; native; contentfiles; analyzers; buildtransitive<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeAssets</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PrivateAssets</span>&gt;</span>all<span class="hljs-tag">&lt;/<span class="hljs-name">PrivateAssets</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">PackageReference</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.Sqlite"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"9.0.6"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>What this configuration does:</p>
<ul>
<li><p><code>Include="Protos\product.proto"</code> tells .NET to process our proto file</p>
</li>
<li><p><code>GrpcServices="Server"</code> generates server-side code (service base classes)</p>
</li>
</ul>
<h3 id="heading-building-the-project">Building the Project</h3>
<p>Now let's build the project to generate the C# code from our proto file:</p>
<pre><code class="lang-bash">dotnet build
</code></pre>
<p>This command will compile your <code>.proto</code> files into C# classes, generate the <code>ProductsServiceProto. ProductsServiceProtoBase</code> class we'll inherit from, create all the request/response message classes, and validate that everything compiles correctly.</p>
<p>You should see output like:</p>
<pre><code class="lang-yaml"><span class="hljs-string">Build</span> <span class="hljs-string">succeeded.</span>
    <span class="hljs-number">0</span> <span class="hljs-string">Warning(s)</span>
    <span class="hljs-number">0</span> <span class="hljs-string">Error(s)</span>
</code></pre>
<h3 id="heading-creating-the-productservice-class">Creating the ProductService Class</h3>
<p>Navigate to the <code>Services</code> folder and create a new file called <code>ProductService.cs</code>. This will contain our gRPC service implementation.</p>
<p>Your project structure should now look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">ProductGrpc/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Services/</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">GreeterService.cs</span>    <span class="hljs-comment"># Default template service</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">ProductService.cs</span>    <span class="hljs-comment"># Our new product service</span>
<span class="hljs-string">└──</span> <span class="hljs-string">...</span>
</code></pre>
<h3 id="heading-setting-up-the-service-foundation">Setting Up the Service Foundation</h3>
<p>Start by creating the basic service class structure:</p>
<pre><code class="lang-csharp">
 <span class="hljs-comment">// Services/ProductService.cs</span>
<span class="hljs-keyword">using</span> Grpc.Core;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> ProductGrpc.Data;
<span class="hljs-keyword">using</span> ProductGrpc.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ProductGrpc.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProductService</span> :  <span class="hljs-title">ProductsServiceProto</span> .<span class="hljs-title">ProductsServiceProtoBase</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> AppDbContext _dbContext;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;ProductService&gt; _logger;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ProductService</span>(<span class="hljs-params">AppDbContext dbContext, ILogger&lt;ProductService&gt; logger</span>)</span>
        {
            _dbContext = dbContext;
            _logger = logger;
        }

        <span class="hljs-comment">// CRUD methods will be implemented here</span>
    }
}
</code></pre>
<p>Key components of this code:</p>
<ul>
<li><p><strong>Inheritance</strong>: <code>ProductsServiceProto .ProductsServiceProtoBase</code> is generated from our proto file</p>
</li>
<li><p><strong>Dependency injection</strong>: We inject <code>AppDbContext</code> for database operations and <code>ILogger</code> for logging</p>
</li>
<li><p><strong>Constructor</strong>: Initializes our dependencies</p>
</li>
</ul>
<h3 id="heading-implementing-method-signatures">Implementing Method Signatures</h3>
<p>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:</p>
<pre><code class="lang-csharp"> <span class="hljs-comment">//Services/ProductService.cs</span>
<span class="hljs-keyword">using</span> Grpc.Core;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> ProductGrpc.Data;
<span class="hljs-keyword">using</span> ProductGrpc.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ProductGrpc.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProductService</span> :  <span class="hljs-title">ProductsServiceProto</span> .<span class="hljs-title">ProductsServiceProtoBase</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> AppDbContext _dbContext;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;ProductService&gt; _logger;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ProductService</span>(<span class="hljs-params">AppDbContext dbContext, ILogger&lt;ProductService&gt; logger</span>)</span>
        {
            _dbContext = dbContext;
            _logger = logger;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;CreateProductResponse&gt; <span class="hljs-title">CreateProduct</span>(<span class="hljs-params">
            CreateProductRequest request, 
            ServerCallContext context</span>)</span>
        {
            <span class="hljs-comment">// Implementation will go here</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;GetProductResponse&gt; <span class="hljs-title">GetProduct</span>(<span class="hljs-params">
            GetProductRequest request, 
            ServerCallContext context</span>)</span>
        {
            <span class="hljs-comment">// Implementation will go here</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;ListProductsResponse&gt; <span class="hljs-title">ListProducts</span>(<span class="hljs-params">
            ListProductsRequest request, 
            ServerCallContext context</span>)</span>
        {
            <span class="hljs-comment">// Implementation will go here</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;UpdateProductResponse&gt; <span class="hljs-title">UpdateProduct</span>(<span class="hljs-params">
            UpdateProductRequest request, 
            ServerCallContext context</span>)</span>
        {
            <span class="hljs-comment">// Implementation will go here</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;DeleteProductResponse&gt; <span class="hljs-title">DeleteProduct</span>(<span class="hljs-params">
            DeleteProductRequest request, 
            ServerCallContext context</span>)</span>
        {
            <span class="hljs-comment">// Implementation will go here</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }
    }
}
</code></pre>
<h3 id="heading-understanding-method-parameters">Understanding Method Parameters</h3>
<p>Each gRPC method receives two parameters:</p>
<ol>
<li><p><strong>Request parameter</strong>: Contains the data sent by the client (for example, <code>CreateProductRequest</code>)</p>
</li>
<li><p><strong>ServerCallContext</strong>: Provides access to request metadata, cancellation tokens, and response headers</p>
</li>
</ol>
<p><strong>Method Signature Pattern:</strong></p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;ResponseType&gt; <span class="hljs-title">MethodName</span>(<span class="hljs-params">
    RequestType request, 
    ServerCallContext context</span>)</span>
</code></pre>
<h3 id="heading-registering-the-service">Registering the Service</h3>
<p>Before we implement the methods, we need to register our service with the application. Open <code>Program.cs</code> and add the service:</p>
<pre><code class="lang-cs">
 <span class="hljs-comment">// Program.cs</span>
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> ProductGrpc.Data;
<span class="hljs-keyword">using</span> ProductGrpc.Services; <span class="hljs-comment">// Add this using statement</span>

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

<span class="hljs-comment">// Add services to the container.</span>
builder.Services.AddGrpc();

<span class="hljs-comment">// Register our database context</span>
builder.Services.AddDbContext&lt;AppDbContext&gt;(options =&gt;
    options.UseSqlite(<span class="hljs-string">"Data Source=ProductGrpc.db"</span>));

<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Configure the HTTP request pipeline.</span>
app.MapGrpcService&lt;GreeterService&gt;();
app.MapGrpcService&lt;ProductService&gt;(); <span class="hljs-comment">// Add this line</span>

app.MapGet(<span class="hljs-string">"/"</span>, () =&gt; <span class="hljs-string">"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"</span>);

app.Run();
</code></pre>
<h3 id="heading-handling-compiler-warnings">Handling Compiler Warnings</h3>
<p>You might see warnings like:</p>
<pre><code class="lang-bash">This async method lacks <span class="hljs-string">'await'</span> operators and will run synchronously.
</code></pre>
<p>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.</p>
<h3 id="heading-creating-helper-methods">Creating Helper Methods</h3>
<p>Before implementing CRUD operations, let's add some helper methods for converting between our database models and Protocol Buffer messages:</p>
<pre><code class="lang-cs">:Services/ProductService.cs
<span class="hljs-comment">// Add these helper methods to your ProductService class</span>


 <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ProductModel <span class="hljs-title">MapToProductModel</span>(<span class="hljs-params">Product product</span>)</span>
{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ProductModel
    {
        Id = product.Id.ToString(),
        Name = product.Name,
        Description = product.Description,
        Price = (<span class="hljs-keyword">double</span>)product.Price,
        CreatedAt = product.Created.ToString(<span class="hljs-string">"yyyy-MM-ddTHH:mm:ssZ"</span>),
        UpdatedAt = product.Updated.ToString(<span class="hljs-string">"yyyy-MM-ddTHH:mm:ssZ"</span>),
        Tag = product.Tags ?? <span class="hljs-keyword">string</span>.Empty
    };
}
</code></pre>
<p>Excellent work! You've successfully:</p>
<ul>
<li><p>Configured proto file compilation</p>
</li>
<li><p>Created the ProductService class structure</p>
</li>
<li><p>Set up dependency injection</p>
</li>
<li><p>Defined all CRUD method signatures</p>
</li>
<li><p>Registered the service with the application</p>
</li>
<li><p>Created helper methods for data mapping</p>
</li>
</ul>
<h3 id="heading-whats-next-2">What's Next?</h3>
<p>Now that we have our service foundation ready, we'll implement each CRUD operation one by one:</p>
<ol>
<li><p><strong>CreateProduct</strong>: Add new products to the database</p>
</li>
<li><p><strong>GetProduct</strong>: Retrieve a single product by ID</p>
</li>
<li><p><strong>ListProducts</strong>: Get a paginated list of products</p>
</li>
<li><p><strong>UpdateProduct</strong>: Modify existing products</p>
</li>
<li><p><strong>DeleteProduct</strong>: Remove products from the database</p>
</li>
</ol>
<p>In the next sections, we'll dive deep into each implementation, handling error cases, validation, and best practices.</p>
<p><strong>💡 Pro Tip:</strong> The <code>ServerCallContext</code> 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.</p>
<p><strong>Note:</strong> The <code>override</code> keyword is crucial – it tells C# that we're implementing the virtual methods defined in the generated base class from our proto file.</p>
<h2 id="heading-how-to-implement-grpc-crud-database-operations-with-sqlite">How to Implement gRPC CRUD Database Operations With SQLite</h2>
<p>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.</p>
<h3 id="heading-understanding-the-implementation-pattern">Understanding the Implementation Pattern</h3>
<p>Each CRUD operation follows a consistent pattern:</p>
<ol>
<li><p><strong>Input Validation</strong>: Validate request parameters</p>
</li>
<li><p><strong>Database Operation</strong>: Perform the actual database work</p>
</li>
<li><p><strong>Response Mapping</strong>: Convert database models to Protocol Buffer messages</p>
</li>
<li><p><strong>Error Handling</strong>: Catch and handle exceptions gracefully</p>
</li>
</ol>
<h3 id="heading-creating-the-createproduct-service">Creating the <code>CreateProduct</code> Service</h3>
<p>The <code>CreateProduct</code> method handles adding new products to our database. This is the <strong>"C"</strong> in CRUD (Create).</p>
<pre><code class="lang-cs"><span class="hljs-comment">//Services/ProductService.cs</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;CreateProductResponse&gt; <span class="hljs-title">CreateProduct</span>(<span class="hljs-params">
    CreateProductRequest request, 
    ServerCallContext context</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-comment">// Input validation</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(request.Name))
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CreateProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Product name is required"</span>
            };
        }

        <span class="hljs-keyword">if</span> (request.Price &lt;= <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CreateProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Product price must be greater than zero"</span>
            };
        }

        <span class="hljs-comment">// Create new product entity</span>
        <span class="hljs-keyword">var</span> productItem = <span class="hljs-keyword">new</span> Product
        {
            Id = Guid.NewGuid(),
            Name = request.Name,
            Description = request.Description,
            Price = Convert.ToDecimal(request.Price),
            Created = DateTime.UtcNow,
            Updated = DateTime.UtcNow,
            Tags = request.Tags
        };

        <span class="hljs-comment">// Add to database</span>
        _dbContext.Products.Add(productItem);
        <span class="hljs-keyword">await</span> _dbContext.SaveChangesAsync();

        _logger.LogInformation(<span class="hljs-string">"Product created successfully with ID: {ProductId}"</span>, productItem.Id);

        <span class="hljs-comment">// Return success response</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CreateProductResponse
        {
            Success = <span class="hljs-literal">true</span>,
            Message = <span class="hljs-string">"Product created successfully"</span>,
            Product = MapToProductModel(productItem)
        };
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
        _logger.LogError(ex, <span class="hljs-string">"Error creating product"</span>);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CreateProductResponse
        {
            Success = <span class="hljs-literal">false</span>,
            Message = <span class="hljs-string">$"Error creating product: <span class="hljs-subst">{ex.Message}</span>"</span>
        };
    }
}
</code></pre>
<p>These are the important implementation details:</p>
<ul>
<li><p><strong>Unique ID generation</strong>: <code>Guid.NewGuid()</code> Creates a unique identifier</p>
</li>
<li><p><strong>Timestamp management</strong>: <code>DateTime.UtcNow</code> Ensures consistent timezone handling</p>
</li>
<li><p><strong>Type conversion</strong>: <code>Convert.ToDecimal()</code> converts double to decimal for database storage</p>
</li>
<li><p><strong>Input validation</strong>: Checks for required fields and valid values</p>
</li>
<li><p><strong>Logging</strong>: Records successful operations and errors for debugging</p>
</li>
</ul>
<h3 id="heading-creating-the-getproduct-service">Creating the <code>GetProduct</code> Service</h3>
<p>The <code>GetProduct</code> method retrieves a single product by its ID. This is the <strong>"R"</strong> in CRUD (Read).</p>
<pre><code class="lang-cs"><span class="hljs-comment">//Services/ProductService.cs</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;GetProductResponse&gt; <span class="hljs-title">GetProduct</span>(<span class="hljs-params">
    GetProductRequest request, 
    ServerCallContext context</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-comment">// Validate and parse the product ID</span>
        <span class="hljs-keyword">if</span> (!Guid.TryParse(request.Id, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> productId))
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> GetProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Invalid product ID format. Please provide a valid GUID."</span>
            };
        }

        <span class="hljs-comment">// Find product in database</span>
        <span class="hljs-keyword">var</span> product = <span class="hljs-keyword">await</span> _dbContext.Products.FindAsync(productId);

        <span class="hljs-keyword">if</span> (product == <span class="hljs-literal">null</span>)
        {
            _logger.LogWarning(<span class="hljs-string">"Product not found with ID: {ProductId}"</span>, productId);

            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> GetProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Product not found"</span>
            };
        }

        _logger.LogInformation(<span class="hljs-string">"Product retrieved successfully with ID: {ProductId}"</span>, productId);

        <span class="hljs-comment">// Return success response with product data</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> GetProductResponse
        {
            Success = <span class="hljs-literal">true</span>,
            Message = <span class="hljs-string">"Product retrieved successfully"</span>,
            Product = MapToProductModel(product)
        };
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
        _logger.LogError(ex, <span class="hljs-string">"Error retrieving product with ID: {ProductId}"</span>, request.Id);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> GetProductResponse
        {
            Success = <span class="hljs-literal">false</span>,
            Message = <span class="hljs-string">$"Error retrieving product: <span class="hljs-subst">{ex.Message}</span>"</span>
        };
    }
}
</code></pre>
<p>Important implementation details:</p>
<ul>
<li><p><strong>ID validation</strong>: <code>Guid.TryParse()</code> safely validates the ID format</p>
</li>
<li><p><strong>Database query</strong>: <code>FindAsync()</code> efficiently finds records by primary key</p>
</li>
<li><p><strong>Null checking</strong>: Handles cases where the product doesn't exist</p>
</li>
<li><p><strong>Detailed logging</strong>: Tracks both successful retrievals and missing products</p>
</li>
</ul>
<h3 id="heading-creating-the-listproducts-service">Creating the <code>ListProducts</code> Service</h3>
<p>The <code>ListProducts</code> method retrieves multiple products with pagination support. This is also part of the <strong>"R"</strong> in CRUD (Read).</p>
<pre><code class="lang-cs"><span class="hljs-comment">// Services/ProductService.cs</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;ListProductsResponse&gt; <span class="hljs-title">ListProducts</span>(<span class="hljs-params">
    ListProductsRequest request, 
    ServerCallContext context</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-comment">// Set default pagination values</span>
        <span class="hljs-keyword">var</span> pageSize = request.PageSize &lt;= <span class="hljs-number">0</span> ? <span class="hljs-number">10</span> : Math.Min(request.PageSize, <span class="hljs-number">100</span>); <span class="hljs-comment">// Max 100 items per page</span>
        <span class="hljs-keyword">var</span> page = request.Page &lt;= <span class="hljs-number">0</span> ? <span class="hljs-number">1</span> : request.Page;

        <span class="hljs-comment">// Calculate skip amount for pagination</span>
        <span class="hljs-keyword">var</span> skip = (page - <span class="hljs-number">1</span>) * pageSize;

        <span class="hljs-comment">// Get total count for pagination metadata</span>
        <span class="hljs-keyword">var</span> totalCount = <span class="hljs-keyword">await</span> _dbContext.Products.CountAsync();

        <span class="hljs-comment">// Retrieve paginated products</span>
        <span class="hljs-keyword">var</span> products = <span class="hljs-keyword">await</span> _dbContext.Products
            .OrderBy(p =&gt; p.Created) <span class="hljs-comment">// Consistent ordering</span>
            .Skip(skip)
            .Take(pageSize)
            .ToListAsync();

        <span class="hljs-comment">// Create response</span>
        <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">new</span> ListProductsResponse
        {
            Success = <span class="hljs-literal">true</span>,
            Message = products.Any() 
                ? <span class="hljs-string">$"Retrieved <span class="hljs-subst">{products.Count}</span> products (Page <span class="hljs-subst">{page}</span> of <span class="hljs-subst">{Math.Ceiling((<span class="hljs-keyword">double</span>)totalCount / pageSize)}</span>)"</span>
                : <span class="hljs-string">"No products found"</span>,
            TotalCount = totalCount
        };

        <span class="hljs-comment">// Add products to response</span>
        response.Products.AddRange(products.Select(MapToProductModel));

        _logger.LogInformation(<span class="hljs-string">"Listed {ProductCount} products for page {Page}"</span>, products.Count, page);

        <span class="hljs-keyword">return</span> response;
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
        _logger.LogError(ex, <span class="hljs-string">"Error retrieving products list"</span>);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ListProductsResponse
        {
            Success = <span class="hljs-literal">false</span>,
            Message = <span class="hljs-string">$"Error retrieving products: <span class="hljs-subst">{ex.Message}</span>"</span>,
            TotalCount = <span class="hljs-number">0</span>
        };
    }
}
</code></pre>
<p>Here are the key implementation details:</p>
<ul>
<li><p><strong>Pagination logic</strong>: Calculates <code>skip</code> and <code>take</code> values for efficient data retrieval</p>
</li>
<li><p><strong>Default values</strong>: Sets sensible defaults for page size and page number</p>
</li>
<li><p><strong>Performance optimization</strong>: Uses <code>Skip()</code> and <code>Take()</code> for database-level pagination</p>
</li>
<li><p><strong>Consistent ordering</strong>: <code>OrderBy()</code> ensures predictable results across requests</p>
</li>
<li><p><strong>Metadata</strong>: Returns total count for client-side pagination UI</p>
</li>
</ul>
<h3 id="heading-creating-the-updateproduct-service">Creating the <code>UpdateProduct</code> Service</h3>
<p>The <code>UpdateProduct</code> method modifies existing products. This is the <strong>"U"</strong> in CRUD (Update).</p>
<pre><code class="lang-cs"><span class="hljs-comment">// Services/ProductService.cs</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;UpdateProductResponse&gt; <span class="hljs-title">UpdateProduct</span>(<span class="hljs-params">
    UpdateProductRequest request, 
    ServerCallContext context</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-comment">// Validate product ID</span>
        <span class="hljs-keyword">if</span> (!Guid.TryParse(request.Id, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> productId))
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UpdateProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Invalid product ID format. Please provide a valid GUID."</span>
            };
        }

        <span class="hljs-comment">// Input validation</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(request.Name))
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UpdateProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Product name is required"</span>
            };
        }

        <span class="hljs-keyword">if</span> (request.Price &lt;= <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UpdateProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Product price must be greater than zero"</span>
            };
        }

        <span class="hljs-comment">// Find existing product</span>
        <span class="hljs-keyword">var</span> existingProduct = <span class="hljs-keyword">await</span> _dbContext.Products.FindAsync(productId);

        <span class="hljs-keyword">if</span> (existingProduct == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UpdateProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Product not found"</span>
            };
        }

        <span class="hljs-comment">// Update product properties</span>
        existingProduct.Name = request.Name;
        existingProduct.Description = request.Description;
        existingProduct.Price = Convert.ToDecimal(request.Price);
        existingProduct.Tags = request.Tags;
        existingProduct.Updated = DateTime.UtcNow; <span class="hljs-comment">// Track when updated</span>

        <span class="hljs-comment">// Save changes to database</span>
        <span class="hljs-keyword">await</span> _dbContext.SaveChangesAsync();

        _logger.LogInformation(<span class="hljs-string">"Product updated successfully with ID: {ProductId}"</span>, productId);

        <span class="hljs-comment">// Return success response with updated product</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UpdateProductResponse
        {
            Success = <span class="hljs-literal">true</span>,
            Message = <span class="hljs-string">"Product updated successfully"</span>,
            Product = MapToProductModel(existingProduct)
        };
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
        _logger.LogError(ex, <span class="hljs-string">"Error updating product with ID: {ProductId}"</span>, request.Id);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> UpdateProductResponse
        {
            Success = <span class="hljs-literal">false</span>,
            Message = <span class="hljs-string">$"Error updating product: <span class="hljs-subst">{ex.Message}</span>"</span>
        };
    }
}
</code></pre>
<p>Key details:</p>
<ul>
<li><p><strong>Existence check</strong>: Verifies the product exists before attempting updates</p>
</li>
<li><p><strong>Selective updates</strong>: Only updates the fields provided in the request</p>
</li>
<li><p><strong>Timestamp tracking</strong>: Updates the <code>Updated</code> field to track modification time</p>
</li>
<li><p><strong>Input validation</strong>: Ensures data integrity before saving</p>
</li>
<li><p><strong>Atomic operation</strong>: All changes are saved together or not at all</p>
</li>
</ul>
<h3 id="heading-creating-the-deleteproduct-service">Creating the <code>DeleteProduct</code> Service</h3>
<p>The <code>DeleteProduct</code> method removes products from the database. This is the <strong>"D"</strong> in CRUD (Delete).</p>
<pre><code class="lang-cs"> <span class="hljs-comment">// Services/ProductService.cs</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;DeleteProductResponse&gt; <span class="hljs-title">DeleteProduct</span>(<span class="hljs-params">
    DeleteProductRequest request, 
    ServerCallContext context</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-comment">// Validate product ID</span>
        <span class="hljs-keyword">if</span> (!Guid.TryParse(request.Id, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> productId))
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DeleteProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Invalid product ID format. Please provide a valid GUID."</span>
            };
        }

        <span class="hljs-comment">// Find the product to delete</span>
        <span class="hljs-keyword">var</span> product = <span class="hljs-keyword">await</span> _dbContext.Products.FindAsync(productId);

        <span class="hljs-keyword">if</span> (product == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DeleteProductResponse
            {
                Success = <span class="hljs-literal">false</span>,
                Message = <span class="hljs-string">"Product not found"</span>
            };
        }

        <span class="hljs-comment">// Remove product from database</span>
        _dbContext.Products.Remove(product);
        <span class="hljs-keyword">await</span> _dbContext.SaveChangesAsync();

        _logger.LogInformation(<span class="hljs-string">"Product deleted successfully with ID: {ProductId}"</span>, productId);

        <span class="hljs-comment">// Return success response</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DeleteProductResponse
        {
            Success = <span class="hljs-literal">true</span>,
            Message = <span class="hljs-string">"Product deleted successfully"</span>
        };
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
        _logger.LogError(ex, <span class="hljs-string">"Error deleting product with ID: {ProductId}"</span>, request.Id);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DeleteProductResponse
        {
            Success = <span class="hljs-literal">false</span>,
            Message = <span class="hljs-string">$"Error deleting product: <span class="hljs-subst">{ex.Message}</span>"</span>
        };
    }
}
</code></pre>
<p>Key details:</p>
<ul>
<li><p><strong>Soft vs hard delete</strong>: This implements hard delete (permanent removal)</p>
</li>
<li><p><strong>Existence verification</strong>: Checks if the product exists before deletion</p>
</li>
<li><p><strong>Clean removal</strong>: Uses Entity Framework's <code>Remove()</code> method</p>
</li>
<li><p><strong>Confirmation</strong>: Returns a success message confirming deletion</p>
</li>
</ul>
<h3 id="heading-complete-productservice-implementation">Complete ProductService Implementation</h3>
<p>Here's your complete <code>ProductService.cs</code> file with all CRUD operations:</p>
<pre><code class="lang-cs"><span class="hljs-comment">// Services/ProductService.cs</span>
<span class="hljs-keyword">using</span> Grpc.Core;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> ProductGrpc.Data;
<span class="hljs-keyword">using</span> ProductGrpc.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ProductGrpc.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProductService</span> : <span class="hljs-title">Product.ProductServiceBase</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> AppDbContext _dbContext;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;ProductService&gt; _logger;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ProductService</span>(<span class="hljs-params">AppDbContext dbContext, ILogger&lt;ProductService&gt; logger</span>)</span>
        {
            _dbContext = dbContext;
            _logger = logger;
        }

        <span class="hljs-comment">// Helper method to map Product entity to ProductModel message</span>
        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ProductModel <span class="hljs-title">MapToProductModel</span>(<span class="hljs-params">Product product</span>)</span>
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ProductModel
            {
                Id = product.Id.ToString(),
                Name = product.Name,
                Description = product.Description,
                Price = (<span class="hljs-keyword">double</span>)product.Price,
                CreatedAt = product.Created.ToString(<span class="hljs-string">"yyyy-MM-ddTHH:mm:ssZ"</span>),
                UpdatedAt = product.Updated.ToString(<span class="hljs-string">"yyyy-MM-ddTHH:mm:ssZ"</span>),
                Tags = product.Tags ?? <span class="hljs-keyword">string</span>.Empty
            };
        }

        <span class="hljs-comment">// All CRUD methods go here (as implemented above)</span>
        <span class="hljs-comment">// CreateProduct, GetProduct, ListProducts, UpdateProduct, DeleteProduct</span>
    }
}
</code></pre>
<p>Excellent work! You've successfully implemented all CRUD operations:</p>
<ul>
<li><p><strong>Create</strong>: Add new products with validation</p>
</li>
<li><p><strong>Read</strong>: Retrieve single products and paginated lists</p>
</li>
<li><p><strong>Update</strong>: Modify existing products with validation</p>
</li>
<li><p><strong>Delete</strong>: Remove products safely</p>
</li>
</ul>
<h3 id="heading-whats-next-3">What's Next?</h3>
<p>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.</p>
<h2 id="heading-how-to-test-grpc-services-with-postman">How to Test gRPC Services with Postman</h2>
<p>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.</p>
<h3 id="heading-why-grpc-testing-is-different">Why gRPC Testing is Different</h3>
<p>Here are the important differences between gRPC and REST API testing summarized:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>REST API</td><td>gRPC</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Protocol</strong></td><td>HTTP/1.1</td><td>HTTP/2</td></tr>
<tr>
<td><strong>Data Format</strong></td><td>JSON/XML</td><td>Protocol Buffers (Binary)</td></tr>
<tr>
<td><strong>Schema</strong></td><td>Optional (OpenAPI/Swagger)</td><td>Required (.proto files)</td></tr>
<tr>
<td><strong>Content-Type</strong></td><td>application/json</td><td>application/grpc</td></tr>
<tr>
<td><strong>Testing Tools</strong></td><td>Any HTTP client</td><td>Specialized gRPC clients</td></tr>
</tbody>
</table>
</div><p>Why we need the proto file:</p>
<ul>
<li><p>gRPC requires the service contract (.proto file) to understand available methods</p>
</li>
<li><p>Protocol Buffers need schema definition for serialization/deserialization</p>
</li>
<li><p>Postman uses the proto file to generate the correct request/response structure</p>
</li>
</ul>
<h3 id="heading-setting-up-postman-for-grpc-testing">Setting Up Postman for gRPC Testing</h3>
<h4 id="heading-step-1-launch-postman">Step 1: Launch Postman</h4>
<p>Open Postman on your machine. You should see the main dashboard similar to this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753879108777/a98c6fa4-08c7-49f1-94a4-9138b69ad0a1.png" alt="Postman main dashboard showing the workspace interface" width="1908" height="1040" loading="lazy"></p>
<h4 id="heading-step-2-create-a-new-grpc-request">Step 2: Create a New gRPC Request</h4>
<p>Click on "New" in the top-left corner or use the "+" button. Then select "gRPC Request" from the available options.</p>
<p>You should see a modal dialog with different request types:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753879545910/840f2004-73c5-4c1e-97a6-62b39eb278f9.png" alt="Postman's new request modal showing gRPC option" width="2560" height="1371" loading="lazy"></p>
<p>Click on "gRPC Request" to create a new gRPC request.</p>
<h4 id="heading-step-3-configure-the-grpc-request-interface">Step 3: Configure the gRPC Request Interface</h4>
<p>After creating a gRPC request, you'll see the gRPC request interface:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753879652894/b339e198-27e9-4618-8cdb-54718da65841.png" alt="Postman gRPC request interface with service definition section" width="2560" height="1388" loading="lazy"></p>
<p>These are the notable components of the gRPC interface:</p>
<ul>
<li><p><strong>Server URL</strong>: Where your gRPC service is running</p>
</li>
<li><p><strong>Service definition</strong>: Where you import your .proto file</p>
</li>
<li><p><strong>Method selection</strong>: Choose which RPC method to call</p>
</li>
<li><p><strong>Message body</strong>: Request payload based on proto definitions</p>
</li>
</ul>
<h3 id="heading-importing-the-proto-file">Importing the Proto File</h3>
<h4 id="heading-step-4-access-service-definition">Step 4: Access Service Definition</h4>
<p>Locate the "Service definition" section in the gRPC request interface. Then click on "Import .proto file" or a similar option.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753879690499/b2d53cab-3d0c-4c84-8a12-7777480e6860.png" alt="Service definition section where proto files are imported" width="2560" height="1393" loading="lazy"></p>
<h4 id="heading-step-5-import-your-proto-file">Step 5: Import Your Proto File</h4>
<ol>
<li><p>Click "Select Files" or "Import" button</p>
</li>
<li><p>Navigate to your project directory</p>
</li>
<li><p>Go to the <code>Protos</code> folder</p>
</li>
<li><p>Select <code>product.proto</code> file</p>
</li>
<li><p>Click "Open" to import</p>
</li>
</ol>
<p><strong>File Path Structure:</strong></p>
<pre><code class="lang-bash">YourProject/
├── Protos/
│   ├── greet.proto
│   └── product.proto    ← Select this file
└── ...
</code></pre>
<h4 id="heading-step-6-configure-import-settings">Step 6: Configure Import Settings</h4>
<p>When importing, you'll see options like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753879892390/625b1fbd-cdd6-4437-af9e-441a39780c5e.png" alt="Proto file import configuration in Postman" width="2560" height="1393" loading="lazy"></p>
<p><strong>Import Configuration:</strong></p>
<ul>
<li><p><strong>Import as</strong>: Select "API"</p>
</li>
<li><p><strong>API name</strong>: Choose a descriptive name (for example, "ProductGrpc API")</p>
</li>
<li><p><strong>Import location</strong>: Select your workspace</p>
</li>
</ul>
<p>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.</p>
<h4 id="heading-step-7-verify-successful-import">Step 7: Verify Successful Import</h4>
<p>After successful import, you should see:</p>
<ol>
<li><p><strong>API Collection</strong>: Your named API appears in the left sidebar under "APIs."</p>
</li>
<li><p><strong>Available Methods</strong>: All RPC methods from your proto file are listed</p>
</li>
<li><p><strong>Request/Response Schemas</strong>: Postman understands your message structures</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753879919558/a9c264a6-b021-4c7a-8221-557350c07f53.png" alt="Successfully imported proto file showing available methods" width="2560" height="1393" loading="lazy"></p>
<h3 id="heading-understanding-the-grpc-request-interface">Understanding the gRPC Request Interface</h3>
<p>Once connected, you'll see a method selection dropdown with the following:</p>
<ul>
<li><p>CreateProduct</p>
</li>
<li><p>GetProduct</p>
</li>
<li><p>ListProducts</p>
</li>
<li><p>UpdateProduct</p>
</li>
<li><p>DeleteProduct</p>
</li>
</ul>
<p>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.</p>
<h3 id="heading-whats-next-4">What's Next?</h3>
<p>Now that Postman is configured with your proto file, you're ready to test each CRUD operation:</p>
<ol>
<li><p><strong>CreateProduct</strong>: Test adding new products</p>
</li>
<li><p><strong>GetProduct</strong>: Test retrieving single products</p>
</li>
<li><p><strong>ListProducts</strong>: Test pagination and listing</p>
</li>
<li><p><strong>UpdateProduct</strong>: Test modifying existing products</p>
</li>
<li><p><strong>DeleteProduct</strong>: Test removing products</p>
</li>
</ol>
<p>In the next section, we'll walk through testing each operation with sample data and expected responses.</p>
<h2 id="heading-how-to-test-product-creation">How to Test Product Creation</h2>
<p>Now that we have Postman configured with our proto file, it's time to test our gRPC service! We'll start by testing the <code>CreateProduct</code> Method to add a new product to our database.</p>
<h3 id="heading-request-structure">Request Structure</h3>
<p>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 <code>.proto</code> 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).</p>
<h3 id="heading-step-1-select-the-createproduct-method">Step 1: Select the CreateProduct Method</h3>
<p>Open your gRPC request in Postman and click the method dropdown (should show available methods). Select "CreateProduct" from the list.</p>
<p>You should see all the methods we defined in our proto file:</p>
<ul>
<li><p>CreateProduct</p>
</li>
<li><p>GetProduct</p>
</li>
<li><p>ListProducts</p>
</li>
<li><p>UpdateProduct</p>
</li>
<li><p>DeleteProduct</p>
</li>
</ul>
<h3 id="heading-step-2-request-schema">Step 2: Request Schema</h3>
<p>When you select <code>CreateProduct</code>, Postman automatically generates the request structure based on our <code>CreateProductRequest</code> message from the proto file:</p>
<p><strong>Proto Definition Reminder:</strong></p>
<pre><code class="lang-csharp">message CreateProductRequest {
  <span class="hljs-keyword">string</span> name = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">string</span> description = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">double</span> price = <span class="hljs-number">3</span>;
  <span class="hljs-keyword">string</span> tags = <span class="hljs-number">4</span>;
}
</code></pre>
<p><strong>Postman JSON Representation:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"price"</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">"tags"</span>: <span class="hljs-string">""</span>
}
</code></pre>
<h3 id="heading-step-3-prepare-test-data">Step 3: Prepare Test Data</h3>
<p>Let's create our first product with meaningful test data. In the request body, enter:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"MacBook Pro 16-inch"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD"</span>,
  <span class="hljs-attr">"price"</span>: <span class="hljs-number">2499.99</span>,
  <span class="hljs-attr">"tags"</span>: <span class="hljs-string">"laptop, apple, professional"</span>
}
</code></pre>
<p>So what are these fields?</p>
<ul>
<li><p><strong>name</strong>: Product title (required, string)</p>
</li>
<li><p><strong>description</strong>: Detailed product information (string)</p>
</li>
<li><p><strong>price</strong>: Product cost (double/number, must be &gt; 0)</p>
</li>
<li><p><strong>tags</strong>: Comma-separated keywords (string)</p>
</li>
</ul>
<h3 id="heading-step-4-send-the-request">Step 4: Send the Request</h3>
<p>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).</p>
<h3 id="heading-step-5-analyze-the-response">Step 5: Analyze the Response</h3>
<p>If everything works correctly, you should receive a response like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Product created successfully"</span>,
  <span class="hljs-attr">"product"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"920b98d2-4feb-4705-8303-ce6e28bd3694"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"MacBook Pro 16-inch"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD"</span>,
    <span class="hljs-attr">"price"</span>: <span class="hljs-number">2499.99</span>,
    <span class="hljs-attr">"created_at"</span>: <span class="hljs-string">"2024-01-15T16:11:38Z"</span>,
    <span class="hljs-attr">"updated_at"</span>: <span class="hljs-string">"2024-01-15T16:11:38Z"</span>,
    <span class="hljs-attr">"tags"</span>: <span class="hljs-string">"laptop, apple, professional"</span>
  }
}
</code></pre>
<p>Response fields:</p>
<ul>
<li><p><strong>success</strong>: Boolean indicating operation success</p>
</li>
<li><p><strong>message</strong>: Human-readable status message</p>
</li>
<li><p><strong>product</strong>: The created product with generated fields</p>
<ul>
<li><p><strong>id</strong>: Auto-generated GUID</p>
</li>
<li><p><strong>created_at/updated_at</strong>: UTC timestamps</p>
</li>
<li><p><strong>Other fields</strong>: Echo of the input data</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-visual-confirmation-in-postman">Visual Confirmation in Postman</h3>
<p>Here's how a successful request looks in Postman:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753880143034/f7e81267-8d45-4835-b1d9-ad091563b99c.png" alt="Postman interface showing successful product creation with request and response" width="2511" height="1342" loading="lazy"></p>
<p>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.</p>
<h3 id="heading-whats-next-5">What's Next?</h3>
<p>Now that we've successfully tested product creation, let's test the other CRUD operations:</p>
<ol>
<li><p><strong>GetProduct</strong>: Retrieve the product we just created</p>
</li>
<li><p><strong>ListProducts</strong>: See all products with pagination</p>
</li>
<li><p><strong>UpdateProduct</strong>: Modify the existing product</p>
</li>
<li><p><strong>DeleteProduct</strong>: Remove the product from the database</p>
</li>
</ol>
<p>Each operation will help us verify that our complete gRPC service is working correctly.</p>
<h2 id="heading-how-to-test-all-product-operations">How to Test All Product Operations</h2>
<p>Now that we've successfully created a product, let's test all the remaining CRUD operations to ensure our complete gRPC service works correctly.</p>
<h3 id="heading-get-all-products-listproducts">Get All Products (ListProducts)</h3>
<p>The <code>ListProducts</code> 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.</p>
<h4 id="heading-step-1-select-listproducts-method">Step 1: Select ListProducts Method</h4>
<p>Click the method dropdown in your Postman gRPC request. Then select "ListProducts" from the available methods. Notice the request structure – it includes pagination parameters.</p>
<h4 id="heading-step-2-configure-the-request">Step 2: Configure the Request</h4>
<p>The <code>ListProductsRequest</code> supports pagination parameters:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"page"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">"pageSize"</span>: <span class="hljs-number">10</span>
}
</code></pre>
<p>Here’s what’s going on with these parameters:</p>
<ul>
<li><p><strong>page</strong>: Which page of results to retrieve (default: 1)</p>
</li>
<li><p><strong>pageSize</strong>: Number of products per page (default: 10, max: 100)</p>
</li>
</ul>
<h4 id="heading-step-3-send-the-request">Step 3: Send the Request</h4>
<p>Click "Invoke" to send the request and wait for the response containing all your products.</p>
<h4 id="heading-step-4-response">Step 4: Response</h4>
<p>You should receive a response like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Retrieved 2 products (Page 1 of 1)"</span>,
  <span class="hljs-attr">"totalCount"</span>: <span class="hljs-number">2</span>,
  <span class="hljs-attr">"products"</span>: [
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"920b98d2-4feb-4705-8303-ce6e28bd3694"</span>,
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"MacBook Pro 16-inch"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD"</span>,
      <span class="hljs-attr">"price"</span>: <span class="hljs-number">2499.99</span>,
      <span class="hljs-attr">"created_at"</span>: <span class="hljs-string">"2024-01-15T16:11:38Z"</span>,
      <span class="hljs-attr">"updated_at"</span>: <span class="hljs-string">"2024-01-15T16:11:38Z"</span>,
      <span class="hljs-attr">"tags"</span>: <span class="hljs-string">"laptop, apple, professional"</span>
    },
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"a1b2c3d4-5e6f-7890-abcd-ef1234567890"</span>,
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"iPhone 15 Pro"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Latest iPhone with titanium design"</span>,
      <span class="hljs-attr">"price"</span>: <span class="hljs-number">999.99</span>,
      <span class="hljs-attr">"created_at"</span>: <span class="hljs-string">"2024-01-15T16:15:22Z"</span>,
      <span class="hljs-attr">"updated_at"</span>: <span class="hljs-string">"2024-01-15T16:15:22Z"</span>,
      <span class="hljs-attr">"tags"</span>: <span class="hljs-string">"smartphone, apple, premium"</span>
    }
  ]
}
</code></pre>
<p>Response structure:</p>
<ul>
<li><p><strong>success</strong>: Operation status</p>
</li>
<li><p><strong>message</strong>: Descriptive message with pagination info</p>
</li>
<li><p><strong>totalCount</strong>: Total number of products in the database</p>
</li>
<li><p><strong>products</strong>: Array of product objects</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753880306062/af0bd1d4-24d0-4be8-96a1-f59404f36672.png" alt="Postman showing successful retrieval of all products with pagination" width="2469" height="1384" loading="lazy"></p>
<h3 id="heading-testing-pagination">Testing Pagination</h3>
<p>Let’s now test some different pagination scenarios:</p>
<p><strong>Get the first 5 products:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"page"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">"pageSize"</span>: <span class="hljs-number">5</span>
}
</code></pre>
<p><strong>Get the second page:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"page"</span>: <span class="hljs-number">2</span>,
  <span class="hljs-attr">"pageSize"</span>: <span class="hljs-number">5</span>
}
</code></pre>
<h3 id="heading-get-product-by-id-getproduct">Get Product By ID (GetProduct)</h3>
<p>The <code>GetProduct</code> 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.</p>
<h4 id="heading-step-1-select-the-getproduct-method">Step 1: Select the GetProduct Method</h4>
<p>Select "GetProduct" from the method dropdown. Notice that the request structure requires an ID field.</p>
<h4 id="heading-step-2-prepare-the-request">Step 2: Prepare the Request</h4>
<p>Copy a product ID from your previous <code>ListProducts</code> response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"920b98d2-4feb-4705-8303-ce6e28bd3694"</span>
}
</code></pre>
<p>Important notes:</p>
<ul>
<li><p><strong>ID Format</strong>: Must be a valid GUID string</p>
</li>
<li><p><strong>Case Sensitivity</strong>: GUIDs are case-insensitive</p>
</li>
<li><p><strong>Validation</strong>: Invalid GUIDs will return an error</p>
</li>
</ul>
<h4 id="heading-step-3-send-the-request-1">Step 3: Send the Request</h4>
<p>Paste a valid product ID from your ListProducts response. Click "Invoke" to send the request.</p>
<h4 id="heading-step-4-analyze-the-response">Step 4: Analyze the Response</h4>
<p>Successful response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Product retrieved successfully"</span>,
  <span class="hljs-attr">"product"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"920b98d2-4feb-4705-8303-ce6e28bd3694"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"MacBook Pro 16-inch"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Apple MacBook Pro with M2 Pro chip, 16GB RAM, 512GB SSD"</span>,
    <span class="hljs-attr">"price"</span>: <span class="hljs-number">2499.99</span>,
    <span class="hljs-attr">"created_at"</span>: <span class="hljs-string">"2024-01-15T16:11:38Z"</span>,
    <span class="hljs-attr">"updated_at"</span>: <span class="hljs-string">"2024-01-15T16:11:38Z"</span>,
    <span class="hljs-attr">"tags"</span>: <span class="hljs-string">"laptop, apple, professional"</span>
  }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753880592637/ebbacee5-1333-4328-8c46-ab7dd24d824d.png" alt="Postman showing successful retrieval of a single product by ID" width="2550" height="1377" loading="lazy"></p>
<h3 id="heading-update-product-updateproduct">Update Product (UpdateProduct)</h3>
<p>The <code>UpdateProduct</code> method modifies an existing product. You need to provide the product ID and the fields you want to update.</p>
<h4 id="heading-step-1-select-the-updateproduct-method">Step 1: Select the UpdateProduct Method</h4>
<p>Select "UpdateProduct" from the method dropdown. Review the request structure which includes ID and all updatable fields.</p>
<h4 id="heading-step-2-prepare-the-update-request">Step 2: Prepare the Update Request</h4>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"920b98d2-4feb-4705-8303-ce6e28bd3694"</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"MacBook Pro 16-inch (Updated)"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Apple MacBook Pro with M2 Pro chip, 16GB RAM, 1TB SSD - Updated Storage"</span>,
  <span class="hljs-attr">"price"</span>: <span class="hljs-number">2799.99</span>,
  <span class="hljs-attr">"tags"</span>: <span class="hljs-string">"laptop, apple, professional, updated"</span>
}
</code></pre>
<p>Update guidelines:</p>
<ul>
<li><p><strong>ID</strong>: Must match an existing product</p>
</li>
<li><p><strong>All Fields</strong>: Currently required (not partial updates)</p>
</li>
<li><p><strong>Price</strong>: Must be greater than 0</p>
</li>
<li><p><strong>Name</strong>: Cannot be empty</p>
</li>
</ul>
<h4 id="heading-step-3-send-the-update-request">Step 3: Send the Update Request</h4>
<p>Make sure the ID exists (use one from your ListProducts response). Then click "Invoke" to send the update.</p>
<h4 id="heading-step-4-verify-the-update">Step 4: Verify the Update</h4>
<p>Successful response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Product updated successfully"</span>,
  <span class="hljs-attr">"product"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"920b98d2-4feb-4705-8303-ce6e28bd3694"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"MacBook Pro 16-inch (Updated)"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Apple MacBook Pro with M2 Pro chip, 16GB RAM, 1TB SSD - Updated Storage"</span>,
    <span class="hljs-attr">"price"</span>: <span class="hljs-number">2799.99</span>,
    <span class="hljs-attr">"created_at"</span>: <span class="hljs-string">"2024-01-15T16:11:38Z"</span>,
    <span class="hljs-attr">"updated_at"</span>: <span class="hljs-string">"2024-01-15T16:25:14Z"</span>,
    <span class="hljs-attr">"tags"</span>: <span class="hljs-string">"laptop, apple, professional, updated"</span>
  }
}
</code></pre>
<p>Notice the changes:</p>
<ul>
<li><p><strong>updated_at</strong>: Timestamp changed to reflect the update</p>
</li>
<li><p><strong>Modified Fields</strong>: All updated fields reflect new values</p>
</li>
<li><p><strong>created_at</strong>: Remains unchanged (original creation time)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753880928160/d12e58ff-3c14-49f6-a3dd-1d1d2c452e09.png" alt="Postman showing successful product update with modified fields" width="2524" height="1327" loading="lazy"></p>
<h3 id="heading-delete-product-by-id-deleteproduct">Delete Product By ID (DeleteProduct)</h3>
<p>The <code>DeleteProduct</code> method permanently removes a product from the database using its ID.</p>
<h4 id="heading-step-1-select-deleteproduct-method">Step 1: Select DeleteProduct Method</h4>
<p>Select "DeleteProduct" from the method dropdown. Note the simple request structure – it only requires an ID.</p>
<h4 id="heading-step-2-prepare-the-delete-request">Step 2: Prepare the Delete Request</h4>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"a1b2c3d4-5e6f-7890-abcd-ef1234567890"</span>
}
</code></pre>
<p><strong>⚠️ Warning</strong>: This operation permanently deletes the product. Make sure you're using the correct ID.</p>
<h4 id="heading-step-3-send-the-delete-request">Step 3: Send the Delete Request</h4>
<p>Double-check the product ID you want to delete. Click "Invoke" to send the delete request.</p>
<h4 id="heading-step-4-confirm-deletion">Step 4: Confirm Deletion</h4>
<p>Successful response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Product deleted successfully"</span>
}
</code></pre>
<p>Verification steps:</p>
<ol>
<li><p><strong>Try GetProduct</strong> with the same ID – it should return "Product not found"</p>
</li>
<li><p><strong>Run ListProducts</strong> – the product should no longer appear in the list</p>
</li>
<li><p><strong>Check totalCount</strong> – should be reduced by 1</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753880987953/23bb19f0-7ca0-4cec-b9bb-3774737f98c9.png" alt="Postman showing successful product deletion" width="2514" height="1372" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>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.</p>
<h3 id="heading-what-youve-accomplished">What You've Accomplished</h3>
<p>Let's recap the impressive skills and knowledge you've acquired:</p>
<h4 id="heading-foundation-building">Foundation building</h4>
<ul>
<li><p>Set up a complete gRPC project from scratch using .NET CLI</p>
</li>
<li><p>Configured SQLite database with Entity Framework Core</p>
</li>
<li><p>Created robust data models with proper validation</p>
</li>
<li><p>Implemented database migrations and seeding</p>
</li>
</ul>
<h4 id="heading-learning-protocol-buffers">Learning protocol buffers</h4>
<ul>
<li><p>Designed comprehensive .proto files with service definitions</p>
</li>
<li><p>Created strongly-typed message contracts for all CRUD operations</p>
</li>
<li><p>Understood the advantages of binary serialization over JSON</p>
</li>
<li><p>Implemented efficient data transfer objects (DTOs)</p>
</li>
</ul>
<h4 id="heading-service-implementation">Service implementation</h4>
<ul>
<li><p>Built a complete ProductService with all CRUD operations</p>
</li>
<li><p>Implemented proper error handling and validation</p>
</li>
<li><p>Added comprehensive logging for debugging and monitoring</p>
</li>
<li><p>Created efficient pagination for large datasets</p>
</li>
<li><p>Handled data mapping between entities and Protocol Buffer messages</p>
</li>
</ul>
<h4 id="heading-testing-and-validation">Testing and validation</h4>
<ul>
<li><p>Configured Postman for gRPC testing</p>
</li>
<li><p>Tested all CRUD operations with real data</p>
</li>
<li><p>Verified data integrity and proper response formatting</p>
</li>
</ul>
<h3 id="heading-key-technical-skills-gained">Key Technical Skills Gained</h3>
<p><strong>gRPC Expertise:</strong></p>
<ul>
<li><p>Understanding of the HTTP/2 protocol advantages</p>
</li>
<li><p>Protocol Buffer schema design and evolution</p>
</li>
<li><p>Service-to-service communication patterns</p>
</li>
<li><p>Performance optimization techniques</p>
</li>
</ul>
<h4 id="heading-you-can-access-the-code-in-this-github-repositoryhttpsgithubcomclifftech123isaiahcliffordopokublog">🔗 You can access the code <a target="_blank" href="https://github.com/Clifftech123/IsaiahCliffordOpokuBlog">in this GitHub Repository</a>.</h4>
<h3 id="heading-thank-you">Thank You!</h3>
<p>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.</p>
<p><strong>Happy coding, and may your services be fast, reliable, and scalable!</strong></p>
<p>If you want to learn more about .NET Core, you can subscribe to my YouTube channel <a target="_blank" href="https://youtube.com/@clifftech?si=QgdE39q4iYIPEN23">here</a>.</p>
<p><strong>🔗 Connect with the author:</strong></p>
<ul>
<li><p>GitHub: <a target="_blank" href="https://github.com/Clifftech123">@CliffTech123</a></p>
</li>
<li><p>Twitter: <a target="_blank" href="https://x.com/Clifftech_Dev">@Clifftech_Dev</a></p>
</li>
<li><p>LinkedIn: <a target="_blank" href="https://www.linkedin.com/in/isaiah-clifford-opoku/">Isaiah Clifford Opoku</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
