Featured

gRPC Service Create Using .NET Core 6.0 Entity Framework (Server & Client)

Last modified: May 12, 2022

Over few years, we have been using REST(Representational State Transfer) that use HTTP1.1 method GET, POST, PUT, and DELETE to work with server-side resource to create Web API. One of the advantage of Web API is that, it serve single point of consuming server-side resource, e.g. desktop application and mobile apps.

gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It usage Http 2 and 7 times or more faster than Web API. It use Proto Buffer, As we can see below, how the server and client work bi-direction

diagram (source : https://grpc.io/docs/what-is-grpc/introduction)

1 Server

1.1. Create a project

project

project

project

1.2. Install Nuget Packages

  • We need going to use Entity Framework Core and InMemory Database (for demo). For you production, you need to select something like SQLServer or MySQL

project

project

1.3. Data

  • Create a folder called Entities and create a file named Product and save to this folder
public class Product { public int Id { get; set; } public string Name { get; set; } public int Price { get; set; } }
  • Create a folder called Data and create a file named ProductContext and save to this folder.
  • The DbContext use Entity Framework Core.
  • By using DbSet, we mapped C# class file to Database table
public class ProductContext : DbContext { public ProductContext(DbContextOptions<ProductContext> options) : base(options) { } public DbSet<Product> Product { get; set; } }
  • Finally for Data, we need to configure the Database Connection. It is mostly done in Program.cs (in .NET 6) however it is also possible to in DbContext
  • We are use Program.cs to register it
  • As mentioned before, here you might need to change to MySQL Server or MySQL server
builder.Services.AddDbContext<ProductContext> (x => x.UseInMemoryDatabase("demo"));

1.4. Proto

  • Create Proto as shown below
    • Product is used to exchange between Server and Client
    • ProductRowIdFilter is like get by id
    • Products is collection of Product
    • Empty is null/nothing pass
syntax = "proto3"; option csharp_namespace = "gRPC_Server"; package product; service ProductSrv { rpc GetAll(Empty) returns (Products); rpc GetById(ProductRowIdFilter) returns (Product); rpc Post(Product) returns (Product); rpc Put (Product) returns (Product); rpc Delete (ProductRowIdFilter) returns (Empty); } message Empty {} message Product{ int32 ProductRowId = 1; int32 ProductId = 2; string Name = 3; int32 Price = 4; } message ProductRowIdFilter{ int32 ProductRowId = 1; } message Products{ repeated Product items = 1; }
  • Mark product proto as Server by editing Project File
<ItemGroup> <Protobuf Include="Protos\productProtobuf.proto" GrpcServices="Server" /> </ItemGroup>

Or you can right click the productProtobuf as below

project

1.5. Service

  • Create a file named ProductService and save it to Services folder.
public class ProductService : ProductSrv.ProductSrvBase { private readonly ProductContext _context; public ProductService(ProductContext context) { _context = context; } public override Task<Products> GetAll(Empty request, ServerCallContext context) { var response = new Products(); var products = (from c in _context.Product select new Product { ProductId = c.Id, Name = c.Name, Price = c.Price, }).ToArray(); response.Items.AddRange(products); return Task.FromResult(response); } public override Task<Product> GetById(ProductRowIdFilter request, ServerCallContext context) { var product = _context.Product.Where(w => w.Id == request.ProductId).FirstOrDefault(); var sProduct = new Product { Name = product.Name, Price = product.Price, ProductId = product.Id }; return Task.FromResult(sProduct); } public override Task<Product> Post(Product request, ServerCallContext context) { var product = new gRPCServer.Entities.Product() { Name = request.Name, Price = request.Price, Id = request.ProductId }; var response = _context.Product.Add(product); _context.SaveChanges(); var resProduct = new Product() { Name = response.Entity.Name, Price = response.Entity.Price, ProductId = response.Entity.Id }; return Task.FromResult(resProduct); } public override Task<Product> Put(Product request, ServerCallContext context) { var product = _context.Product.Where(w => w.Id == request.ProductId).FirstOrDefault(); if (product == null) return Task.FromResult<Product>(null); product.Id = request.ProductId; product.Name = request.Name; product.Price = request.Price; _context.Update(product); _context.SaveChanges(); var sProduct = new Product() { ProductId = request.ProductId, Name = request.Name, Price = request.Price }; return Task.FromResult(sProduct); } public override Task<Empty> Delete(ProductRowIdFilter request, ServerCallContext context) { var product = _context.Product.Where(w => w.Id == request.ProductId).FirstOrDefault(); if (product == null) return Task.FromResult<Empty>(null); _context.Remove(product); _context.SaveChanges(); return Task.FromResult<Empty>(new Empty()); } }

1.5. Register Service in Program.cs

app.MapGrpcService<ProductService>();

project

Consuming gRPC Server

2 Client (ASP.NET 6 MVC)

  • Create an ASP.NET 6 MVC project
  • Copy the product.proto from server and paste it to client
  • rename csharp_namespace to gRPC_Client
  • Right click product.proto as mark it as client

project project project

2.1 Install Nuget Packages

project project project

2.2 Create a Product Controller and save it to Controllers folder

You can get Server url from launchSettings.json from Properties folder of Server App

  • project
using Grpc.Net.Client; using Grpc.Net.Client; using gRPC_Client; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using static gRPC_Client.ProductSrv; namespace gRPCClient.Controllers { public class ProductController : Controller { private readonly string GrpcChannelURL = "https://localhost:7018"; // GET: ProductController public ActionResult Index() { using var channel = GrpcChannel.ForAddress(GrpcChannelURL); var client = new ProductSrvClient(channel); var products = client.GetAll(new Empty { }); return View(products); } // GET: ProductController/Details/5 public ActionResult Details(int id) { return View(); } // GET: ProductController/Create public ActionResult Create() { return View(); } // POST: ProductController/Create [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Create(Product product) { try { using var channel = GrpcChannel.ForAddress(GrpcChannelURL); var client = new ProductSrvClient(channel); await client.PostAsync(product); return RedirectToAction(nameof(Index)); } catch { return View(); } } // GET: ProductController/Edit/5 public async Task<ActionResult> Edit(int id) { var channel = GrpcChannel.ForAddress(GrpcChannelURL); var client = new ProductSrvClient(channel); var product = await client.GetByIdAsync(new ProductRowIdFilter { ProductId = id }); return View(product); } // POST: ProductController/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Edit(int id, Product product) { try { var channel = GrpcChannel.ForAddress(GrpcChannelURL); var client = new ProductSrvClient(channel); await client.PutAsync(product); return RedirectToAction(nameof(Index)); } catch { return View(); } } // GET: ProductController/Delete/5 public async Task<ActionResult> DeleteAsync(int id) { var channel = GrpcChannel.ForAddress(GrpcChannelURL); var client = new ProductSrvClient(channel); var product = await client.GetByIdAsync(new ProductRowIdFilter { ProductId = id }); return View(product); } // POST: ProductController/Delete/5 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(int id, IFormCollection collection) { try { var channel = GrpcChannel.ForAddress(GrpcChannelURL); var client = new ProductSrvClient(channel); client.DeleteAsync(new ProductRowIdFilter { ProductId = id }); return RedirectToAction(nameof(Index)); } catch { return View(); } } } }

2.3 - Index View

@model gRPC_Client.Products @* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } <a asp-action="Create" >Create</a> <table class="table table-striped" > <thead> <tr> <th>Id</th> <th>Name</th> <th>Price</th> <th></th> </tr> </thead> <tbody> @foreach(var product in Model.Items) { <tr> <td>@product.ProductId</td> <td>@product.Name</td> <td>@product.Price</td> <td> <a asp-controller="Product" asp-action="Edit" class="btn btn-primary" asp-route-id="@product.ProductId">Edit</a> <a asp-controller="Product" asp-action="Delete" class="btn btn-danger" asp-route-id="@product.ProductId">Delete</a> </td> </tr> } </tbody> </table>

2.4 - Create View

@model gRPC_Client.Product @* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } @Html.AntiForgeryToken() <form asp-action="Create" method="post"> <table class="table table-striped table-striped" > <tr> <td>Name</td> <td>@Html.TextBoxFor(m => m.Name)</td> </tr> <tr> <td>Price</td> <td>@Html.TextBoxFor(m => m.Price, new { @type = "number" })</td> </tr> </table> <button type="submit" class="btn btn-primary">Submit</button> </form> <a asp-action="Index">Back to list</a>

2.5 - Edit View

@model gRPC_Client.Product @* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } @Html.AntiForgeryToken() <form asp-action="Edit" method="post"> <input type="hidden" asp-for="@Model.ProductId"/> <table class="table table-striped" > <tr> <td>Name</td> <td>Price</td> </tr> <tr> <td> <input type="text" asp-for="@Model.Name" /> </td> <td> <input type="text" asp-for="@Model.Price" /> </td> </tr> </table> <button type="submit" class="btn btn-primary">Update</button> </form> <a asp-action="Index">Back to list</a>

2.6 - Delete View

@model gRPC_Client.Product @* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } @Html.AntiForgeryToken() <form asp-action="Delete" method="post"> <table class="table table-striped" > <input type="hidden" asp-for="@Model.ProductId"/> <tr> <td>Id</td> <td>Name</td> <td>Price</td> </tr> <tr> <td>@Model.ProductId</td> <td>@Model.Name</td> <td>@Model.Price</td> </tr> </table> <button type="submit" class="btn btn-danger">Delete</button> </form> <a asp-action="Index">Back to list</a>

2 - Test

test test test test test